summaryrefslogtreecommitdiff
path: root/Lib
diff options
context:
space:
mode:
authorWalter Doerwald <walter@livinglogic.de>2013-12-02 11:43:20 +0100
committerWalter Doerwald <walter@livinglogic.de>2013-12-02 11:43:20 +0100
commit1a7a32b4875516d530b78f524e83aefac9cad1ab (patch)
tree49024aa50be776f7233b7a7b3083549f258822dd /Lib
parent97261caa90b6e16b6866de47febd059f3b0cd4a1 (diff)
parentd2fb1d098e418d099d7a2b7893a1201f606122fe (diff)
downloadcpython-1a7a32b4875516d530b78f524e83aefac9cad1ab.tar.gz
Fix #19834: merge with 3.3.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/_bootlocale.py34
-rw-r--r--Lib/_collections_abc.py734
-rw-r--r--Lib/_dummy_thread.py4
-rw-r--r--Lib/_osx_support.py4
-rw-r--r--Lib/_pyio.py209
-rw-r--r--Lib/_sitebuiltins.py99
-rw-r--r--Lib/_strptime.py2
-rw-r--r--Lib/abc.py20
-rw-r--r--Lib/aifc.py41
-rw-r--r--Lib/argparse.py31
-rw-r--r--Lib/ast.py6
-rw-r--r--Lib/asynchat.py8
-rw-r--r--Lib/asyncio/__init__.py41
-rw-r--r--Lib/asyncio/base_events.py644
-rw-r--r--Lib/asyncio/base_subprocess.py166
-rw-r--r--Lib/asyncio/constants.py7
-rw-r--r--Lib/asyncio/events.py438
-rw-r--r--Lib/asyncio/futures.py343
-rw-r--r--Lib/asyncio/locks.py433
-rw-r--r--Lib/asyncio/log.py7
-rw-r--r--Lib/asyncio/proactor_events.py360
-rw-r--r--Lib/asyncio/protocols.py128
-rw-r--r--Lib/asyncio/queues.py284
-rw-r--r--Lib/asyncio/selector_events.py862
-rw-r--r--Lib/asyncio/streams.py398
-rw-r--r--Lib/asyncio/tasks.py637
-rw-r--r--Lib/asyncio/test_utils.py276
-rw-r--r--Lib/asyncio/transports.py211
-rw-r--r--Lib/asyncio/unix_events.py744
-rw-r--r--Lib/asyncio/windows_events.py475
-rw-r--r--Lib/asyncio/windows_utils.py192
-rw-r--r--Lib/asyncore.py29
-rwxr-xr-xLib/base64.py346
-rw-r--r--Lib/bdb.py36
-rw-r--r--Lib/bz2.py50
-rwxr-xr-xLib/cProfile.py53
-rwxr-xr-xLib/cgi.py13
-rw-r--r--Lib/chunk.py6
-rw-r--r--Lib/code.py2
-rw-r--r--Lib/codecs.py14
-rw-r--r--Lib/collections/__init__.py24
-rw-r--r--Lib/collections/abc.py730
-rw-r--r--Lib/colorsys.py18
-rw-r--r--Lib/compileall.py19
-rw-r--r--Lib/concurrent/futures/_base.py3
-rw-r--r--Lib/concurrent/futures/process.py11
-rw-r--r--Lib/concurrent/futures/thread.py2
-rw-r--r--Lib/configparser.py2
-rw-r--r--Lib/contextlib.py71
-rw-r--r--Lib/copyreg.py6
-rw-r--r--Lib/ctypes/__init__.py26
-rw-r--r--Lib/ctypes/test/__init__.py2
-rw-r--r--Lib/ctypes/test/test_checkretval.py2
-rw-r--r--Lib/ctypes/test/test_internals.py19
-rw-r--r--Lib/ctypes/test/test_macholib.py56
-rw-r--r--Lib/ctypes/test/test_win32.py2
-rw-r--r--Lib/ctypes/util.py20
-rw-r--r--Lib/datetime.py395
-rw-r--r--Lib/dbm/__init__.py16
-rw-r--r--Lib/dbm/dumb.py16
-rw-r--r--Lib/decimal.py4
-rw-r--r--Lib/difflib.py35
-rw-r--r--Lib/dis.py396
-rw-r--r--Lib/distutils/__init__.py2
-rw-r--r--Lib/distutils/archive_util.py71
-rw-r--r--Lib/distutils/ccompiler.py5
-rw-r--r--Lib/distutils/cmd.py6
-rw-r--r--Lib/distutils/command/bdist.py16
-rw-r--r--Lib/distutils/command/bdist_dumb.py19
-rw-r--r--Lib/distutils/command/build_ext.py26
-rw-r--r--Lib/distutils/command/build_py.py10
-rw-r--r--Lib/distutils/command/build_scripts.py2
-rw-r--r--Lib/distutils/command/install.py15
-rw-r--r--Lib/distutils/command/install_lib.py6
-rw-r--r--Lib/distutils/command/sdist.py9
-rw-r--r--Lib/distutils/command/upload.py2
-rw-r--r--Lib/distutils/config.py11
-rw-r--r--Lib/distutils/core.py7
-rw-r--r--Lib/distutils/cygwinccompiler.py28
-rw-r--r--Lib/distutils/dir_util.py4
-rw-r--r--Lib/distutils/dist.py35
-rw-r--r--Lib/distutils/emxccompiler.py315
-rw-r--r--Lib/distutils/errors.py4
-rw-r--r--Lib/distutils/file_util.py16
-rw-r--r--Lib/distutils/msvc9compiler.py2
-rw-r--r--Lib/distutils/spawn.py24
-rw-r--r--Lib/distutils/sysconfig.py38
-rw-r--r--Lib/distutils/tests/test_archive_util.py61
-rw-r--r--Lib/distutils/tests/test_bdist_dumb.py5
-rw-r--r--Lib/distutils/tests/test_build_py.py10
-rw-r--r--Lib/distutils/tests/test_config.py4
-rw-r--r--Lib/distutils/tests/test_dist.py45
-rw-r--r--Lib/distutils/tests/test_install.py6
-rw-r--r--Lib/distutils/tests/test_install_lib.py8
-rw-r--r--Lib/distutils/tests/test_sdist.py56
-rw-r--r--Lib/distutils/tests/test_sysconfig.py48
-rw-r--r--Lib/distutils/tests/test_upload.py13
-rw-r--r--Lib/distutils/tests/test_util.py2
-rw-r--r--Lib/distutils/util.py17
-rw-r--r--Lib/doctest.py70
-rw-r--r--Lib/email/_header_value_parser.py37
-rw-r--r--Lib/email/contentmanager.py249
-rw-r--r--Lib/email/feedparser.py27
-rw-r--r--Lib/email/iterators.py6
-rw-r--r--Lib/email/message.py260
-rw-r--r--Lib/email/parser.py3
-rw-r--r--Lib/email/policy.py13
-rw-r--r--Lib/email/utils.py24
-rw-r--r--Lib/encodings/aliases.py42
-rw-r--r--Lib/encodings/base64_codec.py1
-rw-r--r--Lib/encodings/bz2_codec.py1
-rw-r--r--Lib/encodings/cp037.py1
-rw-r--r--Lib/encodings/cp1125.py698
-rw-r--r--Lib/encodings/cp273.py307
-rw-r--r--Lib/encodings/cp500.py1
-rw-r--r--Lib/encodings/hex_codec.py1
-rw-r--r--Lib/encodings/iso8859_1.py1
-rw-r--r--Lib/encodings/quopri_codec.py1
-rwxr-xr-xLib/encodings/rot_13.py1
-rw-r--r--Lib/encodings/uu_codec.py1
-rw-r--r--Lib/encodings/zlib_codec.py1
-rw-r--r--Lib/ensurepip/__init__.py114
-rw-r--r--Lib/ensurepip/__main__.py66
-rw-r--r--Lib/ensurepip/_bundled/pip-1.5rc1-py2.py3-none-any.whlbin0 -> 931600 bytes
-rw-r--r--Lib/ensurepip/_bundled/setuptools-1.3.2-py2.py3-none-any.whlbin0 -> 549754 bytes
-rw-r--r--Lib/ensurepip/_uninstall.py30
-rw-r--r--Lib/enum.py504
-rw-r--r--Lib/filecmp.py25
-rw-r--r--Lib/fileinput.py16
-rw-r--r--Lib/formatter.py3
-rw-r--r--Lib/fractions.py14
-rw-r--r--Lib/ftplib.py189
-rw-r--r--Lib/functools.py421
-rw-r--r--Lib/genericpath.py34
-rw-r--r--Lib/getpass.py99
-rw-r--r--Lib/gettext.py14
-rw-r--r--Lib/glob.py21
-rw-r--r--Lib/gzip.py115
-rw-r--r--Lib/hashlib.py107
-rw-r--r--Lib/hmac.py31
-rw-r--r--Lib/html/__init__.py123
-rw-r--r--Lib/html/parser.py111
-rw-r--r--Lib/http/client.py48
-rw-r--r--Lib/http/cookiejar.py31
-rw-r--r--Lib/http/server.py42
-rw-r--r--Lib/idlelib/EditorWindow.py4
-rw-r--r--Lib/idlelib/FileList.py2
-rw-r--r--Lib/idlelib/GrepDialog.py1
-rw-r--r--Lib/idlelib/IOBinding.py2
-rw-r--r--Lib/idlelib/NEWS.txt6
-rw-r--r--Lib/idlelib/PathBrowser.py2
-rw-r--r--Lib/idlelib/PyShell.py12
-rw-r--r--Lib/idlelib/TreeWidget.py4
-rw-r--r--Lib/idlelib/configHandler.py4
-rw-r--r--Lib/idlelib/help.txt556
-rw-r--r--Lib/idlelib/idlever.py2
-rw-r--r--Lib/idlelib/rpc.py8
-rw-r--r--Lib/idlelib/run.py4
-rw-r--r--Lib/imaplib.py24
-rw-r--r--Lib/imghdr.py2
-rw-r--r--Lib/imp.py224
-rw-r--r--Lib/importlib/__init__.py96
-rw-r--r--Lib/importlib/_bootstrap.py1680
-rw-r--r--Lib/importlib/abc.py344
-rw-r--r--Lib/importlib/machinery.py1
-rw-r--r--Lib/importlib/util.py105
-rw-r--r--Lib/inspect.py346
-rw-r--r--Lib/io.py2
-rw-r--r--Lib/ipaddress.py81
-rw-r--r--Lib/json/__init__.py30
-rw-r--r--Lib/json/decoder.py28
-rw-r--r--Lib/json/encoder.py56
-rw-r--r--Lib/json/scanner.py4
-rw-r--r--Lib/json/tool.py3
-rwxr-xr-xLib/keyword.py23
-rw-r--r--Lib/lib2to3/btm_utils.py6
-rw-r--r--Lib/lib2to3/fixer_util.py23
-rw-r--r--Lib/lib2to3/fixes/fix_asserts.py34
-rw-r--r--Lib/lib2to3/fixes/fix_intern.py21
-rw-r--r--Lib/lib2to3/fixes/fix_reload.py28
-rw-r--r--Lib/lib2to3/main.py4
-rw-r--r--Lib/lib2to3/pgen2/conv.py4
-rw-r--r--Lib/lib2to3/pgen2/driver.py2
-rw-r--r--Lib/lib2to3/pgen2/grammar.py10
-rw-r--r--Lib/lib2to3/pytree.py9
-rw-r--r--Lib/lib2to3/refactor.py6
-rwxr-xr-xLib/lib2to3/tests/pytree_idempotency.py2
-rw-r--r--Lib/lib2to3/tests/test_fixers.py109
-rw-r--r--Lib/linecache.py8
-rw-r--r--Lib/locale.py21
-rw-r--r--Lib/logging/__init__.py78
-rw-r--r--Lib/logging/config.py76
-rw-r--r--Lib/logging/handlers.py124
-rw-r--r--Lib/lzma.py137
-rw-r--r--Lib/macpath.py2
-rw-r--r--Lib/mailbox.py83
-rw-r--r--Lib/mailcap.py2
-rw-r--r--Lib/mimetypes.py2
-rw-r--r--Lib/modulefinder.py7
-rw-r--r--Lib/multiprocessing/__init__.py251
-rw-r--r--Lib/multiprocessing/connection.py147
-rw-r--r--Lib/multiprocessing/context.py348
-rw-r--r--Lib/multiprocessing/dummy/__init__.py31
-rw-r--r--Lib/multiprocessing/dummy/connection.py27
-rw-r--r--Lib/multiprocessing/forking.py474
-rw-r--r--Lib/multiprocessing/forkserver.py267
-rw-r--r--Lib/multiprocessing/heap.py56
-rw-r--r--Lib/multiprocessing/managers.py51
-rw-r--r--Lib/multiprocessing/pool.py133
-rw-r--r--Lib/multiprocessing/popen_fork.py83
-rw-r--r--Lib/multiprocessing/popen_forkserver.py69
-rw-r--r--Lib/multiprocessing/popen_spawn_posix.py72
-rw-r--r--Lib/multiprocessing/popen_spawn_win32.py99
-rw-r--r--Lib/multiprocessing/process.py83
-rw-r--r--Lib/multiprocessing/queues.py106
-rw-r--r--Lib/multiprocessing/reduction.py363
-rw-r--r--Lib/multiprocessing/resource_sharer.py158
-rw-r--r--Lib/multiprocessing/semaphore_tracker.py145
-rw-r--r--Lib/multiprocessing/sharedctypes.py40
-rw-r--r--Lib/multiprocessing/spawn.py258
-rw-r--r--Lib/multiprocessing/synchronize.py112
-rw-r--r--Lib/multiprocessing/util.py52
-rw-r--r--Lib/netrc.py5
-rw-r--r--Lib/nntplib.py8
-rw-r--r--Lib/ntpath.py60
-rw-r--r--Lib/nturl2path.py4
-rw-r--r--Lib/opcode.py17
-rw-r--r--Lib/operator.py412
-rw-r--r--Lib/os.py95
-rw-r--r--Lib/os2emxpath.py158
-rw-r--r--Lib/pathlib.py1284
-rwxr-xr-xLib/pdb.py47
-rw-r--r--Lib/pickle.py801
-rw-r--r--Lib/pickletools.py698
-rw-r--r--Lib/pkgutil.py107
-rw-r--r--Lib/plat-os2emx/IN.py82
-rw-r--r--Lib/plat-os2emx/SOCKET.py106
-rw-r--r--Lib/plat-os2emx/_emx_link.py79
-rw-r--r--Lib/plat-os2emx/grp.py182
-rw-r--r--Lib/plat-os2emx/pwd.py208
-rw-r--r--Lib/plat-os2emx/regen7
-rwxr-xr-xLib/platform.py91
-rw-r--r--Lib/plistlib.py1098
-rw-r--r--Lib/poplib.py98
-rw-r--r--Lib/posixpath.py57
-rw-r--r--Lib/pprint.py124
-rwxr-xr-xLib/profile.py63
-rw-r--r--Lib/pstats.py12
-rw-r--r--Lib/pty.py22
-rw-r--r--Lib/py_compile.py77
-rwxr-xr-xLib/pydoc.py154
-rw-r--r--Lib/pydoc_data/topics.py22
-rwxr-xr-xLib/quopri.py2
-rw-r--r--Lib/random.py16
-rw-r--r--Lib/re.py29
-rw-r--r--Lib/rlcompleter.py5
-rw-r--r--Lib/runpy.py11
-rw-r--r--Lib/sched.py11
-rw-r--r--Lib/selectors.py455
-rw-r--r--Lib/shelve.py9
-rw-r--r--Lib/shutil.py39
-rw-r--r--Lib/site.py202
-rwxr-xr-xLib/smtpd.py21
-rw-r--r--Lib/smtplib.py28
-rw-r--r--Lib/sndhdr.py17
-rw-r--r--Lib/socket.py116
-rw-r--r--Lib/socketserver.py8
-rw-r--r--Lib/sqlite3/test/dbapi.py18
-rw-r--r--Lib/sre_compile.py270
-rw-r--r--Lib/sre_constants.py8
-rw-r--r--Lib/sre_parse.py75
-rw-r--r--Lib/ssl.py361
-rw-r--r--Lib/stat.py6
-rw-r--r--Lib/statistics.py610
-rw-r--r--Lib/struct.py1
-rw-r--r--Lib/subprocess.py362
-rw-r--r--Lib/sunau.py40
-rwxr-xr-xLib/symbol.py4
-rw-r--r--Lib/symtable.py3
-rw-r--r--Lib/sysconfig.py46
-rwxr-xr-xLib/tabnanny.py2
-rw-r--r--Lib/tarfile.py147
-rw-r--r--Lib/telnetlib.py273
-rw-r--r--Lib/tempfile.py35
-rw-r--r--Lib/test/__main__.py14
-rw-r--r--Lib/test/_test_multiprocessing.py (renamed from Lib/test/test_multiprocessing.py)588
-rw-r--r--Lib/test/audiodata/pluck-pcm24.aubin0 -> 19866 bytes
-rw-r--r--Lib/test/audiotests.py161
-rw-r--r--Lib/test/badsyntax_future10.py3
-rw-r--r--Lib/test/buffer_tests.py16
-rw-r--r--Lib/test/bytecode_helper.py41
-rw-r--r--Lib/test/datetimetester.py4
-rw-r--r--Lib/test/final_a.py19
-rw-r--r--Lib/test/final_b.py19
-rw-r--r--Lib/test/fork_wait.py2
-rw-r--r--Lib/test/keycert3.pem73
-rw-r--r--Lib/test/keycert4.pem73
-rw-r--r--Lib/test/leakers/test_gestalt.py14
-rw-r--r--Lib/test/lock_tests.py20
-rw-r--r--Lib/test/make_ssl_certs.py118
-rw-r--r--Lib/test/mock_socket.py8
-rw-r--r--Lib/test/mp_fork_bomb.py5
-rw-r--r--Lib/test/multibytecodec_support.py2
-rw-r--r--Lib/test/pickletester.py580
-rw-r--r--Lib/test/pycacert.pem78
-rw-r--r--Lib/test/pycakey.pem28
-rwxr-xr-xLib/test/regrtest.py1153
-rw-r--r--Lib/test/revocation.crl11
-rw-r--r--Lib/test/script_helper.py36
-rw-r--r--Lib/test/sortperf.py6
-rw-r--r--Lib/test/ssl_servers.py10
-rw-r--r--Lib/test/string_tests.py27
-rw-r--r--Lib/test/subprocessdata/fd_status.py26
-rw-r--r--Lib/test/support/__init__.py252
-rw-r--r--Lib/test/test___all__.py30
-rw-r--r--Lib/test/test__opcode.py23
-rw-r--r--Lib/test/test_abc.py16
-rw-r--r--Lib/test/test_aifc.py70
-rw-r--r--Lib/test/test_argparse.py36
-rwxr-xr-xLib/test/test_array.py16
-rw-r--r--Lib/test/test_ast.py98
-rw-r--r--Lib/test/test_asynchat.py35
-rw-r--r--Lib/test/test_asyncio/__init__.py31
-rw-r--r--Lib/test/test_asyncio/__main__.py5
-rw-r--r--Lib/test/test_asyncio/echo.py8
-rw-r--r--Lib/test/test_asyncio/echo2.py6
-rw-r--r--Lib/test/test_asyncio/echo3.py11
-rw-r--r--Lib/test/test_asyncio/sample.crt14
-rw-r--r--Lib/test/test_asyncio/sample.key15
-rw-r--r--Lib/test/test_asyncio/test_base_events.py684
-rw-r--r--Lib/test/test_asyncio/test_events.py1672
-rw-r--r--Lib/test/test_asyncio/test_futures.py347
-rw-r--r--Lib/test/test_asyncio/test_locks.py838
-rw-r--r--Lib/test/test_asyncio/test_proactor_events.py484
-rw-r--r--Lib/test/test_asyncio/test_queues.py470
-rw-r--r--Lib/test/test_asyncio/test_selector_events.py1663
-rw-r--r--Lib/test/test_asyncio/test_streams.py430
-rw-r--r--Lib/test/test_asyncio/test_tasks.py1518
-rw-r--r--Lib/test/test_asyncio/test_transports.py59
-rw-r--r--Lib/test/test_asyncio/test_unix_events.py1488
-rw-r--r--Lib/test/test_asyncio/test_windows_events.py139
-rw-r--r--Lib/test/test_asyncio/test_windows_utils.py141
-rw-r--r--Lib/test/test_asyncio/tests.txt13
-rw-r--r--Lib/test/test_asyncore.py25
-rw-r--r--Lib/test/test_atexit.py42
-rw-r--r--Lib/test/test_audioop.py238
-rw-r--r--Lib/test/test_base64.py362
-rw-r--r--Lib/test/test_bisect.py10
-rw-r--r--Lib/test/test_buffer.py6
-rw-r--r--Lib/test/test_builtin.py94
-rw-r--r--Lib/test/test_bytes.py34
-rw-r--r--Lib/test/test_bz2.py218
-rw-r--r--Lib/test/test_capi.py152
-rw-r--r--Lib/test/test_cmd_line.py100
-rw-r--r--Lib/test/test_cmd_line_script.py10
-rw-r--r--Lib/test/test_code_module.py14
-rw-r--r--Lib/test/test_codeccallbacks.py4
-rw-r--r--Lib/test/test_codecs.py387
-rw-r--r--Lib/test/test_coding.py63
-rw-r--r--Lib/test/test_collections.py126
-rw-r--r--Lib/test/test_colorsys.py32
-rw-r--r--Lib/test/test_compileall.py52
-rw-r--r--Lib/test/test_complex.py3
-rw-r--r--Lib/test/test_concurrent_futures.py22
-rw-r--r--Lib/test/test_contextlib.py141
-rw-r--r--Lib/test/test_cprofile.py2
-rw-r--r--Lib/test/test_crypt.py6
-rw-r--r--Lib/test/test_csv.py21
-rw-r--r--Lib/test/test_dbm.py2
-rw-r--r--Lib/test/test_dbm_dumb.py13
-rw-r--r--Lib/test/test_dbm_gnu.py11
-rw-r--r--Lib/test/test_dbm_ndbm.py13
-rw-r--r--Lib/test/test_decimal.py1
-rw-r--r--Lib/test/test_deque.py4
-rw-r--r--Lib/test/test_descr.py627
-rw-r--r--Lib/test/test_devpoll.py38
-rw-r--r--Lib/test/test_dis.py475
-rw-r--r--Lib/test/test_doctest.py317
-rw-r--r--Lib/test/test_dynamicclassattribute.py304
-rw-r--r--Lib/test/test_email/__init__.py26
-rw-r--r--Lib/test/test_email/test_contentmanager.py796
-rw-r--r--Lib/test/test_email/test_email.py33
-rw-r--r--Lib/test/test_email/test_headerregistry.py14
-rw-r--r--Lib/test/test_email/test_message.py742
-rw-r--r--Lib/test/test_email/test_policy.py1
-rw-r--r--Lib/test/test_email/torture_test.py2
-rw-r--r--Lib/test/test_ensurepip.py203
-rw-r--r--Lib/test/test_enum.py1325
-rw-r--r--Lib/test/test_enumerate.py10
-rw-r--r--Lib/test/test_epoll.py54
-rw-r--r--Lib/test/test_exceptions.py28
-rw-r--r--Lib/test/test_faulthandler.py77
-rw-r--r--Lib/test/test_fcntl.py19
-rw-r--r--Lib/test/test_file.py6
-rw-r--r--Lib/test/test_filecmp.py23
-rw-r--r--Lib/test/test_fileinput.py30
-rw-r--r--Lib/test/test_fileio.py20
-rw-r--r--Lib/test/test_finalization.py513
-rw-r--r--Lib/test/test_float.py1
-rw-r--r--Lib/test/test_fork1.py2
-rw-r--r--Lib/test/test_format.py30
-rw-r--r--Lib/test/test_fractions.py32
-rw-r--r--Lib/test/test_frame.py116
-rw-r--r--Lib/test/test_frozen.py79
-rw-r--r--Lib/test/test_ftplib.py87
-rw-r--r--Lib/test/test_funcattrs.py7
-rw-r--r--Lib/test/test_functools.py921
-rw-r--r--Lib/test/test_future.py8
-rw-r--r--Lib/test/test_gc.py80
-rw-r--r--Lib/test/test_gdb.py31
-rw-r--r--Lib/test/test_generators.py74
-rw-r--r--Lib/test/test_genericpath.py82
-rw-r--r--Lib/test/test_getargs2.py88
-rw-r--r--Lib/test/test_getpass.py155
-rw-r--r--Lib/test/test_glob.py22
-rw-r--r--[-rwxr-xr-x]Lib/test/test_gzip.py51
-rw-r--r--Lib/test/test_hash.py146
-rw-r--r--Lib/test/test_hashlib.py267
-rw-r--r--Lib/test/test_hmac.py99
-rw-r--r--Lib/test/test_html.py86
-rw-r--r--Lib/test/test_htmlparser.py98
-rw-r--r--Lib/test/test_httplib.py30
-rw-r--r--Lib/test/test_httpservers.py21
-rw-r--r--Lib/test/test_imaplib.py2
-rw-r--r--Lib/test/test_imp.py89
-rw-r--r--Lib/test/test_import.py102
-rw-r--r--Lib/test/test_importhooks.py250
-rw-r--r--Lib/test/test_importlib/__main__.py13
-rw-r--r--Lib/test/test_importlib/abc.py9
-rw-r--r--Lib/test/test_importlib/builtin/test_finder.py60
-rw-r--r--Lib/test/test_importlib/builtin/test_loader.py47
-rw-r--r--Lib/test/test_importlib/extension/test_case_sensitivity.py29
-rw-r--r--Lib/test/test_importlib/extension/test_finder.py20
-rw-r--r--Lib/test/test_importlib/extension/test_loader.py21
-rw-r--r--Lib/test/test_importlib/extension/test_path_hook.py20
-rw-r--r--Lib/test/test_importlib/extension/util.py1
-rw-r--r--Lib/test/test_importlib/frozen/test_finder.py49
-rw-r--r--Lib/test/test_importlib/frozen/test_loader.py128
-rw-r--r--Lib/test/test_importlib/import_/test___loader__.py70
-rw-r--r--Lib/test/test_importlib/import_/test___package__.py33
-rw-r--r--Lib/test/test_importlib/import_/test_api.py73
-rw-r--r--Lib/test/test_importlib/import_/test_caching.py30
-rw-r--r--Lib/test/test_importlib/import_/test_fromlist.py34
-rw-r--r--Lib/test/test_importlib/import_/test_meta_path.py28
-rw-r--r--Lib/test/test_importlib/import_/test_packages.py30
-rw-r--r--Lib/test/test_importlib/import_/test_path.py42
-rw-r--r--Lib/test/test_importlib/import_/test_relative_imports.py51
-rw-r--r--Lib/test/test_importlib/import_/util.py20
-rw-r--r--Lib/test/test_importlib/source/test_abc_loader.py906
-rw-r--r--Lib/test/test_importlib/source/test_case_sensitivity.py30
-rw-r--r--Lib/test/test_importlib/source/test_file_loader.py110
-rw-r--r--Lib/test/test_importlib/source/test_finder.py29
-rw-r--r--Lib/test/test_importlib/source/test_path_hook.py18
-rw-r--r--Lib/test/test_importlib/source/test_source_encoding.py20
-rw-r--r--Lib/test/test_importlib/source/util.py1
-rw-r--r--Lib/test/test_importlib/test_abc.py791
-rw-r--r--Lib/test/test_importlib/test_api.py395
-rw-r--r--Lib/test/test_importlib/test_locks.py71
-rw-r--r--Lib/test/test_importlib/test_spec.py968
-rw-r--r--Lib/test/test_importlib/test_util.py295
-rw-r--r--Lib/test/test_importlib/util.py25
-rw-r--r--Lib/test/test_inspect.py266
-rw-r--r--Lib/test/test_int.py41
-rw-r--r--Lib/test/test_io.py119
-rw-r--r--Lib/test/test_ioctl.py6
-rw-r--r--Lib/test/test_ipaddress.py12
-rw-r--r--Lib/test/test_iterlen.py62
-rw-r--r--Lib/test/test_itertools.py5
-rw-r--r--Lib/test/test_json/test_decode.py23
-rw-r--r--Lib/test/test_json/test_enum.py120
-rw-r--r--Lib/test/test_json/test_fail.py89
-rw-r--r--Lib/test/test_json/test_indent.py4
-rw-r--r--Lib/test/test_keyword.py138
-rw-r--r--Lib/test/test_keywordonlyarg.py12
-rw-r--r--Lib/test/test_kqueue.py29
-rw-r--r--Lib/test/test_largefile.py2
-rw-r--r--Lib/test/test_logging.py439
-rw-r--r--Lib/test/test_long.py2
-rw-r--r--Lib/test/test_lzma.py46
-rw-r--r--Lib/test/test_mailbox.py2
-rw-r--r--Lib/test/test_marshal.py163
-rw-r--r--Lib/test/test_memoryio.py12
-rw-r--r--Lib/test/test_memoryview.py9
-rw-r--r--Lib/test/test_mmap.py26
-rw-r--r--Lib/test/test_module.py64
-rw-r--r--Lib/test/test_multibytecodec.py73
-rw-r--r--Lib/test/test_multiprocessing_fork.py7
-rw-r--r--Lib/test/test_multiprocessing_forkserver.py7
-rw-r--r--Lib/test/test_multiprocessing_spawn.py7
-rw-r--r--Lib/test/test_namespace_pkgs.py13
-rw-r--r--Lib/test/test_nntplib.py2
-rw-r--r--Lib/test/test_normalization.py2
-rw-r--r--Lib/test/test_ntpath.py34
-rw-r--r--Lib/test/test_openpty.py2
-rw-r--r--Lib/test/test_operator.py122
-rw-r--r--Lib/test/test_optparse.py8
-rw-r--r--Lib/test/test_os.py329
-rw-r--r--Lib/test/test_ossaudiodev.py4
-rwxr-xr-xLib/test/test_pathlib.py1676
-rw-r--r--Lib/test/test_pdb.py348
-rw-r--r--Lib/test/test_peepholer.py295
-rw-r--r--Lib/test/test_pep247.py12
-rw-r--r--Lib/test/test_pep277.py4
-rw-r--r--Lib/test/test_pep352.py5
-rw-r--r--Lib/test/test_pkg.py20
-rw-r--r--Lib/test/test_pkgimport.py2
-rw-r--r--Lib/test/test_pkgutil.py22
-rw-r--r--Lib/test/test_plistlib.py497
-rw-r--r--Lib/test/test_poll.py20
-rw-r--r--Lib/test/test_poplib.py178
-rw-r--r--Lib/test/test_posix.py72
-rw-r--r--Lib/test/test_posixpath.py62
-rw-r--r--Lib/test/test_pprint.py50
-rw-r--r--Lib/test/test_print.py74
-rw-r--r--Lib/test/test_profile.py29
-rw-r--r--Lib/test/test_pty.py2
-rw-r--r--Lib/test/test_py_compile.py46
-rw-r--r--Lib/test/test_pyclbr.py4
-rw-r--r--Lib/test/test_pydoc.py221
-rw-r--r--Lib/test/test_random.py207
-rw-r--r--Lib/test/test_range.py9
-rw-r--r--Lib/test/test_re.py239
-rw-r--r--Lib/test/test_regrtest.py275
-rw-r--r--Lib/test/test_reprlib.py6
-rw-r--r--Lib/test/test_resource.py26
-rw-r--r--Lib/test/test_runpy.py14
-rw-r--r--Lib/test/test_sched.py5
-rw-r--r--Lib/test/test_scope.py17
-rw-r--r--Lib/test/test_select.py4
-rw-r--r--Lib/test/test_selectors.py430
-rw-r--r--Lib/test/test_set.py2
-rw-r--r--Lib/test/test_shelve.py13
-rw-r--r--Lib/test/test_shutil.py45
-rw-r--r--Lib/test/test_signal.py60
-rw-r--r--Lib/test/test_site.py39
-rw-r--r--Lib/test/test_slice.py114
-rw-r--r--Lib/test/test_smtplib.py23
-rw-r--r--Lib/test/test_sndhdr.py2
-rw-r--r--Lib/test/test_socket.py392
-rw-r--r--Lib/test/test_socketserver.py20
-rw-r--r--Lib/test/test_source_encoding.py (renamed from Lib/test/test_pep263.py)67
-rw-r--r--Lib/test/test_ssl.py786
-rw-r--r--Lib/test/test_stat.py61
-rw-r--r--Lib/test/test_statistics.py1534
-rw-r--r--Lib/test/test_strftime.py27
-rw-r--r--Lib/test/test_strtod.py2
-rw-r--r--Lib/test/test_struct.py89
-rw-r--r--Lib/test/test_structseq.py2
-rw-r--r--Lib/test/test_subprocess.py206
-rw-r--r--Lib/test/test_sunau.py31
-rw-r--r--Lib/test/test_sundry.py39
-rw-r--r--Lib/test/test_super.py33
-rw-r--r--Lib/test/test_support.py1
-rw-r--r--Lib/test/test_syntax.py4
-rw-r--r--Lib/test/test_sys.py107
-rw-r--r--Lib/test/test_sysconfig.py66
-rw-r--r--Lib/test/test_tarfile.py179
-rw-r--r--Lib/test/test_tcl.py34
-rw-r--r--Lib/test/test_telnetlib.py102
-rw-r--r--Lib/test/test_tempfile.py29
-rw-r--r--Lib/test/test_textwrap.py159
-rw-r--r--Lib/test/test_thread.py2
-rw-r--r--Lib/test/test_threaded_import.py2
-rw-r--r--Lib/test/test_threading.py433
-rw-r--r--Lib/test/test_threadsignals.py2
-rw-r--r--Lib/test/test_time.py9
-rw-r--r--Lib/test/test_timeout.py2
-rw-r--r--Lib/test/test_traceback.py82
-rw-r--r--Lib/test/test_tracemalloc.py792
-rw-r--r--Lib/test/test_types.py37
-rw-r--r--Lib/test/test_ucn.py2
-rw-r--r--Lib/test/test_unicode.py374
-rw-r--r--Lib/test/test_unicodedata.py4
-rw-r--r--Lib/test/test_urllib.py103
-rw-r--r--Lib/test/test_urllib2.py137
-rw-r--r--Lib/test/test_urllib2_localnet.py27
-rw-r--r--Lib/test/test_urllib2net.py43
-rw-r--r--Lib/test/test_urllibnet.py19
-rwxr-xr-xLib/test/test_urlparse.py8
-rw-r--r--Lib/test/test_userdict.py9
-rw-r--r--Lib/test/test_venv.py174
-rw-r--r--Lib/test/test_wait3.py12
-rw-r--r--Lib/test/test_warnings.py32
-rw-r--r--Lib/test/test_wave.py18
-rw-r--r--Lib/test/test_weakref.py316
-rw-r--r--Lib/test/test_winreg.py28
-rw-r--r--Lib/test/test_winsound.py2
-rw-r--r--Lib/test/test_xml_etree.py200
-rw-r--r--Lib/test/test_xml_etree_c.py9
-rw-r--r--Lib/test/test_xmlrpc.py74
-rw-r--r--Lib/test/test_xmlrpc_net.py31
-rw-r--r--Lib/test/test_zipfile.py254
-rw-r--r--Lib/test/test_zipfile64.py4
-rw-r--r--Lib/test/test_zipimport.py37
-rw-r--r--Lib/test/test_zipimport_support.py6
-rw-r--r--Lib/test/tf_inherit_check.py2
-rw-r--r--Lib/textwrap.py73
-rw-r--r--Lib/threading.py188
-rw-r--r--Lib/timeit.py46
-rw-r--r--Lib/tkinter/__init__.py45
-rw-r--r--Lib/tkinter/filedialog.py4
-rw-r--r--Lib/tkinter/test/test_tkinter/test_widgets.py4
-rwxr-xr-xLib/token.py6
-rw-r--r--Lib/tokenize.py2
-rw-r--r--Lib/trace.py10
-rw-r--r--Lib/traceback.py259
-rw-r--r--Lib/tracemalloc.py459
-rw-r--r--Lib/turtle.py22
-rwxr-xr-xLib/turtledemo/__main__.py2
-rw-r--r--Lib/types.py57
-rw-r--r--Lib/unittest/__main__.py3
-rw-r--r--Lib/unittest/case.py379
-rw-r--r--Lib/unittest/loader.py76
-rw-r--r--Lib/unittest/main.py247
-rw-r--r--Lib/unittest/mock.py229
-rw-r--r--Lib/unittest/result.py16
-rw-r--r--Lib/unittest/suite.py19
-rw-r--r--Lib/unittest/test/__main__.py18
-rw-r--r--Lib/unittest/test/support.py38
-rw-r--r--Lib/unittest/test/test_assertions.py4
-rw-r--r--Lib/unittest/test/test_break.py4
-rw-r--r--Lib/unittest/test/test_case.py231
-rw-r--r--Lib/unittest/test/test_discovery.py169
-rw-r--r--Lib/unittest/test/test_functiontestcase.py4
-rw-r--r--Lib/unittest/test/test_loader.py4
-rw-r--r--Lib/unittest/test/test_program.py72
-rw-r--r--Lib/unittest/test/test_result.py92
-rw-r--r--Lib/unittest/test/test_runner.py16
-rw-r--r--Lib/unittest/test/test_setups.py7
-rw-r--r--Lib/unittest/test/test_skipping.py78
-rw-r--r--Lib/unittest/test/test_suite.py50
-rw-r--r--Lib/unittest/test/testmock/__main__.py18
-rw-r--r--Lib/unittest/test/testmock/testcallable.py4
-rw-r--r--Lib/unittest/test/testmock/testhelpers.py59
-rw-r--r--Lib/unittest/test/testmock/testmock.py143
-rw-r--r--Lib/unittest/test/testmock/testpatch.py1
-rw-r--r--Lib/unittest/test/testmock/testwith.py83
-rw-r--r--Lib/unittest/util.py37
-rw-r--r--Lib/urllib/error.py14
-rw-r--r--Lib/urllib/parse.py9
-rw-r--r--Lib/urllib/request.py201
-rw-r--r--Lib/uuid.py6
-rw-r--r--Lib/venv/__init__.py79
-rw-r--r--Lib/venv/scripts/nt/Activate.ps145
-rw-r--r--Lib/venv/scripts/nt/Deactivate.ps119
-rw-r--r--Lib/venv/scripts/nt/activate.bat2
-rw-r--r--Lib/venv/scripts/posix/activate4
-rw-r--r--Lib/venv/scripts/posix/activate.csh37
-rw-r--r--Lib/venv/scripts/posix/activate.fish74
-rw-r--r--Lib/warnings.py8
-rw-r--r--Lib/wave.py88
-rw-r--r--Lib/weakref.py198
-rw-r--r--Lib/webbrowser.py92
-rw-r--r--Lib/xml/dom/expatbuilder.py10
-rw-r--r--Lib/xml/etree/ElementInclude.py6
-rw-r--r--Lib/xml/etree/ElementPath.py15
-rw-r--r--Lib/xml/etree/ElementTree.py1431
-rw-r--r--Lib/xmlrpc/client.py9
-rw-r--r--Lib/xmlrpc/server.py7
-rw-r--r--Lib/zipfile.py200
660 files changed, 65303 insertions, 16817 deletions
diff --git a/Lib/_bootlocale.py b/Lib/_bootlocale.py
new file mode 100644
index 0000000000..4bccac1139
--- /dev/null
+++ b/Lib/_bootlocale.py
@@ -0,0 +1,34 @@
+"""A minimal subset of the locale module used at interpreter startup
+(imported by the _io module), in order to reduce startup time.
+
+Don't import directly from third-party code; use the `locale` module instead!
+"""
+
+import sys
+import _locale
+
+if sys.platform.startswith("win"):
+ def getpreferredencoding(do_setlocale=True):
+ return _locale._getdefaultlocale()[1]
+else:
+ try:
+ _locale.CODESET
+ except AttributeError:
+ def getpreferredencoding(do_setlocale=True):
+ # This path for legacy systems needs the more complex
+ # getdefaultlocale() function, import the full locale module.
+ import locale
+ return locale.getpreferredencoding(do_setlocale)
+ else:
+ def getpreferredencoding(do_setlocale=True):
+ assert not do_setlocale
+ result = _locale.nl_langinfo(_locale.CODESET)
+ if not result and sys.platform == 'darwin':
+ # nl_langinfo can return an empty string
+ # when the setting has an invalid value.
+ # Default to UTF-8 in that case because
+ # UTF-8 is the default charset on OSX and
+ # returning nothing will crash the
+ # interpreter.
+ result = 'UTF-8'
+ return result
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
new file mode 100644
index 0000000000..faa1ff22ff
--- /dev/null
+++ b/Lib/_collections_abc.py
@@ -0,0 +1,734 @@
+# Copyright 2007 Google, Inc. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Abstract Base Classes (ABCs) for collections, according to PEP 3119.
+
+Unit tests are in test_collections.
+"""
+
+from abc import ABCMeta, abstractmethod
+import sys
+
+__all__ = ["Hashable", "Iterable", "Iterator",
+ "Sized", "Container", "Callable",
+ "Set", "MutableSet",
+ "Mapping", "MutableMapping",
+ "MappingView", "KeysView", "ItemsView", "ValuesView",
+ "Sequence", "MutableSequence",
+ "ByteString",
+ ]
+
+# This module has been renamed from collections.abc to _collections_abc to
+# speed up interpreter startup. Some of the types such as MutableMapping are
+# required early but collections module imports a lot of other modules.
+# See issue #19218
+__name__ = "collections.abc"
+
+# Private list of types that we want to register with the various ABCs
+# so that they will pass tests like:
+# it = iter(somebytearray)
+# assert isinstance(it, Iterable)
+# Note: in other implementations, these types many not be distinct
+# and they make have their own implementation specific types that
+# are not included on this list.
+bytes_iterator = type(iter(b''))
+bytearray_iterator = type(iter(bytearray()))
+#callable_iterator = ???
+dict_keyiterator = type(iter({}.keys()))
+dict_valueiterator = type(iter({}.values()))
+dict_itemiterator = type(iter({}.items()))
+list_iterator = type(iter([]))
+list_reverseiterator = type(iter(reversed([])))
+range_iterator = type(iter(range(0)))
+set_iterator = type(iter(set()))
+str_iterator = type(iter(""))
+tuple_iterator = type(iter(()))
+zip_iterator = type(iter(zip()))
+## views ##
+dict_keys = type({}.keys())
+dict_values = type({}.values())
+dict_items = type({}.items())
+## misc ##
+mappingproxy = type(type.__dict__)
+
+
+### ONE-TRICK PONIES ###
+
+class Hashable(metaclass=ABCMeta):
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __hash__(self):
+ return 0
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is Hashable:
+ for B in C.__mro__:
+ if "__hash__" in B.__dict__:
+ if B.__dict__["__hash__"]:
+ return True
+ break
+ return NotImplemented
+
+
+class Iterable(metaclass=ABCMeta):
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __iter__(self):
+ while False:
+ yield None
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is Iterable:
+ if any("__iter__" in B.__dict__ for B in C.__mro__):
+ return True
+ return NotImplemented
+
+
+class Iterator(Iterable):
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __next__(self):
+ 'Return the next item from the iterator. When exhausted, raise StopIteration'
+ raise StopIteration
+
+ def __iter__(self):
+ return self
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is Iterator:
+ if (any("__next__" in B.__dict__ for B in C.__mro__) and
+ any("__iter__" in B.__dict__ for B in C.__mro__)):
+ return True
+ return NotImplemented
+
+Iterator.register(bytes_iterator)
+Iterator.register(bytearray_iterator)
+#Iterator.register(callable_iterator)
+Iterator.register(dict_keyiterator)
+Iterator.register(dict_valueiterator)
+Iterator.register(dict_itemiterator)
+Iterator.register(list_iterator)
+Iterator.register(list_reverseiterator)
+Iterator.register(range_iterator)
+Iterator.register(set_iterator)
+Iterator.register(str_iterator)
+Iterator.register(tuple_iterator)
+Iterator.register(zip_iterator)
+
+class Sized(metaclass=ABCMeta):
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __len__(self):
+ return 0
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is Sized:
+ if any("__len__" in B.__dict__ for B in C.__mro__):
+ return True
+ return NotImplemented
+
+
+class Container(metaclass=ABCMeta):
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __contains__(self, x):
+ return False
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is Container:
+ if any("__contains__" in B.__dict__ for B in C.__mro__):
+ return True
+ return NotImplemented
+
+
+class Callable(metaclass=ABCMeta):
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __call__(self, *args, **kwds):
+ return False
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is Callable:
+ if any("__call__" in B.__dict__ for B in C.__mro__):
+ return True
+ return NotImplemented
+
+
+### SETS ###
+
+
+class Set(Sized, Iterable, Container):
+
+ """A set is a finite, iterable container.
+
+ This class provides concrete generic implementations of all
+ methods except for __contains__, __iter__ and __len__.
+
+ To override the comparisons (presumably for speed, as the
+ semantics are fixed), all you have to do is redefine __le__ and
+ then the other operations will automatically follow suit.
+ """
+
+ __slots__ = ()
+
+ def __le__(self, other):
+ if not isinstance(other, Set):
+ return NotImplemented
+ if len(self) > len(other):
+ return False
+ for elem in self:
+ if elem not in other:
+ return False
+ return True
+
+ def __lt__(self, other):
+ if not isinstance(other, Set):
+ return NotImplemented
+ return len(self) < len(other) and self.__le__(other)
+
+ def __gt__(self, other):
+ if not isinstance(other, Set):
+ return NotImplemented
+ return other.__lt__(self)
+
+ def __ge__(self, other):
+ if not isinstance(other, Set):
+ return NotImplemented
+ return other.__le__(self)
+
+ def __eq__(self, other):
+ if not isinstance(other, Set):
+ return NotImplemented
+ return len(self) == len(other) and self.__le__(other)
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ @classmethod
+ def _from_iterable(cls, it):
+ '''Construct an instance of the class from any iterable input.
+
+ Must override this method if the class constructor signature
+ does not accept an iterable for an input.
+ '''
+ return cls(it)
+
+ def __and__(self, other):
+ if not isinstance(other, Iterable):
+ return NotImplemented
+ return self._from_iterable(value for value in other if value in self)
+
+ def isdisjoint(self, other):
+ 'Return True if two sets have a null intersection.'
+ for value in other:
+ if value in self:
+ return False
+ return True
+
+ def __or__(self, other):
+ if not isinstance(other, Iterable):
+ return NotImplemented
+ chain = (e for s in (self, other) for e in s)
+ return self._from_iterable(chain)
+
+ def __sub__(self, other):
+ if not isinstance(other, Set):
+ if not isinstance(other, Iterable):
+ return NotImplemented
+ other = self._from_iterable(other)
+ return self._from_iterable(value for value in self
+ if value not in other)
+
+ def __xor__(self, other):
+ if not isinstance(other, Set):
+ if not isinstance(other, Iterable):
+ return NotImplemented
+ other = self._from_iterable(other)
+ return (self - other) | (other - self)
+
+ def _hash(self):
+ """Compute the hash value of a set.
+
+ Note that we don't define __hash__: not all sets are hashable.
+ But if you define a hashable set type, its __hash__ should
+ call this function.
+
+ This must be compatible __eq__.
+
+ All sets ought to compare equal if they contain the same
+ elements, regardless of how they are implemented, and
+ regardless of the order of the elements; so there's not much
+ freedom for __eq__ or __hash__. We match the algorithm used
+ by the built-in frozenset type.
+ """
+ MAX = sys.maxsize
+ MASK = 2 * MAX + 1
+ n = len(self)
+ h = 1927868237 * (n + 1)
+ h &= MASK
+ for x in self:
+ hx = hash(x)
+ h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167
+ h &= MASK
+ h = h * 69069 + 907133923
+ h &= MASK
+ if h > MAX:
+ h -= MASK + 1
+ if h == -1:
+ h = 590923713
+ return h
+
+Set.register(frozenset)
+
+
+class MutableSet(Set):
+ """A mutable set is a finite, iterable container.
+
+ This class provides concrete generic implementations of all
+ methods except for __contains__, __iter__, __len__,
+ add(), and discard().
+
+ To override the comparisons (presumably for speed, as the
+ semantics are fixed), all you have to do is redefine __le__ and
+ then the other operations will automatically follow suit.
+ """
+
+ __slots__ = ()
+
+ @abstractmethod
+ def add(self, value):
+ """Add an element."""
+ raise NotImplementedError
+
+ @abstractmethod
+ def discard(self, value):
+ """Remove an element. Do not raise an exception if absent."""
+ raise NotImplementedError
+
+ def remove(self, value):
+ """Remove an element. If not a member, raise a KeyError."""
+ if value not in self:
+ raise KeyError(value)
+ self.discard(value)
+
+ def pop(self):
+ """Return the popped value. Raise KeyError if empty."""
+ it = iter(self)
+ try:
+ value = next(it)
+ except StopIteration:
+ raise KeyError
+ self.discard(value)
+ return value
+
+ def clear(self):
+ """This is slow (creates N new iterators!) but effective."""
+ try:
+ while True:
+ self.pop()
+ except KeyError:
+ pass
+
+ def __ior__(self, it):
+ for value in it:
+ self.add(value)
+ return self
+
+ def __iand__(self, it):
+ for value in (self - it):
+ self.discard(value)
+ return self
+
+ def __ixor__(self, it):
+ if it is self:
+ self.clear()
+ else:
+ if not isinstance(it, Set):
+ it = self._from_iterable(it)
+ for value in it:
+ if value in self:
+ self.discard(value)
+ else:
+ self.add(value)
+ return self
+
+ def __isub__(self, it):
+ if it is self:
+ self.clear()
+ else:
+ for value in it:
+ self.discard(value)
+ return self
+
+MutableSet.register(set)
+
+
+### MAPPINGS ###
+
+
+class Mapping(Sized, Iterable, Container):
+
+ __slots__ = ()
+
+ """A Mapping is a generic container for associating key/value
+ pairs.
+
+ This class provides concrete generic implementations of all
+ methods except for __getitem__, __iter__, and __len__.
+
+ """
+
+ @abstractmethod
+ def __getitem__(self, key):
+ raise KeyError
+
+ def get(self, key, default=None):
+ 'D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.'
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def __contains__(self, key):
+ try:
+ self[key]
+ except KeyError:
+ return False
+ else:
+ return True
+
+ def keys(self):
+ "D.keys() -> a set-like object providing a view on D's keys"
+ return KeysView(self)
+
+ def items(self):
+ "D.items() -> a set-like object providing a view on D's items"
+ return ItemsView(self)
+
+ def values(self):
+ "D.values() -> an object providing a view on D's values"
+ return ValuesView(self)
+
+ def __eq__(self, other):
+ if not isinstance(other, Mapping):
+ return NotImplemented
+ return dict(self.items()) == dict(other.items())
+
+ def __ne__(self, other):
+ return not (self == other)
+
+Mapping.register(mappingproxy)
+
+
+class MappingView(Sized):
+
+ def __init__(self, mapping):
+ self._mapping = mapping
+
+ def __len__(self):
+ return len(self._mapping)
+
+ def __repr__(self):
+ return '{0.__class__.__name__}({0._mapping!r})'.format(self)
+
+
+class KeysView(MappingView, Set):
+
+ @classmethod
+ def _from_iterable(self, it):
+ return set(it)
+
+ def __contains__(self, key):
+ return key in self._mapping
+
+ def __iter__(self):
+ yield from self._mapping
+
+KeysView.register(dict_keys)
+
+
+class ItemsView(MappingView, Set):
+
+ @classmethod
+ def _from_iterable(self, it):
+ return set(it)
+
+ def __contains__(self, item):
+ key, value = item
+ try:
+ v = self._mapping[key]
+ except KeyError:
+ return False
+ else:
+ return v == value
+
+ def __iter__(self):
+ for key in self._mapping:
+ yield (key, self._mapping[key])
+
+ItemsView.register(dict_items)
+
+
+class ValuesView(MappingView):
+
+ def __contains__(self, value):
+ for key in self._mapping:
+ if value == self._mapping[key]:
+ return True
+ return False
+
+ def __iter__(self):
+ for key in self._mapping:
+ yield self._mapping[key]
+
+ValuesView.register(dict_values)
+
+
+class MutableMapping(Mapping):
+
+ __slots__ = ()
+
+ """A MutableMapping is a generic container for associating
+ key/value pairs.
+
+ This class provides concrete generic implementations of all
+ methods except for __getitem__, __setitem__, __delitem__,
+ __iter__, and __len__.
+
+ """
+
+ @abstractmethod
+ def __setitem__(self, key, value):
+ raise KeyError
+
+ @abstractmethod
+ def __delitem__(self, key):
+ raise KeyError
+
+ __marker = object()
+
+ def pop(self, key, default=__marker):
+ '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+ If key is not found, d is returned if given, otherwise KeyError is raised.
+ '''
+ try:
+ value = self[key]
+ except KeyError:
+ if default is self.__marker:
+ raise
+ return default
+ else:
+ del self[key]
+ return value
+
+ def popitem(self):
+ '''D.popitem() -> (k, v), remove and return some (key, value) pair
+ as a 2-tuple; but raise KeyError if D is empty.
+ '''
+ try:
+ key = next(iter(self))
+ except StopIteration:
+ raise KeyError
+ value = self[key]
+ del self[key]
+ return key, value
+
+ def clear(self):
+ 'D.clear() -> None. Remove all items from D.'
+ try:
+ while True:
+ self.popitem()
+ except KeyError:
+ pass
+
+ def update(*args, **kwds):
+ ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.
+ If E present and has a .keys() method, does: for k in E: D[k] = E[k]
+ If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
+ In either case, this is followed by: for k, v in F.items(): D[k] = v
+ '''
+ if len(args) > 2:
+ raise TypeError("update() takes at most 2 positional "
+ "arguments ({} given)".format(len(args)))
+ elif not args:
+ raise TypeError("update() takes at least 1 argument (0 given)")
+ self = args[0]
+ other = args[1] if len(args) >= 2 else ()
+
+ if isinstance(other, Mapping):
+ for key in other:
+ self[key] = other[key]
+ elif hasattr(other, "keys"):
+ for key in other.keys():
+ self[key] = other[key]
+ else:
+ for key, value in other:
+ self[key] = value
+ for key, value in kwds.items():
+ self[key] = value
+
+ def setdefault(self, key, default=None):
+ 'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
+ try:
+ return self[key]
+ except KeyError:
+ self[key] = default
+ return default
+
+MutableMapping.register(dict)
+
+
+### SEQUENCES ###
+
+
+class Sequence(Sized, Iterable, Container):
+
+ """All the operations on a read-only sequence.
+
+ Concrete subclasses must override __new__ or __init__,
+ __getitem__, and __len__.
+ """
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __getitem__(self, index):
+ raise IndexError
+
+ def __iter__(self):
+ i = 0
+ try:
+ while True:
+ v = self[i]
+ yield v
+ i += 1
+ except IndexError:
+ return
+
+ def __contains__(self, value):
+ for v in self:
+ if v == value:
+ return True
+ return False
+
+ def __reversed__(self):
+ for i in reversed(range(len(self))):
+ yield self[i]
+
+ def index(self, value):
+ '''S.index(value) -> integer -- return first index of value.
+ Raises ValueError if the value is not present.
+ '''
+ for i, v in enumerate(self):
+ if v == value:
+ return i
+ raise ValueError
+
+ def count(self, value):
+ 'S.count(value) -> integer -- return number of occurrences of value'
+ return sum(1 for v in self if v == value)
+
+Sequence.register(tuple)
+Sequence.register(str)
+Sequence.register(range)
+Sequence.register(memoryview)
+
+
+class ByteString(Sequence):
+
+ """This unifies bytes and bytearray.
+
+ XXX Should add all their methods.
+ """
+
+ __slots__ = ()
+
+ByteString.register(bytes)
+ByteString.register(bytearray)
+
+
+class MutableSequence(Sequence):
+
+ __slots__ = ()
+
+ """All the operations on a read-write sequence.
+
+ Concrete subclasses must provide __new__ or __init__,
+ __getitem__, __setitem__, __delitem__, __len__, and insert().
+
+ """
+
+ @abstractmethod
+ def __setitem__(self, index, value):
+ raise IndexError
+
+ @abstractmethod
+ def __delitem__(self, index):
+ raise IndexError
+
+ @abstractmethod
+ def insert(self, index, value):
+ 'S.insert(index, value) -- insert value before index'
+ raise IndexError
+
+ def append(self, value):
+ 'S.append(value) -- append value to the end of the sequence'
+ self.insert(len(self), value)
+
+ def clear(self):
+ 'S.clear() -> None -- remove all items from S'
+ try:
+ while True:
+ self.pop()
+ except IndexError:
+ pass
+
+ def reverse(self):
+ 'S.reverse() -- reverse *IN PLACE*'
+ n = len(self)
+ for i in range(n//2):
+ self[i], self[n-i-1] = self[n-i-1], self[i]
+
+ def extend(self, values):
+ 'S.extend(iterable) -- extend sequence by appending elements from the iterable'
+ for v in values:
+ self.append(v)
+
+ def pop(self, index=-1):
+ '''S.pop([index]) -> item -- remove and return item at index (default last).
+ Raise IndexError if list is empty or index is out of range.
+ '''
+ v = self[index]
+ del self[index]
+ return v
+
+ def remove(self, value):
+ '''S.remove(value) -- remove first occurrence of value.
+ Raise ValueError if the value is not present.
+ '''
+ del self[self.index(value)]
+
+ def __iadd__(self, values):
+ self.extend(values)
+ return self
+
+MutableSequence.register(list)
+MutableSequence.register(bytearray) # Multiply inheriting, see ByteString
diff --git a/Lib/_dummy_thread.py b/Lib/_dummy_thread.py
index 13b1f26965..b67cfb95be 100644
--- a/Lib/_dummy_thread.py
+++ b/Lib/_dummy_thread.py
@@ -81,6 +81,10 @@ def stack_size(size=None):
raise error("setting thread stack size not supported")
return 0
+def _set_sentinel():
+ """Dummy implementation of _thread._set_sentinel()."""
+ return LockType()
+
class LockType(object):
"""Class implementing dummy implementation of _thread.LockType.
diff --git a/Lib/_osx_support.py b/Lib/_osx_support.py
index 50b2d17e90..40fc78b698 100644
--- a/Lib/_osx_support.py
+++ b/Lib/_osx_support.py
@@ -38,7 +38,7 @@ def _find_executable(executable, path=None):
paths = path.split(os.pathsep)
base, ext = os.path.splitext(executable)
- if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
+ if (sys.platform == 'win32') and (ext != '.exe'):
executable = executable + '.exe'
if not os.path.isfile(executable):
@@ -94,7 +94,7 @@ def _get_system_version():
_SYSTEM_VERSION = ''
try:
f = open('/System/Library/CoreServices/SystemVersion.plist')
- except IOError:
+ except OSError:
# We're on a plain darwin box, fall back to the default
# behaviour.
pass
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index aab60db4be..d6eee79e01 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -34,7 +34,7 @@ BlockingIOError = BlockingIOError
def open(file, mode="r", buffering=-1, encoding=None, errors=None,
newline=None, closefd=True, opener=None):
- r"""Open file and return a stream. Raise IOError upon failure.
+ r"""Open file and return a stream. Raise OSError upon failure.
file is either a text or byte string giving the name (and the path
if the file isn't in the current working directory) of the file to
@@ -62,8 +62,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
'b' binary mode
't' text mode (default)
'+' open a disk file for updating (reading and writing)
- 'U' universal newline mode (for backwards compatibility; unneeded
- for new code)
+ 'U' universal newline mode (deprecated)
========= ===============================================================
The default mode is 'rt' (open for reading text). For binary random
@@ -79,6 +78,10 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
returned as strings, the bytes having been first decoded using a
platform-dependent encoding or using the specified encoding if given.
+ 'U' mode is deprecated and will raise an exception in future versions
+ of Python. It has no effect in Python 3. Use newline to control
+ universal newlines mode.
+
buffering is an optional integer used to set the buffering policy.
Pass 0 to switch buffering off (only allowed in binary mode), 1 to select
line buffering (only usable in text mode), and an integer > 1 to indicate
@@ -129,6 +132,8 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
be kept open when the file is closed. This does not work when a file name is
given and must be True in that case.
+ The newly created file is non-inheritable.
+
A custom opener can be used by passing a callable as *opener*. The
underlying file descriptor for the file object is then obtained by calling
*opener* with (*file*, *flags*). *opener* must return an open file
@@ -172,6 +177,9 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
if "U" in modes:
if creating or writing or appending:
raise ValueError("can't use U and writing mode at once")
+ import warnings
+ warnings.warn("'U' mode is deprecated",
+ DeprecationWarning, 2)
reading = True
if text and binary:
raise ValueError("can't have text and binary mode at once")
@@ -200,7 +208,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
buffering = DEFAULT_BUFFER_SIZE
try:
bs = os.fstat(raw.fileno()).st_blksize
- except (os.error, AttributeError):
+ except (OSError, AttributeError):
pass
else:
if bs > 1:
@@ -254,7 +262,7 @@ class OpenWrapper:
try:
UnsupportedOperation = io.UnsupportedOperation
except AttributeError:
- class UnsupportedOperation(ValueError, IOError):
+ class UnsupportedOperation(ValueError, OSError):
pass
@@ -278,7 +286,7 @@ class IOBase(metaclass=abc.ABCMeta):
readinto) needed. Text I/O classes work with str data.
Note that calling any method (even inquiries) on a closed stream is
- undefined. Implementations may raise IOError in this case.
+ undefined. Implementations may raise OSError in this case.
IOBase (and its subclasses) support the iterator protocol, meaning
that an IOBase object can be iterated over yielding the lines in a
@@ -294,7 +302,7 @@ class IOBase(metaclass=abc.ABCMeta):
### Internal ###
def _unsupported(self, name):
- """Internal: raise an IOError exception for unsupported operations."""
+ """Internal: raise an OSError exception for unsupported operations."""
raise UnsupportedOperation("%s.%s() not supported" %
(self.__class__.__name__, name))
@@ -441,7 +449,7 @@ class IOBase(metaclass=abc.ABCMeta):
def fileno(self):
"""Returns underlying file descriptor (an int) if one exists.
- An IOError is raised if the IO object does not use a file descriptor.
+ An OSError is raised if the IO object does not use a file descriptor.
"""
self._unsupported("fileno")
@@ -455,11 +463,11 @@ class IOBase(metaclass=abc.ABCMeta):
### Readline[s] and writelines ###
- def readline(self, limit=-1):
+ def readline(self, size=-1):
r"""Read and return a line of bytes from the stream.
- If limit is specified, at most limit bytes will be read.
- Limit should be an int.
+ If size is specified, at most size bytes will be read.
+ Size should be an int.
The line terminator is always b'\n' for binary files; for text
files, the newlines argument to open can be used to select the line
@@ -472,18 +480,18 @@ class IOBase(metaclass=abc.ABCMeta):
if not readahead:
return 1
n = (readahead.find(b"\n") + 1) or len(readahead)
- if limit >= 0:
- n = min(n, limit)
+ if size >= 0:
+ n = min(n, size)
return n
else:
def nreadahead():
return 1
- if limit is None:
- limit = -1
- elif not isinstance(limit, int):
- raise TypeError("limit must be an integer")
+ if size is None:
+ size = -1
+ elif not isinstance(size, int):
+ raise TypeError("size must be an integer")
res = bytearray()
- while limit < 0 or len(res) < limit:
+ while size < 0 or len(res) < size:
b = self.read(nreadahead())
if not b:
break
@@ -542,17 +550,17 @@ class RawIOBase(IOBase):
# primitive operation, but that would lead to nasty recursion in case
# a subclass doesn't implement either.)
- def read(self, n=-1):
- """Read and return up to n bytes, where n is an int.
+ def read(self, size=-1):
+ """Read and return up to size bytes, where size is an int.
Returns an empty bytes object on EOF, or None if the object is
set not to block and has no data to read.
"""
- if n is None:
- n = -1
- if n < 0:
+ if size is None:
+ size = -1
+ if size < 0:
return self.readall()
- b = bytearray(n.__index__())
+ b = bytearray(size.__index__())
n = self.readinto(b)
if n is None:
return None
@@ -610,8 +618,8 @@ class BufferedIOBase(IOBase):
implementation, but wrap one.
"""
- def read(self, n=None):
- """Read and return up to n bytes, where n is an int.
+ def read(self, size=None):
+ """Read and return up to size bytes, where size is an int.
If the argument is omitted, None, or negative, reads and
returns all data until EOF.
@@ -630,9 +638,9 @@ class BufferedIOBase(IOBase):
"""
self._unsupported("read")
- def read1(self, n=None):
- """Read up to n bytes with at most one read() system call,
- where n is an int.
+ def read1(self, size=None):
+ """Read up to size bytes with at most one read() system call,
+ where size is an int.
"""
self._unsupported("read1")
@@ -699,13 +707,13 @@ class _BufferedIOMixin(BufferedIOBase):
def seek(self, pos, whence=0):
new_position = self.raw.seek(pos, whence)
if new_position < 0:
- raise IOError("seek() returned an invalid position")
+ raise OSError("seek() returned an invalid position")
return new_position
def tell(self):
pos = self.raw.tell()
if pos < 0:
- raise IOError("tell() returned an invalid position")
+ raise OSError("tell() returned an invalid position")
return pos
def truncate(self, pos=None):
@@ -820,24 +828,24 @@ class BytesIO(BufferedIOBase):
"""
return memoryview(self._buffer)
- def read(self, n=None):
+ def read(self, size=None):
if self.closed:
raise ValueError("read from closed file")
- if n is None:
- n = -1
- if n < 0:
- n = len(self._buffer)
+ if size is None:
+ size = -1
+ if size < 0:
+ size = len(self._buffer)
if len(self._buffer) <= self._pos:
return b""
- newpos = min(len(self._buffer), self._pos + n)
+ newpos = min(len(self._buffer), self._pos + size)
b = self._buffer[self._pos : newpos]
self._pos = newpos
return bytes(b)
- def read1(self, n):
+ def read1(self, size):
"""This is the same as read.
"""
- return self.read(n)
+ return self.read(size)
def write(self, b):
if self.closed:
@@ -927,7 +935,7 @@ class BufferedReader(_BufferedIOMixin):
"""Create a new buffered reader using the given readable raw IO object.
"""
if not raw.readable():
- raise IOError('"raw" argument must be readable.')
+ raise OSError('"raw" argument must be readable.')
_BufferedIOMixin.__init__(self, raw)
if buffer_size <= 0:
@@ -940,18 +948,18 @@ class BufferedReader(_BufferedIOMixin):
self._read_buf = b""
self._read_pos = 0
- def read(self, n=None):
- """Read n bytes.
+ def read(self, size=None):
+ """Read size bytes.
- Returns exactly n bytes of data unless the underlying raw IO
+ Returns exactly size bytes of data unless the underlying raw IO
stream reaches EOF or if the call would block in non-blocking
- mode. If n is negative, read until EOF or until read() would
+ mode. If size is negative, read until EOF or until read() would
block.
"""
- if n is not None and n < -1:
+ if size is not None and size < -1:
raise ValueError("invalid number of bytes to read")
with self._read_lock:
- return self._read_unlocked(n)
+ return self._read_unlocked(size)
def _read_unlocked(self, n=None):
nodata_val = b""
@@ -1011,7 +1019,7 @@ class BufferedReader(_BufferedIOMixin):
self._read_pos = 0
return out[:n] if out else nodata_val
- def peek(self, n=0):
+ def peek(self, size=0):
"""Returns buffered bytes without advancing the position.
The argument indicates a desired minimal number of bytes; we
@@ -1019,7 +1027,7 @@ class BufferedReader(_BufferedIOMixin):
than self.buffer_size.
"""
with self._read_lock:
- return self._peek_unlocked(n)
+ return self._peek_unlocked(size)
def _peek_unlocked(self, n=0):
want = min(n, self.buffer_size)
@@ -1037,18 +1045,18 @@ class BufferedReader(_BufferedIOMixin):
self._read_pos = 0
return self._read_buf[self._read_pos:]
- def read1(self, n):
- """Reads up to n bytes, with at most one read() system call."""
- # Returns up to n bytes. If at least one byte is buffered, we
+ def read1(self, size):
+ """Reads up to size bytes, with at most one read() system call."""
+ # Returns up to size bytes. If at least one byte is buffered, we
# only return buffered bytes. Otherwise, we do one raw read.
- if n < 0:
+ if size < 0:
raise ValueError("number of bytes to read must be positive")
- if n == 0:
+ if size == 0:
return b""
with self._read_lock:
self._peek_unlocked(1)
return self._read_unlocked(
- min(n, len(self._read_buf) - self._read_pos))
+ min(size, len(self._read_buf) - self._read_pos))
def tell(self):
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
@@ -1074,7 +1082,7 @@ class BufferedWriter(_BufferedIOMixin):
def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE):
if not raw.writable():
- raise IOError('"raw" argument must be writable.')
+ raise OSError('"raw" argument must be writable.')
_BufferedIOMixin.__init__(self, raw)
if buffer_size <= 0:
@@ -1138,7 +1146,7 @@ class BufferedWriter(_BufferedIOMixin):
errno.EAGAIN,
"write could not complete without blocking", 0)
if n > len(self._write_buf) or n < 0:
- raise IOError("write() returned incorrect number of bytes")
+ raise OSError("write() returned incorrect number of bytes")
del self._write_buf[:n]
def tell(self):
@@ -1174,18 +1182,18 @@ class BufferedRWPair(BufferedIOBase):
The arguments are two RawIO instances.
"""
if not reader.readable():
- raise IOError('"reader" argument must be readable.')
+ raise OSError('"reader" argument must be readable.')
if not writer.writable():
- raise IOError('"writer" argument must be writable.')
+ raise OSError('"writer" argument must be writable.')
self.reader = BufferedReader(reader, buffer_size)
self.writer = BufferedWriter(writer, buffer_size)
- def read(self, n=None):
- if n is None:
- n = -1
- return self.reader.read(n)
+ def read(self, size=None):
+ if size is None:
+ size = -1
+ return self.reader.read(size)
def readinto(self, b):
return self.reader.readinto(b)
@@ -1193,11 +1201,11 @@ class BufferedRWPair(BufferedIOBase):
def write(self, b):
return self.writer.write(b)
- def peek(self, n=0):
- return self.reader.peek(n)
+ def peek(self, size=0):
+ return self.reader.peek(size)
- def read1(self, n):
- return self.reader.read1(n)
+ def read1(self, size):
+ return self.reader.read1(size)
def readable(self):
return self.reader.readable()
@@ -1248,7 +1256,7 @@ class BufferedRandom(BufferedWriter, BufferedReader):
with self._read_lock:
self._reset_read_buf()
if pos < 0:
- raise IOError("seek() returned invalid position")
+ raise OSError("seek() returned invalid position")
return pos
def tell(self):
@@ -1263,23 +1271,23 @@ class BufferedRandom(BufferedWriter, BufferedReader):
# Use seek to flush the read buffer.
return BufferedWriter.truncate(self, pos)
- def read(self, n=None):
- if n is None:
- n = -1
+ def read(self, size=None):
+ if size is None:
+ size = -1
self.flush()
- return BufferedReader.read(self, n)
+ return BufferedReader.read(self, size)
def readinto(self, b):
self.flush()
return BufferedReader.readinto(self, b)
- def peek(self, n=0):
+ def peek(self, size=0):
self.flush()
- return BufferedReader.peek(self, n)
+ return BufferedReader.peek(self, size)
- def read1(self, n):
+ def read1(self, size):
self.flush()
- return BufferedReader.read1(self, n)
+ return BufferedReader.read1(self, size)
def write(self, b):
if self._read_buf:
@@ -1299,11 +1307,11 @@ class TextIOBase(IOBase):
are immutable. There is no public constructor.
"""
- def read(self, n=-1):
- """Read at most n characters from stream, where n is an int.
+ def read(self, size=-1):
+ """Read at most size characters from stream, where size is an int.
- Read from underlying buffer until we have n characters or we hit EOF.
- If n is negative or omitted, read until EOF.
+ Read from underlying buffer until we have size characters or we hit EOF.
+ If size is negative or omitted, read until EOF.
Returns a string.
"""
@@ -1727,7 +1735,7 @@ class TextIOWrapper(TextIOBase):
if not self._seekable:
raise UnsupportedOperation("underlying stream is not seekable")
if not self._telling:
- raise IOError("telling position disabled by next() call")
+ raise OSError("telling position disabled by next() call")
self.flush()
position = self.buffer.tell()
decoder = self._decoder
@@ -1814,7 +1822,7 @@ class TextIOWrapper(TextIOBase):
chars_decoded += len(decoder.decode(b'', final=True))
need_eof = 1
if chars_decoded < chars_to_skip:
- raise IOError("can't reconstruct logical file position")
+ raise OSError("can't reconstruct logical file position")
# The returned cookie corresponds to the last safe start point.
return self._pack_cookie(
@@ -1891,7 +1899,7 @@ class TextIOWrapper(TextIOBase):
# Skip chars_to_skip of the decoded characters.
if len(self._decoded_chars) < chars_to_skip:
- raise IOError("can't restore logical file position")
+ raise OSError("can't restore logical file position")
self._decoded_chars_used = chars_to_skip
# Finally, reset the encoder (merely useful for proper BOM handling)
@@ -1907,16 +1915,16 @@ class TextIOWrapper(TextIOBase):
encoder.reset()
return cookie
- def read(self, n=None):
+ def read(self, size=None):
self._checkReadable()
- if n is None:
- n = -1
+ if size is None:
+ size = -1
decoder = self._decoder or self._get_decoder()
try:
- n.__index__
+ size.__index__
except AttributeError as err:
raise TypeError("an integer is required") from err
- if n < 0:
+ if size < 0:
# Read everything.
result = (self._get_decoded_chars() +
decoder.decode(self.buffer.read(), final=True))
@@ -1924,12 +1932,12 @@ class TextIOWrapper(TextIOBase):
self._snapshot = None
return result
else:
- # Keep reading chunks until we have n characters to return.
+ # Keep reading chunks until we have size characters to return.
eof = False
- result = self._get_decoded_chars(n)
- while len(result) < n and not eof:
+ result = self._get_decoded_chars(size)
+ while len(result) < size and not eof:
eof = not self._read_chunk()
- result += self._get_decoded_chars(n - len(result))
+ result += self._get_decoded_chars(size - len(result))
return result
def __next__(self):
@@ -1941,13 +1949,13 @@ class TextIOWrapper(TextIOBase):
raise StopIteration
return line
- def readline(self, limit=None):
+ def readline(self, size=None):
if self.closed:
raise ValueError("read from closed file")
- if limit is None:
- limit = -1
- elif not isinstance(limit, int):
- raise TypeError("limit must be an integer")
+ if size is None:
+ size = -1
+ elif not isinstance(size, int):
+ raise TypeError("size must be an integer")
# Grab all the decoded text (we will rewind any extra bits later).
line = self._get_decoded_chars()
@@ -2006,8 +2014,8 @@ class TextIOWrapper(TextIOBase):
endpos = pos + len(self._readnl)
break
- if limit >= 0 and len(line) >= limit:
- endpos = limit # reached length limit
+ if size >= 0 and len(line) >= size:
+ endpos = size # reached length size
break
# No line ending seen yet - get more data'
@@ -2022,8 +2030,8 @@ class TextIOWrapper(TextIOBase):
self._snapshot = None
return line
- if limit >= 0 and endpos > limit:
- endpos = limit # don't exceed limit
+ if size >= 0 and endpos > size:
+ endpos = size # don't exceed size
# Rewind _decoded_chars to just after the line ending we found.
self._rewind_decoded_chars(len(line) - endpos)
@@ -2054,7 +2062,6 @@ class StringIO(TextIOWrapper):
if not isinstance(initial_value, str):
raise TypeError("initial_value must be str or None, not {0}"
.format(type(initial_value).__name__))
- initial_value = str(initial_value)
self.write(initial_value)
self.seek(0)
diff --git a/Lib/_sitebuiltins.py b/Lib/_sitebuiltins.py
new file mode 100644
index 0000000000..1f21358e26
--- /dev/null
+++ b/Lib/_sitebuiltins.py
@@ -0,0 +1,99 @@
+"""
+The objects used by the site module to add custom builtins.
+"""
+
+# Those objects are almost immortal and they keep a reference to their module
+# globals. Defining them in the site module would keep too many references
+# alive.
+# Note this means this module should also avoid keep things alive in its
+# globals.
+
+import sys
+
+class Quitter(object):
+ def __init__(self, name, eof):
+ self.name = name
+ self.eof = eof
+ def __repr__(self):
+ return 'Use %s() or %s to exit' % (self.name, self.eof)
+ def __call__(self, code=None):
+ # Shells like IDLE catch the SystemExit, but listen when their
+ # stdin wrapper is closed.
+ try:
+ sys.stdin.close()
+ except:
+ pass
+ raise SystemExit(code)
+
+
+class _Printer(object):
+ """interactive prompt objects for printing the license text, a list of
+ contributors and the copyright notice."""
+
+ MAXLINES = 23
+
+ def __init__(self, name, data, files=(), dirs=()):
+ import os
+ self.__name = name
+ self.__data = data
+ self.__lines = None
+ self.__filenames = [os.path.join(dir, filename)
+ for dir in dirs
+ for filename in files]
+
+ def __setup(self):
+ if self.__lines:
+ return
+ data = None
+ for filename in self.__filenames:
+ try:
+ with open(filename, "r") as fp:
+ data = fp.read()
+ break
+ except OSError:
+ pass
+ if not data:
+ data = self.__data
+ self.__lines = data.split('\n')
+ self.__linecnt = len(self.__lines)
+
+ def __repr__(self):
+ self.__setup()
+ if len(self.__lines) <= self.MAXLINES:
+ return "\n".join(self.__lines)
+ else:
+ return "Type %s() to see the full %s text" % ((self.__name,)*2)
+
+ def __call__(self):
+ self.__setup()
+ prompt = 'Hit Return for more, or q (and Return) to quit: '
+ lineno = 0
+ while 1:
+ try:
+ for i in range(lineno, lineno + self.MAXLINES):
+ print(self.__lines[i])
+ except IndexError:
+ break
+ else:
+ lineno += self.MAXLINES
+ key = None
+ while key is None:
+ key = input(prompt)
+ if key not in ('', 'q'):
+ key = None
+ if key == 'q':
+ break
+
+
+class _Helper(object):
+ """Define the builtin 'help'.
+ This is a wrapper around pydoc.help (with a twist).
+
+ """
+
+ def __repr__(self):
+ return "Type help() for interactive help, " \
+ "or help(object) for help about object."
+ def __call__(self, *args, **kwds):
+ import pydoc
+ return pydoc.help(*args, **kwds)
diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index 9058a69861..d101c6de05 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -21,7 +21,7 @@ from datetime import (date as datetime_date,
timezone as datetime_timezone)
try:
from _thread import allocate_lock as _thread_allocate_lock
-except:
+except ImportError:
from _dummy_thread import allocate_lock as _thread_allocate_lock
__all__ = []
diff --git a/Lib/abc.py b/Lib/abc.py
index 09778e8609..264c60c428 100644
--- a/Lib/abc.py
+++ b/Lib/abc.py
@@ -5,6 +5,7 @@
from _weakrefset import WeakSet
+
def abstractmethod(funcobj):
"""A decorator indicating abstract methods.
@@ -124,6 +125,8 @@ class ABCMeta(type):
# A global counter that is incremented each time a class is
# registered as a virtual subclass of anything. It forces the
# negative cache to be cleared before its next use.
+ # Note: this counter is private. Use `abc.get_cache_token()` for
+ # external code.
_abc_invalidation_counter = 0
def __new__(mcls, name, bases, namespace):
@@ -226,3 +229,20 @@ class ABCMeta(type):
# No dice; update negative cache
cls._abc_negative_cache.add(subclass)
return False
+
+
+class ABC(metaclass=ABCMeta):
+ """Helper class that provides a standard way to create an ABC using
+ inheritance.
+ """
+ pass
+
+
+def get_cache_token():
+ """Returns the current ABC cache token.
+
+ The token is an opaque integer identifying the current version of
+ the ABC cache for virtual subclasses. This number changes with
+ every call to ``register()`` on any ABC.
+ """
+ return ABCMeta._abc_invalidation_counter
diff --git a/Lib/aifc.py b/Lib/aifc.py
index 86a5edc8c9..c1c8ea7055 100644
--- a/Lib/aifc.py
+++ b/Lib/aifc.py
@@ -69,7 +69,7 @@ This returns an instance of a class with the following public methods:
getcomptype() -- returns compression type ('NONE' for AIFF files)
getcompname() -- returns human-readable version of
compression type ('not compressed' for AIFF files)
- getparams() -- returns a tuple consisting of all of the
+ getparams() -- returns a namedtuple consisting of all of the
above in the above order
getmarkers() -- get the list of marks in the audio file or None
if there are no marks
@@ -252,6 +252,11 @@ def _write_float(f, x):
_write_ulong(f, lomant)
from chunk import Chunk
+from collections import namedtuple
+
+_aifc_params = namedtuple('_aifc_params',
+ 'nchannels sampwidth framerate nframes comptype compname')
+
class Aifc_read:
# Variables used in this class:
@@ -334,6 +339,12 @@ class Aifc_read:
# else, assume it is an open file object already
self.initfp(f)
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
#
# User visible methods.
#
@@ -372,9 +383,9 @@ class Aifc_read:
## return self._version
def getparams(self):
- return self.getnchannels(), self.getsampwidth(), \
- self.getframerate(), self.getnframes(), \
- self.getcomptype(), self.getcompname()
+ return _aifc_params(self.getnchannels(), self.getsampwidth(),
+ self.getframerate(), self.getnframes(),
+ self.getcomptype(), self.getcompname())
def getmarkers(self):
if len(self._markers) == 0:
@@ -551,6 +562,12 @@ class Aifc_write:
def __del__(self):
self.close()
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
#
# User visible methods.
#
@@ -644,8 +661,8 @@ class Aifc_write:
def getparams(self):
if not self._nchannels or not self._sampwidth or not self._framerate:
raise Error('not all parameters set')
- return self._nchannels, self._sampwidth, self._framerate, \
- self._nframes, self._comptype, self._compname
+ return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
+ self._nframes, self._comptype, self._compname)
def setmark(self, id, pos, name):
if id <= 0:
@@ -675,6 +692,8 @@ class Aifc_write:
return self._nframeswritten
def writeframesraw(self, data):
+ if not isinstance(data, (bytes, bytearray)):
+ data = memoryview(data).cast('B')
self._ensure_header_written(len(data))
nframes = len(data) // (self._sampwidth * self._nchannels)
if self._convert:
@@ -873,8 +892,7 @@ if __name__ == '__main__':
if not sys.argv[1:]:
sys.argv.append('/usr/demos/data/audio/bach.aiff')
fn = sys.argv[1]
- f = open(fn, 'r')
- try:
+ with open(fn, 'r') as f:
print("Reading", fn)
print("nchannels =", f.getnchannels())
print("nframes =", f.getnframes())
@@ -885,16 +903,11 @@ if __name__ == '__main__':
if sys.argv[2:]:
gn = sys.argv[2]
print("Writing", gn)
- g = open(gn, 'w')
- try:
+ with open(gn, 'w') as g:
g.setparams(f.getparams())
while 1:
data = f.readframes(1024)
if not data:
break
g.writeframes(data)
- finally:
- g.close()
print("Done.")
- finally:
- f.close()
diff --git a/Lib/argparse.py b/Lib/argparse.py
index f25b1b6610..9520e0ea7c 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -606,8 +606,7 @@ class HelpFormatter(object):
pass
else:
self._indent()
- for subaction in get_subactions():
- yield subaction
+ yield from get_subactions()
self._dedent()
def _split_lines(self, text, width):
@@ -1038,7 +1037,8 @@ class _VersionAction(Action):
version = parser.version
formatter = parser._get_formatter()
formatter.add_text(version)
- parser.exit(message=formatter.format_help())
+ parser._print_message(formatter.format_help(), _sys.stdout)
+ parser.exit()
class _SubParsersAction(Action):
@@ -1141,11 +1141,17 @@ class FileType(object):
same values as the builtin open() function.
- bufsize -- The file's desired buffer size. Accepts the same values as
the builtin open() function.
+ - encoding -- The file's encoding. Accepts the same values as the
+ builtin open() function.
+ - errors -- A string indicating how encoding and decoding errors are to
+ be handled. Accepts the same value as the builtin open() function.
"""
- def __init__(self, mode='r', bufsize=-1):
+ def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None):
self._mode = mode
self._bufsize = bufsize
+ self._encoding = encoding
+ self._errors = errors
def __call__(self, string):
# the special argument "-" means sys.std{in,out}
@@ -1160,14 +1166,18 @@ class FileType(object):
# all other arguments are used as file names
try:
- return open(string, self._mode, self._bufsize)
- except IOError as e:
+ return open(string, self._mode, self._bufsize, self._encoding,
+ self._errors)
+ except OSError as e:
message = _("can't open '%s': %s")
raise ArgumentTypeError(message % (string, e))
def __repr__(self):
args = self._mode, self._bufsize
- args_str = ', '.join(repr(arg) for arg in args if arg != -1)
+ kwargs = [('encoding', self._encoding), ('errors', self._errors)]
+ args_str = ', '.join([repr(arg) for arg in args if arg != -1] +
+ ['%s=%r' % (kw, arg) for kw, arg in kwargs
+ if arg is not None])
return '%s(%s)' % (type(self).__name__, args_str)
# ===========================
@@ -2001,17 +2011,14 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# replace arguments referencing files with the file content
else:
try:
- args_file = open(arg_string[1:])
- try:
+ with open(arg_string[1:]) as args_file:
arg_strings = []
for arg_line in args_file.read().splitlines():
for arg in self.convert_arg_line_to_args(arg_line):
arg_strings.append(arg)
arg_strings = self._read_args_from_files(arg_strings)
new_arg_strings.extend(arg_strings)
- finally:
- args_file.close()
- except IOError:
+ except OSError:
err = _sys.exc_info()[1]
self.error(str(err))
diff --git a/Lib/ast.py b/Lib/ast.py
index 13f59f9dfa..02c3b2867f 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -42,7 +42,6 @@ def literal_eval(node_or_string):
Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
sets, booleans, and None.
"""
- _safe_names = {'None': None, 'True': True, 'False': False}
if isinstance(node_or_string, str):
node_or_string = parse(node_or_string, mode='eval')
if isinstance(node_or_string, Expression):
@@ -61,9 +60,8 @@ def literal_eval(node_or_string):
elif isinstance(node, Dict):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
- elif isinstance(node, Name):
- if node.id in _safe_names:
- return _safe_names[node.id]
+ elif isinstance(node, NameConstant):
+ return node.value
elif isinstance(node, UnaryOp) and \
isinstance(node.op, (UAdd, USub)) and \
isinstance(node.operand, (Num, UnaryOp, BinOp)):
diff --git a/Lib/asynchat.py b/Lib/asynchat.py
index eea3418762..0378fa7069 100644
--- a/Lib/asynchat.py
+++ b/Lib/asynchat.py
@@ -56,8 +56,8 @@ class async_chat (asyncore.dispatcher):
# these are overridable defaults
- ac_in_buffer_size = 4096
- ac_out_buffer_size = 4096
+ ac_in_buffer_size = 65536
+ ac_out_buffer_size = 65536
# we don't want to enable the use of encoding by default, because that is a
# sign of an application bug that we don't want to pass silently
@@ -111,7 +111,7 @@ class async_chat (asyncore.dispatcher):
try:
data = self.recv (self.ac_in_buffer_size)
- except socket.error as why:
+ except OSError as why:
self.handle_error()
return
@@ -240,7 +240,7 @@ class async_chat (asyncore.dispatcher):
# send the data
try:
num_sent = self.send(data)
- except socket.error:
+ except OSError:
self.handle_error()
return
diff --git a/Lib/asyncio/__init__.py b/Lib/asyncio/__init__.py
new file mode 100644
index 0000000000..0d288d5a02
--- /dev/null
+++ b/Lib/asyncio/__init__.py
@@ -0,0 +1,41 @@
+"""The asyncio package, tracking PEP 3156."""
+
+import sys
+
+# The selectors module is in the stdlib in Python 3.4 but not in 3.3.
+# Do this first, so the other submodules can use "from . import selectors".
+# Prefer asyncio/selectors.py over the stdlib one, as ours may be newer.
+try:
+ from . import selectors
+except ImportError:
+ import selectors # Will also be exported.
+
+if sys.platform == 'win32':
+ # Similar thing for _overlapped.
+ try:
+ from . import _overlapped
+ except ImportError:
+ import _overlapped # Will also be exported.
+
+# This relies on each of the submodules having an __all__ variable.
+from .futures import *
+from .events import *
+from .locks import *
+from .transports import *
+from .protocols import *
+from .streams import *
+from .tasks import *
+
+if sys.platform == 'win32': # pragma: no cover
+ from .windows_events import *
+else:
+ from .unix_events import * # pragma: no cover
+
+
+__all__ = (futures.__all__ +
+ events.__all__ +
+ locks.__all__ +
+ transports.__all__ +
+ protocols.__all__ +
+ streams.__all__ +
+ tasks.__all__)
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
new file mode 100644
index 0000000000..f2d117bdbd
--- /dev/null
+++ b/Lib/asyncio/base_events.py
@@ -0,0 +1,644 @@
+"""Base implementation of event loop.
+
+The event loop can be broken up into a multiplexer (the part
+responsible for notifying us of IO events) and the event loop proper,
+which wraps a multiplexer with functionality for scheduling callbacks,
+immediately or at a given time in the future.
+
+Whenever a public API takes a callback, subsequent positional
+arguments will be passed to the callback if/when it is called. This
+avoids the proliferation of trivial lambdas implementing closures.
+Keyword arguments for the callback are not supported; this is a
+conscious design decision, leaving the door open for keyword arguments
+to modify the meaning of the API call itself.
+"""
+
+
+import collections
+import concurrent.futures
+import heapq
+import logging
+import socket
+import subprocess
+import time
+import os
+import sys
+
+from . import events
+from . import futures
+from . import tasks
+from .log import logger
+
+
+__all__ = ['BaseEventLoop', 'Server']
+
+
+# Argument for default thread pool executor creation.
+_MAX_WORKERS = 5
+
+
+class _StopError(BaseException):
+ """Raised to stop the event loop."""
+
+
+def _raise_stop_error(*args):
+ raise _StopError
+
+
+class Server(events.AbstractServer):
+
+ def __init__(self, loop, sockets):
+ self.loop = loop
+ self.sockets = sockets
+ self.active_count = 0
+ self.waiters = []
+
+ def attach(self, transport):
+ assert self.sockets is not None
+ self.active_count += 1
+
+ def detach(self, transport):
+ assert self.active_count > 0
+ self.active_count -= 1
+ if self.active_count == 0 and self.sockets is None:
+ self._wakeup()
+
+ def close(self):
+ sockets = self.sockets
+ if sockets is not None:
+ self.sockets = None
+ for sock in sockets:
+ self.loop._stop_serving(sock)
+ if self.active_count == 0:
+ self._wakeup()
+
+ def _wakeup(self):
+ waiters = self.waiters
+ self.waiters = None
+ for waiter in waiters:
+ if not waiter.done():
+ waiter.set_result(waiter)
+
+ @tasks.coroutine
+ def wait_closed(self):
+ if self.sockets is None or self.waiters is None:
+ return
+ waiter = futures.Future(loop=self.loop)
+ self.waiters.append(waiter)
+ yield from waiter
+
+
+class BaseEventLoop(events.AbstractEventLoop):
+
+ def __init__(self):
+ self._ready = collections.deque()
+ self._scheduled = []
+ self._default_executor = None
+ self._internal_fds = 0
+ self._running = False
+
+ def _make_socket_transport(self, sock, protocol, waiter=None, *,
+ extra=None, server=None):
+ """Create socket transport."""
+ raise NotImplementedError
+
+ def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter, *,
+ server_side=False, server_hostname=None,
+ extra=None, server=None):
+ """Create SSL transport."""
+ raise NotImplementedError
+
+ def _make_datagram_transport(self, sock, protocol,
+ address=None, extra=None):
+ """Create datagram transport."""
+ raise NotImplementedError
+
+ def _make_read_pipe_transport(self, pipe, protocol, waiter=None,
+ extra=None):
+ """Create read pipe transport."""
+ raise NotImplementedError
+
+ def _make_write_pipe_transport(self, pipe, protocol, waiter=None,
+ extra=None):
+ """Create write pipe transport."""
+ raise NotImplementedError
+
+ @tasks.coroutine
+ def _make_subprocess_transport(self, protocol, args, shell,
+ stdin, stdout, stderr, bufsize,
+ extra=None, **kwargs):
+ """Create subprocess transport."""
+ raise NotImplementedError
+
+ def _read_from_self(self):
+ """XXX"""
+ raise NotImplementedError
+
+ def _write_to_self(self):
+ """XXX"""
+ raise NotImplementedError
+
+ def _process_events(self, event_list):
+ """Process selector events."""
+ raise NotImplementedError
+
+ def run_forever(self):
+ """Run until stop() is called."""
+ if self._running:
+ raise RuntimeError('Event loop is running.')
+ self._running = True
+ try:
+ while True:
+ try:
+ self._run_once()
+ except _StopError:
+ break
+ finally:
+ self._running = False
+
+ def run_until_complete(self, future):
+ """Run until the Future is done.
+
+ If the argument is a coroutine, it is wrapped in a Task.
+
+ XXX TBD: It would be disastrous to call run_until_complete()
+ with the same coroutine twice -- it would wrap it in two
+ different Tasks and that can't be good.
+
+ Return the Future's result, or raise its exception.
+ """
+ future = tasks.async(future, loop=self)
+ future.add_done_callback(_raise_stop_error)
+ self.run_forever()
+ future.remove_done_callback(_raise_stop_error)
+ if not future.done():
+ raise RuntimeError('Event loop stopped before Future completed.')
+
+ return future.result()
+
+ def stop(self):
+ """Stop running the event loop.
+
+ Every callback scheduled before stop() is called will run.
+ Callback scheduled after stop() is called won't. However,
+ those callbacks will run if run() is called again later.
+ """
+ self.call_soon(_raise_stop_error)
+
+ def close(self):
+ """Close the event loop.
+
+ This clears the queues and shuts down the executor,
+ but does not wait for the executor to finish.
+ """
+ self._ready.clear()
+ self._scheduled.clear()
+ executor = self._default_executor
+ if executor is not None:
+ self._default_executor = None
+ executor.shutdown(wait=False)
+
+ def is_running(self):
+ """Returns running status of event loop."""
+ return self._running
+
+ def time(self):
+ """Return the time according to the event loop's clock."""
+ return time.monotonic()
+
+ def call_later(self, delay, callback, *args):
+ """Arrange for a callback to be called at a given time.
+
+ Return a Handle: an opaque object with a cancel() method that
+ can be used to cancel the call.
+
+ The delay can be an int or float, expressed in seconds. It is
+ always a relative time.
+
+ Each callback will be called exactly once. If two callbacks
+ are scheduled for exactly the same time, it undefined which
+ will be called first.
+
+ Any positional arguments after the callback will be passed to
+ the callback when it is called.
+ """
+ return self.call_at(self.time() + delay, callback, *args)
+
+ def call_at(self, when, callback, *args):
+ """Like call_later(), but uses an absolute time."""
+ timer = events.TimerHandle(when, callback, args)
+ heapq.heappush(self._scheduled, timer)
+ return timer
+
+ def call_soon(self, callback, *args):
+ """Arrange for a callback to be called as soon as possible.
+
+ This operates as a FIFO queue, callbacks are called in the
+ order in which they are registered. Each callback will be
+ called exactly once.
+
+ Any positional arguments after the callback will be passed to
+ the callback when it is called.
+ """
+ handle = events.make_handle(callback, args)
+ self._ready.append(handle)
+ return handle
+
+ def call_soon_threadsafe(self, callback, *args):
+ """XXX"""
+ handle = self.call_soon(callback, *args)
+ self._write_to_self()
+ return handle
+
+ def run_in_executor(self, executor, callback, *args):
+ if isinstance(callback, events.Handle):
+ assert not args
+ assert not isinstance(callback, events.TimerHandle)
+ if callback._cancelled:
+ f = futures.Future(loop=self)
+ f.set_result(None)
+ return f
+ callback, args = callback._callback, callback._args
+ if executor is None:
+ executor = self._default_executor
+ if executor is None:
+ executor = concurrent.futures.ThreadPoolExecutor(_MAX_WORKERS)
+ self._default_executor = executor
+ return futures.wrap_future(executor.submit(callback, *args), loop=self)
+
+ def set_default_executor(self, executor):
+ self._default_executor = executor
+
+ def getaddrinfo(self, host, port, *,
+ family=0, type=0, proto=0, flags=0):
+ return self.run_in_executor(None, socket.getaddrinfo,
+ host, port, family, type, proto, flags)
+
+ def getnameinfo(self, sockaddr, flags=0):
+ return self.run_in_executor(None, socket.getnameinfo, sockaddr, flags)
+
+ @tasks.coroutine
+ def create_connection(self, protocol_factory, host=None, port=None, *,
+ ssl=None, family=0, proto=0, flags=0, sock=None,
+ local_addr=None, server_hostname=None):
+ """XXX"""
+ if server_hostname is not None and not ssl:
+ raise ValueError('server_hostname is only meaningful with ssl')
+
+ if server_hostname is None and ssl:
+ # Use host as default for server_hostname. It is an error
+ # if host is empty or not set, e.g. when an
+ # already-connected socket was passed or when only a port
+ # is given. To avoid this error, you can pass
+ # server_hostname='' -- this will bypass the hostname
+ # check. (This also means that if host is a numeric
+ # IP/IPv6 address, we will attempt to verify that exact
+ # address; this will probably fail, but it is possible to
+ # create a certificate for a specific IP address, so we
+ # don't judge it here.)
+ if not host:
+ raise ValueError('You must set server_hostname '
+ 'when using ssl without a host')
+ server_hostname = host
+
+ if host is not None or port is not None:
+ if sock is not None:
+ raise ValueError(
+ 'host/port and sock can not be specified at the same time')
+
+ f1 = self.getaddrinfo(
+ host, port, family=family,
+ type=socket.SOCK_STREAM, proto=proto, flags=flags)
+ fs = [f1]
+ if local_addr is not None:
+ f2 = self.getaddrinfo(
+ *local_addr, family=family,
+ type=socket.SOCK_STREAM, proto=proto, flags=flags)
+ fs.append(f2)
+ else:
+ f2 = None
+
+ yield from tasks.wait(fs, loop=self)
+
+ infos = f1.result()
+ if not infos:
+ raise OSError('getaddrinfo() returned empty list')
+ if f2 is not None:
+ laddr_infos = f2.result()
+ if not laddr_infos:
+ raise OSError('getaddrinfo() returned empty list')
+
+ exceptions = []
+ for family, type, proto, cname, address in infos:
+ try:
+ sock = socket.socket(family=family, type=type, proto=proto)
+ sock.setblocking(False)
+ if f2 is not None:
+ for _, _, _, _, laddr in laddr_infos:
+ try:
+ sock.bind(laddr)
+ break
+ except OSError as exc:
+ exc = OSError(
+ exc.errno, 'error while '
+ 'attempting to bind on address '
+ '{!r}: {}'.format(
+ laddr, exc.strerror.lower()))
+ exceptions.append(exc)
+ else:
+ sock.close()
+ sock = None
+ continue
+ yield from self.sock_connect(sock, address)
+ except OSError as exc:
+ if sock is not None:
+ sock.close()
+ exceptions.append(exc)
+ else:
+ break
+ else:
+ if len(exceptions) == 1:
+ raise exceptions[0]
+ else:
+ # If they all have the same str(), raise one.
+ model = str(exceptions[0])
+ if all(str(exc) == model for exc in exceptions):
+ raise exceptions[0]
+ # Raise a combined exception so the user can see all
+ # the various error messages.
+ raise OSError('Multiple exceptions: {}'.format(
+ ', '.join(str(exc) for exc in exceptions)))
+
+ elif sock is None:
+ raise ValueError(
+ 'host and port was not specified and no sock specified')
+
+ sock.setblocking(False)
+
+ protocol = protocol_factory()
+ waiter = futures.Future(loop=self)
+ if ssl:
+ sslcontext = None if isinstance(ssl, bool) else ssl
+ transport = self._make_ssl_transport(
+ sock, protocol, sslcontext, waiter,
+ server_side=False, server_hostname=server_hostname)
+ else:
+ transport = self._make_socket_transport(sock, protocol, waiter)
+
+ yield from waiter
+ return transport, protocol
+
+ @tasks.coroutine
+ def create_datagram_endpoint(self, protocol_factory,
+ local_addr=None, remote_addr=None, *,
+ family=0, proto=0, flags=0):
+ """Create datagram connection."""
+ if not (local_addr or remote_addr):
+ if family == 0:
+ raise ValueError('unexpected address family')
+ addr_pairs_info = (((family, proto), (None, None)),)
+ else:
+ # join addresss by (family, protocol)
+ addr_infos = collections.OrderedDict()
+ for idx, addr in ((0, local_addr), (1, remote_addr)):
+ if addr is not None:
+ assert isinstance(addr, tuple) and len(addr) == 2, (
+ '2-tuple is expected')
+
+ infos = yield from self.getaddrinfo(
+ *addr, family=family, type=socket.SOCK_DGRAM,
+ proto=proto, flags=flags)
+ if not infos:
+ raise OSError('getaddrinfo() returned empty list')
+
+ for fam, _, pro, _, address in infos:
+ key = (fam, pro)
+ if key not in addr_infos:
+ addr_infos[key] = [None, None]
+ addr_infos[key][idx] = address
+
+ # each addr has to have info for each (family, proto) pair
+ addr_pairs_info = [
+ (key, addr_pair) for key, addr_pair in addr_infos.items()
+ if not ((local_addr and addr_pair[0] is None) or
+ (remote_addr and addr_pair[1] is None))]
+
+ if not addr_pairs_info:
+ raise ValueError('can not get address information')
+
+ exceptions = []
+
+ for ((family, proto),
+ (local_address, remote_address)) in addr_pairs_info:
+ sock = None
+ r_addr = None
+ try:
+ sock = socket.socket(
+ family=family, type=socket.SOCK_DGRAM, proto=proto)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setblocking(False)
+
+ if local_addr:
+ sock.bind(local_address)
+ if remote_addr:
+ yield from self.sock_connect(sock, remote_address)
+ r_addr = remote_address
+ except OSError as exc:
+ if sock is not None:
+ sock.close()
+ exceptions.append(exc)
+ else:
+ break
+ else:
+ raise exceptions[0]
+
+ protocol = protocol_factory()
+ transport = self._make_datagram_transport(sock, protocol, r_addr)
+ return transport, protocol
+
+ @tasks.coroutine
+ def create_server(self, protocol_factory, host=None, port=None,
+ *,
+ family=socket.AF_UNSPEC,
+ flags=socket.AI_PASSIVE,
+ sock=None,
+ backlog=100,
+ ssl=None,
+ reuse_address=None):
+ """XXX"""
+ if isinstance(ssl, bool):
+ raise TypeError('ssl argument must be an SSLContext or None')
+ if host is not None or port is not None:
+ if sock is not None:
+ raise ValueError(
+ 'host/port and sock can not be specified at the same time')
+
+ AF_INET6 = getattr(socket, 'AF_INET6', 0)
+ if reuse_address is None:
+ reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
+ sockets = []
+ if host == '':
+ host = None
+
+ infos = yield from self.getaddrinfo(
+ host, port, family=family,
+ type=socket.SOCK_STREAM, proto=0, flags=flags)
+ if not infos:
+ raise OSError('getaddrinfo() returned empty list')
+
+ completed = False
+ try:
+ for res in infos:
+ af, socktype, proto, canonname, sa = res
+ try:
+ sock = socket.socket(af, socktype, proto)
+ except socket.error:
+ # Assume it's a bad family/type/protocol combination.
+ continue
+ sockets.append(sock)
+ if reuse_address:
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
+ True)
+ # Disable IPv4/IPv6 dual stack support (enabled by
+ # default on Linux) which makes a single socket
+ # listen on both address families.
+ if af == AF_INET6 and hasattr(socket, 'IPPROTO_IPV6'):
+ sock.setsockopt(socket.IPPROTO_IPV6,
+ socket.IPV6_V6ONLY,
+ True)
+ try:
+ sock.bind(sa)
+ except OSError as err:
+ raise OSError(err.errno, 'error while attempting '
+ 'to bind on address %r: %s'
+ % (sa, err.strerror.lower()))
+ completed = True
+ finally:
+ if not completed:
+ for sock in sockets:
+ sock.close()
+ else:
+ if sock is None:
+ raise ValueError(
+ 'host and port was not specified and no sock specified')
+ sockets = [sock]
+
+ server = Server(self, sockets)
+ for sock in sockets:
+ sock.listen(backlog)
+ sock.setblocking(False)
+ self._start_serving(protocol_factory, sock, ssl, server)
+ return server
+
+ @tasks.coroutine
+ def connect_read_pipe(self, protocol_factory, pipe):
+ protocol = protocol_factory()
+ waiter = futures.Future(loop=self)
+ transport = self._make_read_pipe_transport(pipe, protocol, waiter)
+ yield from waiter
+ return transport, protocol
+
+ @tasks.coroutine
+ def connect_write_pipe(self, protocol_factory, pipe):
+ protocol = protocol_factory()
+ waiter = futures.Future(loop=self)
+ transport = self._make_write_pipe_transport(pipe, protocol, waiter)
+ yield from waiter
+ return transport, protocol
+
+ @tasks.coroutine
+ def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=False, shell=True, bufsize=0,
+ **kwargs):
+ assert not universal_newlines, "universal_newlines must be False"
+ assert shell, "shell must be True"
+ assert isinstance(cmd, str), cmd
+ protocol = protocol_factory()
+ transport = yield from self._make_subprocess_transport(
+ protocol, cmd, True, stdin, stdout, stderr, bufsize, **kwargs)
+ return transport, protocol
+
+ @tasks.coroutine
+ def subprocess_exec(self, protocol_factory, *args, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=False, shell=False, bufsize=0,
+ **kwargs):
+ assert not universal_newlines, "universal_newlines must be False"
+ assert not shell, "shell must be False"
+ protocol = protocol_factory()
+ transport = yield from self._make_subprocess_transport(
+ protocol, args, False, stdin, stdout, stderr, bufsize, **kwargs)
+ return transport, protocol
+
+ def _add_callback(self, handle):
+ """Add a Handle to ready or scheduled."""
+ assert isinstance(handle, events.Handle), 'A Handle is required here'
+ if handle._cancelled:
+ return
+ if isinstance(handle, events.TimerHandle):
+ heapq.heappush(self._scheduled, handle)
+ else:
+ self._ready.append(handle)
+
+ def _add_callback_signalsafe(self, handle):
+ """Like _add_callback() but called from a signal handler."""
+ self._add_callback(handle)
+ self._write_to_self()
+
+ def _run_once(self):
+ """Run one full iteration of the event loop.
+
+ This calls all currently ready callbacks, polls for I/O,
+ schedules the resulting callbacks, and finally schedules
+ 'call_later' callbacks.
+ """
+ # Remove delayed calls that were cancelled from head of queue.
+ while self._scheduled and self._scheduled[0]._cancelled:
+ heapq.heappop(self._scheduled)
+
+ timeout = None
+ if self._ready:
+ timeout = 0
+ elif self._scheduled:
+ # Compute the desired timeout.
+ when = self._scheduled[0]._when
+ deadline = max(0, when - self.time())
+ if timeout is None:
+ timeout = deadline
+ else:
+ timeout = min(timeout, deadline)
+
+ # TODO: Instrumentation only in debug mode?
+ t0 = self.time()
+ event_list = self._selector.select(timeout)
+ t1 = self.time()
+ argstr = '' if timeout is None else '{:.3f}'.format(timeout)
+ if t1-t0 >= 1:
+ level = logging.INFO
+ else:
+ level = logging.DEBUG
+ logger.log(level, 'poll%s took %.3f seconds', argstr, t1-t0)
+ self._process_events(event_list)
+
+ # Handle 'later' callbacks that are ready.
+ now = self.time()
+ while self._scheduled:
+ handle = self._scheduled[0]
+ if handle._when > now:
+ break
+ handle = heapq.heappop(self._scheduled)
+ self._ready.append(handle)
+
+ # This is the only place where callbacks are actually *called*.
+ # All other places just add them to ready.
+ # Note: We run all currently scheduled callbacks, but not any
+ # callbacks scheduled by callbacks run this time around --
+ # they will be run the next time (after another I/O poll).
+ # Use an idiom that is threadsafe without using locks.
+ ntodo = len(self._ready)
+ for i in range(ntodo):
+ handle = self._ready.popleft()
+ if not handle._cancelled:
+ handle._run()
+ handle = None # Needed to break cycles when an exception occurs.
diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py
new file mode 100644
index 0000000000..d15fb159d9
--- /dev/null
+++ b/Lib/asyncio/base_subprocess.py
@@ -0,0 +1,166 @@
+import collections
+import subprocess
+
+from . import protocols
+from . import tasks
+from . import transports
+
+
+STDIN = 0
+STDOUT = 1
+STDERR = 2
+
+
+class BaseSubprocessTransport(transports.SubprocessTransport):
+
+ def __init__(self, loop, protocol, args, shell,
+ stdin, stdout, stderr, bufsize,
+ extra=None, **kwargs):
+ super().__init__(extra)
+ self._protocol = protocol
+ self._loop = loop
+
+ self._pipes = {}
+ if stdin == subprocess.PIPE:
+ self._pipes[STDIN] = None
+ if stdout == subprocess.PIPE:
+ self._pipes[STDOUT] = None
+ if stderr == subprocess.PIPE:
+ self._pipes[STDERR] = None
+ self._pending_calls = collections.deque()
+ self._finished = False
+ self._returncode = None
+ self._start(args=args, shell=shell, stdin=stdin, stdout=stdout,
+ stderr=stderr, bufsize=bufsize, **kwargs)
+ self._extra['subprocess'] = self._proc
+
+ def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
+ raise NotImplementedError
+
+ def _make_write_subprocess_pipe_proto(self, fd):
+ raise NotImplementedError
+
+ def _make_read_subprocess_pipe_proto(self, fd):
+ raise NotImplementedError
+
+ def close(self):
+ for proto in self._pipes.values():
+ proto.pipe.close()
+ if self._returncode is None:
+ self.terminate()
+
+ def get_pid(self):
+ return self._proc.pid
+
+ def get_returncode(self):
+ return self._returncode
+
+ def get_pipe_transport(self, fd):
+ if fd in self._pipes:
+ return self._pipes[fd].pipe
+ else:
+ return None
+
+ def send_signal(self, signal):
+ self._proc.send_signal(signal)
+
+ def terminate(self):
+ self._proc.terminate()
+
+ def kill(self):
+ self._proc.kill()
+
+ @tasks.coroutine
+ def _post_init(self):
+ proc = self._proc
+ loop = self._loop
+ if proc.stdin is not None:
+ transp, proto = yield from loop.connect_write_pipe(
+ lambda: WriteSubprocessPipeProto(self, STDIN),
+ proc.stdin)
+ if proc.stdout is not None:
+ transp, proto = yield from loop.connect_read_pipe(
+ lambda: ReadSubprocessPipeProto(self, STDOUT),
+ proc.stdout)
+ if proc.stderr is not None:
+ transp, proto = yield from loop.connect_read_pipe(
+ lambda: ReadSubprocessPipeProto(self, STDERR),
+ proc.stderr)
+ if not self._pipes:
+ self._try_connected()
+
+ def _call(self, cb, *data):
+ if self._pending_calls is not None:
+ self._pending_calls.append((cb, data))
+ else:
+ self._loop.call_soon(cb, *data)
+
+ def _try_connected(self):
+ assert self._pending_calls is not None
+ if all(p is not None and p.connected for p in self._pipes.values()):
+ self._loop.call_soon(self._protocol.connection_made, self)
+ for callback, data in self._pending_calls:
+ self._loop.call_soon(callback, *data)
+ self._pending_calls = None
+
+ def _pipe_connection_lost(self, fd, exc):
+ self._call(self._protocol.pipe_connection_lost, fd, exc)
+ self._try_finish()
+
+ def _pipe_data_received(self, fd, data):
+ self._call(self._protocol.pipe_data_received, fd, data)
+
+ def _process_exited(self, returncode):
+ assert returncode is not None, returncode
+ assert self._returncode is None, self._returncode
+ self._returncode = returncode
+ self._loop._subprocess_closed(self)
+ self._call(self._protocol.process_exited)
+ self._try_finish()
+
+ def _try_finish(self):
+ assert not self._finished
+ if self._returncode is None:
+ return
+ if all(p is not None and p.disconnected
+ for p in self._pipes.values()):
+ self._finished = True
+ self._loop.call_soon(self._call_connection_lost, None)
+
+ def _call_connection_lost(self, exc):
+ try:
+ self._protocol.connection_lost(exc)
+ finally:
+ self._proc = None
+ self._protocol = None
+ self._loop = None
+
+
+class WriteSubprocessPipeProto(protocols.BaseProtocol):
+ pipe = None
+
+ def __init__(self, proc, fd):
+ self.proc = proc
+ self.fd = fd
+ self.connected = False
+ self.disconnected = False
+ proc._pipes[fd] = self
+
+ def connection_made(self, transport):
+ self.connected = True
+ self.pipe = transport
+ self.proc._try_connected()
+
+ def connection_lost(self, exc):
+ self.disconnected = True
+ self.proc._pipe_connection_lost(self.fd, exc)
+
+ def eof_received(self):
+ pass
+
+
+class ReadSubprocessPipeProto(WriteSubprocessPipeProto,
+ protocols.Protocol):
+
+ def data_received(self, data):
+ self.proc._pipe_data_received(self.fd, data)
diff --git a/Lib/asyncio/constants.py b/Lib/asyncio/constants.py
new file mode 100644
index 0000000000..f9e123281e
--- /dev/null
+++ b/Lib/asyncio/constants.py
@@ -0,0 +1,7 @@
+"""Constants."""
+
+# After the connection is lost, log warnings after this many write()s.
+LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5
+
+# Seconds to wait before retrying accept().
+ACCEPT_RETRY_DELAY = 1
diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py
new file mode 100644
index 0000000000..c10faa75b0
--- /dev/null
+++ b/Lib/asyncio/events.py
@@ -0,0 +1,438 @@
+"""Event loop and event loop policy."""
+
+__all__ = ['AbstractEventLoopPolicy',
+ 'AbstractEventLoop', 'AbstractServer',
+ 'Handle', 'TimerHandle',
+ 'get_event_loop_policy', 'set_event_loop_policy',
+ 'get_event_loop', 'set_event_loop', 'new_event_loop',
+ 'get_child_watcher', 'set_child_watcher',
+ ]
+
+import subprocess
+import sys
+import threading
+import socket
+
+from .log import logger
+
+
+class Handle:
+ """Object returned by callback registration methods."""
+
+ def __init__(self, callback, args):
+ self._callback = callback
+ self._args = args
+ self._cancelled = False
+
+ def __repr__(self):
+ res = 'Handle({}, {})'.format(self._callback, self._args)
+ if self._cancelled:
+ res += '<cancelled>'
+ return res
+
+ def cancel(self):
+ self._cancelled = True
+
+ def _run(self):
+ try:
+ self._callback(*self._args)
+ except Exception:
+ logger.exception('Exception in callback %s %r',
+ self._callback, self._args)
+ self = None # Needed to break cycles when an exception occurs.
+
+
+def make_handle(callback, args):
+ # TODO: Inline this? Or make it a private EventLoop method?
+ assert not isinstance(callback, Handle), 'A Handle is not a callback'
+ return Handle(callback, args)
+
+
+class TimerHandle(Handle):
+ """Object returned by timed callback registration methods."""
+
+ def __init__(self, when, callback, args):
+ assert when is not None
+ super().__init__(callback, args)
+
+ self._when = when
+
+ def __repr__(self):
+ res = 'TimerHandle({}, {}, {})'.format(self._when,
+ self._callback,
+ self._args)
+ if self._cancelled:
+ res += '<cancelled>'
+
+ return res
+
+ def __hash__(self):
+ return hash(self._when)
+
+ def __lt__(self, other):
+ return self._when < other._when
+
+ def __le__(self, other):
+ if self._when < other._when:
+ return True
+ return self.__eq__(other)
+
+ def __gt__(self, other):
+ return self._when > other._when
+
+ def __ge__(self, other):
+ if self._when > other._when:
+ return True
+ return self.__eq__(other)
+
+ def __eq__(self, other):
+ if isinstance(other, TimerHandle):
+ return (self._when == other._when and
+ self._callback == other._callback and
+ self._args == other._args and
+ self._cancelled == other._cancelled)
+ return NotImplemented
+
+ def __ne__(self, other):
+ equal = self.__eq__(other)
+ return NotImplemented if equal is NotImplemented else not equal
+
+
+class AbstractServer:
+ """Abstract server returned by create_service()."""
+
+ def close(self):
+ """Stop serving. This leaves existing connections open."""
+ return NotImplemented
+
+ def wait_closed(self):
+ """Coroutine to wait until service is closed."""
+ return NotImplemented
+
+
+class AbstractEventLoop:
+ """Abstract event loop."""
+
+ # Running and stopping the event loop.
+
+ def run_forever(self):
+ """Run the event loop until stop() is called."""
+ raise NotImplementedError
+
+ def run_until_complete(self, future):
+ """Run the event loop until a Future is done.
+
+ Return the Future's result, or raise its exception.
+ """
+ raise NotImplementedError
+
+ def stop(self):
+ """Stop the event loop as soon as reasonable.
+
+ Exactly how soon that is may depend on the implementation, but
+ no more I/O callbacks should be scheduled.
+ """
+ raise NotImplementedError
+
+ def is_running(self):
+ """Return whether the event loop is currently running."""
+ raise NotImplementedError
+
+ def close(self):
+ """Close the loop.
+
+ The loop should not be running.
+
+ This is idempotent and irreversible.
+
+ No other methods should be called after this one.
+ """
+ raise NotImplementedError
+
+ # Methods scheduling callbacks. All these return Handles.
+
+ def call_soon(self, callback, *args):
+ return self.call_later(0, callback, *args)
+
+ def call_later(self, delay, callback, *args):
+ raise NotImplementedError
+
+ def call_at(self, when, callback, *args):
+ raise NotImplementedError
+
+ def time(self):
+ raise NotImplementedError
+
+ # Methods for interacting with threads.
+
+ def call_soon_threadsafe(self, callback, *args):
+ raise NotImplementedError
+
+ def run_in_executor(self, executor, callback, *args):
+ raise NotImplementedError
+
+ def set_default_executor(self, executor):
+ raise NotImplementedError
+
+ # Network I/O methods returning Futures.
+
+ def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
+ raise NotImplementedError
+
+ def getnameinfo(self, sockaddr, flags=0):
+ raise NotImplementedError
+
+ def create_connection(self, protocol_factory, host=None, port=None, *,
+ ssl=None, family=0, proto=0, flags=0, sock=None,
+ local_addr=None, server_hostname=None):
+ raise NotImplementedError
+
+ def create_server(self, protocol_factory, host=None, port=None, *,
+ family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE,
+ sock=None, backlog=100, ssl=None, reuse_address=None):
+ """A coroutine which creates a TCP server bound to host and port.
+
+ The return value is a Server object which can be used to stop
+ the service.
+
+ If host is an empty string or None all interfaces are assumed
+ and a list of multiple sockets will be returned (most likely
+ one for IPv4 and another one for IPv6).
+
+ family can be set to either AF_INET or AF_INET6 to force the
+ socket to use IPv4 or IPv6. If not set it will be determined
+ from host (defaults to AF_UNSPEC).
+
+ flags is a bitmask for getaddrinfo().
+
+ sock can optionally be specified in order to use a preexisting
+ socket object.
+
+ backlog is the maximum number of queued connections passed to
+ listen() (defaults to 100).
+
+ ssl can be set to an SSLContext to enable SSL over the
+ accepted connections.
+
+ reuse_address tells the kernel to reuse a local socket in
+ TIME_WAIT state, without waiting for its natural timeout to
+ expire. If not specified will automatically be set to True on
+ UNIX.
+ """
+ raise NotImplementedError
+
+ def create_datagram_endpoint(self, protocol_factory,
+ local_addr=None, remote_addr=None, *,
+ family=0, proto=0, flags=0):
+ raise NotImplementedError
+
+ # Pipes and subprocesses.
+
+ def connect_read_pipe(self, protocol_factory, pipe):
+ """Register read pipe in eventloop.
+
+ protocol_factory should instantiate object with Protocol interface.
+ pipe is file-like object already switched to nonblocking.
+ Return pair (transport, protocol), where transport support
+ ReadTransport interface."""
+ # The reason to accept file-like object instead of just file descriptor
+ # is: we need to own pipe and close it at transport finishing
+ # Can got complicated errors if pass f.fileno(),
+ # close fd in pipe transport then close f and vise versa.
+ raise NotImplementedError
+
+ def connect_write_pipe(self, protocol_factory, pipe):
+ """Register write pipe in eventloop.
+
+ protocol_factory should instantiate object with BaseProtocol interface.
+ Pipe is file-like object already switched to nonblocking.
+ Return pair (transport, protocol), where transport support
+ WriteTransport interface."""
+ # The reason to accept file-like object instead of just file descriptor
+ # is: we need to own pipe and close it at transport finishing
+ # Can got complicated errors if pass f.fileno(),
+ # close fd in pipe transport then close f and vise versa.
+ raise NotImplementedError
+
+ def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ **kwargs):
+ raise NotImplementedError
+
+ def subprocess_exec(self, protocol_factory, *args, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ **kwargs):
+ raise NotImplementedError
+
+ # Ready-based callback registration methods.
+ # The add_*() methods return None.
+ # The remove_*() methods return True if something was removed,
+ # False if there was nothing to delete.
+
+ def add_reader(self, fd, callback, *args):
+ raise NotImplementedError
+
+ def remove_reader(self, fd):
+ raise NotImplementedError
+
+ def add_writer(self, fd, callback, *args):
+ raise NotImplementedError
+
+ def remove_writer(self, fd):
+ raise NotImplementedError
+
+ # Completion based I/O methods returning Futures.
+
+ def sock_recv(self, sock, nbytes):
+ raise NotImplementedError
+
+ def sock_sendall(self, sock, data):
+ raise NotImplementedError
+
+ def sock_connect(self, sock, address):
+ raise NotImplementedError
+
+ def sock_accept(self, sock):
+ raise NotImplementedError
+
+ # Signal handling.
+
+ def add_signal_handler(self, sig, callback, *args):
+ raise NotImplementedError
+
+ def remove_signal_handler(self, sig):
+ raise NotImplementedError
+
+
+class AbstractEventLoopPolicy:
+ """Abstract policy for accessing the event loop."""
+
+ def get_event_loop(self):
+ """XXX"""
+ raise NotImplementedError
+
+ def set_event_loop(self, loop):
+ """XXX"""
+ raise NotImplementedError
+
+ def new_event_loop(self):
+ """XXX"""
+ raise NotImplementedError
+
+ # Child processes handling (Unix only).
+
+ def get_child_watcher(self):
+ """XXX"""
+ raise NotImplementedError
+
+ def set_child_watcher(self, watcher):
+ """XXX"""
+ raise NotImplementedError
+
+
+class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
+ """Default policy implementation for accessing the event loop.
+
+ In this policy, each thread has its own event loop. However, we
+ only automatically create an event loop by default for the main
+ thread; other threads by default have no event loop.
+
+ Other policies may have different rules (e.g. a single global
+ event loop, or automatically creating an event loop per thread, or
+ using some other notion of context to which an event loop is
+ associated).
+ """
+
+ _loop_factory = None
+
+ class _Local(threading.local):
+ _loop = None
+ _set_called = False
+
+ def __init__(self):
+ self._local = self._Local()
+
+ def get_event_loop(self):
+ """Get the event loop.
+
+ This may be None or an instance of EventLoop.
+ """
+ if (self._local._loop is None and
+ not self._local._set_called and
+ isinstance(threading.current_thread(), threading._MainThread)):
+ self.set_event_loop(self.new_event_loop())
+ assert self._local._loop is not None, \
+ ('There is no current event loop in thread %r.' %
+ threading.current_thread().name)
+ return self._local._loop
+
+ def set_event_loop(self, loop):
+ """Set the event loop."""
+ self._local._set_called = True
+ assert loop is None or isinstance(loop, AbstractEventLoop)
+ self._local._loop = loop
+
+ def new_event_loop(self):
+ """Create a new event loop.
+
+ You must call set_event_loop() to make this the current event
+ loop.
+ """
+ return self._loop_factory()
+
+
+# Event loop policy. The policy itself is always global, even if the
+# policy's rules say that there is an event loop per thread (or other
+# notion of context). The default policy is installed by the first
+# call to get_event_loop_policy().
+_event_loop_policy = None
+
+# Lock for protecting the on-the-fly creation of the event loop policy.
+_lock = threading.Lock()
+
+
+def _init_event_loop_policy():
+ global _event_loop_policy
+ with _lock:
+ if _event_loop_policy is None: # pragma: no branch
+ from . import DefaultEventLoopPolicy
+ _event_loop_policy = DefaultEventLoopPolicy()
+
+
+def get_event_loop_policy():
+ """XXX"""
+ if _event_loop_policy is None:
+ _init_event_loop_policy()
+ return _event_loop_policy
+
+
+def set_event_loop_policy(policy):
+ """XXX"""
+ global _event_loop_policy
+ assert policy is None or isinstance(policy, AbstractEventLoopPolicy)
+ _event_loop_policy = policy
+
+
+def get_event_loop():
+ """XXX"""
+ return get_event_loop_policy().get_event_loop()
+
+
+def set_event_loop(loop):
+ """XXX"""
+ get_event_loop_policy().set_event_loop(loop)
+
+
+def new_event_loop():
+ """XXX"""
+ return get_event_loop_policy().new_event_loop()
+
+
+def get_child_watcher():
+ """XXX"""
+ return get_event_loop_policy().get_child_watcher()
+
+
+def set_child_watcher(watcher):
+ """XXX"""
+ return get_event_loop_policy().set_child_watcher(watcher)
diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py
new file mode 100644
index 0000000000..dd3e718d9a
--- /dev/null
+++ b/Lib/asyncio/futures.py
@@ -0,0 +1,343 @@
+"""A Future class similar to the one in PEP 3148."""
+
+__all__ = ['CancelledError', 'TimeoutError',
+ 'InvalidStateError',
+ 'Future', 'wrap_future',
+ ]
+
+import concurrent.futures._base
+import logging
+import traceback
+
+from . import events
+from .log import logger
+
+# States for Future.
+_PENDING = 'PENDING'
+_CANCELLED = 'CANCELLED'
+_FINISHED = 'FINISHED'
+
+# TODO: Do we really want to depend on concurrent.futures internals?
+Error = concurrent.futures._base.Error
+CancelledError = concurrent.futures.CancelledError
+TimeoutError = concurrent.futures.TimeoutError
+
+STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
+
+
+class InvalidStateError(Error):
+ """The operation is not allowed in this state."""
+ # TODO: Show the future, its state, the method, and the required state.
+
+
+class _TracebackLogger:
+ """Helper to log a traceback upon destruction if not cleared.
+
+ This solves a nasty problem with Futures and Tasks that have an
+ exception set: if nobody asks for the exception, the exception is
+ never logged. This violates the Zen of Python: 'Errors should
+ never pass silently. Unless explicitly silenced.'
+
+ However, we don't want to log the exception as soon as
+ set_exception() is called: if the calling code is written
+ properly, it will get the exception and handle it properly. But
+ we *do* want to log it if result() or exception() was never called
+ -- otherwise developers waste a lot of time wondering why their
+ buggy code fails silently.
+
+ An earlier attempt added a __del__() method to the Future class
+ itself, but this backfired because the presence of __del__()
+ prevents garbage collection from breaking cycles. A way out of
+ this catch-22 is to avoid having a __del__() method on the Future
+ class itself, but instead to have a reference to a helper object
+ with a __del__() method that logs the traceback, where we ensure
+ that the helper object doesn't participate in cycles, and only the
+ Future has a reference to it.
+
+ The helper object is added when set_exception() is called. When
+ the Future is collected, and the helper is present, the helper
+ object is also collected, and its __del__() method will log the
+ traceback. When the Future's result() or exception() method is
+ called (and a helper object is present), it removes the the helper
+ object, after calling its clear() method to prevent it from
+ logging.
+
+ One downside is that we do a fair amount of work to extract the
+ traceback from the exception, even when it is never logged. It
+ would seem cheaper to just store the exception object, but that
+ references the traceback, which references stack frames, which may
+ reference the Future, which references the _TracebackLogger, and
+ then the _TracebackLogger would be included in a cycle, which is
+ what we're trying to avoid! As an optimization, we don't
+ immediately format the exception; we only do the work when
+ activate() is called, which call is delayed until after all the
+ Future's callbacks have run. Since usually a Future has at least
+ one callback (typically set by 'yield from') and usually that
+ callback extracts the callback, thereby removing the need to
+ format the exception.
+
+ PS. I don't claim credit for this solution. I first heard of it
+ in a discussion about closing files when they are collected.
+ """
+
+ __slots__ = ['exc', 'tb']
+
+ def __init__(self, exc):
+ self.exc = exc
+ self.tb = None
+
+ def activate(self):
+ exc = self.exc
+ if exc is not None:
+ self.exc = None
+ self.tb = traceback.format_exception(exc.__class__, exc,
+ exc.__traceback__)
+
+ def clear(self):
+ self.exc = None
+ self.tb = None
+
+ def __del__(self):
+ if self.tb:
+ logger.error('Future/Task exception was never retrieved:\n%s',
+ ''.join(self.tb))
+
+
+class Future:
+ """This class is *almost* compatible with concurrent.futures.Future.
+
+ Differences:
+
+ - result() and exception() do not take a timeout argument and
+ raise an exception when the future isn't done yet.
+
+ - Callbacks registered with add_done_callback() are always called
+ via the event loop's call_soon_threadsafe().
+
+ - This class is not compatible with the wait() and as_completed()
+ methods in the concurrent.futures package.
+
+ (In Python 3.4 or later we may be able to unify the implementations.)
+ """
+
+ # Class variables serving as defaults for instance variables.
+ _state = _PENDING
+ _result = None
+ _exception = None
+ _loop = None
+
+ _blocking = False # proper use of future (yield vs yield from)
+
+ _tb_logger = None
+
+ def __init__(self, *, loop=None):
+ """Initialize the future.
+
+ The optional event_loop argument allows to explicitly set the event
+ loop object used by the future. If it's not provided, the future uses
+ the default event loop.
+ """
+ if loop is None:
+ self._loop = events.get_event_loop()
+ else:
+ self._loop = loop
+ self._callbacks = []
+
+ def __repr__(self):
+ res = self.__class__.__name__
+ if self._state == _FINISHED:
+ if self._exception is not None:
+ res += '<exception={!r}>'.format(self._exception)
+ else:
+ res += '<result={!r}>'.format(self._result)
+ elif self._callbacks:
+ size = len(self._callbacks)
+ if size > 2:
+ res += '<{}, [{}, <{} more>, {}]>'.format(
+ self._state, self._callbacks[0],
+ size-2, self._callbacks[-1])
+ else:
+ res += '<{}, {}>'.format(self._state, self._callbacks)
+ else:
+ res += '<{}>'.format(self._state)
+ return res
+
+ def cancel(self):
+ """Cancel the future and schedule callbacks.
+
+ If the future is already done or cancelled, return False. Otherwise,
+ change the future's state to cancelled, schedule the callbacks and
+ return True.
+ """
+ if self._state != _PENDING:
+ return False
+ self._state = _CANCELLED
+ self._schedule_callbacks()
+ return True
+
+ def _schedule_callbacks(self):
+ """Internal: Ask the event loop to call all callbacks.
+
+ The callbacks are scheduled to be called as soon as possible. Also
+ clears the callback list.
+ """
+ callbacks = self._callbacks[:]
+ if not callbacks:
+ return
+
+ self._callbacks[:] = []
+ for callback in callbacks:
+ self._loop.call_soon(callback, self)
+
+ def cancelled(self):
+ """Return True if the future was cancelled."""
+ return self._state == _CANCELLED
+
+ # Don't implement running(); see http://bugs.python.org/issue18699
+
+ def done(self):
+ """Return True if the future is done.
+
+ Done means either that a result / exception are available, or that the
+ future was cancelled.
+ """
+ return self._state != _PENDING
+
+ def result(self):
+ """Return the result this future represents.
+
+ If the future has been cancelled, raises CancelledError. If the
+ future's result isn't yet available, raises InvalidStateError. If
+ the future is done and has an exception set, this exception is raised.
+ """
+ if self._state == _CANCELLED:
+ raise CancelledError
+ if self._state != _FINISHED:
+ raise InvalidStateError('Result is not ready.')
+ if self._tb_logger is not None:
+ self._tb_logger.clear()
+ self._tb_logger = None
+ if self._exception is not None:
+ raise self._exception
+ return self._result
+
+ def exception(self):
+ """Return the exception that was set on this future.
+
+ The exception (or None if no exception was set) is returned only if
+ the future is done. If the future has been cancelled, raises
+ CancelledError. If the future isn't done yet, raises
+ InvalidStateError.
+ """
+ if self._state == _CANCELLED:
+ raise CancelledError
+ if self._state != _FINISHED:
+ raise InvalidStateError('Exception is not set.')
+ if self._tb_logger is not None:
+ self._tb_logger.clear()
+ self._tb_logger = None
+ return self._exception
+
+ def add_done_callback(self, fn):
+ """Add a callback to be run when the future becomes done.
+
+ The callback is called with a single argument - the future object. If
+ the future is already done when this is called, the callback is
+ scheduled with call_soon.
+ """
+ if self._state != _PENDING:
+ self._loop.call_soon(fn, self)
+ else:
+ self._callbacks.append(fn)
+
+ # New method not in PEP 3148.
+
+ def remove_done_callback(self, fn):
+ """Remove all instances of a callback from the "call when done" list.
+
+ Returns the number of callbacks removed.
+ """
+ filtered_callbacks = [f for f in self._callbacks if f != fn]
+ removed_count = len(self._callbacks) - len(filtered_callbacks)
+ if removed_count:
+ self._callbacks[:] = filtered_callbacks
+ return removed_count
+
+ # So-called internal methods (note: no set_running_or_notify_cancel()).
+
+ def set_result(self, result):
+ """Mark the future done and set its result.
+
+ If the future is already done when this method is called, raises
+ InvalidStateError.
+ """
+ if self._state != _PENDING:
+ raise InvalidStateError('{}: {!r}'.format(self._state, self))
+ self._result = result
+ self._state = _FINISHED
+ self._schedule_callbacks()
+
+ def set_exception(self, exception):
+ """Mark the future done and set an exception.
+
+ If the future is already done when this method is called, raises
+ InvalidStateError.
+ """
+ if self._state != _PENDING:
+ raise InvalidStateError('{}: {!r}'.format(self._state, self))
+ self._exception = exception
+ self._tb_logger = _TracebackLogger(exception)
+ self._state = _FINISHED
+ self._schedule_callbacks()
+ # Arrange for the logger to be activated after all callbacks
+ # have had a chance to call result() or exception().
+ self._loop.call_soon(self._tb_logger.activate)
+
+ # Truly internal methods.
+
+ def _copy_state(self, other):
+ """Internal helper to copy state from another Future.
+
+ The other Future may be a concurrent.futures.Future.
+ """
+ assert other.done()
+ if self.cancelled():
+ return
+ assert not self.done()
+ if other.cancelled():
+ self.cancel()
+ else:
+ exception = other.exception()
+ if exception is not None:
+ self.set_exception(exception)
+ else:
+ result = other.result()
+ self.set_result(result)
+
+ def __iter__(self):
+ if not self.done():
+ self._blocking = True
+ yield self # This tells Task to wait for completion.
+ assert self.done(), "yield from wasn't used with future"
+ return self.result() # May raise too.
+
+
+def wrap_future(fut, *, loop=None):
+ """Wrap concurrent.futures.Future object."""
+ if isinstance(fut, Future):
+ return fut
+ assert isinstance(fut, concurrent.futures.Future), \
+ 'concurrent.futures.Future is expected, got {!r}'.format(fut)
+ if loop is None:
+ loop = events.get_event_loop()
+ new_future = Future(loop=loop)
+
+ def _check_cancel_other(f):
+ if f.cancelled():
+ fut.cancel()
+
+ new_future.add_done_callback(_check_cancel_other)
+ fut.add_done_callback(
+ lambda future: loop.call_soon_threadsafe(
+ new_future._copy_state, fut))
+ return new_future
diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py
new file mode 100644
index 0000000000..2d458a9b0e
--- /dev/null
+++ b/Lib/asyncio/locks.py
@@ -0,0 +1,433 @@
+"""Synchronization primitives."""
+
+__all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore']
+
+import collections
+
+from . import events
+from . import futures
+from . import tasks
+
+
+class Lock:
+ """Primitive lock objects.
+
+ A primitive lock is a synchronization primitive that is not owned
+ by a particular coroutine when locked. A primitive lock is in one
+ of two states, 'locked' or 'unlocked'.
+
+ It is created in the unlocked state. It has two basic methods,
+ acquire() and release(). When the state is unlocked, acquire()
+ changes the state to locked and returns immediately. When the
+ state is locked, acquire() blocks until a call to release() in
+ another coroutine changes it to unlocked, then the acquire() call
+ resets it to locked and returns. The release() method should only
+ be called in the locked state; it changes the state to unlocked
+ and returns immediately. If an attempt is made to release an
+ unlocked lock, a RuntimeError will be raised.
+
+ When more than one coroutine is blocked in acquire() waiting for
+ the state to turn to unlocked, only one coroutine proceeds when a
+ release() call resets the state to unlocked; first coroutine which
+ is blocked in acquire() is being processed.
+
+ acquire() is a coroutine and should be called with 'yield from'.
+
+ Locks also support the context manager protocol. '(yield from lock)'
+ should be used as context manager expression.
+
+ Usage:
+
+ lock = Lock()
+ ...
+ yield from lock
+ try:
+ ...
+ finally:
+ lock.release()
+
+ Context manager usage:
+
+ lock = Lock()
+ ...
+ with (yield from lock):
+ ...
+
+ Lock objects can be tested for locking state:
+
+ if not lock.locked():
+ yield from lock
+ else:
+ # lock is acquired
+ ...
+
+ """
+
+ def __init__(self, *, loop=None):
+ self._waiters = collections.deque()
+ self._locked = False
+ if loop is not None:
+ self._loop = loop
+ else:
+ self._loop = events.get_event_loop()
+
+ def __repr__(self):
+ res = super().__repr__()
+ extra = 'locked' if self._locked else 'unlocked'
+ if self._waiters:
+ extra = '{},waiters:{}'.format(extra, len(self._waiters))
+ return '<{} [{}]>'.format(res[1:-1], extra)
+
+ def locked(self):
+ """Return true if lock is acquired."""
+ return self._locked
+
+ @tasks.coroutine
+ def acquire(self):
+ """Acquire a lock.
+
+ This method blocks until the lock is unlocked, then sets it to
+ locked and returns True.
+ """
+ if not self._waiters and not self._locked:
+ self._locked = True
+ return True
+
+ fut = futures.Future(loop=self._loop)
+ self._waiters.append(fut)
+ try:
+ yield from fut
+ self._locked = True
+ return True
+ finally:
+ self._waiters.remove(fut)
+
+ def release(self):
+ """Release a lock.
+
+ When the lock is locked, reset it to unlocked, and return.
+ If any other coroutines are blocked waiting for the lock to become
+ unlocked, allow exactly one of them to proceed.
+
+ When invoked on an unlocked lock, a RuntimeError is raised.
+
+ There is no return value.
+ """
+ if self._locked:
+ self._locked = False
+ # Wake up the first waiter who isn't cancelled.
+ for fut in self._waiters:
+ if not fut.done():
+ fut.set_result(True)
+ break
+ else:
+ raise RuntimeError('Lock is not acquired.')
+
+ def __enter__(self):
+ if not self._locked:
+ raise RuntimeError(
+ '"yield from" should be used as context manager expression')
+ return True
+
+ def __exit__(self, *args):
+ self.release()
+
+ def __iter__(self):
+ yield from self.acquire()
+ return self
+
+
+class Event:
+ """An Event implementation, our equivalent to threading.Event.
+
+ Class implementing event objects. An event manages a flag that can be set
+ to true with the set() method and reset to false with the clear() method.
+ The wait() method blocks until the flag is true. The flag is initially
+ false.
+ """
+
+ def __init__(self, *, loop=None):
+ self._waiters = collections.deque()
+ self._value = False
+ if loop is not None:
+ self._loop = loop
+ else:
+ self._loop = events.get_event_loop()
+
+ def __repr__(self):
+ res = super().__repr__()
+ extra = 'set' if self._value else 'unset'
+ if self._waiters:
+ extra = '{},waiters:{}'.format(extra, len(self._waiters))
+ return '<{} [{}]>'.format(res[1:-1], extra)
+
+ def is_set(self):
+ """Return true if and only if the internal flag is true."""
+ return self._value
+
+ def set(self):
+ """Set the internal flag to true. All coroutines waiting for it to
+ become true are awakened. Coroutine that call wait() once the flag is
+ true will not block at all.
+ """
+ if not self._value:
+ self._value = True
+
+ for fut in self._waiters:
+ if not fut.done():
+ fut.set_result(True)
+
+ def clear(self):
+ """Reset the internal flag to false. Subsequently, coroutines calling
+ wait() will block until set() is called to set the internal flag
+ to true again."""
+ self._value = False
+
+ @tasks.coroutine
+ def wait(self):
+ """Block until the internal flag is true.
+
+ If the internal flag is true on entry, return True
+ immediately. Otherwise, block until another coroutine calls
+ set() to set the flag to true, then return True.
+ """
+ if self._value:
+ return True
+
+ fut = futures.Future(loop=self._loop)
+ self._waiters.append(fut)
+ try:
+ yield from fut
+ return True
+ finally:
+ self._waiters.remove(fut)
+
+
+class Condition:
+ """A Condition implementation, our equivalent to threading.Condition.
+
+ This class implements condition variable objects. A condition variable
+ allows one or more coroutines to wait until they are notified by another
+ coroutine.
+
+ A new Lock object is created and used as the underlying lock.
+ """
+
+ def __init__(self, *, loop=None):
+ if loop is not None:
+ self._loop = loop
+ else:
+ self._loop = events.get_event_loop()
+
+ # Lock as an attribute as in threading.Condition.
+ lock = Lock(loop=self._loop)
+ self._lock = lock
+ # Export the lock's locked(), acquire() and release() methods.
+ self.locked = lock.locked
+ self.acquire = lock.acquire
+ self.release = lock.release
+
+ self._waiters = collections.deque()
+
+ def __repr__(self):
+ res = super().__repr__()
+ extra = 'locked' if self.locked() else 'unlocked'
+ if self._waiters:
+ extra = '{},waiters:{}'.format(extra, len(self._waiters))
+ return '<{} [{}]>'.format(res[1:-1], extra)
+
+ @tasks.coroutine
+ def wait(self):
+ """Wait until notified.
+
+ If the calling coroutine has not acquired the lock when this
+ method is called, a RuntimeError is raised.
+
+ This method releases the underlying lock, and then blocks
+ until it is awakened by a notify() or notify_all() call for
+ the same condition variable in another coroutine. Once
+ awakened, it re-acquires the lock and returns True.
+ """
+ if not self.locked():
+ raise RuntimeError('cannot wait on un-acquired lock')
+
+ keep_lock = True
+ self.release()
+ try:
+ fut = futures.Future(loop=self._loop)
+ self._waiters.append(fut)
+ try:
+ yield from fut
+ return True
+ finally:
+ self._waiters.remove(fut)
+
+ except GeneratorExit:
+ keep_lock = False # Prevent yield in finally clause.
+ raise
+ finally:
+ if keep_lock:
+ yield from self.acquire()
+
+ @tasks.coroutine
+ def wait_for(self, predicate):
+ """Wait until a predicate becomes true.
+
+ The predicate should be a callable which result will be
+ interpreted as a boolean value. The final predicate value is
+ the return value.
+ """
+ result = predicate()
+ while not result:
+ yield from self.wait()
+ result = predicate()
+ return result
+
+ def notify(self, n=1):
+ """By default, wake up one coroutine waiting on this condition, if any.
+ If the calling coroutine has not acquired the lock when this method
+ is called, a RuntimeError is raised.
+
+ This method wakes up at most n of the coroutines waiting for the
+ condition variable; it is a no-op if no coroutines are waiting.
+
+ Note: an awakened coroutine does not actually return from its
+ wait() call until it can reacquire the lock. Since notify() does
+ not release the lock, its caller should.
+ """
+ if not self.locked():
+ raise RuntimeError('cannot notify on un-acquired lock')
+
+ idx = 0
+ for fut in self._waiters:
+ if idx >= n:
+ break
+
+ if not fut.done():
+ idx += 1
+ fut.set_result(False)
+
+ def notify_all(self):
+ """Wake up all threads waiting on this condition. This method acts
+ like notify(), but wakes up all waiting threads instead of one. If the
+ calling thread has not acquired the lock when this method is called,
+ a RuntimeError is raised.
+ """
+ self.notify(len(self._waiters))
+
+ def __enter__(self):
+ return self._lock.__enter__()
+
+ def __exit__(self, *args):
+ return self._lock.__exit__(*args)
+
+ def __iter__(self):
+ yield from self.acquire()
+ return self
+
+
+class Semaphore:
+ """A Semaphore implementation.
+
+ A semaphore manages an internal counter which is decremented by each
+ acquire() call and incremented by each release() call. The counter
+ can never go below zero; when acquire() finds that it is zero, it blocks,
+ waiting until some other thread calls release().
+
+ Semaphores also support the context manager protocol.
+
+ The optional argument gives the initial value for the internal
+ counter; it defaults to 1. If the value given is less than 0,
+ ValueError is raised.
+ """
+
+ def __init__(self, value=1, *, loop=None):
+ if value < 0:
+ raise ValueError("Semaphore initial value must be >= 0")
+ self._value = value
+ self._waiters = collections.deque()
+ self._locked = (value == 0)
+ if loop is not None:
+ self._loop = loop
+ else:
+ self._loop = events.get_event_loop()
+
+ def __repr__(self):
+ res = super().__repr__()
+ extra = 'locked' if self._locked else 'unlocked,value:{}'.format(
+ self._value)
+ if self._waiters:
+ extra = '{},waiters:{}'.format(extra, len(self._waiters))
+ return '<{} [{}]>'.format(res[1:-1], extra)
+
+ def locked(self):
+ """Returns True if semaphore can not be acquired immediately."""
+ return self._locked
+
+ @tasks.coroutine
+ def acquire(self):
+ """Acquire a semaphore.
+
+ If the internal counter is larger than zero on entry,
+ decrement it by one and return True immediately. If it is
+ zero on entry, block, waiting until some other coroutine has
+ called release() to make it larger than 0, and then return
+ True.
+ """
+ if not self._waiters and self._value > 0:
+ self._value -= 1
+ if self._value == 0:
+ self._locked = True
+ return True
+
+ fut = futures.Future(loop=self._loop)
+ self._waiters.append(fut)
+ try:
+ yield from fut
+ self._value -= 1
+ if self._value == 0:
+ self._locked = True
+ return True
+ finally:
+ self._waiters.remove(fut)
+
+ def release(self):
+ """Release a semaphore, incrementing the internal counter by one.
+ When it was zero on entry and another coroutine is waiting for it to
+ become larger than zero again, wake up that coroutine.
+ """
+ self._value += 1
+ self._locked = False
+ for waiter in self._waiters:
+ if not waiter.done():
+ waiter.set_result(True)
+ break
+
+ def __enter__(self):
+ # TODO: This is questionable. How do we know the user actually
+ # wrote "with (yield from sema)" instead of "with sema"?
+ return True
+
+ def __exit__(self, *args):
+ self.release()
+
+ def __iter__(self):
+ yield from self.acquire()
+ return self
+
+
+class BoundedSemaphore(Semaphore):
+ """A bounded semaphore implementation.
+
+ This raises ValueError in release() if it would increase the value
+ above the initial value.
+ """
+
+ def __init__(self, value=1, *, loop=None):
+ self._bound_value = value
+ super().__init__(value, loop=loop)
+
+ def release(self):
+ if self._value >= self._bound_value:
+ raise ValueError('BoundedSemaphore released too many times')
+ super().release()
diff --git a/Lib/asyncio/log.py b/Lib/asyncio/log.py
new file mode 100644
index 0000000000..23a7074afb
--- /dev/null
+++ b/Lib/asyncio/log.py
@@ -0,0 +1,7 @@
+"""Logging configuration."""
+
+import logging
+
+
+# Name the logger after the package.
+logger = logging.getLogger(__package__)
diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py
new file mode 100644
index 0000000000..ce226b9ba5
--- /dev/null
+++ b/Lib/asyncio/proactor_events.py
@@ -0,0 +1,360 @@
+"""Event loop using a proactor and related classes.
+
+A proactor is a "notify-on-completion" multiplexer. Currently a
+proactor is only implemented on Windows with IOCP.
+"""
+
+import socket
+
+from . import base_events
+from . import constants
+from . import futures
+from . import transports
+from .log import logger
+
+
+class _ProactorBasePipeTransport(transports.BaseTransport):
+ """Base class for pipe and socket transports."""
+
+ def __init__(self, loop, sock, protocol, waiter=None,
+ extra=None, server=None):
+ super().__init__(extra)
+ self._set_extra(sock)
+ self._loop = loop
+ self._sock = sock
+ self._protocol = protocol
+ self._server = server
+ self._buffer = []
+ self._read_fut = None
+ self._write_fut = None
+ self._conn_lost = 0
+ self._closing = False # Set when close() called.
+ self._eof_written = False
+ if self._server is not None:
+ self._server.attach(self)
+ self._loop.call_soon(self._protocol.connection_made, self)
+ if waiter is not None:
+ self._loop.call_soon(waiter.set_result, None)
+
+ def _set_extra(self, sock):
+ self._extra['pipe'] = sock
+
+ def close(self):
+ if self._closing:
+ return
+ self._closing = True
+ self._conn_lost += 1
+ if not self._buffer and self._write_fut is None:
+ self._loop.call_soon(self._call_connection_lost, None)
+ if self._read_fut is not None:
+ self._read_fut.cancel()
+
+ def _fatal_error(self, exc):
+ logger.exception('Fatal error for %s', self)
+ self._force_close(exc)
+
+ def _force_close(self, exc):
+ if self._closing:
+ return
+ self._closing = True
+ self._conn_lost += 1
+ if self._write_fut:
+ self._write_fut.cancel()
+ if self._read_fut:
+ self._read_fut.cancel()
+ self._write_fut = self._read_fut = None
+ self._buffer = []
+ self._loop.call_soon(self._call_connection_lost, exc)
+
+ def _call_connection_lost(self, exc):
+ try:
+ self._protocol.connection_lost(exc)
+ finally:
+ # XXX If there is a pending overlapped read on the other
+ # end then it may fail with ERROR_NETNAME_DELETED if we
+ # just close our end. First calling shutdown() seems to
+ # cure it, but maybe using DisconnectEx() would be better.
+ if hasattr(self._sock, 'shutdown'):
+ self._sock.shutdown(socket.SHUT_RDWR)
+ self._sock.close()
+ server = self._server
+ if server is not None:
+ server.detach(self)
+ self._server = None
+
+
+class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
+ transports.ReadTransport):
+ """Transport for read pipes."""
+
+ def __init__(self, loop, sock, protocol, waiter=None,
+ extra=None, server=None):
+ super().__init__(loop, sock, protocol, waiter, extra, server)
+ self._read_fut = None
+ self._paused = False
+ self._loop.call_soon(self._loop_reading)
+
+ def pause_reading(self):
+ assert not self._closing, 'Cannot pause_reading() when closing'
+ assert not self._paused, 'Already paused'
+ self._paused = True
+
+ def resume_reading(self):
+ assert self._paused, 'Not paused'
+ self._paused = False
+ if self._closing:
+ return
+ self._loop.call_soon(self._loop_reading, self._read_fut)
+
+ def _loop_reading(self, fut=None):
+ if self._paused:
+ return
+ data = None
+
+ try:
+ if fut is not None:
+ assert self._read_fut is fut or (self._read_fut is None and
+ self._closing)
+ self._read_fut = None
+ data = fut.result() # deliver data later in "finally" clause
+
+ if self._closing:
+ # since close() has been called we ignore any read data
+ data = None
+ return
+
+ if data == b'':
+ # we got end-of-file so no need to reschedule a new read
+ return
+
+ # reschedule a new read
+ self._read_fut = self._loop._proactor.recv(self._sock, 4096)
+ except ConnectionAbortedError as exc:
+ if not self._closing:
+ self._fatal_error(exc)
+ except ConnectionResetError as exc:
+ self._force_close(exc)
+ except OSError as exc:
+ self._fatal_error(exc)
+ except futures.CancelledError:
+ if not self._closing:
+ raise
+ else:
+ self._read_fut.add_done_callback(self._loop_reading)
+ finally:
+ if data:
+ self._protocol.data_received(data)
+ elif data is not None:
+ keep_open = self._protocol.eof_received()
+ if not keep_open:
+ self.close()
+
+
+class _ProactorWritePipeTransport(_ProactorBasePipeTransport,
+ transports.WriteTransport):
+ """Transport for write pipes."""
+
+ def write(self, data):
+ assert isinstance(data, bytes), repr(data)
+ if self._eof_written:
+ raise IOError('write_eof() already called')
+
+ if not data:
+ return
+
+ if self._conn_lost:
+ if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
+ logger.warning('socket.send() raised exception.')
+ self._conn_lost += 1
+ return
+ self._buffer.append(data)
+ if self._write_fut is None:
+ self._loop_writing()
+
+ def _loop_writing(self, f=None):
+ try:
+ assert f is self._write_fut
+ self._write_fut = None
+ if f:
+ f.result()
+ data = b''.join(self._buffer)
+ self._buffer = []
+ if not data:
+ if self._closing:
+ self._loop.call_soon(self._call_connection_lost, None)
+ if self._eof_written:
+ self._sock.shutdown(socket.SHUT_WR)
+ return
+ self._write_fut = self._loop._proactor.send(self._sock, data)
+ self._write_fut.add_done_callback(self._loop_writing)
+ except ConnectionResetError as exc:
+ self._force_close(exc)
+ except OSError as exc:
+ self._fatal_error(exc)
+
+ def can_write_eof(self):
+ return True
+
+ def write_eof(self):
+ self.close()
+
+ def abort(self):
+ self._force_close(None)
+
+
+class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport,
+ _ProactorWritePipeTransport,
+ transports.Transport):
+ """Transport for duplex pipes."""
+
+ def can_write_eof(self):
+ return False
+
+ def write_eof(self):
+ raise NotImplementedError
+
+
+class _ProactorSocketTransport(_ProactorReadPipeTransport,
+ _ProactorWritePipeTransport,
+ transports.Transport):
+ """Transport for connected sockets."""
+
+ def _set_extra(self, sock):
+ self._extra['socket'] = sock
+ try:
+ self._extra['sockname'] = sock.getsockname()
+ except (socket.error, AttributeError):
+ pass
+ if 'peername' not in self._extra:
+ try:
+ self._extra['peername'] = sock.getpeername()
+ except (socket.error, AttributeError):
+ pass
+
+ def can_write_eof(self):
+ return True
+
+ def write_eof(self):
+ if self._closing or self._eof_written:
+ return
+ self._eof_written = True
+ if self._write_fut is None:
+ self._sock.shutdown(socket.SHUT_WR)
+
+
+class BaseProactorEventLoop(base_events.BaseEventLoop):
+
+ def __init__(self, proactor):
+ super().__init__()
+ logger.debug('Using proactor: %s', proactor.__class__.__name__)
+ self._proactor = proactor
+ self._selector = proactor # convenient alias
+ proactor.set_loop(self)
+ self._make_self_pipe()
+
+ def _make_socket_transport(self, sock, protocol, waiter=None,
+ extra=None, server=None):
+ return _ProactorSocketTransport(self, sock, protocol, waiter,
+ extra, server)
+
+ def _make_duplex_pipe_transport(self, sock, protocol, waiter=None,
+ extra=None):
+ return _ProactorDuplexPipeTransport(self,
+ sock, protocol, waiter, extra)
+
+ def _make_read_pipe_transport(self, sock, protocol, waiter=None,
+ extra=None):
+ return _ProactorReadPipeTransport(self, sock, protocol, waiter, extra)
+
+ def _make_write_pipe_transport(self, sock, protocol, waiter=None,
+ extra=None, check_for_hangup=True):
+ if check_for_hangup:
+ # We want connection_lost() to be called when other end closes
+ return _ProactorDuplexPipeTransport(self,
+ sock, protocol, waiter, extra)
+ else:
+ # If other end closes we may not notice for a long time
+ return _ProactorWritePipeTransport(self, sock, protocol, waiter,
+ extra)
+
+ def close(self):
+ if self._proactor is not None:
+ self._close_self_pipe()
+ self._proactor.close()
+ self._proactor = None
+ self._selector = None
+ super().close()
+
+ def sock_recv(self, sock, n):
+ return self._proactor.recv(sock, n)
+
+ def sock_sendall(self, sock, data):
+ return self._proactor.send(sock, data)
+
+ def sock_connect(self, sock, address):
+ return self._proactor.connect(sock, address)
+
+ def sock_accept(self, sock):
+ return self._proactor.accept(sock)
+
+ def _socketpair(self):
+ raise NotImplementedError
+
+ def _close_self_pipe(self):
+ self._ssock.close()
+ self._ssock = None
+ self._csock.close()
+ self._csock = None
+ self._internal_fds -= 1
+
+ def _make_self_pipe(self):
+ # A self-socket, really. :-)
+ self._ssock, self._csock = self._socketpair()
+ self._ssock.setblocking(False)
+ self._csock.setblocking(False)
+ self._internal_fds += 1
+ self.call_soon(self._loop_self_reading)
+
+ def _loop_self_reading(self, f=None):
+ try:
+ if f is not None:
+ f.result() # may raise
+ f = self._proactor.recv(self._ssock, 4096)
+ except:
+ self.close()
+ raise
+ else:
+ f.add_done_callback(self._loop_self_reading)
+
+ def _write_to_self(self):
+ self._csock.send(b'x')
+
+ def _start_serving(self, protocol_factory, sock, ssl=None, server=None):
+ assert not ssl, 'IocpEventLoop is incompatible with SSL.'
+
+ def loop(f=None):
+ try:
+ if f is not None:
+ conn, addr = f.result()
+ protocol = protocol_factory()
+ self._make_socket_transport(
+ conn, protocol,
+ extra={'peername': addr}, server=server)
+ f = self._proactor.accept(sock)
+ except OSError:
+ if sock.fileno() != -1:
+ logger.exception('Accept failed')
+ sock.close()
+ except futures.CancelledError:
+ sock.close()
+ else:
+ f.add_done_callback(loop)
+
+ self.call_soon(loop)
+
+ def _process_events(self, event_list):
+ pass # XXX hard work currently done in poll
+
+ def _stop_serving(self, sock):
+ self._proactor._stop_serving(sock)
+ sock.close()
diff --git a/Lib/asyncio/protocols.py b/Lib/asyncio/protocols.py
new file mode 100644
index 0000000000..1b8870c6d5
--- /dev/null
+++ b/Lib/asyncio/protocols.py
@@ -0,0 +1,128 @@
+"""Abstract Protocol class."""
+
+__all__ = ['Protocol', 'DatagramProtocol']
+
+
+class BaseProtocol:
+ """Common base class for protocol interfaces.
+
+ Usually user implements protocols that derived from BaseProtocol
+ like Protocol or ProcessProtocol.
+
+ The only case when BaseProtocol should be implemented directly is
+ write-only transport like write pipe
+ """
+
+ def connection_made(self, transport):
+ """Called when a connection is made.
+
+ The argument is the transport representing the pipe connection.
+ To receive data, wait for data_received() calls.
+ When the connection is closed, connection_lost() is called.
+ """
+
+ def connection_lost(self, exc):
+ """Called when the connection is lost or closed.
+
+ The argument is an exception object or None (the latter
+ meaning a regular EOF is received or the connection was
+ aborted or closed).
+ """
+
+ def pause_writing(self):
+ """Called when the transport's buffer goes over the high-water mark.
+
+ Pause and resume calls are paired -- pause_writing() is called
+ once when the buffer goes strictly over the high-water mark
+ (even if subsequent writes increases the buffer size even
+ more), and eventually resume_writing() is called once when the
+ buffer size reaches the low-water mark.
+
+ Note that if the buffer size equals the high-water mark,
+ pause_writing() is not called -- it must go strictly over.
+ Conversely, resume_writing() is called when the buffer size is
+ equal or lower than the low-water mark. These end conditions
+ are important to ensure that things go as expected when either
+ mark is zero.
+
+ NOTE: This is the only Protocol callback that is not called
+ through EventLoop.call_soon() -- if it were, it would have no
+ effect when it's most needed (when the app keeps writing
+ without yielding until pause_writing() is called).
+ """
+
+ def resume_writing(self):
+ """Called when the transport's buffer drains below the low-water mark.
+
+ See pause_writing() for details.
+ """
+
+
+class Protocol(BaseProtocol):
+ """Interface for stream protocol.
+
+ The user should implement this interface. They can inherit from
+ this class but don't need to. The implementations here do
+ nothing (they don't raise exceptions).
+
+ When the user wants to requests a transport, they pass a protocol
+ factory to a utility function (e.g., EventLoop.create_connection()).
+
+ When the connection is made successfully, connection_made() is
+ called with a suitable transport object. Then data_received()
+ will be called 0 or more times with data (bytes) received from the
+ transport; finally, connection_lost() will be called exactly once
+ with either an exception object or None as an argument.
+
+ State machine of calls:
+
+ start -> CM [-> DR*] [-> ER?] -> CL -> end
+ """
+
+ def data_received(self, data):
+ """Called when some data is received.
+
+ The argument is a bytes object.
+ """
+
+ def eof_received(self):
+ """Called when the other end calls write_eof() or equivalent.
+
+ If this returns a false value (including None), the transport
+ will close itself. If it returns a true value, closing the
+ transport is up to the protocol.
+ """
+
+
+class DatagramProtocol(BaseProtocol):
+ """Interface for datagram protocol."""
+
+ def datagram_received(self, data, addr):
+ """Called when some datagram is received."""
+
+ def error_received(self, exc):
+ """Called when a send or receive operation raises an OSError.
+
+ (Other than BlockingIOError or InterruptedError.)
+ """
+
+
+class SubprocessProtocol(BaseProtocol):
+ """Interface for protocol for subprocess calls."""
+
+ def pipe_data_received(self, fd, data):
+ """Called when the subprocess writes data into stdout/stderr pipe.
+
+ fd is int file dascriptor.
+ data is bytes object.
+ """
+
+ def pipe_connection_lost(self, fd, exc):
+ """Called when a file descriptor associated with the child process is
+ closed.
+
+ fd is the int file descriptor that was closed.
+ """
+
+ def process_exited(self):
+ """Called when subprocess has exited."""
diff --git a/Lib/asyncio/queues.py b/Lib/asyncio/queues.py
new file mode 100644
index 0000000000..3f5bf4479e
--- /dev/null
+++ b/Lib/asyncio/queues.py
@@ -0,0 +1,284 @@
+"""Queues"""
+
+__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'JoinableQueue',
+ 'Full', 'Empty']
+
+import collections
+import heapq
+import queue
+
+from . import events
+from . import futures
+from . import locks
+from .tasks import coroutine
+
+
+# Re-export queue.Full and .Empty exceptions.
+Full = queue.Full
+Empty = queue.Empty
+
+
+class Queue:
+ """A queue, useful for coordinating producer and consumer coroutines.
+
+ If maxsize is less than or equal to zero, the queue size is infinite. If it
+ is an integer greater than 0, then "yield from put()" will block when the
+ queue reaches maxsize, until an item is removed by get().
+
+ Unlike the standard library Queue, you can reliably know this Queue's size
+ with qsize(), since your single-threaded Tulip application won't be
+ interrupted between calling qsize() and doing an operation on the Queue.
+ """
+
+ def __init__(self, maxsize=0, *, loop=None):
+ if loop is None:
+ self._loop = events.get_event_loop()
+ else:
+ self._loop = loop
+ self._maxsize = maxsize
+
+ # Futures.
+ self._getters = collections.deque()
+ # Pairs of (item, Future).
+ self._putters = collections.deque()
+ self._init(maxsize)
+
+ def _init(self, maxsize):
+ self._queue = collections.deque()
+
+ def _get(self):
+ return self._queue.popleft()
+
+ def _put(self, item):
+ self._queue.append(item)
+
+ def __repr__(self):
+ return '<{} at {:#x} {}>'.format(
+ type(self).__name__, id(self), self._format())
+
+ def __str__(self):
+ return '<{} {}>'.format(type(self).__name__, self._format())
+
+ def _format(self):
+ result = 'maxsize={!r}'.format(self._maxsize)
+ if getattr(self, '_queue', None):
+ result += ' _queue={!r}'.format(list(self._queue))
+ if self._getters:
+ result += ' _getters[{}]'.format(len(self._getters))
+ if self._putters:
+ result += ' _putters[{}]'.format(len(self._putters))
+ return result
+
+ def _consume_done_getters(self):
+ # Delete waiters at the head of the get() queue who've timed out.
+ while self._getters and self._getters[0].done():
+ self._getters.popleft()
+
+ def _consume_done_putters(self):
+ # Delete waiters at the head of the put() queue who've timed out.
+ while self._putters and self._putters[0][1].done():
+ self._putters.popleft()
+
+ def qsize(self):
+ """Number of items in the queue."""
+ return len(self._queue)
+
+ @property
+ def maxsize(self):
+ """Number of items allowed in the queue."""
+ return self._maxsize
+
+ def empty(self):
+ """Return True if the queue is empty, False otherwise."""
+ return not self._queue
+
+ def full(self):
+ """Return True if there are maxsize items in the queue.
+
+ Note: if the Queue was initialized with maxsize=0 (the default),
+ then full() is never True.
+ """
+ if self._maxsize <= 0:
+ return False
+ else:
+ return self.qsize() == self._maxsize
+
+ @coroutine
+ def put(self, item):
+ """Put an item into the queue.
+
+ If you yield from put(), wait until a free slot is available
+ before adding item.
+ """
+ self._consume_done_getters()
+ if self._getters:
+ assert not self._queue, (
+ 'queue non-empty, why are getters waiting?')
+
+ getter = self._getters.popleft()
+
+ # Use _put and _get instead of passing item straight to getter, in
+ # case a subclass has logic that must run (e.g. JoinableQueue).
+ self._put(item)
+ getter.set_result(self._get())
+
+ elif self._maxsize > 0 and self._maxsize == self.qsize():
+ waiter = futures.Future(loop=self._loop)
+
+ self._putters.append((item, waiter))
+ yield from waiter
+
+ else:
+ self._put(item)
+
+ def put_nowait(self, item):
+ """Put an item into the queue without blocking.
+
+ If no free slot is immediately available, raise Full.
+ """
+ self._consume_done_getters()
+ if self._getters:
+ assert not self._queue, (
+ 'queue non-empty, why are getters waiting?')
+
+ getter = self._getters.popleft()
+
+ # Use _put and _get instead of passing item straight to getter, in
+ # case a subclass has logic that must run (e.g. JoinableQueue).
+ self._put(item)
+ getter.set_result(self._get())
+
+ elif self._maxsize > 0 and self._maxsize == self.qsize():
+ raise Full
+ else:
+ self._put(item)
+
+ @coroutine
+ def get(self):
+ """Remove and return an item from the queue.
+
+ If you yield from get(), wait until a item is available.
+ """
+ self._consume_done_putters()
+ if self._putters:
+ assert self.full(), 'queue not full, why are putters waiting?'
+ item, putter = self._putters.popleft()
+ self._put(item)
+
+ # When a getter runs and frees up a slot so this putter can
+ # run, we need to defer the put for a tick to ensure that
+ # getters and putters alternate perfectly. See
+ # ChannelTest.test_wait.
+ self._loop.call_soon(putter.set_result, None)
+
+ return self._get()
+
+ elif self.qsize():
+ return self._get()
+ else:
+ waiter = futures.Future(loop=self._loop)
+
+ self._getters.append(waiter)
+ return (yield from waiter)
+
+ def get_nowait(self):
+ """Remove and return an item from the queue.
+
+ Return an item if one is immediately available, else raise Empty.
+ """
+ self._consume_done_putters()
+ if self._putters:
+ assert self.full(), 'queue not full, why are putters waiting?'
+ item, putter = self._putters.popleft()
+ self._put(item)
+ # Wake putter on next tick.
+ putter.set_result(None)
+
+ return self._get()
+
+ elif self.qsize():
+ return self._get()
+ else:
+ raise Empty
+
+
+class PriorityQueue(Queue):
+ """A subclass of Queue; retrieves entries in priority order (lowest first).
+
+ Entries are typically tuples of the form: (priority number, data).
+ """
+
+ def _init(self, maxsize):
+ self._queue = []
+
+ def _put(self, item, heappush=heapq.heappush):
+ heappush(self._queue, item)
+
+ def _get(self, heappop=heapq.heappop):
+ return heappop(self._queue)
+
+
+class LifoQueue(Queue):
+ """A subclass of Queue that retrieves most recently added entries first."""
+
+ def _init(self, maxsize):
+ self._queue = []
+
+ def _put(self, item):
+ self._queue.append(item)
+
+ def _get(self):
+ return self._queue.pop()
+
+
+class JoinableQueue(Queue):
+ """A subclass of Queue with task_done() and join() methods."""
+
+ def __init__(self, maxsize=0, *, loop=None):
+ super().__init__(maxsize=maxsize, loop=loop)
+ self._unfinished_tasks = 0
+ self._finished = locks.Event(loop=self._loop)
+ self._finished.set()
+
+ def _format(self):
+ result = Queue._format(self)
+ if self._unfinished_tasks:
+ result += ' tasks={}'.format(self._unfinished_tasks)
+ return result
+
+ def _put(self, item):
+ super()._put(item)
+ self._unfinished_tasks += 1
+ self._finished.clear()
+
+ def task_done(self):
+ """Indicate that a formerly enqueued task is complete.
+
+ Used by queue consumers. For each get() used to fetch a task,
+ a subsequent call to task_done() tells the queue that the processing
+ on the task is complete.
+
+ If a join() is currently blocking, it will resume when all items have
+ been processed (meaning that a task_done() call was received for every
+ item that had been put() into the queue).
+
+ Raises ValueError if called more times than there were items placed in
+ the queue.
+ """
+ if self._unfinished_tasks <= 0:
+ raise ValueError('task_done() called too many times')
+ self._unfinished_tasks -= 1
+ if self._unfinished_tasks == 0:
+ self._finished.set()
+
+ @coroutine
+ def join(self):
+ """Block until all items in the queue have been gotten and processed.
+
+ The count of unfinished tasks goes up whenever an item is added to the
+ queue. The count goes down whenever a consumer thread calls task_done()
+ to indicate that the item was retrieved and all work on it is complete.
+ When the count of unfinished tasks drops to zero, join() unblocks.
+ """
+ if self._unfinished_tasks > 0:
+ yield from self._finished.wait()
diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
new file mode 100644
index 0000000000..93efddc95a
--- /dev/null
+++ b/Lib/asyncio/selector_events.py
@@ -0,0 +1,862 @@
+"""Event loop using a selector and related classes.
+
+A selector is a "notify-when-ready" multiplexer. For a subclass which
+also includes support for signal handling, see the unix_events sub-module.
+"""
+
+import collections
+import errno
+import socket
+try:
+ import ssl
+except ImportError: # pragma: no cover
+ ssl = None
+
+from . import base_events
+from . import constants
+from . import events
+from . import futures
+from . import selectors
+from . import transports
+from .log import logger
+
+
+class BaseSelectorEventLoop(base_events.BaseEventLoop):
+ """Selector event loop.
+
+ See events.EventLoop for API specification.
+ """
+
+ def __init__(self, selector=None):
+ super().__init__()
+
+ if selector is None:
+ selector = selectors.DefaultSelector()
+ logger.debug('Using selector: %s', selector.__class__.__name__)
+ self._selector = selector
+ self._make_self_pipe()
+
+ def _make_socket_transport(self, sock, protocol, waiter=None, *,
+ extra=None, server=None):
+ return _SelectorSocketTransport(self, sock, protocol, waiter,
+ extra, server)
+
+ def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter, *,
+ server_side=False, server_hostname=None,
+ extra=None, server=None):
+ return _SelectorSslTransport(
+ self, rawsock, protocol, sslcontext, waiter,
+ server_side, server_hostname, extra, server)
+
+ def _make_datagram_transport(self, sock, protocol,
+ address=None, extra=None):
+ return _SelectorDatagramTransport(self, sock, protocol, address, extra)
+
+ def close(self):
+ if self._selector is not None:
+ self._close_self_pipe()
+ self._selector.close()
+ self._selector = None
+ super().close()
+
+ def _socketpair(self):
+ raise NotImplementedError
+
+ def _close_self_pipe(self):
+ self.remove_reader(self._ssock.fileno())
+ self._ssock.close()
+ self._ssock = None
+ self._csock.close()
+ self._csock = None
+ self._internal_fds -= 1
+
+ def _make_self_pipe(self):
+ # A self-socket, really. :-)
+ self._ssock, self._csock = self._socketpair()
+ self._ssock.setblocking(False)
+ self._csock.setblocking(False)
+ self._internal_fds += 1
+ self.add_reader(self._ssock.fileno(), self._read_from_self)
+
+ def _read_from_self(self):
+ try:
+ self._ssock.recv(1)
+ except (BlockingIOError, InterruptedError):
+ pass
+
+ def _write_to_self(self):
+ try:
+ self._csock.send(b'x')
+ except (BlockingIOError, InterruptedError):
+ pass
+
+ def _start_serving(self, protocol_factory, sock,
+ sslcontext=None, server=None):
+ self.add_reader(sock.fileno(), self._accept_connection,
+ protocol_factory, sock, sslcontext, server)
+
+ def _accept_connection(self, protocol_factory, sock,
+ sslcontext=None, server=None):
+ try:
+ conn, addr = sock.accept()
+ conn.setblocking(False)
+ except (BlockingIOError, InterruptedError, ConnectionAbortedError):
+ pass # False alarm.
+ except OSError as exc:
+ # There's nowhere to send the error, so just log it.
+ # TODO: Someone will want an error handler for this.
+ if exc.errno in (errno.EMFILE, errno.ENFILE,
+ errno.ENOBUFS, errno.ENOMEM):
+ # Some platforms (e.g. Linux keep reporting the FD as
+ # ready, so we remove the read handler temporarily.
+ # We'll try again in a while.
+ logger.exception('Accept out of system resource (%s)', exc)
+ self.remove_reader(sock.fileno())
+ self.call_later(constants.ACCEPT_RETRY_DELAY,
+ self._start_serving,
+ protocol_factory, sock, sslcontext, server)
+ else:
+ raise # The event loop will catch, log and ignore it.
+ else:
+ if sslcontext:
+ self._make_ssl_transport(
+ conn, protocol_factory(), sslcontext, None,
+ server_side=True, extra={'peername': addr}, server=server)
+ else:
+ self._make_socket_transport(
+ conn, protocol_factory(), extra={'peername': addr},
+ server=server)
+ # It's now up to the protocol to handle the connection.
+
+ def add_reader(self, fd, callback, *args):
+ """Add a reader callback."""
+ handle = events.make_handle(callback, args)
+ try:
+ key = self._selector.get_key(fd)
+ except KeyError:
+ self._selector.register(fd, selectors.EVENT_READ,
+ (handle, None))
+ else:
+ mask, (reader, writer) = key.events, key.data
+ self._selector.modify(fd, mask | selectors.EVENT_READ,
+ (handle, writer))
+ if reader is not None:
+ reader.cancel()
+
+ def remove_reader(self, fd):
+ """Remove a reader callback."""
+ try:
+ key = self._selector.get_key(fd)
+ except KeyError:
+ return False
+ else:
+ mask, (reader, writer) = key.events, key.data
+ mask &= ~selectors.EVENT_READ
+ if not mask:
+ self._selector.unregister(fd)
+ else:
+ self._selector.modify(fd, mask, (None, writer))
+
+ if reader is not None:
+ reader.cancel()
+ return True
+ else:
+ return False
+
+ def add_writer(self, fd, callback, *args):
+ """Add a writer callback.."""
+ handle = events.make_handle(callback, args)
+ try:
+ key = self._selector.get_key(fd)
+ except KeyError:
+ self._selector.register(fd, selectors.EVENT_WRITE,
+ (None, handle))
+ else:
+ mask, (reader, writer) = key.events, key.data
+ self._selector.modify(fd, mask | selectors.EVENT_WRITE,
+ (reader, handle))
+ if writer is not None:
+ writer.cancel()
+
+ def remove_writer(self, fd):
+ """Remove a writer callback."""
+ try:
+ key = self._selector.get_key(fd)
+ except KeyError:
+ return False
+ else:
+ mask, (reader, writer) = key.events, key.data
+ # Remove both writer and connector.
+ mask &= ~selectors.EVENT_WRITE
+ if not mask:
+ self._selector.unregister(fd)
+ else:
+ self._selector.modify(fd, mask, (reader, None))
+
+ if writer is not None:
+ writer.cancel()
+ return True
+ else:
+ return False
+
+ def sock_recv(self, sock, n):
+ """XXX"""
+ fut = futures.Future(loop=self)
+ self._sock_recv(fut, False, sock, n)
+ return fut
+
+ def _sock_recv(self, fut, registered, sock, n):
+ fd = sock.fileno()
+ if registered:
+ # Remove the callback early. It should be rare that the
+ # selector says the fd is ready but the call still returns
+ # EAGAIN, and I am willing to take a hit in that case in
+ # order to simplify the common case.
+ self.remove_reader(fd)
+ if fut.cancelled():
+ return
+ try:
+ data = sock.recv(n)
+ except (BlockingIOError, InterruptedError):
+ self.add_reader(fd, self._sock_recv, fut, True, sock, n)
+ except Exception as exc:
+ fut.set_exception(exc)
+ else:
+ fut.set_result(data)
+
+ def sock_sendall(self, sock, data):
+ """XXX"""
+ fut = futures.Future(loop=self)
+ if data:
+ self._sock_sendall(fut, False, sock, data)
+ else:
+ fut.set_result(None)
+ return fut
+
+ def _sock_sendall(self, fut, registered, sock, data):
+ fd = sock.fileno()
+
+ if registered:
+ self.remove_writer(fd)
+ if fut.cancelled():
+ return
+
+ try:
+ n = sock.send(data)
+ except (BlockingIOError, InterruptedError):
+ n = 0
+ except Exception as exc:
+ fut.set_exception(exc)
+ return
+
+ if n == len(data):
+ fut.set_result(None)
+ else:
+ if n:
+ data = data[n:]
+ self.add_writer(fd, self._sock_sendall, fut, True, sock, data)
+
+ def sock_connect(self, sock, address):
+ """XXX"""
+ # That address better not require a lookup! We're not calling
+ # self.getaddrinfo() for you here. But verifying this is
+ # complicated; the socket module doesn't have a pattern for
+ # IPv6 addresses (there are too many forms, apparently).
+ fut = futures.Future(loop=self)
+ self._sock_connect(fut, False, sock, address)
+ return fut
+
+ def _sock_connect(self, fut, registered, sock, address):
+ # TODO: Use getaddrinfo() to look up the address, to avoid the
+ # trap of hanging the entire event loop when the address
+ # requires doing a DNS lookup. (OTOH, the caller should
+ # already have done this, so it would be nice if we could
+ # easily tell whether the address needs looking up or not. I
+ # know how to do this for IPv4, but IPv6 addresses have many
+ # syntaxes.)
+ fd = sock.fileno()
+ if registered:
+ self.remove_writer(fd)
+ if fut.cancelled():
+ return
+ try:
+ if not registered:
+ # First time around.
+ sock.connect(address)
+ else:
+ err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if err != 0:
+ # Jump to the except clause below.
+ raise OSError(err, 'Connect call failed %s' % (address,))
+ except (BlockingIOError, InterruptedError):
+ self.add_writer(fd, self._sock_connect, fut, True, sock, address)
+ except Exception as exc:
+ fut.set_exception(exc)
+ else:
+ fut.set_result(None)
+
+ def sock_accept(self, sock):
+ """XXX"""
+ fut = futures.Future(loop=self)
+ self._sock_accept(fut, False, sock)
+ return fut
+
+ def _sock_accept(self, fut, registered, sock):
+ fd = sock.fileno()
+ if registered:
+ self.remove_reader(fd)
+ if fut.cancelled():
+ return
+ try:
+ conn, address = sock.accept()
+ conn.setblocking(False)
+ except (BlockingIOError, InterruptedError):
+ self.add_reader(fd, self._sock_accept, fut, True, sock)
+ except Exception as exc:
+ fut.set_exception(exc)
+ else:
+ fut.set_result((conn, address))
+
+ def _process_events(self, event_list):
+ for key, mask in event_list:
+ fileobj, (reader, writer) = key.fileobj, key.data
+ if mask & selectors.EVENT_READ and reader is not None:
+ if reader._cancelled:
+ self.remove_reader(fileobj)
+ else:
+ self._add_callback(reader)
+ if mask & selectors.EVENT_WRITE and writer is not None:
+ if writer._cancelled:
+ self.remove_writer(fileobj)
+ else:
+ self._add_callback(writer)
+
+ def _stop_serving(self, sock):
+ self.remove_reader(sock.fileno())
+ sock.close()
+
+
+class _SelectorTransport(transports.Transport):
+
+ max_size = 256 * 1024 # Buffer size passed to recv().
+
+ _buffer_factory = bytearray # Constructs initial value for self._buffer.
+
+ def __init__(self, loop, sock, protocol, extra, server=None):
+ super().__init__(extra)
+ self._extra['socket'] = sock
+ self._extra['sockname'] = sock.getsockname()
+ if 'peername' not in self._extra:
+ try:
+ self._extra['peername'] = sock.getpeername()
+ except socket.error:
+ self._extra['peername'] = None
+ self._loop = loop
+ self._sock = sock
+ self._sock_fd = sock.fileno()
+ self._protocol = protocol
+ self._server = server
+ self._buffer = self._buffer_factory()
+ self._conn_lost = 0 # Set when call to connection_lost scheduled.
+ self._closing = False # Set when close() called.
+ self._protocol_paused = False
+ self.set_write_buffer_limits()
+ if self._server is not None:
+ self._server.attach(self)
+
+ def abort(self):
+ self._force_close(None)
+
+ def close(self):
+ if self._closing:
+ return
+ self._closing = True
+ self._loop.remove_reader(self._sock_fd)
+ if not self._buffer:
+ self._conn_lost += 1
+ self._loop.call_soon(self._call_connection_lost, None)
+
+ def _fatal_error(self, exc):
+ # Should be called from exception handler only.
+ if not isinstance(exc, (BrokenPipeError, ConnectionResetError)):
+ logger.exception('Fatal error for %s', self)
+ self._force_close(exc)
+
+ def _force_close(self, exc):
+ if self._conn_lost:
+ return
+ if self._buffer:
+ self._buffer.clear()
+ self._loop.remove_writer(self._sock_fd)
+ if not self._closing:
+ self._closing = True
+ self._loop.remove_reader(self._sock_fd)
+ self._conn_lost += 1
+ self._loop.call_soon(self._call_connection_lost, exc)
+
+ def _call_connection_lost(self, exc):
+ try:
+ self._protocol.connection_lost(exc)
+ finally:
+ self._sock.close()
+ self._sock = None
+ self._protocol = None
+ self._loop = None
+ server = self._server
+ if server is not None:
+ server.detach(self)
+ self._server = None
+
+ def _maybe_pause_protocol(self):
+ size = self.get_write_buffer_size()
+ if size <= self._high_water:
+ return
+ if not self._protocol_paused:
+ self._protocol_paused = True
+ try:
+ self._protocol.pause_writing()
+ except Exception:
+ logger.exception('pause_writing() failed')
+
+ def _maybe_resume_protocol(self):
+ if (self._protocol_paused and
+ self.get_write_buffer_size() <= self._low_water):
+ self._protocol_paused = False
+ try:
+ self._protocol.resume_writing()
+ except Exception:
+ logger.exception('resume_writing() failed')
+
+ def set_write_buffer_limits(self, high=None, low=None):
+ if high is None:
+ if low is None:
+ high = 64*1024
+ else:
+ high = 4*low
+ if low is None:
+ low = high // 4
+ if not high >= low >= 0:
+ raise ValueError('high (%r) must be >= low (%r) must be >= 0' %
+ (high, low))
+ self._high_water = high
+ self._low_water = low
+
+ def get_write_buffer_size(self):
+ return len(self._buffer)
+
+
+class _SelectorSocketTransport(_SelectorTransport):
+
+ def __init__(self, loop, sock, protocol, waiter=None,
+ extra=None, server=None):
+ super().__init__(loop, sock, protocol, extra, server)
+ self._eof = False
+ self._paused = False
+
+ self._loop.add_reader(self._sock_fd, self._read_ready)
+ self._loop.call_soon(self._protocol.connection_made, self)
+ if waiter is not None:
+ self._loop.call_soon(waiter.set_result, None)
+
+ def pause_reading(self):
+ if self._closing:
+ raise RuntimeError('Cannot pause_reading() when closing')
+ if self._paused:
+ raise RuntimeError('Already paused')
+ self._paused = True
+ self._loop.remove_reader(self._sock_fd)
+
+ def resume_reading(self):
+ if not self._paused:
+ raise RuntimeError('Not paused')
+ self._paused = False
+ if self._closing:
+ return
+ self._loop.add_reader(self._sock_fd, self._read_ready)
+
+ def _read_ready(self):
+ try:
+ data = self._sock.recv(self.max_size)
+ except (BlockingIOError, InterruptedError):
+ pass
+ except Exception as exc:
+ self._fatal_error(exc)
+ else:
+ if data:
+ self._protocol.data_received(data)
+ else:
+ keep_open = self._protocol.eof_received()
+ if keep_open:
+ # We're keeping the connection open so the
+ # protocol can write more, but we still can't
+ # receive more, so remove the reader callback.
+ self._loop.remove_reader(self._sock_fd)
+ else:
+ self.close()
+
+ def write(self, data):
+ if not isinstance(data, (bytes, bytearray, memoryview)):
+ raise TypeError('data argument must be byte-ish (%r)',
+ type(data))
+ if self._eof:
+ raise RuntimeError('Cannot call write() after write_eof()')
+ if not data:
+ return
+
+ if self._conn_lost:
+ if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
+ logger.warning('socket.send() raised exception.')
+ self._conn_lost += 1
+ return
+
+ if not self._buffer:
+ # Optimization: try to send now.
+ try:
+ n = self._sock.send(data)
+ except (BlockingIOError, InterruptedError):
+ pass
+ except Exception as exc:
+ self._fatal_error(exc)
+ return
+ else:
+ data = data[n:]
+ if not data:
+ return
+ # Not all was written; register write handler.
+ self._loop.add_writer(self._sock_fd, self._write_ready)
+
+ # Add it to the buffer.
+ self._buffer.extend(data)
+ self._maybe_pause_protocol()
+
+ def _write_ready(self):
+ assert self._buffer, 'Data should not be empty'
+
+ try:
+ n = self._sock.send(self._buffer)
+ except (BlockingIOError, InterruptedError):
+ pass
+ except Exception as exc:
+ self._loop.remove_writer(self._sock_fd)
+ self._buffer.clear()
+ self._fatal_error(exc)
+ else:
+ if n:
+ del self._buffer[:n]
+ self._maybe_resume_protocol() # May append to buffer.
+ if not self._buffer:
+ self._loop.remove_writer(self._sock_fd)
+ if self._closing:
+ self._call_connection_lost(None)
+ elif self._eof:
+ self._sock.shutdown(socket.SHUT_WR)
+
+ def write_eof(self):
+ if self._eof:
+ return
+ self._eof = True
+ if not self._buffer:
+ self._sock.shutdown(socket.SHUT_WR)
+
+ def can_write_eof(self):
+ return True
+
+
+class _SelectorSslTransport(_SelectorTransport):
+
+ _buffer_factory = bytearray
+
+ def __init__(self, loop, rawsock, protocol, sslcontext, waiter=None,
+ server_side=False, server_hostname=None,
+ extra=None, server=None):
+ if ssl is None:
+ raise RuntimeError('stdlib ssl module not available')
+
+ if server_side:
+ if not sslcontext:
+ raise ValueError('Server side ssl needs a valid SSLContext')
+ else:
+ if not sslcontext:
+ # Client side may pass ssl=True to use a default
+ # context; in that case the sslcontext passed is None.
+ # The default is the same as used by urllib with
+ # cadefault=True.
+ if hasattr(ssl, '_create_stdlib_context'):
+ sslcontext = ssl._create_stdlib_context(
+ cert_reqs=ssl.CERT_REQUIRED)
+ else:
+ # Fallback for Python 3.3.
+ sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ sslcontext.options |= ssl.OP_NO_SSLv2
+ sslcontext.set_default_verify_paths()
+ sslcontext.verify_mode = ssl.CERT_REQUIRED
+
+ wrap_kwargs = {
+ 'server_side': server_side,
+ 'do_handshake_on_connect': False,
+ }
+ if server_hostname and not server_side and ssl.HAS_SNI:
+ wrap_kwargs['server_hostname'] = server_hostname
+ sslsock = sslcontext.wrap_socket(rawsock, **wrap_kwargs)
+
+ super().__init__(loop, sslsock, protocol, extra, server)
+
+ self._server_hostname = server_hostname
+ self._waiter = waiter
+ self._rawsock = rawsock
+ self._sslcontext = sslcontext
+ self._paused = False
+
+ # SSL-specific extra info. (peercert is set later)
+ self._extra.update(sslcontext=sslcontext)
+
+ self._on_handshake()
+
+ def _on_handshake(self):
+ try:
+ self._sock.do_handshake()
+ except ssl.SSLWantReadError:
+ self._loop.add_reader(self._sock_fd, self._on_handshake)
+ return
+ except ssl.SSLWantWriteError:
+ self._loop.add_writer(self._sock_fd, self._on_handshake)
+ return
+ except Exception as exc:
+ self._loop.remove_reader(self._sock_fd)
+ self._loop.remove_writer(self._sock_fd)
+ self._sock.close()
+ if self._waiter is not None:
+ self._waiter.set_exception(exc)
+ return
+ except BaseException as exc:
+ self._loop.remove_reader(self._sock_fd)
+ self._loop.remove_writer(self._sock_fd)
+ self._sock.close()
+ if self._waiter is not None:
+ self._waiter.set_exception(exc)
+ raise
+
+ self._loop.remove_reader(self._sock_fd)
+ self._loop.remove_writer(self._sock_fd)
+
+ # Verify hostname if requested.
+ peercert = self._sock.getpeercert()
+ if (self._server_hostname and
+ self._sslcontext.verify_mode != ssl.CERT_NONE):
+ try:
+ ssl.match_hostname(peercert, self._server_hostname)
+ except Exception as exc:
+ self._sock.close()
+ if self._waiter is not None:
+ self._waiter.set_exception(exc)
+ return
+
+ # Add extra info that becomes available after handshake.
+ self._extra.update(peercert=peercert,
+ cipher=self._sock.cipher(),
+ compression=self._sock.compression(),
+ )
+
+ self._read_wants_write = False
+ self._write_wants_read = False
+ self._loop.add_reader(self._sock_fd, self._read_ready)
+ self._loop.call_soon(self._protocol.connection_made, self)
+ if self._waiter is not None:
+ self._loop.call_soon(self._waiter.set_result, None)
+
+ def pause_reading(self):
+ # XXX This is a bit icky, given the comment at the top of
+ # _read_ready(). Is it possible to evoke a deadlock? I don't
+ # know, although it doesn't look like it; write() will still
+ # accept more data for the buffer and eventually the app will
+ # call resume_reading() again, and things will flow again.
+
+ if self._closing:
+ raise RuntimeError('Cannot pause_reading() when closing')
+ if self._paused:
+ raise RuntimeError('Already paused')
+ self._paused = True
+ self._loop.remove_reader(self._sock_fd)
+
+ def resume_reading(self):
+ if not self._paused:
+ raise ('Not paused')
+ self._paused = False
+ if self._closing:
+ return
+ self._loop.add_reader(self._sock_fd, self._read_ready)
+
+ def _read_ready(self):
+ if self._write_wants_read:
+ self._write_wants_read = False
+ self._write_ready()
+
+ if self._buffer:
+ self._loop.add_writer(self._sock_fd, self._write_ready)
+
+ try:
+ data = self._sock.recv(self.max_size)
+ except (BlockingIOError, InterruptedError, ssl.SSLWantReadError):
+ pass
+ except ssl.SSLWantWriteError:
+ self._read_wants_write = True
+ self._loop.remove_reader(self._sock_fd)
+ self._loop.add_writer(self._sock_fd, self._write_ready)
+ except Exception as exc:
+ self._fatal_error(exc)
+ else:
+ if data:
+ self._protocol.data_received(data)
+ else:
+ try:
+ keep_open = self._protocol.eof_received()
+ if keep_open:
+ logger.warning('returning true from eof_received() '
+ 'has no effect when using ssl')
+ finally:
+ self.close()
+
+ def _write_ready(self):
+ if self._read_wants_write:
+ self._read_wants_write = False
+ self._read_ready()
+
+ if not (self._paused or self._closing):
+ self._loop.add_reader(self._sock_fd, self._read_ready)
+
+ if self._buffer:
+ try:
+ n = self._sock.send(self._buffer)
+ except (BlockingIOError, InterruptedError,
+ ssl.SSLWantWriteError):
+ n = 0
+ except ssl.SSLWantReadError:
+ n = 0
+ self._loop.remove_writer(self._sock_fd)
+ self._write_wants_read = True
+ except Exception as exc:
+ self._loop.remove_writer(self._sock_fd)
+ self._buffer.clear()
+ self._fatal_error(exc)
+ return
+
+ if n:
+ del self._buffer[:n]
+
+ self._maybe_resume_protocol() # May append to buffer.
+
+ if not self._buffer:
+ self._loop.remove_writer(self._sock_fd)
+ if self._closing:
+ self._call_connection_lost(None)
+
+ def write(self, data):
+ if not isinstance(data, (bytes, bytearray, memoryview)):
+ raise TypeError('data argument must be byte-ish (%r)',
+ type(data))
+ if not data:
+ return
+
+ if self._conn_lost:
+ if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
+ logger.warning('socket.send() raised exception.')
+ self._conn_lost += 1
+ return
+
+ if not self._buffer:
+ self._loop.add_writer(self._sock_fd, self._write_ready)
+
+ # Add it to the buffer.
+ self._buffer.extend(data)
+ self._maybe_pause_protocol()
+
+ def can_write_eof(self):
+ return False
+
+
+class _SelectorDatagramTransport(_SelectorTransport):
+
+ _buffer_factory = collections.deque
+
+ def __init__(self, loop, sock, protocol, address=None, extra=None):
+ super().__init__(loop, sock, protocol, extra)
+ self._address = address
+ self._loop.add_reader(self._sock_fd, self._read_ready)
+ self._loop.call_soon(self._protocol.connection_made, self)
+
+ def get_write_buffer_size(self):
+ return sum(len(data) for data, _ in self._buffer)
+
+ def _read_ready(self):
+ try:
+ data, addr = self._sock.recvfrom(self.max_size)
+ except (BlockingIOError, InterruptedError):
+ pass
+ except OSError as exc:
+ self._protocol.error_received(exc)
+ except Exception as exc:
+ self._fatal_error(exc)
+ else:
+ self._protocol.datagram_received(data, addr)
+
+ def sendto(self, data, addr=None):
+ if not isinstance(data, (bytes, bytearray, memoryview)):
+ raise TypeError('data argument must be byte-ish (%r)',
+ type(data))
+ if not data:
+ return
+
+ if self._address and addr not in (None, self._address):
+ raise ValueError('Invalid address: must be None or %s' %
+ (self._address,))
+
+ if self._conn_lost and self._address:
+ if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
+ logger.warning('socket.send() raised exception.')
+ self._conn_lost += 1
+ return
+
+ if not self._buffer:
+ # Attempt to send it right away first.
+ try:
+ if self._address:
+ self._sock.send(data)
+ else:
+ self._sock.sendto(data, addr)
+ return
+ except (BlockingIOError, InterruptedError):
+ self._loop.add_writer(self._sock_fd, self._sendto_ready)
+ except OSError as exc:
+ self._protocol.error_received(exc)
+ return
+ except Exception as exc:
+ self._fatal_error(exc)
+ return
+
+ # Ensure that what we buffer is immutable.
+ self._buffer.append((bytes(data), addr))
+ self._maybe_pause_protocol()
+
+ def _sendto_ready(self):
+ while self._buffer:
+ data, addr = self._buffer.popleft()
+ try:
+ if self._address:
+ self._sock.send(data)
+ else:
+ self._sock.sendto(data, addr)
+ except (BlockingIOError, InterruptedError):
+ self._buffer.appendleft((data, addr)) # Try again later.
+ break
+ except OSError as exc:
+ self._protocol.error_received(exc)
+ return
+ except Exception as exc:
+ self._fatal_error(exc)
+ return
+
+ self._maybe_resume_protocol() # May append to buffer.
+ if not self._buffer:
+ self._loop.remove_writer(self._sock_fd)
+ if self._closing:
+ self._call_connection_lost(None)
diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py
new file mode 100644
index 0000000000..50c4c5d1c2
--- /dev/null
+++ b/Lib/asyncio/streams.py
@@ -0,0 +1,398 @@
+"""Stream-related things."""
+
+__all__ = ['StreamReader', 'StreamWriter', 'StreamReaderProtocol',
+ 'open_connection', 'start_server',
+ ]
+
+import collections
+
+from . import events
+from . import futures
+from . import protocols
+from . import tasks
+
+
+_DEFAULT_LIMIT = 2**16
+
+
+@tasks.coroutine
+def open_connection(host=None, port=None, *,
+ loop=None, limit=_DEFAULT_LIMIT, **kwds):
+ """A wrapper for create_connection() returning a (reader, writer) pair.
+
+ The reader returned is a StreamReader instance; the writer is a
+ Transport.
+
+ The arguments are all the usual arguments to create_connection()
+ except protocol_factory; most common are positional host and port,
+ with various optional keyword arguments following.
+
+ Additional optional keyword arguments are loop (to set the event loop
+ instance to use) and limit (to set the buffer limit passed to the
+ StreamReader).
+
+ (If you want to customize the StreamReader and/or
+ StreamReaderProtocol classes, just copy the code -- there's
+ really nothing special here except some convenience.)
+ """
+ if loop is None:
+ loop = events.get_event_loop()
+ reader = StreamReader(limit=limit, loop=loop)
+ protocol = StreamReaderProtocol(reader)
+ transport, _ = yield from loop.create_connection(
+ lambda: protocol, host, port, **kwds)
+ writer = StreamWriter(transport, protocol, reader, loop)
+ return reader, writer
+
+
+@tasks.coroutine
+def start_server(client_connected_cb, host=None, port=None, *,
+ loop=None, limit=_DEFAULT_LIMIT, **kwds):
+ """Start a socket server, call back for each client connected.
+
+ The first parameter, `client_connected_cb`, takes two parameters:
+ client_reader, client_writer. client_reader is a StreamReader
+ object, while client_writer is a StreamWriter object. This
+ parameter can either be a plain callback function or a coroutine;
+ if it is a coroutine, it will be automatically converted into a
+ Task.
+
+ The rest of the arguments are all the usual arguments to
+ loop.create_server() except protocol_factory; most common are
+ positional host and port, with various optional keyword arguments
+ following. The return value is the same as loop.create_server().
+
+ Additional optional keyword arguments are loop (to set the event loop
+ instance to use) and limit (to set the buffer limit passed to the
+ StreamReader).
+
+ The return value is the same as loop.create_server(), i.e. a
+ Server object which can be used to stop the service.
+ """
+ if loop is None:
+ loop = events.get_event_loop()
+
+ def factory():
+ reader = StreamReader(limit=limit, loop=loop)
+ protocol = StreamReaderProtocol(reader, client_connected_cb,
+ loop=loop)
+ return protocol
+
+ return (yield from loop.create_server(factory, host, port, **kwds))
+
+
+class StreamReaderProtocol(protocols.Protocol):
+ """Trivial helper class to adapt between Protocol and StreamReader.
+
+ (This is a helper class instead of making StreamReader itself a
+ Protocol subclass, because the StreamReader has other potential
+ uses, and to prevent the user of the StreamReader to accidentally
+ call inappropriate methods of the protocol.)
+ """
+
+ def __init__(self, stream_reader, client_connected_cb=None, loop=None):
+ self._stream_reader = stream_reader
+ self._stream_writer = None
+ self._drain_waiter = None
+ self._paused = False
+ self._client_connected_cb = client_connected_cb
+ self._loop = loop # May be None; we may never need it.
+
+ def connection_made(self, transport):
+ self._stream_reader.set_transport(transport)
+ if self._client_connected_cb is not None:
+ self._stream_writer = StreamWriter(transport, self,
+ self._stream_reader,
+ self._loop)
+ res = self._client_connected_cb(self._stream_reader,
+ self._stream_writer)
+ if tasks.iscoroutine(res):
+ tasks.Task(res, loop=self._loop)
+
+ def connection_lost(self, exc):
+ if exc is None:
+ self._stream_reader.feed_eof()
+ else:
+ self._stream_reader.set_exception(exc)
+ # Also wake up the writing side.
+ if self._paused:
+ waiter = self._drain_waiter
+ if waiter is not None:
+ self._drain_waiter = None
+ if not waiter.done():
+ if exc is None:
+ waiter.set_result(None)
+ else:
+ waiter.set_exception(exc)
+
+ def data_received(self, data):
+ self._stream_reader.feed_data(data)
+
+ def eof_received(self):
+ self._stream_reader.feed_eof()
+
+ def pause_writing(self):
+ assert not self._paused
+ self._paused = True
+
+ def resume_writing(self):
+ assert self._paused
+ self._paused = False
+ waiter = self._drain_waiter
+ if waiter is not None:
+ self._drain_waiter = None
+ if not waiter.done():
+ waiter.set_result(None)
+
+
+class StreamWriter:
+ """Wraps a Transport.
+
+ This exposes write(), writelines(), [can_]write_eof(),
+ get_extra_info() and close(). It adds drain() which returns an
+ optional Future on which you can wait for flow control. It also
+ adds a transport attribute which references the Transport
+ directly.
+ """
+
+ def __init__(self, transport, protocol, reader, loop):
+ self._transport = transport
+ self._protocol = protocol
+ self._reader = reader
+ self._loop = loop
+
+ @property
+ def transport(self):
+ return self._transport
+
+ def write(self, data):
+ self._transport.write(data)
+
+ def writelines(self, data):
+ self._transport.writelines(data)
+
+ def write_eof(self):
+ return self._transport.write_eof()
+
+ def can_write_eof(self):
+ return self._transport.can_write_eof()
+
+ def close(self):
+ return self._transport.close()
+
+ def get_extra_info(self, name, default=None):
+ return self._transport.get_extra_info(name, default)
+
+ def drain(self):
+ """This method has an unusual return value.
+
+ The intended use is to write
+
+ w.write(data)
+ yield from w.drain()
+
+ When there's nothing to wait for, drain() returns (), and the
+ yield-from continues immediately. When the transport buffer
+ is full (the protocol is paused), drain() creates and returns
+ a Future and the yield-from will block until that Future is
+ completed, which will happen when the buffer is (partially)
+ drained and the protocol is resumed.
+ """
+ if self._reader._exception is not None:
+ raise self._writer._exception
+ if self._transport._conn_lost: # Uses private variable.
+ raise ConnectionResetError('Connection lost')
+ if not self._protocol._paused:
+ return ()
+ waiter = self._protocol._drain_waiter
+ assert waiter is None or waiter.cancelled()
+ waiter = futures.Future(loop=self._loop)
+ self._protocol._drain_waiter = waiter
+ return waiter
+
+
+class StreamReader:
+
+ def __init__(self, limit=_DEFAULT_LIMIT, loop=None):
+ # The line length limit is a security feature;
+ # it also doubles as half the buffer limit.
+ self._limit = limit
+ if loop is None:
+ loop = events.get_event_loop()
+ self._loop = loop
+ self._buffer = collections.deque() # Deque of bytes objects.
+ self._byte_count = 0 # Bytes in buffer.
+ self._eof = False # Whether we're done.
+ self._waiter = None # A future.
+ self._exception = None
+ self._transport = None
+ self._paused = False
+
+ def exception(self):
+ return self._exception
+
+ def set_exception(self, exc):
+ self._exception = exc
+
+ waiter = self._waiter
+ if waiter is not None:
+ self._waiter = None
+ if not waiter.cancelled():
+ waiter.set_exception(exc)
+
+ def set_transport(self, transport):
+ assert self._transport is None, 'Transport already set'
+ self._transport = transport
+
+ def _maybe_resume_transport(self):
+ if self._paused and self._byte_count <= self._limit:
+ self._paused = False
+ self._transport.resume_reading()
+
+ def feed_eof(self):
+ self._eof = True
+ waiter = self._waiter
+ if waiter is not None:
+ self._waiter = None
+ if not waiter.cancelled():
+ waiter.set_result(True)
+
+ def feed_data(self, data):
+ if not data:
+ return
+
+ self._buffer.append(data)
+ self._byte_count += len(data)
+
+ waiter = self._waiter
+ if waiter is not None:
+ self._waiter = None
+ if not waiter.cancelled():
+ waiter.set_result(False)
+
+ if (self._transport is not None and
+ not self._paused and
+ self._byte_count > 2*self._limit):
+ try:
+ self._transport.pause_reading()
+ except NotImplementedError:
+ # The transport can't be paused.
+ # We'll just have to buffer all data.
+ # Forget the transport so we don't keep trying.
+ self._transport = None
+ else:
+ self._paused = True
+
+ @tasks.coroutine
+ def readline(self):
+ if self._exception is not None:
+ raise self._exception
+
+ parts = []
+ parts_size = 0
+ not_enough = True
+
+ while not_enough:
+ while self._buffer and not_enough:
+ data = self._buffer.popleft()
+ ichar = data.find(b'\n')
+ if ichar < 0:
+ parts.append(data)
+ parts_size += len(data)
+ else:
+ ichar += 1
+ head, tail = data[:ichar], data[ichar:]
+ if tail:
+ self._buffer.appendleft(tail)
+ not_enough = False
+ parts.append(head)
+ parts_size += len(head)
+
+ if parts_size > self._limit:
+ self._byte_count -= parts_size
+ self._maybe_resume_transport()
+ raise ValueError('Line is too long')
+
+ if self._eof:
+ break
+
+ if not_enough:
+ assert self._waiter is None
+ self._waiter = futures.Future(loop=self._loop)
+ try:
+ yield from self._waiter
+ finally:
+ self._waiter = None
+
+ line = b''.join(parts)
+ self._byte_count -= parts_size
+ self._maybe_resume_transport()
+
+ return line
+
+ @tasks.coroutine
+ def read(self, n=-1):
+ if self._exception is not None:
+ raise self._exception
+
+ if not n:
+ return b''
+
+ if n < 0:
+ while not self._eof:
+ assert not self._waiter
+ self._waiter = futures.Future(loop=self._loop)
+ try:
+ yield from self._waiter
+ finally:
+ self._waiter = None
+ else:
+ if not self._byte_count and not self._eof:
+ assert not self._waiter
+ self._waiter = futures.Future(loop=self._loop)
+ try:
+ yield from self._waiter
+ finally:
+ self._waiter = None
+
+ if n < 0 or self._byte_count <= n:
+ data = b''.join(self._buffer)
+ self._buffer.clear()
+ self._byte_count = 0
+ self._maybe_resume_transport()
+ return data
+
+ parts = []
+ parts_bytes = 0
+ while self._buffer and parts_bytes < n:
+ data = self._buffer.popleft()
+ data_bytes = len(data)
+ if n < parts_bytes + data_bytes:
+ data_bytes = n - parts_bytes
+ data, rest = data[:data_bytes], data[data_bytes:]
+ self._buffer.appendleft(rest)
+
+ parts.append(data)
+ parts_bytes += data_bytes
+ self._byte_count -= data_bytes
+ self._maybe_resume_transport()
+
+ return b''.join(parts)
+
+ @tasks.coroutine
+ def readexactly(self, n):
+ if self._exception is not None:
+ raise self._exception
+
+ if n <= 0:
+ return b''
+
+ while self._byte_count < n and not self._eof:
+ assert not self._waiter
+ self._waiter = futures.Future(loop=self._loop)
+ try:
+ yield from self._waiter
+ finally:
+ self._waiter = None
+
+ return (yield from self.read(n))
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
new file mode 100644
index 0000000000..999e9629bc
--- /dev/null
+++ b/Lib/asyncio/tasks.py
@@ -0,0 +1,637 @@
+"""Support for tasks, coroutines and the scheduler."""
+
+__all__ = ['coroutine', 'Task',
+ 'FIRST_COMPLETED', 'FIRST_EXCEPTION', 'ALL_COMPLETED',
+ 'wait', 'wait_for', 'as_completed', 'sleep', 'async',
+ 'gather', 'shield',
+ ]
+
+import collections
+import concurrent.futures
+import functools
+import inspect
+import linecache
+import traceback
+import weakref
+
+from . import events
+from . import futures
+from .log import logger
+
+# If you set _DEBUG to true, @coroutine will wrap the resulting
+# generator objects in a CoroWrapper instance (defined below). That
+# instance will log a message when the generator is never iterated
+# over, which may happen when you forget to use "yield from" with a
+# coroutine call. Note that the value of the _DEBUG flag is taken
+# when the decorator is used, so to be of any use it must be set
+# before you define your coroutines. A downside of using this feature
+# is that tracebacks show entries for the CoroWrapper.__next__ method
+# when _DEBUG is true.
+_DEBUG = False
+
+
+class CoroWrapper:
+ """Wrapper for coroutine in _DEBUG mode."""
+
+ __slot__ = ['gen', 'func']
+
+ def __init__(self, gen, func):
+ assert inspect.isgenerator(gen), gen
+ self.gen = gen
+ self.func = func
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return next(self.gen)
+
+ def send(self, value):
+ return self.gen.send(value)
+
+ def throw(self, exc):
+ return self.gen.throw(exc)
+
+ def close(self):
+ return self.gen.close()
+
+ def __del__(self):
+ frame = self.gen.gi_frame
+ if frame is not None and frame.f_lasti == -1:
+ func = self.func
+ code = func.__code__
+ filename = code.co_filename
+ lineno = code.co_firstlineno
+ logger.error(
+ 'Coroutine %r defined at %s:%s was never yielded from',
+ func.__name__, filename, lineno)
+
+
+def coroutine(func):
+ """Decorator to mark coroutines.
+
+ If the coroutine is not yielded from before it is destroyed,
+ an error message is logged.
+ """
+ if inspect.isgeneratorfunction(func):
+ coro = func
+ else:
+ @functools.wraps(func)
+ def coro(*args, **kw):
+ res = func(*args, **kw)
+ if isinstance(res, futures.Future) or inspect.isgenerator(res):
+ res = yield from res
+ return res
+
+ if not _DEBUG:
+ wrapper = coro
+ else:
+ @functools.wraps(func)
+ def wrapper(*args, **kwds):
+ w = CoroWrapper(coro(*args, **kwds), func)
+ w.__name__ = coro.__name__
+ w.__doc__ = coro.__doc__
+ return w
+
+ wrapper._is_coroutine = True # For iscoroutinefunction().
+ return wrapper
+
+
+def iscoroutinefunction(func):
+ """Return True if func is a decorated coroutine function."""
+ return getattr(func, '_is_coroutine', False)
+
+
+def iscoroutine(obj):
+ """Return True if obj is a coroutine object."""
+ return isinstance(obj, CoroWrapper) or inspect.isgenerator(obj)
+
+
+class Task(futures.Future):
+ """A coroutine wrapped in a Future."""
+
+ # An important invariant maintained while a Task not done:
+ #
+ # - Either _fut_waiter is None, and _step() is scheduled;
+ # - or _fut_waiter is some Future, and _step() is *not* scheduled.
+ #
+ # The only transition from the latter to the former is through
+ # _wakeup(). When _fut_waiter is not None, one of its callbacks
+ # must be _wakeup().
+
+ # Weak set containing all tasks alive.
+ _all_tasks = weakref.WeakSet()
+
+ @classmethod
+ def all_tasks(cls, loop=None):
+ """Return a set of all tasks for an event loop.
+
+ By default all tasks for the current event loop are returned.
+ """
+ if loop is None:
+ loop = events.get_event_loop()
+ return {t for t in cls._all_tasks if t._loop is loop}
+
+ def __init__(self, coro, *, loop=None):
+ assert iscoroutine(coro), repr(coro) # Not a coroutine function!
+ super().__init__(loop=loop)
+ self._coro = iter(coro) # Use the iterator just in case.
+ self._fut_waiter = None
+ self._must_cancel = False
+ self._loop.call_soon(self._step)
+ self.__class__._all_tasks.add(self)
+
+ def __repr__(self):
+ res = super().__repr__()
+ if (self._must_cancel and
+ self._state == futures._PENDING and
+ '<PENDING' in res):
+ res = res.replace('<PENDING', '<CANCELLING', 1)
+ i = res.find('<')
+ if i < 0:
+ i = len(res)
+ res = res[:i] + '(<{}>)'.format(self._coro.__name__) + res[i:]
+ return res
+
+ def get_stack(self, *, limit=None):
+ """Return the list of stack frames for this task's coroutine.
+
+ If the coroutine is active, this returns the stack where it is
+ suspended. If the coroutine has completed successfully or was
+ cancelled, this returns an empty list. If the coroutine was
+ terminated by an exception, this returns the list of traceback
+ frames.
+
+ The frames are always ordered from oldest to newest.
+
+ The optional limit gives the maximum nummber of frames to
+ return; by default all available frames are returned. Its
+ meaning differs depending on whether a stack or a traceback is
+ returned: the newest frames of a stack are returned, but the
+ oldest frames of a traceback are returned. (This matches the
+ behavior of the traceback module.)
+
+ For reasons beyond our control, only one stack frame is
+ returned for a suspended coroutine.
+ """
+ frames = []
+ f = self._coro.gi_frame
+ if f is not None:
+ while f is not None:
+ if limit is not None:
+ if limit <= 0:
+ break
+ limit -= 1
+ frames.append(f)
+ f = f.f_back
+ frames.reverse()
+ elif self._exception is not None:
+ tb = self._exception.__traceback__
+ while tb is not None:
+ if limit is not None:
+ if limit <= 0:
+ break
+ limit -= 1
+ frames.append(tb.tb_frame)
+ tb = tb.tb_next
+ return frames
+
+ def print_stack(self, *, limit=None, file=None):
+ """Print the stack or traceback for this task's coroutine.
+
+ This produces output similar to that of the traceback module,
+ for the frames retrieved by get_stack(). The limit argument
+ is passed to get_stack(). The file argument is an I/O stream
+ to which the output goes; by default it goes to sys.stderr.
+ """
+ extracted_list = []
+ checked = set()
+ for f in self.get_stack(limit=limit):
+ lineno = f.f_lineno
+ co = f.f_code
+ filename = co.co_filename
+ name = co.co_name
+ if filename not in checked:
+ checked.add(filename)
+ linecache.checkcache(filename)
+ line = linecache.getline(filename, lineno, f.f_globals)
+ extracted_list.append((filename, lineno, name, line))
+ exc = self._exception
+ if not extracted_list:
+ print('No stack for %r' % self, file=file)
+ elif exc is not None:
+ print('Traceback for %r (most recent call last):' % self,
+ file=file)
+ else:
+ print('Stack for %r (most recent call last):' % self,
+ file=file)
+ traceback.print_list(extracted_list, file=file)
+ if exc is not None:
+ for line in traceback.format_exception_only(exc.__class__, exc):
+ print(line, file=file, end='')
+
+ def cancel(self):
+ if self.done():
+ return False
+ if self._fut_waiter is not None:
+ if self._fut_waiter.cancel():
+ # Leave self._fut_waiter; it may be a Task that
+ # catches and ignores the cancellation so we may have
+ # to cancel it again later.
+ return True
+ # It must be the case that self._step is already scheduled.
+ self._must_cancel = True
+ return True
+
+ def _step(self, value=None, exc=None):
+ assert not self.done(), \
+ '_step(): already done: {!r}, {!r}, {!r}'.format(self, value, exc)
+ if self._must_cancel:
+ if not isinstance(exc, futures.CancelledError):
+ exc = futures.CancelledError()
+ self._must_cancel = False
+ coro = self._coro
+ self._fut_waiter = None
+ # Call either coro.throw(exc) or coro.send(value).
+ try:
+ if exc is not None:
+ result = coro.throw(exc)
+ elif value is not None:
+ result = coro.send(value)
+ else:
+ result = next(coro)
+ except StopIteration as exc:
+ self.set_result(exc.value)
+ except futures.CancelledError as exc:
+ super().cancel() # I.e., Future.cancel(self).
+ except Exception as exc:
+ self.set_exception(exc)
+ except BaseException as exc:
+ self.set_exception(exc)
+ raise
+ else:
+ if isinstance(result, futures.Future):
+ # Yielded Future must come from Future.__iter__().
+ if result._blocking:
+ result._blocking = False
+ result.add_done_callback(self._wakeup)
+ self._fut_waiter = result
+ if self._must_cancel:
+ if self._fut_waiter.cancel():
+ self._must_cancel = False
+ else:
+ self._loop.call_soon(
+ self._step, None,
+ RuntimeError(
+ 'yield was used instead of yield from '
+ 'in task {!r} with {!r}'.format(self, result)))
+ elif result is None:
+ # Bare yield relinquishes control for one event loop iteration.
+ self._loop.call_soon(self._step)
+ elif inspect.isgenerator(result):
+ # Yielding a generator is just wrong.
+ self._loop.call_soon(
+ self._step, None,
+ RuntimeError(
+ 'yield was used instead of yield from for '
+ 'generator in task {!r} with {}'.format(
+ self, result)))
+ else:
+ # Yielding something else is an error.
+ self._loop.call_soon(
+ self._step, None,
+ RuntimeError(
+ 'Task got bad yield: {!r}'.format(result)))
+ self = None
+
+ def _wakeup(self, future):
+ try:
+ value = future.result()
+ except Exception as exc:
+ # This may also be a cancellation.
+ self._step(None, exc)
+ else:
+ self._step(value, None)
+ self = None # Needed to break cycles when an exception occurs.
+
+
+# wait() and as_completed() similar to those in PEP 3148.
+
+FIRST_COMPLETED = concurrent.futures.FIRST_COMPLETED
+FIRST_EXCEPTION = concurrent.futures.FIRST_EXCEPTION
+ALL_COMPLETED = concurrent.futures.ALL_COMPLETED
+
+
+@coroutine
+def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
+ """Wait for the Futures and coroutines given by fs to complete.
+
+ Coroutines will be wrapped in Tasks.
+
+ Returns two sets of Future: (done, pending).
+
+ Usage:
+
+ done, pending = yield from asyncio.wait(fs)
+
+ Note: This does not raise TimeoutError! Futures that aren't done
+ when the timeout occurs are returned in the second set.
+ """
+ if not fs:
+ raise ValueError('Set of coroutines/Futures is empty.')
+
+ if loop is None:
+ loop = events.get_event_loop()
+
+ fs = set(async(f, loop=loop) for f in fs)
+
+ if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
+ raise ValueError('Invalid return_when value: {}'.format(return_when))
+ return (yield from _wait(fs, timeout, return_when, loop))
+
+
+def _release_waiter(waiter, value=True, *args):
+ if not waiter.done():
+ waiter.set_result(value)
+
+
+@coroutine
+def wait_for(fut, timeout, *, loop=None):
+ """Wait for the single Future or coroutine to complete, with timeout.
+
+ Coroutine will be wrapped in Task.
+
+ Returns result of the Future or coroutine. Raises TimeoutError when
+ timeout occurs.
+
+ Usage:
+
+ result = yield from asyncio.wait_for(fut, 10.0)
+
+ """
+ if loop is None:
+ loop = events.get_event_loop()
+
+ waiter = futures.Future(loop=loop)
+ timeout_handle = loop.call_later(timeout, _release_waiter, waiter, False)
+ cb = functools.partial(_release_waiter, waiter, True)
+
+ fut = async(fut, loop=loop)
+ fut.add_done_callback(cb)
+
+ try:
+ if (yield from waiter):
+ return fut.result()
+ else:
+ fut.remove_done_callback(cb)
+ raise futures.TimeoutError()
+ finally:
+ timeout_handle.cancel()
+
+
+@coroutine
+def _wait(fs, timeout, return_when, loop):
+ """Internal helper for wait() and _wait_for().
+
+ The fs argument must be a collection of Futures.
+ """
+ assert fs, 'Set of Futures is empty.'
+ waiter = futures.Future(loop=loop)
+ timeout_handle = None
+ if timeout is not None:
+ timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
+ counter = len(fs)
+
+ def _on_completion(f):
+ nonlocal counter
+ counter -= 1
+ if (counter <= 0 or
+ return_when == FIRST_COMPLETED or
+ return_when == FIRST_EXCEPTION and (not f.cancelled() and
+ f.exception() is not None)):
+ if timeout_handle is not None:
+ timeout_handle.cancel()
+ if not waiter.done():
+ waiter.set_result(False)
+
+ for f in fs:
+ f.add_done_callback(_on_completion)
+
+ try:
+ yield from waiter
+ finally:
+ if timeout_handle is not None:
+ timeout_handle.cancel()
+
+ done, pending = set(), set()
+ for f in fs:
+ f.remove_done_callback(_on_completion)
+ if f.done():
+ done.add(f)
+ else:
+ pending.add(f)
+ return done, pending
+
+
+# This is *not* a @coroutine! It is just an iterator (yielding Futures).
+def as_completed(fs, *, loop=None, timeout=None):
+ """Return an iterator whose values, when waited for, are Futures.
+
+ This differs from PEP 3148; the proper way to use this is:
+
+ for f in as_completed(fs):
+ result = yield from f # The 'yield from' may raise.
+ # Use result.
+
+ Raises TimeoutError if the timeout occurs before all Futures are
+ done.
+
+ Note: The futures 'f' are not necessarily members of fs.
+ """
+ loop = loop if loop is not None else events.get_event_loop()
+ deadline = None if timeout is None else loop.time() + timeout
+ todo = set(async(f, loop=loop) for f in fs)
+ completed = collections.deque()
+
+ @coroutine
+ def _wait_for_one():
+ while not completed:
+ timeout = None
+ if deadline is not None:
+ timeout = deadline - loop.time()
+ if timeout < 0:
+ raise futures.TimeoutError()
+ done, pending = yield from _wait(
+ todo, timeout, FIRST_COMPLETED, loop)
+ # Multiple callers might be waiting for the same events
+ # and getting the same outcome. Dedupe by updating todo.
+ for f in done:
+ if f in todo:
+ todo.remove(f)
+ completed.append(f)
+ f = completed.popleft()
+ return f.result() # May raise.
+
+ for _ in range(len(todo)):
+ yield _wait_for_one()
+
+
+@coroutine
+def sleep(delay, result=None, *, loop=None):
+ """Coroutine that completes after a given time (in seconds)."""
+ future = futures.Future(loop=loop)
+ h = future._loop.call_later(delay, future.set_result, result)
+ try:
+ return (yield from future)
+ finally:
+ h.cancel()
+
+
+def async(coro_or_future, *, loop=None):
+ """Wrap a coroutine in a future.
+
+ If the argument is a Future, it is returned directly.
+ """
+ if isinstance(coro_or_future, futures.Future):
+ if loop is not None and loop is not coro_or_future._loop:
+ raise ValueError('loop argument must agree with Future')
+ return coro_or_future
+ elif iscoroutine(coro_or_future):
+ return Task(coro_or_future, loop=loop)
+ else:
+ raise TypeError('A Future or coroutine is required')
+
+
+class _GatheringFuture(futures.Future):
+ """Helper for gather().
+
+ This overrides cancel() to cancel all the children and act more
+ like Task.cancel(), which doesn't immediately mark itself as
+ cancelled.
+ """
+
+ def __init__(self, children, *, loop=None):
+ super().__init__(loop=loop)
+ self._children = children
+
+ def cancel(self):
+ if self.done():
+ return False
+ for child in self._children:
+ child.cancel()
+ return True
+
+
+def gather(*coros_or_futures, loop=None, return_exceptions=False):
+ """Return a future aggregating results from the given coroutines
+ or futures.
+
+ All futures must share the same event loop. If all the tasks are
+ done successfully, the returned future's result is the list of
+ results (in the order of the original sequence, not necessarily
+ the order of results arrival). If *result_exception* is True,
+ exceptions in the tasks are treated the same as successful
+ results, and gathered in the result list; otherwise, the first
+ raised exception will be immediately propagated to the returned
+ future.
+
+ Cancellation: if the outer Future is cancelled, all children (that
+ have not completed yet) are also cancelled. If any child is
+ cancelled, this is treated as if it raised CancelledError --
+ the outer Future is *not* cancelled in this case. (This is to
+ prevent the cancellation of one child to cause other children to
+ be cancelled.)
+ """
+ children = [async(fut, loop=loop) for fut in coros_or_futures]
+ n = len(children)
+ if n == 0:
+ outer = futures.Future(loop=loop)
+ outer.set_result([])
+ return outer
+ if loop is None:
+ loop = children[0]._loop
+ for fut in children:
+ if fut._loop is not loop:
+ raise ValueError("futures are tied to different event loops")
+ outer = _GatheringFuture(children, loop=loop)
+ nfinished = 0
+ results = [None] * n
+
+ def _done_callback(i, fut):
+ nonlocal nfinished
+ if outer._state != futures._PENDING:
+ if fut._exception is not None:
+ # Mark exception retrieved.
+ fut.exception()
+ return
+ if fut._state == futures._CANCELLED:
+ res = futures.CancelledError()
+ if not return_exceptions:
+ outer.set_exception(res)
+ return
+ elif fut._exception is not None:
+ res = fut.exception() # Mark exception retrieved.
+ if not return_exceptions:
+ outer.set_exception(res)
+ return
+ else:
+ res = fut._result
+ results[i] = res
+ nfinished += 1
+ if nfinished == n:
+ outer.set_result(results)
+
+ for i, fut in enumerate(children):
+ fut.add_done_callback(functools.partial(_done_callback, i))
+ return outer
+
+
+def shield(arg, *, loop=None):
+ """Wait for a future, shielding it from cancellation.
+
+ The statement
+
+ res = yield from shield(something())
+
+ is exactly equivalent to the statement
+
+ res = yield from something()
+
+ *except* that if the coroutine containing it is cancelled, the
+ task running in something() is not cancelled. From the POV of
+ something(), the cancellation did not happen. But its caller is
+ still cancelled, so the yield-from expression still raises
+ CancelledError. Note: If something() is cancelled by other means
+ this will still cancel shield().
+
+ If you want to completely ignore cancellation (not recommended)
+ you can combine shield() with a try/except clause, as follows:
+
+ try:
+ res = yield from shield(something())
+ except CancelledError:
+ res = None
+ """
+ inner = async(arg, loop=loop)
+ if inner.done():
+ # Shortcut.
+ return inner
+ loop = inner._loop
+ outer = futures.Future(loop=loop)
+
+ def _done_callback(inner):
+ if outer.cancelled():
+ # Mark inner's result as retrieved.
+ inner.cancelled() or inner.exception()
+ return
+ if inner.cancelled():
+ outer.cancel()
+ else:
+ exc = inner.exception()
+ if exc is not None:
+ outer.set_exception(exc)
+ else:
+ outer.set_result(inner.result())
+
+ inner.add_done_callback(_done_callback)
+ return outer
diff --git a/Lib/asyncio/test_utils.py b/Lib/asyncio/test_utils.py
new file mode 100644
index 0000000000..d7d844249c
--- /dev/null
+++ b/Lib/asyncio/test_utils.py
@@ -0,0 +1,276 @@
+"""Utilities shared by tests."""
+
+import collections
+import contextlib
+import io
+import unittest.mock
+import os
+import sys
+import threading
+import time
+import unittest
+import unittest.mock
+from wsgiref.simple_server import make_server, WSGIRequestHandler, WSGIServer
+try:
+ import ssl
+except ImportError: # pragma: no cover
+ ssl = None
+
+from . import tasks
+from . import base_events
+from . import events
+from . import selectors
+
+
+if sys.platform == 'win32': # pragma: no cover
+ from .windows_utils import socketpair
+else:
+ from socket import socketpair # pragma: no cover
+
+
+def dummy_ssl_context():
+ if ssl is None:
+ return None
+ else:
+ return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+
+
+def run_briefly(loop):
+ @tasks.coroutine
+ def once():
+ pass
+ gen = once()
+ t = tasks.Task(gen, loop=loop)
+ try:
+ loop.run_until_complete(t)
+ finally:
+ gen.close()
+
+
+def run_until(loop, pred, timeout=None):
+ if timeout is not None:
+ deadline = time.time() + timeout
+ while not pred():
+ if timeout is not None:
+ timeout = deadline - time.time()
+ if timeout <= 0:
+ return False
+ loop.run_until_complete(tasks.sleep(timeout, loop=loop))
+ else:
+ run_briefly(loop)
+ return True
+
+
+def run_once(loop):
+ """loop.stop() schedules _raise_stop_error()
+ and run_forever() runs until _raise_stop_error() callback.
+ this wont work if test waits for some IO events, because
+ _raise_stop_error() runs before any of io events callbacks.
+ """
+ loop.stop()
+ loop.run_forever()
+
+
+@contextlib.contextmanager
+def run_test_server(*, host='127.0.0.1', port=0, use_ssl=False):
+
+ class SilentWSGIRequestHandler(WSGIRequestHandler):
+ def get_stderr(self):
+ return io.StringIO()
+
+ def log_message(self, format, *args):
+ pass
+
+ class SilentWSGIServer(WSGIServer):
+ def handle_error(self, request, client_address):
+ pass
+
+ class SSLWSGIServer(SilentWSGIServer):
+ def finish_request(self, request, client_address):
+ # The relative location of our test directory (which
+ # contains the sample key and certificate files) differs
+ # between the stdlib and stand-alone Tulip/asyncio.
+ # Prefer our own if we can find it.
+ here = os.path.join(os.path.dirname(__file__), '..', 'tests')
+ if not os.path.isdir(here):
+ here = os.path.join(os.path.dirname(os.__file__),
+ 'test', 'test_asyncio')
+ keyfile = os.path.join(here, 'sample.key')
+ certfile = os.path.join(here, 'sample.crt')
+ ssock = ssl.wrap_socket(request,
+ keyfile=keyfile,
+ certfile=certfile,
+ server_side=True)
+ try:
+ self.RequestHandlerClass(ssock, client_address, self)
+ ssock.close()
+ except OSError:
+ # maybe socket has been closed by peer
+ pass
+
+ def app(environ, start_response):
+ status = '200 OK'
+ headers = [('Content-type', 'text/plain')]
+ start_response(status, headers)
+ return [b'Test message']
+
+ # Run the test WSGI server in a separate thread in order not to
+ # interfere with event handling in the main thread
+ server_class = SSLWSGIServer if use_ssl else SilentWSGIServer
+ httpd = make_server(host, port, app,
+ server_class, SilentWSGIRequestHandler)
+ httpd.address = httpd.server_address
+ server_thread = threading.Thread(target=httpd.serve_forever)
+ server_thread.start()
+ try:
+ yield httpd
+ finally:
+ httpd.shutdown()
+ httpd.server_close()
+ server_thread.join()
+
+
+def make_test_protocol(base):
+ dct = {}
+ for name in dir(base):
+ if name.startswith('__') and name.endswith('__'):
+ # skip magic names
+ continue
+ dct[name] = unittest.mock.Mock(return_value=None)
+ return type('TestProtocol', (base,) + base.__bases__, dct)()
+
+
+class TestSelector(selectors.BaseSelector):
+
+ def __init__(self):
+ self.keys = {}
+
+ def register(self, fileobj, events, data=None):
+ key = selectors.SelectorKey(fileobj, 0, events, data)
+ self.keys[fileobj] = key
+ return key
+
+ def unregister(self, fileobj):
+ return self.keys.pop(fileobj)
+
+ def select(self, timeout):
+ return []
+
+ def get_map(self):
+ return self.keys
+
+
+class TestLoop(base_events.BaseEventLoop):
+ """Loop for unittests.
+
+ It manages self time directly.
+ If something scheduled to be executed later then
+ on next loop iteration after all ready handlers done
+ generator passed to __init__ is calling.
+
+ Generator should be like this:
+
+ def gen():
+ ...
+ when = yield ...
+ ... = yield time_advance
+
+ Value retuned by yield is absolute time of next scheduled handler.
+ Value passed to yield is time advance to move loop's time forward.
+ """
+
+ def __init__(self, gen=None):
+ super().__init__()
+
+ if gen is None:
+ def gen():
+ yield
+ self._check_on_close = False
+ else:
+ self._check_on_close = True
+
+ self._gen = gen()
+ next(self._gen)
+ self._time = 0
+ self._timers = []
+ self._selector = TestSelector()
+
+ self.readers = {}
+ self.writers = {}
+ self.reset_counters()
+
+ def time(self):
+ return self._time
+
+ def advance_time(self, advance):
+ """Move test time forward."""
+ if advance:
+ self._time += advance
+
+ def close(self):
+ if self._check_on_close:
+ try:
+ self._gen.send(0)
+ except StopIteration:
+ pass
+ else: # pragma: no cover
+ raise AssertionError("Time generator is not finished")
+
+ def add_reader(self, fd, callback, *args):
+ self.readers[fd] = events.make_handle(callback, args)
+
+ def remove_reader(self, fd):
+ self.remove_reader_count[fd] += 1
+ if fd in self.readers:
+ del self.readers[fd]
+ return True
+ else:
+ return False
+
+ def assert_reader(self, fd, callback, *args):
+ assert fd in self.readers, 'fd {} is not registered'.format(fd)
+ handle = self.readers[fd]
+ assert handle._callback == callback, '{!r} != {!r}'.format(
+ handle._callback, callback)
+ assert handle._args == args, '{!r} != {!r}'.format(
+ handle._args, args)
+
+ def add_writer(self, fd, callback, *args):
+ self.writers[fd] = events.make_handle(callback, args)
+
+ def remove_writer(self, fd):
+ self.remove_writer_count[fd] += 1
+ if fd in self.writers:
+ del self.writers[fd]
+ return True
+ else:
+ return False
+
+ def assert_writer(self, fd, callback, *args):
+ assert fd in self.writers, 'fd {} is not registered'.format(fd)
+ handle = self.writers[fd]
+ assert handle._callback == callback, '{!r} != {!r}'.format(
+ handle._callback, callback)
+ assert handle._args == args, '{!r} != {!r}'.format(
+ handle._args, args)
+
+ def reset_counters(self):
+ self.remove_reader_count = collections.defaultdict(int)
+ self.remove_writer_count = collections.defaultdict(int)
+
+ def _run_once(self):
+ super()._run_once()
+ for when in self._timers:
+ advance = self._gen.send(when)
+ self.advance_time(advance)
+ self._timers = []
+
+ def call_at(self, when, callback, *args):
+ self._timers.append(when)
+ return super().call_at(when, callback, *args)
+
+ def _process_events(self, event_list):
+ return
+
+ def _write_to_self(self):
+ pass
diff --git a/Lib/asyncio/transports.py b/Lib/asyncio/transports.py
new file mode 100644
index 0000000000..86b850e918
--- /dev/null
+++ b/Lib/asyncio/transports.py
@@ -0,0 +1,211 @@
+"""Abstract Transport class."""
+
+__all__ = ['ReadTransport', 'WriteTransport', 'Transport']
+
+
+class BaseTransport:
+ """Base class for transports."""
+
+ def __init__(self, extra=None):
+ if extra is None:
+ extra = {}
+ self._extra = extra
+
+ def get_extra_info(self, name, default=None):
+ """Get optional transport information."""
+ return self._extra.get(name, default)
+
+ def close(self):
+ """Close the transport.
+
+ Buffered data will be flushed asynchronously. No more data
+ will be received. After all buffered data is flushed, the
+ protocol's connection_lost() method will (eventually) called
+ with None as its argument.
+ """
+ raise NotImplementedError
+
+
+class ReadTransport(BaseTransport):
+ """Interface for read-only transports."""
+
+ def pause_reading(self):
+ """Pause the receiving end.
+
+ No data will be passed to the protocol's data_received()
+ method until resume_reading() is called.
+ """
+ raise NotImplementedError
+
+ def resume_reading(self):
+ """Resume the receiving end.
+
+ Data received will once again be passed to the protocol's
+ data_received() method.
+ """
+ raise NotImplementedError
+
+
+class WriteTransport(BaseTransport):
+ """Interface for write-only transports."""
+
+ def set_write_buffer_limits(self, high=None, low=None):
+ """Set the high- and low-water limits for write flow control.
+
+ These two values control when to call the protocol's
+ pause_writing() and resume_writing() methods. If specified,
+ the low-water limit must be less than or equal to the
+ high-water limit. Neither value can be negative.
+
+ The defaults are implementation-specific. If only the
+ high-water limit is given, the low-water limit defaults to a
+ implementation-specific value less than or equal to the
+ high-water limit. Setting high to zero forces low to zero as
+ well, and causes pause_writing() to be called whenever the
+ buffer becomes non-empty. Setting low to zero causes
+ resume_writing() to be called only once the buffer is empty.
+ Use of zero for either limit is generally sub-optimal as it
+ reduces opportunities for doing I/O and computation
+ concurrently.
+ """
+ raise NotImplementedError
+
+ def get_write_buffer_size(self):
+ """Return the current size of the write buffer."""
+ raise NotImplementedError
+
+ def write(self, data):
+ """Write some data bytes to the transport.
+
+ This does not block; it buffers the data and arranges for it
+ to be sent out asynchronously.
+ """
+ raise NotImplementedError
+
+ def writelines(self, list_of_data):
+ """Write a list (or any iterable) of data bytes to the transport.
+
+ The default implementation just calls write() for each item in
+ the list/iterable.
+ """
+ for data in list_of_data:
+ self.write(data)
+
+ def write_eof(self):
+ """Close the write end after flushing buffered data.
+
+ (This is like typing ^D into a UNIX program reading from stdin.)
+
+ Data may still be received.
+ """
+ raise NotImplementedError
+
+ def can_write_eof(self):
+ """Return True if this transport supports write_eof(), False if not."""
+ raise NotImplementedError
+
+ def abort(self):
+ """Close the transport immediately.
+
+ Buffered data will be lost. No more data will be received.
+ The protocol's connection_lost() method will (eventually) be
+ called with None as its argument.
+ """
+ raise NotImplementedError
+
+
+class Transport(ReadTransport, WriteTransport):
+ """Interface representing a bidirectional transport.
+
+ There may be several implementations, but typically, the user does
+ not implement new transports; rather, the platform provides some
+ useful transports that are implemented using the platform's best
+ practices.
+
+ The user never instantiates a transport directly; they call a
+ utility function, passing it a protocol factory and other
+ information necessary to create the transport and protocol. (E.g.
+ EventLoop.create_connection() or EventLoop.create_server().)
+
+ The utility function will asynchronously create a transport and a
+ protocol and hook them up by calling the protocol's
+ connection_made() method, passing it the transport.
+
+ The implementation here raises NotImplemented for every method
+ except writelines(), which calls write() in a loop.
+ """
+
+
+class DatagramTransport(BaseTransport):
+ """Interface for datagram (UDP) transports."""
+
+ def sendto(self, data, addr=None):
+ """Send data to the transport.
+
+ This does not block; it buffers the data and arranges for it
+ to be sent out asynchronously.
+ addr is target socket address.
+ If addr is None use target address pointed on transport creation.
+ """
+ raise NotImplementedError
+
+ def abort(self):
+ """Close the transport immediately.
+
+ Buffered data will be lost. No more data will be received.
+ The protocol's connection_lost() method will (eventually) be
+ called with None as its argument.
+ """
+ raise NotImplementedError
+
+
+class SubprocessTransport(BaseTransport):
+
+ def get_pid(self):
+ """Get subprocess id."""
+ raise NotImplementedError
+
+ def get_returncode(self):
+ """Get subprocess returncode.
+
+ See also
+ http://docs.python.org/3/library/subprocess#subprocess.Popen.returncode
+ """
+ raise NotImplementedError
+
+ def get_pipe_transport(self, fd):
+ """Get transport for pipe with number fd."""
+ raise NotImplementedError
+
+ def send_signal(self, signal):
+ """Send signal to subprocess.
+
+ See also:
+ docs.python.org/3/library/subprocess#subprocess.Popen.send_signal
+ """
+ raise NotImplementedError
+
+ def terminate(self):
+ """Stop the subprocess.
+
+ Alias for close() method.
+
+ On Posix OSs the method sends SIGTERM to the subprocess.
+ On Windows the Win32 API function TerminateProcess()
+ is called to stop the subprocess.
+
+ See also:
+ http://docs.python.org/3/library/subprocess#subprocess.Popen.terminate
+ """
+ raise NotImplementedError
+
+ def kill(self):
+ """Kill the subprocess.
+
+ On Posix OSs the function sends SIGKILL to the subprocess.
+ On Windows kill() is an alias for terminate().
+
+ See also:
+ http://docs.python.org/3/library/subprocess#subprocess.Popen.kill
+ """
+ raise NotImplementedError
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
new file mode 100644
index 0000000000..b611efd17d
--- /dev/null
+++ b/Lib/asyncio/unix_events.py
@@ -0,0 +1,744 @@
+"""Selector eventloop for Unix with signal handling."""
+
+import errno
+import fcntl
+import os
+import signal
+import socket
+import stat
+import subprocess
+import sys
+import threading
+
+
+from . import base_subprocess
+from . import constants
+from . import events
+from . import protocols
+from . import selector_events
+from . import tasks
+from . import transports
+from .log import logger
+
+
+__all__ = ['SelectorEventLoop', 'STDIN', 'STDOUT', 'STDERR',
+ 'AbstractChildWatcher', 'SafeChildWatcher',
+ 'FastChildWatcher', 'DefaultEventLoopPolicy',
+ ]
+
+STDIN = 0
+STDOUT = 1
+STDERR = 2
+
+
+if sys.platform == 'win32': # pragma: no cover
+ raise ImportError('Signals are not really supported on Windows')
+
+
+class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
+ """Unix event loop
+
+ Adds signal handling to SelectorEventLoop
+ """
+
+ def __init__(self, selector=None):
+ super().__init__(selector)
+ self._signal_handlers = {}
+
+ def _socketpair(self):
+ return socket.socketpair()
+
+ def close(self):
+ for sig in list(self._signal_handlers):
+ self.remove_signal_handler(sig)
+ super().close()
+
+ def add_signal_handler(self, sig, callback, *args):
+ """Add a handler for a signal. UNIX only.
+
+ Raise ValueError if the signal number is invalid or uncatchable.
+ Raise RuntimeError if there is a problem setting up the handler.
+ """
+ self._check_signal(sig)
+ try:
+ # set_wakeup_fd() raises ValueError if this is not the
+ # main thread. By calling it early we ensure that an
+ # event loop running in another thread cannot add a signal
+ # handler.
+ signal.set_wakeup_fd(self._csock.fileno())
+ except ValueError as exc:
+ raise RuntimeError(str(exc))
+
+ handle = events.make_handle(callback, args)
+ self._signal_handlers[sig] = handle
+
+ try:
+ signal.signal(sig, self._handle_signal)
+ except OSError as exc:
+ del self._signal_handlers[sig]
+ if not self._signal_handlers:
+ try:
+ signal.set_wakeup_fd(-1)
+ except ValueError as nexc:
+ logger.info('set_wakeup_fd(-1) failed: %s', nexc)
+
+ if exc.errno == errno.EINVAL:
+ raise RuntimeError('sig {} cannot be caught'.format(sig))
+ else:
+ raise
+
+ def _handle_signal(self, sig, arg):
+ """Internal helper that is the actual signal handler."""
+ handle = self._signal_handlers.get(sig)
+ if handle is None:
+ return # Assume it's some race condition.
+ if handle._cancelled:
+ self.remove_signal_handler(sig) # Remove it properly.
+ else:
+ self._add_callback_signalsafe(handle)
+
+ def remove_signal_handler(self, sig):
+ """Remove a handler for a signal. UNIX only.
+
+ Return True if a signal handler was removed, False if not.
+ """
+ self._check_signal(sig)
+ try:
+ del self._signal_handlers[sig]
+ except KeyError:
+ return False
+
+ if sig == signal.SIGINT:
+ handler = signal.default_int_handler
+ else:
+ handler = signal.SIG_DFL
+
+ try:
+ signal.signal(sig, handler)
+ except OSError as exc:
+ if exc.errno == errno.EINVAL:
+ raise RuntimeError('sig {} cannot be caught'.format(sig))
+ else:
+ raise
+
+ if not self._signal_handlers:
+ try:
+ signal.set_wakeup_fd(-1)
+ except ValueError as exc:
+ logger.info('set_wakeup_fd(-1) failed: %s', exc)
+
+ return True
+
+ def _check_signal(self, sig):
+ """Internal helper to validate a signal.
+
+ Raise ValueError if the signal number is invalid or uncatchable.
+ Raise RuntimeError if there is a problem setting up the handler.
+ """
+ if not isinstance(sig, int):
+ raise TypeError('sig must be an int, not {!r}'.format(sig))
+
+ if not (1 <= sig < signal.NSIG):
+ raise ValueError(
+ 'sig {} out of range(1, {})'.format(sig, signal.NSIG))
+
+ def _make_read_pipe_transport(self, pipe, protocol, waiter=None,
+ extra=None):
+ return _UnixReadPipeTransport(self, pipe, protocol, waiter, extra)
+
+ def _make_write_pipe_transport(self, pipe, protocol, waiter=None,
+ extra=None):
+ return _UnixWritePipeTransport(self, pipe, protocol, waiter, extra)
+
+ @tasks.coroutine
+ def _make_subprocess_transport(self, protocol, args, shell,
+ stdin, stdout, stderr, bufsize,
+ extra=None, **kwargs):
+ with events.get_child_watcher() as watcher:
+ transp = _UnixSubprocessTransport(self, protocol, args, shell,
+ stdin, stdout, stderr, bufsize,
+ extra=None, **kwargs)
+ watcher.add_child_handler(transp.get_pid(),
+ self._child_watcher_callback, transp)
+ yield from transp._post_init()
+ return transp
+
+ def _child_watcher_callback(self, pid, returncode, transp):
+ self.call_soon_threadsafe(transp._process_exited, returncode)
+
+ def _subprocess_closed(self, transp):
+ pass
+
+
+def _set_nonblocking(fd):
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ flags = flags | os.O_NONBLOCK
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+
+
+class _UnixReadPipeTransport(transports.ReadTransport):
+
+ max_size = 256 * 1024 # max bytes we read in one eventloop iteration
+
+ def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
+ super().__init__(extra)
+ self._extra['pipe'] = pipe
+ self._loop = loop
+ self._pipe = pipe
+ self._fileno = pipe.fileno()
+ mode = os.fstat(self._fileno).st_mode
+ if not (stat.S_ISFIFO(mode) or stat.S_ISSOCK(mode)):
+ raise ValueError("Pipe transport is for pipes/sockets only.")
+ _set_nonblocking(self._fileno)
+ self._protocol = protocol
+ self._closing = False
+ self._loop.add_reader(self._fileno, self._read_ready)
+ self._loop.call_soon(self._protocol.connection_made, self)
+ if waiter is not None:
+ self._loop.call_soon(waiter.set_result, None)
+
+ def _read_ready(self):
+ try:
+ data = os.read(self._fileno, self.max_size)
+ except (BlockingIOError, InterruptedError):
+ pass
+ except OSError as exc:
+ self._fatal_error(exc)
+ else:
+ if data:
+ self._protocol.data_received(data)
+ else:
+ self._closing = True
+ self._loop.remove_reader(self._fileno)
+ self._loop.call_soon(self._protocol.eof_received)
+ self._loop.call_soon(self._call_connection_lost, None)
+
+ def pause_reading(self):
+ self._loop.remove_reader(self._fileno)
+
+ def resume_reading(self):
+ self._loop.add_reader(self._fileno, self._read_ready)
+
+ def close(self):
+ if not self._closing:
+ self._close(None)
+
+ def _fatal_error(self, exc):
+ # should be called by exception handler only
+ logger.exception('Fatal error for %s', self)
+ self._close(exc)
+
+ def _close(self, exc):
+ self._closing = True
+ self._loop.remove_reader(self._fileno)
+ self._loop.call_soon(self._call_connection_lost, exc)
+
+ def _call_connection_lost(self, exc):
+ try:
+ self._protocol.connection_lost(exc)
+ finally:
+ self._pipe.close()
+ self._pipe = None
+ self._protocol = None
+ self._loop = None
+
+
+class _UnixWritePipeTransport(transports.WriteTransport):
+
+ def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
+ super().__init__(extra)
+ self._extra['pipe'] = pipe
+ self._loop = loop
+ self._pipe = pipe
+ self._fileno = pipe.fileno()
+ mode = os.fstat(self._fileno).st_mode
+ is_socket = stat.S_ISSOCK(mode)
+ is_pipe = stat.S_ISFIFO(mode)
+ if not (is_socket or is_pipe):
+ raise ValueError("Pipe transport is for pipes/sockets only.")
+ _set_nonblocking(self._fileno)
+ self._protocol = protocol
+ self._buffer = []
+ self._conn_lost = 0
+ self._closing = False # Set when close() or write_eof() called.
+
+ # On AIX, the reader trick only works for sockets.
+ # On other platforms it works for pipes and sockets.
+ # (Exception: OS X 10.4? Issue #19294.)
+ if is_socket or not sys.platform.startswith("aix"):
+ self._loop.add_reader(self._fileno, self._read_ready)
+
+ self._loop.call_soon(self._protocol.connection_made, self)
+ if waiter is not None:
+ self._loop.call_soon(waiter.set_result, None)
+
+ def _read_ready(self):
+ # Pipe was closed by peer.
+ self._close()
+
+ def write(self, data):
+ assert isinstance(data, bytes), repr(data)
+ if not data:
+ return
+
+ if self._conn_lost or self._closing:
+ if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
+ logger.warning('pipe closed by peer or '
+ 'os.write(pipe, data) raised exception.')
+ self._conn_lost += 1
+ return
+
+ if not self._buffer:
+ # Attempt to send it right away first.
+ try:
+ n = os.write(self._fileno, data)
+ except (BlockingIOError, InterruptedError):
+ n = 0
+ except Exception as exc:
+ self._conn_lost += 1
+ self._fatal_error(exc)
+ return
+ if n == len(data):
+ return
+ elif n > 0:
+ data = data[n:]
+ self._loop.add_writer(self._fileno, self._write_ready)
+
+ self._buffer.append(data)
+
+ def _write_ready(self):
+ data = b''.join(self._buffer)
+ assert data, 'Data should not be empty'
+
+ self._buffer.clear()
+ try:
+ n = os.write(self._fileno, data)
+ except (BlockingIOError, InterruptedError):
+ self._buffer.append(data)
+ except Exception as exc:
+ self._conn_lost += 1
+ # Remove writer here, _fatal_error() doesn't it
+ # because _buffer is empty.
+ self._loop.remove_writer(self._fileno)
+ self._fatal_error(exc)
+ else:
+ if n == len(data):
+ self._loop.remove_writer(self._fileno)
+ if self._closing:
+ self._loop.remove_reader(self._fileno)
+ self._call_connection_lost(None)
+ return
+ elif n > 0:
+ data = data[n:]
+
+ self._buffer.append(data) # Try again later.
+
+ def can_write_eof(self):
+ return True
+
+ # TODO: Make the relationships between write_eof(), close(),
+ # abort(), _fatal_error() and _close() more straightforward.
+
+ def write_eof(self):
+ if self._closing:
+ return
+ assert self._pipe
+ self._closing = True
+ if not self._buffer:
+ self._loop.remove_reader(self._fileno)
+ self._loop.call_soon(self._call_connection_lost, None)
+
+ def close(self):
+ if not self._closing:
+ # write_eof is all what we needed to close the write pipe
+ self.write_eof()
+
+ def abort(self):
+ self._close(None)
+
+ def _fatal_error(self, exc):
+ # should be called by exception handler only
+ logger.exception('Fatal error for %s', self)
+ self._close(exc)
+
+ def _close(self, exc=None):
+ self._closing = True
+ if self._buffer:
+ self._loop.remove_writer(self._fileno)
+ self._buffer.clear()
+ self._loop.remove_reader(self._fileno)
+ self._loop.call_soon(self._call_connection_lost, exc)
+
+ def _call_connection_lost(self, exc):
+ try:
+ self._protocol.connection_lost(exc)
+ finally:
+ self._pipe.close()
+ self._pipe = None
+ self._protocol = None
+ self._loop = None
+
+
+class _UnixSubprocessTransport(base_subprocess.BaseSubprocessTransport):
+
+ def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
+ stdin_w = None
+ if stdin == subprocess.PIPE:
+ # Use a socket pair for stdin, since not all platforms
+ # support selecting read events on the write end of a
+ # socket (which we use in order to detect closing of the
+ # other end). Notably this is needed on AIX, and works
+ # just fine on other platforms.
+ stdin, stdin_w = self._loop._socketpair()
+ self._proc = subprocess.Popen(
+ args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
+ universal_newlines=False, bufsize=bufsize, **kwargs)
+ if stdin_w is not None:
+ stdin.close()
+ self._proc.stdin = open(stdin_w.detach(), 'rb', buffering=bufsize)
+
+
+class AbstractChildWatcher:
+ """Abstract base class for monitoring child processes.
+
+ Objects derived from this class monitor a collection of subprocesses and
+ report their termination or interruption by a signal.
+
+ New callbacks are registered with .add_child_handler(). Starting a new
+ process must be done within a 'with' block to allow the watcher to suspend
+ its activity until the new process if fully registered (this is needed to
+ prevent a race condition in some implementations).
+
+ Example:
+ with watcher:
+ proc = subprocess.Popen("sleep 1")
+ watcher.add_child_handler(proc.pid, callback)
+
+ Notes:
+ Implementations of this class must be thread-safe.
+
+ Since child watcher objects may catch the SIGCHLD signal and call
+ waitpid(-1), there should be only one active object per process.
+ """
+
+ def add_child_handler(self, pid, callback, *args):
+ """Register a new child handler.
+
+ Arrange for callback(pid, returncode, *args) to be called when
+ process 'pid' terminates. Specifying another callback for the same
+ process replaces the previous handler.
+
+ Note: callback() must be thread-safe
+ """
+ raise NotImplementedError()
+
+ def remove_child_handler(self, pid):
+ """Removes the handler for process 'pid'.
+
+ The function returns True if the handler was successfully removed,
+ False if there was nothing to remove."""
+
+ raise NotImplementedError()
+
+ def attach_loop(self, loop):
+ """Attach the watcher to an event loop.
+
+ If the watcher was previously attached to an event loop, then it is
+ first detached before attaching to the new loop.
+
+ Note: loop may be None.
+ """
+ raise NotImplementedError()
+
+ def close(self):
+ """Close the watcher.
+
+ This must be called to make sure that any underlying resource is freed.
+ """
+ raise NotImplementedError()
+
+ def __enter__(self):
+ """Enter the watcher's context and allow starting new processes
+
+ This function must return self"""
+ raise NotImplementedError()
+
+ def __exit__(self, a, b, c):
+ """Exit the watcher's context"""
+ raise NotImplementedError()
+
+
+class BaseChildWatcher(AbstractChildWatcher):
+
+ def __init__(self):
+ self._loop = None
+
+ def close(self):
+ self.attach_loop(None)
+
+ def _do_waitpid(self, expected_pid):
+ raise NotImplementedError()
+
+ def _do_waitpid_all(self):
+ raise NotImplementedError()
+
+ def attach_loop(self, loop):
+ assert loop is None or isinstance(loop, events.AbstractEventLoop)
+
+ if self._loop is not None:
+ self._loop.remove_signal_handler(signal.SIGCHLD)
+
+ self._loop = loop
+ if loop is not None:
+ loop.add_signal_handler(signal.SIGCHLD, self._sig_chld)
+
+ # Prevent a race condition in case a child terminated
+ # during the switch.
+ self._do_waitpid_all()
+
+ def _sig_chld(self):
+ try:
+ self._do_waitpid_all()
+ except Exception:
+ logger.exception('Unknown exception in SIGCHLD handler')
+
+ def _compute_returncode(self, status):
+ if os.WIFSIGNALED(status):
+ # The child process died because of a signal.
+ return -os.WTERMSIG(status)
+ elif os.WIFEXITED(status):
+ # The child process exited (e.g sys.exit()).
+ return os.WEXITSTATUS(status)
+ else:
+ # The child exited, but we don't understand its status.
+ # This shouldn't happen, but if it does, let's just
+ # return that status; perhaps that helps debug it.
+ return status
+
+
+class SafeChildWatcher(BaseChildWatcher):
+ """'Safe' child watcher implementation.
+
+ This implementation avoids disrupting other code spawning processes by
+ polling explicitly each process in the SIGCHLD handler instead of calling
+ os.waitpid(-1).
+
+ This is a safe solution but it has a significant overhead when handling a
+ big number of children (O(n) each time SIGCHLD is raised)
+ """
+
+ def __init__(self):
+ super().__init__()
+ self._callbacks = {}
+
+ def close(self):
+ self._callbacks.clear()
+ super().close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, a, b, c):
+ pass
+
+ def add_child_handler(self, pid, callback, *args):
+ self._callbacks[pid] = callback, args
+
+ # Prevent a race condition in case the child is already terminated.
+ self._do_waitpid(pid)
+
+ def remove_child_handler(self, pid):
+ try:
+ del self._callbacks[pid]
+ return True
+ except KeyError:
+ return False
+
+ def _do_waitpid_all(self):
+
+ for pid in list(self._callbacks):
+ self._do_waitpid(pid)
+
+ def _do_waitpid(self, expected_pid):
+ assert expected_pid > 0
+
+ try:
+ pid, status = os.waitpid(expected_pid, os.WNOHANG)
+ except ChildProcessError:
+ # The child process is already reaped
+ # (may happen if waitpid() is called elsewhere).
+ pid = expected_pid
+ returncode = 255
+ logger.warning(
+ "Unknown child process pid %d, will report returncode 255",
+ pid)
+ else:
+ if pid == 0:
+ # The child process is still alive.
+ return
+
+ returncode = self._compute_returncode(status)
+
+ try:
+ callback, args = self._callbacks.pop(pid)
+ except KeyError: # pragma: no cover
+ # May happen if .remove_child_handler() is called
+ # after os.waitpid() returns.
+ pass
+ else:
+ callback(pid, returncode, *args)
+
+
+class FastChildWatcher(BaseChildWatcher):
+ """'Fast' child watcher implementation.
+
+ This implementation reaps every terminated processes by calling
+ os.waitpid(-1) directly, possibly breaking other code spawning processes
+ and waiting for their termination.
+
+ There is no noticeable overhead when handling a big number of children
+ (O(1) each time a child terminates).
+ """
+ def __init__(self):
+ super().__init__()
+ self._callbacks = {}
+ self._lock = threading.Lock()
+ self._zombies = {}
+ self._forks = 0
+
+ def close(self):
+ self._callbacks.clear()
+ self._zombies.clear()
+ super().close()
+
+ def __enter__(self):
+ with self._lock:
+ self._forks += 1
+
+ return self
+
+ def __exit__(self, a, b, c):
+ with self._lock:
+ self._forks -= 1
+
+ if self._forks or not self._zombies:
+ return
+
+ collateral_victims = str(self._zombies)
+ self._zombies.clear()
+
+ logger.warning(
+ "Caught subprocesses termination from unknown pids: %s",
+ collateral_victims)
+
+ def add_child_handler(self, pid, callback, *args):
+ assert self._forks, "Must use the context manager"
+
+ self._callbacks[pid] = callback, args
+
+ try:
+ # Ensure that the child is not already terminated.
+ # (raise KeyError if still alive)
+ returncode = self._zombies.pop(pid)
+
+ # Child is dead, therefore we can fire the callback immediately.
+ # First we remove it from the dict.
+ # (raise KeyError if .remove_child_handler() was called in-between)
+ del self._callbacks[pid]
+ except KeyError:
+ pass
+ else:
+ callback(pid, returncode, *args)
+
+ def remove_child_handler(self, pid):
+ try:
+ del self._callbacks[pid]
+ return True
+ except KeyError:
+ return False
+
+ def _do_waitpid_all(self):
+ # Because of signal coalescing, we must keep calling waitpid() as
+ # long as we're able to reap a child.
+ while True:
+ try:
+ pid, status = os.waitpid(-1, os.WNOHANG)
+ except ChildProcessError:
+ # No more child processes exist.
+ return
+ else:
+ if pid == 0:
+ # A child process is still alive.
+ return
+
+ returncode = self._compute_returncode(status)
+
+ try:
+ callback, args = self._callbacks.pop(pid)
+ except KeyError:
+ # unknown child
+ with self._lock:
+ if self._forks:
+ # It may not be registered yet.
+ self._zombies[pid] = returncode
+ continue
+
+ logger.warning(
+ "Caught subprocess termination from unknown pid: "
+ "%d -> %d", pid, returncode)
+ else:
+ callback(pid, returncode, *args)
+
+
+class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
+ """XXX"""
+ _loop_factory = _UnixSelectorEventLoop
+
+ def __init__(self):
+ super().__init__()
+ self._watcher = None
+
+ def _init_watcher(self):
+ with events._lock:
+ if self._watcher is None: # pragma: no branch
+ self._watcher = SafeChildWatcher()
+ if isinstance(threading.current_thread(),
+ threading._MainThread):
+ self._watcher.attach_loop(self._local._loop)
+
+ def set_event_loop(self, loop):
+ """Set the event loop.
+
+ As a side effect, if a child watcher was set before, then calling
+ .set_event_loop() from the main thread will call .attach_loop(loop) on
+ the child watcher.
+ """
+
+ super().set_event_loop(loop)
+
+ if self._watcher is not None and \
+ isinstance(threading.current_thread(), threading._MainThread):
+ self._watcher.attach_loop(loop)
+
+ def get_child_watcher(self):
+ """Get the child watcher
+
+ If not yet set, a SafeChildWatcher object is automatically created.
+ """
+ if self._watcher is None:
+ self._init_watcher()
+
+ return self._watcher
+
+ def set_child_watcher(self, watcher):
+ """Set the child watcher"""
+
+ assert watcher is None or isinstance(watcher, AbstractChildWatcher)
+
+ if self._watcher is not None:
+ self._watcher.close()
+
+ self._watcher = watcher
+
+SelectorEventLoop = _UnixSelectorEventLoop
+DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy
diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py
new file mode 100644
index 0000000000..b2ed2415e4
--- /dev/null
+++ b/Lib/asyncio/windows_events.py
@@ -0,0 +1,475 @@
+"""Selector and proactor eventloops for Windows."""
+
+import errno
+import socket
+import subprocess
+import weakref
+import struct
+import _winapi
+
+from . import events
+from . import base_subprocess
+from . import futures
+from . import proactor_events
+from . import selector_events
+from . import tasks
+from . import windows_utils
+from .log import logger
+from . import _overlapped
+
+
+__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor',
+ 'DefaultEventLoopPolicy',
+ ]
+
+
+NULL = 0
+INFINITE = 0xffffffff
+ERROR_CONNECTION_REFUSED = 1225
+ERROR_CONNECTION_ABORTED = 1236
+
+
+class _OverlappedFuture(futures.Future):
+ """Subclass of Future which represents an overlapped operation.
+
+ Cancelling it will immediately cancel the overlapped operation.
+ """
+
+ def __init__(self, ov, *, loop=None):
+ super().__init__(loop=loop)
+ self.ov = ov
+
+ def cancel(self):
+ try:
+ self.ov.cancel()
+ except OSError:
+ pass
+ return super().cancel()
+
+
+class _WaitHandleFuture(futures.Future):
+ """Subclass of Future which represents a wait handle."""
+
+ def __init__(self, wait_handle, *, loop=None):
+ super().__init__(loop=loop)
+ self._wait_handle = wait_handle
+
+ def cancel(self):
+ super().cancel()
+ try:
+ _overlapped.UnregisterWait(self._wait_handle)
+ except OSError as e:
+ if e.winerror != _overlapped.ERROR_IO_PENDING:
+ raise
+
+
+class PipeServer(object):
+ """Class representing a pipe server.
+
+ This is much like a bound, listening socket.
+ """
+ def __init__(self, address):
+ self._address = address
+ self._free_instances = weakref.WeakSet()
+ self._pipe = self._server_pipe_handle(True)
+
+ def _get_unconnected_pipe(self):
+ # Create new instance and return previous one. This ensures
+ # that (until the server is closed) there is always at least
+ # one pipe handle for address. Therefore if a client attempt
+ # to connect it will not fail with FileNotFoundError.
+ tmp, self._pipe = self._pipe, self._server_pipe_handle(False)
+ return tmp
+
+ def _server_pipe_handle(self, first):
+ # Return a wrapper for a new pipe handle.
+ if self._address is None:
+ return None
+ flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
+ if first:
+ flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
+ h = _winapi.CreateNamedPipe(
+ self._address, flags,
+ _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
+ _winapi.PIPE_WAIT,
+ _winapi.PIPE_UNLIMITED_INSTANCES,
+ windows_utils.BUFSIZE, windows_utils.BUFSIZE,
+ _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
+ pipe = windows_utils.PipeHandle(h)
+ self._free_instances.add(pipe)
+ return pipe
+
+ def close(self):
+ # Close all instances which have not been connected to by a client.
+ if self._address is not None:
+ for pipe in self._free_instances:
+ pipe.close()
+ self._pipe = None
+ self._address = None
+ self._free_instances.clear()
+
+ __del__ = close
+
+
+class _WindowsSelectorEventLoop(selector_events.BaseSelectorEventLoop):
+ """Windows version of selector event loop."""
+
+ def _socketpair(self):
+ return windows_utils.socketpair()
+
+
+class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
+ """Windows version of proactor event loop using IOCP."""
+
+ def __init__(self, proactor=None):
+ if proactor is None:
+ proactor = IocpProactor()
+ super().__init__(proactor)
+
+ def _socketpair(self):
+ return windows_utils.socketpair()
+
+ @tasks.coroutine
+ def create_pipe_connection(self, protocol_factory, address):
+ f = self._proactor.connect_pipe(address)
+ pipe = yield from f
+ protocol = protocol_factory()
+ trans = self._make_duplex_pipe_transport(pipe, protocol,
+ extra={'addr': address})
+ return trans, protocol
+
+ @tasks.coroutine
+ def start_serving_pipe(self, protocol_factory, address):
+ server = PipeServer(address)
+
+ def loop(f=None):
+ pipe = None
+ try:
+ if f:
+ pipe = f.result()
+ server._free_instances.discard(pipe)
+ protocol = protocol_factory()
+ self._make_duplex_pipe_transport(
+ pipe, protocol, extra={'addr': address})
+ pipe = server._get_unconnected_pipe()
+ if pipe is None:
+ return
+ f = self._proactor.accept_pipe(pipe)
+ except OSError:
+ if pipe and pipe.fileno() != -1:
+ logger.exception('Pipe accept failed')
+ pipe.close()
+ except futures.CancelledError:
+ if pipe:
+ pipe.close()
+ else:
+ f.add_done_callback(loop)
+
+ self.call_soon(loop)
+ return [server]
+
+ def _stop_serving(self, server):
+ server.close()
+
+ @tasks.coroutine
+ def _make_subprocess_transport(self, protocol, args, shell,
+ stdin, stdout, stderr, bufsize,
+ extra=None, **kwargs):
+ transp = _WindowsSubprocessTransport(self, protocol, args, shell,
+ stdin, stdout, stderr, bufsize,
+ extra=None, **kwargs)
+ yield from transp._post_init()
+ return transp
+
+ def _subprocess_closed(self, transport):
+ pass
+
+
+class IocpProactor:
+ """Proactor implementation using IOCP."""
+
+ def __init__(self, concurrency=0xffffffff):
+ self._loop = None
+ self._results = []
+ self._iocp = _overlapped.CreateIoCompletionPort(
+ _overlapped.INVALID_HANDLE_VALUE, NULL, 0, concurrency)
+ self._cache = {}
+ self._registered = weakref.WeakSet()
+ self._stopped_serving = weakref.WeakSet()
+
+ def set_loop(self, loop):
+ self._loop = loop
+
+ def select(self, timeout=None):
+ if not self._results:
+ self._poll(timeout)
+ tmp = self._results
+ self._results = []
+ return tmp
+
+ def recv(self, conn, nbytes, flags=0):
+ self._register_with_iocp(conn)
+ ov = _overlapped.Overlapped(NULL)
+ if isinstance(conn, socket.socket):
+ ov.WSARecv(conn.fileno(), nbytes, flags)
+ else:
+ ov.ReadFile(conn.fileno(), nbytes)
+
+ def finish(trans, key, ov):
+ try:
+ return ov.getresult()
+ except OSError as exc:
+ if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
+ raise ConnectionResetError(*exc.args)
+ else:
+ raise
+
+ return self._register(ov, conn, finish)
+
+ def send(self, conn, buf, flags=0):
+ self._register_with_iocp(conn)
+ ov = _overlapped.Overlapped(NULL)
+ if isinstance(conn, socket.socket):
+ ov.WSASend(conn.fileno(), buf, flags)
+ else:
+ ov.WriteFile(conn.fileno(), buf)
+
+ def finish(trans, key, ov):
+ try:
+ return ov.getresult()
+ except OSError as exc:
+ if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
+ raise ConnectionResetError(*exc.args)
+ else:
+ raise
+
+ return self._register(ov, conn, finish)
+
+ def accept(self, listener):
+ self._register_with_iocp(listener)
+ conn = self._get_accept_socket(listener.family)
+ ov = _overlapped.Overlapped(NULL)
+ ov.AcceptEx(listener.fileno(), conn.fileno())
+
+ def finish_accept(trans, key, ov):
+ ov.getresult()
+ # Use SO_UPDATE_ACCEPT_CONTEXT so getsockname() etc work.
+ buf = struct.pack('@P', listener.fileno())
+ conn.setsockopt(socket.SOL_SOCKET,
+ _overlapped.SO_UPDATE_ACCEPT_CONTEXT, buf)
+ conn.settimeout(listener.gettimeout())
+ return conn, conn.getpeername()
+
+ return self._register(ov, listener, finish_accept)
+
+ def connect(self, conn, address):
+ self._register_with_iocp(conn)
+ # The socket needs to be locally bound before we call ConnectEx().
+ try:
+ _overlapped.BindLocal(conn.fileno(), conn.family)
+ except OSError as e:
+ if e.winerror != errno.WSAEINVAL:
+ raise
+ # Probably already locally bound; check using getsockname().
+ if conn.getsockname()[1] == 0:
+ raise
+ ov = _overlapped.Overlapped(NULL)
+ ov.ConnectEx(conn.fileno(), address)
+
+ def finish_connect(trans, key, ov):
+ ov.getresult()
+ # Use SO_UPDATE_CONNECT_CONTEXT so getsockname() etc work.
+ conn.setsockopt(socket.SOL_SOCKET,
+ _overlapped.SO_UPDATE_CONNECT_CONTEXT, 0)
+ return conn
+
+ return self._register(ov, conn, finish_connect)
+
+ def accept_pipe(self, pipe):
+ self._register_with_iocp(pipe)
+ ov = _overlapped.Overlapped(NULL)
+ ov.ConnectNamedPipe(pipe.fileno())
+
+ def finish(trans, key, ov):
+ ov.getresult()
+ return pipe
+
+ return self._register(ov, pipe, finish)
+
+ def connect_pipe(self, address):
+ ov = _overlapped.Overlapped(NULL)
+ ov.WaitNamedPipeAndConnect(address, self._iocp, ov.address)
+
+ def finish(err, handle, ov):
+ # err, handle were arguments passed to PostQueuedCompletionStatus()
+ # in a function run in a thread pool.
+ if err == _overlapped.ERROR_SEM_TIMEOUT:
+ # Connection did not succeed within time limit.
+ msg = _overlapped.FormatMessage(err)
+ raise ConnectionRefusedError(0, msg, None, err)
+ elif err != 0:
+ msg = _overlapped.FormatMessage(err)
+ raise OSError(0, msg, None, err)
+ else:
+ return windows_utils.PipeHandle(handle)
+
+ return self._register(ov, None, finish, wait_for_post=True)
+
+ def wait_for_handle(self, handle, timeout=None):
+ if timeout is None:
+ ms = _winapi.INFINITE
+ else:
+ ms = int(timeout * 1000 + 0.5)
+
+ # We only create ov so we can use ov.address as a key for the cache.
+ ov = _overlapped.Overlapped(NULL)
+ wh = _overlapped.RegisterWaitWithQueue(
+ handle, self._iocp, ov.address, ms)
+ f = _WaitHandleFuture(wh, loop=self._loop)
+
+ def finish(trans, key, ov):
+ if not f.cancelled():
+ try:
+ _overlapped.UnregisterWait(wh)
+ except OSError as e:
+ if e.winerror != _overlapped.ERROR_IO_PENDING:
+ raise
+ # Note that this second wait means that we should only use
+ # this with handles types where a successful wait has no
+ # effect. So events or processes are all right, but locks
+ # or semaphores are not. Also note if the handle is
+ # signalled and then quickly reset, then we may return
+ # False even though we have not timed out.
+ return (_winapi.WaitForSingleObject(handle, 0) ==
+ _winapi.WAIT_OBJECT_0)
+
+ self._cache[ov.address] = (f, ov, None, finish)
+ return f
+
+ def _register_with_iocp(self, obj):
+ # To get notifications of finished ops on this objects sent to the
+ # completion port, were must register the handle.
+ if obj not in self._registered:
+ self._registered.add(obj)
+ _overlapped.CreateIoCompletionPort(obj.fileno(), self._iocp, 0, 0)
+ # XXX We could also use SetFileCompletionNotificationModes()
+ # to avoid sending notifications to completion port of ops
+ # that succeed immediately.
+
+ def _register(self, ov, obj, callback, wait_for_post=False):
+ # Return a future which will be set with the result of the
+ # operation when it completes. The future's value is actually
+ # the value returned by callback().
+ f = _OverlappedFuture(ov, loop=self._loop)
+ if ov.pending or wait_for_post:
+ # Register the overlapped operation for later. Note that
+ # we only store obj to prevent it from being garbage
+ # collected too early.
+ self._cache[ov.address] = (f, ov, obj, callback)
+ else:
+ # The operation has completed, so no need to postpone the
+ # work. We cannot take this short cut if we need the
+ # NumberOfBytes, CompletionKey values returned by
+ # PostQueuedCompletionStatus().
+ try:
+ value = callback(None, None, ov)
+ except OSError as e:
+ f.set_exception(e)
+ else:
+ f.set_result(value)
+ return f
+
+ def _get_accept_socket(self, family):
+ s = socket.socket(family)
+ s.settimeout(0)
+ return s
+
+ def _poll(self, timeout=None):
+ if timeout is None:
+ ms = INFINITE
+ elif timeout < 0:
+ raise ValueError("negative timeout")
+ else:
+ ms = int(timeout * 1000 + 0.5)
+ if ms >= INFINITE:
+ raise ValueError("timeout too big")
+ while True:
+ status = _overlapped.GetQueuedCompletionStatus(self._iocp, ms)
+ if status is None:
+ return
+ err, transferred, key, address = status
+ try:
+ f, ov, obj, callback = self._cache.pop(address)
+ except KeyError:
+ # key is either zero, or it is used to return a pipe
+ # handle which should be closed to avoid a leak.
+ if key not in (0, _overlapped.INVALID_HANDLE_VALUE):
+ _winapi.CloseHandle(key)
+ ms = 0
+ continue
+ if obj in self._stopped_serving:
+ f.cancel()
+ elif not f.cancelled():
+ try:
+ value = callback(transferred, key, ov)
+ except OSError as e:
+ f.set_exception(e)
+ self._results.append(f)
+ else:
+ f.set_result(value)
+ self._results.append(f)
+ ms = 0
+
+ def _stop_serving(self, obj):
+ # obj is a socket or pipe handle. It will be closed in
+ # BaseProactorEventLoop._stop_serving() which will make any
+ # pending operations fail quickly.
+ self._stopped_serving.add(obj)
+
+ def close(self):
+ # Cancel remaining registered operations.
+ for address, (f, ov, obj, callback) in list(self._cache.items()):
+ if obj is None:
+ # The operation was started with connect_pipe() which
+ # queues a task to Windows' thread pool. This cannot
+ # be cancelled, so just forget it.
+ del self._cache[address]
+ else:
+ try:
+ ov.cancel()
+ except OSError:
+ pass
+
+ while self._cache:
+ if not self._poll(1):
+ logger.debug('taking long time to close proactor')
+
+ self._results = []
+ if self._iocp is not None:
+ _winapi.CloseHandle(self._iocp)
+ self._iocp = None
+
+
+class _WindowsSubprocessTransport(base_subprocess.BaseSubprocessTransport):
+
+ def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
+ self._proc = windows_utils.Popen(
+ args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
+ bufsize=bufsize, **kwargs)
+
+ def callback(f):
+ returncode = self._proc.poll()
+ self._process_exited(returncode)
+
+ f = self._loop._proactor.wait_for_handle(int(self._proc._handle))
+ f.add_done_callback(callback)
+
+
+SelectorEventLoop = _WindowsSelectorEventLoop
+
+
+class _WindowsDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
+ _loop_factory = SelectorEventLoop
+
+
+DefaultEventLoopPolicy = _WindowsDefaultEventLoopPolicy
diff --git a/Lib/asyncio/windows_utils.py b/Lib/asyncio/windows_utils.py
new file mode 100644
index 0000000000..aa1c064806
--- /dev/null
+++ b/Lib/asyncio/windows_utils.py
@@ -0,0 +1,192 @@
+"""
+Various Windows specific bits and pieces
+"""
+
+import sys
+
+if sys.platform != 'win32': # pragma: no cover
+ raise ImportError('win32 only')
+
+import socket
+import itertools
+import msvcrt
+import os
+import subprocess
+import tempfile
+import _winapi
+
+
+__all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
+
+
+# Constants/globals
+
+
+BUFSIZE = 8192
+PIPE = subprocess.PIPE
+STDOUT = subprocess.STDOUT
+_mmap_counter = itertools.count()
+
+
+# Replacement for socket.socketpair()
+
+
+def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
+ """A socket pair usable as a self-pipe, for Windows.
+
+ Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain.
+ """
+ # We create a connected TCP socket. Note the trick with setblocking(0)
+ # that prevents us from having to create a thread.
+ lsock = socket.socket(family, type, proto)
+ lsock.bind(('localhost', 0))
+ lsock.listen(1)
+ addr, port = lsock.getsockname()
+ csock = socket.socket(family, type, proto)
+ csock.setblocking(False)
+ try:
+ csock.connect((addr, port))
+ except (BlockingIOError, InterruptedError):
+ pass
+ except Exception:
+ lsock.close()
+ csock.close()
+ raise
+ ssock, _ = lsock.accept()
+ csock.setblocking(True)
+ lsock.close()
+ return (ssock, csock)
+
+
+# Replacement for os.pipe() using handles instead of fds
+
+
+def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
+ """Like os.pipe() but with overlapped support and using handles not fds."""
+ address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
+ (os.getpid(), next(_mmap_counter)))
+
+ if duplex:
+ openmode = _winapi.PIPE_ACCESS_DUPLEX
+ access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
+ obsize, ibsize = bufsize, bufsize
+ else:
+ openmode = _winapi.PIPE_ACCESS_INBOUND
+ access = _winapi.GENERIC_WRITE
+ obsize, ibsize = 0, bufsize
+
+ openmode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
+
+ if overlapped[0]:
+ openmode |= _winapi.FILE_FLAG_OVERLAPPED
+
+ if overlapped[1]:
+ flags_and_attribs = _winapi.FILE_FLAG_OVERLAPPED
+ else:
+ flags_and_attribs = 0
+
+ h1 = h2 = None
+ try:
+ h1 = _winapi.CreateNamedPipe(
+ address, openmode, _winapi.PIPE_WAIT,
+ 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
+
+ h2 = _winapi.CreateFile(
+ address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
+ flags_and_attribs, _winapi.NULL)
+
+ ov = _winapi.ConnectNamedPipe(h1, overlapped=True)
+ ov.GetOverlappedResult(True)
+ return h1, h2
+ except:
+ if h1 is not None:
+ _winapi.CloseHandle(h1)
+ if h2 is not None:
+ _winapi.CloseHandle(h2)
+ raise
+
+
+# Wrapper for a pipe handle
+
+
+class PipeHandle:
+ """Wrapper for an overlapped pipe handle which is vaguely file-object like.
+
+ The IOCP event loop can use these instead of socket objects.
+ """
+ def __init__(self, handle):
+ self._handle = handle
+
+ @property
+ def handle(self):
+ return self._handle
+
+ def fileno(self):
+ return self._handle
+
+ def close(self, *, CloseHandle=_winapi.CloseHandle):
+ if self._handle != -1:
+ CloseHandle(self._handle)
+ self._handle = -1
+
+ __del__ = close
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, t, v, tb):
+ self.close()
+
+
+# Replacement for subprocess.Popen using overlapped pipe handles
+
+
+class Popen(subprocess.Popen):
+ """Replacement for subprocess.Popen using overlapped pipe handles.
+
+ The stdin, stdout, stderr are None or instances of PipeHandle.
+ """
+ def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
+ assert not kwds.get('universal_newlines')
+ assert kwds.get('bufsize', 0) == 0
+ stdin_rfd = stdout_wfd = stderr_wfd = None
+ stdin_wh = stdout_rh = stderr_rh = None
+ if stdin == PIPE:
+ stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
+ stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
+ else:
+ stdin_rfd = stdin
+ if stdout == PIPE:
+ stdout_rh, stdout_wh = pipe(overlapped=(True, False))
+ stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
+ else:
+ stdout_wfd = stdout
+ if stderr == PIPE:
+ stderr_rh, stderr_wh = pipe(overlapped=(True, False))
+ stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
+ elif stderr == STDOUT:
+ stderr_wfd = stdout_wfd
+ else:
+ stderr_wfd = stderr
+ try:
+ super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
+ stderr=stderr_wfd, **kwds)
+ except:
+ for h in (stdin_wh, stdout_rh, stderr_rh):
+ if h is not None:
+ _winapi.CloseHandle(h)
+ raise
+ else:
+ if stdin_wh is not None:
+ self.stdin = PipeHandle(stdin_wh)
+ if stdout_rh is not None:
+ self.stdout = PipeHandle(stdout_rh)
+ if stderr_rh is not None:
+ self.stderr = PipeHandle(stderr_rh)
+ finally:
+ if stdin == PIPE:
+ os.close(stdin_rfd)
+ if stdout == PIPE:
+ os.close(stdout_wfd)
+ if stderr == PIPE:
+ os.close(stderr_wfd)
diff --git a/Lib/asyncore.py b/Lib/asyncore.py
index 909d9f605d..75481ddde0 100644
--- a/Lib/asyncore.py
+++ b/Lib/asyncore.py
@@ -112,7 +112,7 @@ def readwrite(obj, flags):
obj.handle_expt_event()
if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
obj.handle_close()
- except socket.error as e:
+ except OSError as e:
if e.args[0] not in _DISCONNECTED:
obj.handle_error()
else:
@@ -240,7 +240,7 @@ class dispatcher:
# passed be connected.
try:
self.addr = sock.getpeername()
- except socket.error as err:
+ except OSError as err:
if err.args[0] in (ENOTCONN, EINVAL):
# To handle the case where we got an unconnected
# socket.
@@ -304,7 +304,7 @@ class dispatcher:
self.socket.getsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR) | 1
)
- except socket.error:
+ except OSError:
pass
# ==================================================
@@ -345,7 +345,7 @@ class dispatcher:
self.addr = address
self.handle_connect_event()
else:
- raise socket.error(err, errorcode[err])
+ raise OSError(err, errorcode[err])
def accept(self):
# XXX can return either an address pair or None
@@ -353,7 +353,7 @@ class dispatcher:
conn, addr = self.socket.accept()
except TypeError:
return None
- except socket.error as why:
+ except OSError as why:
if why.args[0] in (EWOULDBLOCK, ECONNABORTED, EAGAIN):
return None
else:
@@ -365,7 +365,7 @@ class dispatcher:
try:
result = self.socket.send(data)
return result
- except socket.error as why:
+ except OSError as why:
if why.args[0] == EWOULDBLOCK:
return 0
elif why.args[0] in _DISCONNECTED:
@@ -384,7 +384,7 @@ class dispatcher:
return b''
else:
return data
- except socket.error as why:
+ except OSError as why:
# winsock sometimes raises ENOTCONN
if why.args[0] in _DISCONNECTED:
self.handle_close()
@@ -397,11 +397,12 @@ class dispatcher:
self.accepting = False
self.connecting = False
self.del_channel()
- try:
- self.socket.close()
- except socket.error as why:
- if why.args[0] not in (ENOTCONN, EBADF):
- raise
+ if self.socket is not None:
+ try:
+ self.socket.close()
+ except OSError as why:
+ if why.args[0] not in (ENOTCONN, EBADF):
+ raise
# cheap inheritance, used to pass all other attribute
# references to the underlying socket object.
@@ -443,7 +444,7 @@ class dispatcher:
def handle_connect_event(self):
err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err != 0:
- raise socket.error(err, _strerror(err))
+ raise OSError(err, _strerror(err))
self.handle_connect()
self.connected = True
self.connecting = False
@@ -532,7 +533,7 @@ class dispatcher_with_send(dispatcher):
def initiate_send(self):
num_sent = 0
- num_sent = dispatcher.send(self, self.out_buffer[:512])
+ num_sent = dispatcher.send(self, self.out_buffer[:65536])
self.out_buffer = self.out_buffer[num_sent:]
def handle_write(self):
diff --git a/Lib/base64.py b/Lib/base64.py
index b6e82b69bd..84818cf078 100755
--- a/Lib/base64.py
+++ b/Lib/base64.py
@@ -1,6 +1,6 @@
#! /usr/bin/env python3
-"""RFC 3548: Base16, Base32, Base64 Data Encodings"""
+"""Base16, Base32, Base64 (RFC 3548), Base85 and Ascii85 data encodings"""
# Modified 04-Oct-1995 by Jack Jansen to use binascii module
# Modified 30-Dec-2003 by Barry Warsaw to add full RFC 3548 support
@@ -9,6 +9,7 @@
import re
import struct
import binascii
+import itertools
__all__ = [
@@ -17,6 +18,8 @@ __all__ = [
# Generalized interface for other encodings
'b64encode', 'b64decode', 'b32encode', 'b32decode',
'b16encode', 'b16decode',
+ # Base85 and Ascii85 encodings
+ 'b85encode', 'b85decode', 'a85encode', 'a85decode',
# Standard Base64 encoding
'standard_b64encode', 'standard_b64decode',
# Some common Base64 alternatives. As referenced by RFC 3458, see thread
@@ -35,11 +38,13 @@ def _bytes_from_decode_data(s):
return s.encode('ascii')
except UnicodeEncodeError:
raise ValueError('string argument should contain only ASCII characters')
- elif isinstance(s, bytes_types):
+ if isinstance(s, bytes_types):
return s
- else:
- raise TypeError("argument should be bytes or ASCII string, not %s" % s.__class__.__name__)
-
+ try:
+ return memoryview(s).tobytes()
+ except TypeError:
+ raise TypeError("argument should be a bytes-like object or ASCII "
+ "string, not %r" % s.__class__.__name__) from None
# Base64 encoding/decoding uses binascii
@@ -54,14 +59,9 @@ def b64encode(s, altchars=None):
The encoded byte string is returned.
"""
- if not isinstance(s, bytes_types):
- raise TypeError("expected bytes, not %s" % s.__class__.__name__)
# Strip off the trailing newline
encoded = binascii.b2a_base64(s)[:-1]
if altchars is not None:
- if not isinstance(altchars, bytes_types):
- raise TypeError("expected bytes, not %s"
- % altchars.__class__.__name__)
assert len(altchars) == 2, repr(altchars)
return encoded.translate(bytes.maketrans(b'+/', altchars))
return encoded
@@ -138,21 +138,10 @@ def urlsafe_b64decode(s):
# Base32 encoding/decoding must be done in Python
-_b32alphabet = {
- 0: b'A', 9: b'J', 18: b'S', 27: b'3',
- 1: b'B', 10: b'K', 19: b'T', 28: b'4',
- 2: b'C', 11: b'L', 20: b'U', 29: b'5',
- 3: b'D', 12: b'M', 21: b'V', 30: b'6',
- 4: b'E', 13: b'N', 22: b'W', 31: b'7',
- 5: b'F', 14: b'O', 23: b'X',
- 6: b'G', 15: b'P', 24: b'Y',
- 7: b'H', 16: b'Q', 25: b'Z',
- 8: b'I', 17: b'R', 26: b'2',
- }
-
-_b32tab = [v[0] for k, v in sorted(_b32alphabet.items())]
-_b32rev = dict([(v[0], k) for k, v in _b32alphabet.items()])
-
+_b32alphabet = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
+_b32tab = [bytes([i]) for i in _b32alphabet]
+_b32tab2 = [a + b for a in _b32tab for b in _b32tab]
+_b32rev = {v: k for k, v in enumerate(_b32alphabet)}
def b32encode(s):
"""Encode a byte string using Base32.
@@ -160,31 +149,21 @@ def b32encode(s):
s is the byte string to encode. The encoded byte string is returned.
"""
if not isinstance(s, bytes_types):
- raise TypeError("expected bytes, not %s" % s.__class__.__name__)
- quanta, leftover = divmod(len(s), 5)
+ s = memoryview(s).tobytes()
+ leftover = len(s) % 5
# Pad the last quantum with zero bits if necessary
if leftover:
s = s + bytes(5 - leftover) # Don't use += !
- quanta += 1
encoded = bytearray()
- for i in range(quanta):
- # c1 and c2 are 16 bits wide, c3 is 8 bits wide. The intent of this
- # code is to process the 40 bits in units of 5 bits. So we take the 1
- # leftover bit of c1 and tack it onto c2. Then we take the 2 leftover
- # bits of c2 and tack them onto c3. The shifts and masks are intended
- # to give us values of exactly 5 bits in width.
- c1, c2, c3 = struct.unpack('!HHB', s[i*5:(i+1)*5])
- c2 += (c1 & 1) << 16 # 17 bits wide
- c3 += (c2 & 3) << 8 # 10 bits wide
- encoded += bytes([_b32tab[c1 >> 11], # bits 1 - 5
- _b32tab[(c1 >> 6) & 0x1f], # bits 6 - 10
- _b32tab[(c1 >> 1) & 0x1f], # bits 11 - 15
- _b32tab[c2 >> 12], # bits 16 - 20 (1 - 5)
- _b32tab[(c2 >> 7) & 0x1f], # bits 21 - 25 (6 - 10)
- _b32tab[(c2 >> 2) & 0x1f], # bits 26 - 30 (11 - 15)
- _b32tab[c3 >> 5], # bits 31 - 35 (1 - 5)
- _b32tab[c3 & 0x1f], # bits 36 - 40 (1 - 5)
- ])
+ from_bytes = int.from_bytes
+ b32tab2 = _b32tab2
+ for i in range(0, len(s), 5):
+ c = from_bytes(s[i: i + 5], 'big')
+ encoded += (b32tab2[c >> 30] + # bits 1 - 10
+ b32tab2[(c >> 20) & 0x3ff] + # bits 11 - 20
+ b32tab2[(c >> 10) & 0x3ff] + # bits 21 - 30
+ b32tab2[c & 0x3ff] # bits 31 - 40
+ )
# Adjust for any leftover partial quanta
if leftover == 1:
encoded[-6:] = b'======'
@@ -196,7 +175,6 @@ def b32encode(s):
encoded[-1:] = b'='
return bytes(encoded)
-
def b32decode(s, casefold=False, map01=None):
"""Decode a Base32 encoded byte string.
@@ -217,8 +195,7 @@ def b32decode(s, casefold=False, map01=None):
characters present in the input.
"""
s = _bytes_from_decode_data(s)
- quanta, leftover = divmod(len(s), 8)
- if leftover:
+ if len(s) % 8:
raise binascii.Error('Incorrect padding')
# Handle section 2.4 zero and one mapping. The flag map01 will be either
# False, or the character to map the digit 1 (one) to. It should be
@@ -232,42 +209,36 @@ def b32decode(s, casefold=False, map01=None):
# Strip off pad characters from the right. We need to count the pad
# characters because this will tell us how many null bytes to remove from
# the end of the decoded string.
- padchars = 0
- mo = re.search(b'(?P<pad>[=]*)$', s)
- if mo:
- padchars = len(mo.group('pad'))
- if padchars > 0:
- s = s[:-padchars]
+ l = len(s)
+ s = s.rstrip(b'=')
+ padchars = l - len(s)
# Now decode the full quanta
- parts = []
- acc = 0
- shift = 35
- for c in s:
- val = _b32rev.get(c)
- if val is None:
- raise binascii.Error('Non-base32 digit found')
- acc += _b32rev[c] << shift
- shift -= 5
- if shift < 0:
- parts.append(binascii.unhexlify(bytes('%010x' % acc, "ascii")))
- acc = 0
- shift = 35
+ decoded = bytearray()
+ b32rev = _b32rev
+ for i in range(0, len(s), 8):
+ quanta = s[i: i + 8]
+ acc = 0
+ try:
+ for c in quanta:
+ acc = (acc << 5) + b32rev[c]
+ except KeyError:
+ raise binascii.Error('Non-base32 digit found') from None
+ decoded += acc.to_bytes(5, 'big')
# Process the last, partial quanta
- last = binascii.unhexlify(bytes('%010x' % acc, "ascii"))
- if padchars == 0:
- last = b'' # No characters
- elif padchars == 1:
- last = last[:-1]
- elif padchars == 3:
- last = last[:-2]
- elif padchars == 4:
- last = last[:-3]
- elif padchars == 6:
- last = last[:-4]
- else:
- raise binascii.Error('Incorrect padding')
- parts.append(last)
- return b''.join(parts)
+ if padchars:
+ acc <<= 5 * padchars
+ last = acc.to_bytes(5, 'big')
+ if padchars == 1:
+ decoded[-5:] = last[:-1]
+ elif padchars == 3:
+ decoded[-5:] = last[:-2]
+ elif padchars == 4:
+ decoded[-5:] = last[:-3]
+ elif padchars == 6:
+ decoded[-5:] = last[:-4]
+ else:
+ raise binascii.Error('Incorrect padding')
+ return bytes(decoded)
@@ -279,8 +250,6 @@ def b16encode(s):
s is the byte string to encode. The encoded byte string is returned.
"""
- if not isinstance(s, bytes_types):
- raise TypeError("expected bytes, not %s" % s.__class__.__name__)
return binascii.hexlify(s).upper()
@@ -302,7 +271,193 @@ def b16decode(s, casefold=False):
raise binascii.Error('Non-base16 digit found')
return binascii.unhexlify(s)
+#
+# Ascii85 encoding/decoding
+#
+
+def _85encode(b, chars, chars2, pad=False, foldnuls=False, foldspaces=False):
+ # Helper function for a85encode and b85encode
+ if not isinstance(b, bytes_types):
+ b = memoryview(b).tobytes()
+
+ padding = (-len(b)) % 4
+ if padding:
+ b = b + b'\0' * padding
+ words = struct.Struct('!%dI' % (len(b) // 4)).unpack(b)
+
+ a85chars2 = _a85chars2
+ a85chars = _a85chars
+ chunks = [b'z' if foldnuls and not word else
+ b'y' if foldspaces and word == 0x20202020 else
+ (chars2[word // 614125] +
+ chars2[word // 85 % 7225] +
+ chars[word % 85])
+ for word in words]
+
+ if padding and not pad:
+ if chunks[-1] == b'z':
+ chunks[-1] = chars[0] * 5
+ chunks[-1] = chunks[-1][:-padding]
+
+ return b''.join(chunks)
+
+_A85START = b"<~"
+_A85END = b"~>"
+_a85chars = [bytes([i]) for i in range(33, 118)]
+_a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
+def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False):
+ """Encode a byte string using Ascii85.
+
+ b is the byte string to encode. The encoded byte string is returned.
+
+ foldspaces is an optional flag that uses the special short sequence 'y'
+ instead of 4 consecutive spaces (ASCII 0x20) as supported by 'btoa'. This
+ feature is not supported by the "standard" Adobe encoding.
+
+ wrapcol controls whether the output should have newline ('\n') characters
+ added to it. If this is non-zero, each output line will be at most this
+ many characters long.
+
+ pad controls whether the input string is padded to a multiple of 4 before
+ encoding. Note that the btoa implementation always pads.
+
+ adobe controls whether the encoded byte sequence is framed with <~ and ~>,
+ which is used by the Adobe implementation.
+ """
+ result = _85encode(b, _a85chars, _a85chars2, pad, True, foldspaces)
+
+ if adobe:
+ result = _A85START + result
+ if wrapcol:
+ wrapcol = max(2 if adobe else 1, wrapcol)
+ chunks = [result[i: i + wrapcol]
+ for i in range(0, len(result), wrapcol)]
+ if adobe:
+ if len(chunks[-1]) + 2 > wrapcol:
+ chunks.append(b'')
+ result = b'\n'.join(chunks)
+ if adobe:
+ result += _A85END
+
+ return result
+
+def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'):
+ """Decode an Ascii85 encoded byte string.
+
+ s is the byte string to decode.
+
+ foldspaces is a flag that specifies whether the 'y' short sequence should be
+ accepted as shorthand for 4 consecutive spaces (ASCII 0x20). This feature is
+ not supported by the "standard" Adobe encoding.
+
+ adobe controls whether the input sequence is in Adobe Ascii85 format (i.e.
+ is framed with <~ and ~>).
+
+ ignorechars should be a byte string containing characters to ignore from the
+ input. This should only contain whitespace characters, and by default
+ contains all whitespace characters in ASCII.
+ """
+ b = _bytes_from_decode_data(b)
+ if adobe:
+ if not (b.startswith(_A85START) and b.endswith(_A85END)):
+ raise ValueError("Ascii85 encoded byte sequences must be bracketed "
+ "by {} and {}".format(_A85START, _A85END))
+ b = b[2:-2] # Strip off start/end markers
+ #
+ # We have to go through this stepwise, so as to ignore spaces and handle
+ # special short sequences
+ #
+ packI = struct.Struct('!I').pack
+ decoded = []
+ decoded_append = decoded.append
+ curr = []
+ curr_append = curr.append
+ curr_clear = curr.clear
+ for x in b + b'u' * 4:
+ if b'!'[0] <= x <= b'u'[0]:
+ curr_append(x)
+ if len(curr) == 5:
+ acc = 0
+ for x in curr:
+ acc = 85 * acc + (x - 33)
+ try:
+ decoded_append(packI(acc))
+ except struct.error:
+ raise ValueError('Ascii85 overflow') from None
+ curr_clear()
+ elif x == b'z'[0]:
+ if curr:
+ raise ValueError('z inside Ascii85 5-tuple')
+ decoded_append(b'\0\0\0\0')
+ elif foldspaces and x == b'y'[0]:
+ if curr:
+ raise ValueError('y inside Ascii85 5-tuple')
+ decoded_append(b'\x20\x20\x20\x20')
+ elif x in ignorechars:
+ # Skip whitespace
+ continue
+ else:
+ raise ValueError('Non-Ascii85 digit found: %c' % x)
+
+ result = b''.join(decoded)
+ padding = 4 - len(curr)
+ if padding:
+ # Throw away the extra padding
+ result = result[:-padding]
+ return result
+
+# The following code is originally taken (with permission) from Mercurial
+
+_b85chars = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"
+_b85chars = [bytes([i]) for i in _b85chars]
+_b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
+_b85dec = None
+
+def b85encode(b, pad=False):
+ """Encode an ASCII-encoded byte array in base85 format.
+
+ If pad is true, the input is padded with "\0" so its length is a multiple of
+ 4 characters before encoding.
+ """
+ return _85encode(b, _b85chars, _b85chars2, pad)
+
+def b85decode(b):
+ """Decode base85-encoded byte array"""
+ b = _bytes_from_decode_data(b)
+ global _b85dec
+ if _b85dec is None:
+ _b85dec = [None] * 256
+ for i, c in enumerate(_b85chars):
+ _b85dec[c[0]] = i
+
+ padding = (-len(b)) % 5
+ b = b + b'~' * padding
+ out = []
+ packI = struct.Struct('!I').pack
+ for i in range(0, len(b), 5):
+ chunk = b[i:i + 5]
+ acc = 0
+ try:
+ for c in chunk:
+ acc = acc * 85 + _b85dec[c]
+ except TypeError:
+ for j, c in enumerate(chunk):
+ if _b85dec[c] is None:
+ raise ValueError('bad base85 character at position %d'
+ % (i + j)) from None
+ raise
+ try:
+ out.append(packI(acc))
+ except struct.error:
+ raise ValueError('base85 overflow in hunk starting at byte %d'
+ % i) from None
+
+ result = b''.join(out)
+ if padding:
+ result = result[:-padding]
+ return result
# Legacy interface. This code could be cleaned up since I don't believe
# binascii has any line length limitations. It just doesn't seem worth it
@@ -335,12 +490,26 @@ def decode(input, output):
s = binascii.a2b_base64(line)
output.write(s)
+def _input_type_check(s):
+ try:
+ m = memoryview(s)
+ except TypeError as err:
+ msg = "expected bytes-like object, not %s" % s.__class__.__name__
+ raise TypeError(msg) from err
+ if m.format not in ('c', 'b', 'B'):
+ msg = ("expected single byte elements, not %r from %s" %
+ (m.format, s.__class__.__name__))
+ raise TypeError(msg)
+ if m.ndim != 1:
+ msg = ("expected 1-D data, not %d-D data from %s" %
+ (m.ndim, s.__class__.__name__))
+ raise TypeError(msg)
+
def encodebytes(s):
"""Encode a bytestring into a bytestring containing multiple lines
of base-64 data."""
- if not isinstance(s, bytes_types):
- raise TypeError("expected bytes, not %s" % s.__class__.__name__)
+ _input_type_check(s)
pieces = []
for i in range(0, len(s), MAXBINSIZE):
chunk = s[i : i + MAXBINSIZE]
@@ -357,8 +526,7 @@ def encodestring(s):
def decodebytes(s):
"""Decode a bytestring of base-64 data into a bytestring."""
- if not isinstance(s, bytes_types):
- raise TypeError("expected bytes, not %s" % s.__class__.__name__)
+ _input_type_check(s)
return binascii.a2b_base64(s)
def decodestring(s):
diff --git a/Lib/bdb.py b/Lib/bdb.py
index dd1f4287c1..67a08463fa 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -3,6 +3,7 @@
import fnmatch
import sys
import os
+from inspect import CO_GENERATOR
__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
@@ -75,24 +76,48 @@ class Bdb:
if not (self.stop_here(frame) or self.break_anywhere(frame)):
# No need to trace this function
return # None
+ # Ignore call events in generator except when stepping.
+ if self.stopframe and frame.f_code.co_flags & CO_GENERATOR:
+ return self.trace_dispatch
self.user_call(frame, arg)
if self.quitting: raise BdbQuit
return self.trace_dispatch
def dispatch_return(self, frame, arg):
if self.stop_here(frame) or frame == self.returnframe:
+ # Ignore return events in generator except when stepping.
+ if self.stopframe and frame.f_code.co_flags & CO_GENERATOR:
+ return self.trace_dispatch
try:
self.frame_returning = frame
self.user_return(frame, arg)
finally:
self.frame_returning = None
if self.quitting: raise BdbQuit
+ # The user issued a 'next' or 'until' command.
+ if self.stopframe is frame and self.stoplineno != -1:
+ self._set_stopinfo(None, None)
return self.trace_dispatch
def dispatch_exception(self, frame, arg):
if self.stop_here(frame):
+ # When stepping with next/until/return in a generator frame, skip
+ # the internal StopIteration exception (with no traceback)
+ # triggered by a subiterator run with the 'yield from' statement.
+ if not (frame.f_code.co_flags & CO_GENERATOR
+ and arg[0] is StopIteration and arg[2] is None):
+ self.user_exception(frame, arg)
+ if self.quitting: raise BdbQuit
+ # Stop at the StopIteration or GeneratorExit exception when the user
+ # has set stopframe in a generator by issuing a return command, or a
+ # next/until command at the last statement in the generator before the
+ # exception.
+ elif (self.stopframe and frame is not self.stopframe
+ and self.stopframe.f_code.co_flags & CO_GENERATOR
+ and arg[0] in (StopIteration, GeneratorExit)):
self.user_exception(frame, arg)
if self.quitting: raise BdbQuit
+
return self.trace_dispatch
# Normally derived classes don't override the following
@@ -115,10 +140,8 @@ class Bdb:
if self.stoplineno == -1:
return False
return frame.f_lineno >= self.stoplineno
- while frame is not None and frame is not self.stopframe:
- if frame is self.botframe:
- return True
- frame = frame.f_back
+ if not self.stopframe:
+ return True
return False
def break_here(self, frame):
@@ -207,7 +230,10 @@ class Bdb:
def set_return(self, frame):
"""Stop when returning from the given frame."""
- self._set_stopinfo(frame.f_back, frame)
+ if frame.f_code.co_flags & CO_GENERATOR:
+ self._set_stopinfo(frame, None, -1)
+ else:
+ self._set_stopinfo(frame.f_back, frame)
def set_trace(self, frame=None):
"""Start debugging from `frame`.
diff --git a/Lib/bz2.py b/Lib/bz2.py
index c3075073f0..6bc611e71f 100644
--- a/Lib/bz2.py
+++ b/Lib/bz2.py
@@ -9,7 +9,6 @@ __all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor",
__author__ = "Nadeem Vawda <nadeem.vawda@gmail.com>"
-import builtins
import io
import warnings
@@ -28,6 +27,8 @@ _MODE_WRITE = 3
_BUFFER_SIZE = 8192
+_builtin_open = open
+
class BZ2File(io.BufferedIOBase):
@@ -43,16 +44,17 @@ class BZ2File(io.BufferedIOBase):
def __init__(self, filename, mode="r", buffering=None, compresslevel=9):
"""Open a bzip2-compressed file.
- If filename is a str or bytes object, is gives the name of the file to
- be opened. Otherwise, it should be a file object, which will be used to
- read or write the compressed data.
+ If filename is a str or bytes object, it gives the name
+ of the file to be opened. Otherwise, it should be a file object,
+ which will be used to read or write the compressed data.
- mode can be 'r' for reading (default), 'w' for (over)writing, or 'a' for
- appending. These can equivalently be given as 'rb', 'wb', and 'ab'.
+ mode can be 'r' for reading (default), 'w' for (over)writing,
+ 'x' for creating exclusively, or 'a' for appending. These can
+ equivalently be given as 'rb', 'wb', 'xb', and 'ab'.
buffering is ignored. Its use is deprecated.
- If mode is 'w' or 'a', compresslevel can be a number between 1
+ If mode is 'w', 'x' or 'a', compresslevel can be a number between 1
and 9 specifying the level of compression: 1 produces the least
compression, and 9 (default) produces the most compression.
@@ -85,15 +87,19 @@ class BZ2File(io.BufferedIOBase):
mode = "wb"
mode_code = _MODE_WRITE
self._compressor = BZ2Compressor(compresslevel)
+ elif mode in ("x", "xb"):
+ mode = "xb"
+ mode_code = _MODE_WRITE
+ self._compressor = BZ2Compressor(compresslevel)
elif mode in ("a", "ab"):
mode = "ab"
mode_code = _MODE_WRITE
self._compressor = BZ2Compressor(compresslevel)
else:
- raise ValueError("Invalid mode: {!r}".format(mode))
+ raise ValueError("Invalid mode: %r" % (mode,))
if isinstance(filename, (str, bytes)):
- self._fp = builtins.open(filename, mode)
+ self._fp = _builtin_open(filename, mode)
self._closefp = True
self._mode = mode_code
elif hasattr(filename, "read") or hasattr(filename, "write"):
@@ -189,15 +195,17 @@ class BZ2File(io.BufferedIOBase):
if not rawblock:
if self._decompressor.eof:
+ # End-of-stream marker and end of file. We're good.
self._mode = _MODE_READ_EOF
self._size = self._pos
return False
else:
+ # Problem - we were expecting more compressed data.
raise EOFError("Compressed file ended before the "
"end-of-stream marker was reached")
- # Continue to next stream.
if self._decompressor.eof:
+ # Continue to next stream.
self._decompressor = BZ2Decompressor()
self._buffer = self._decompressor.decompress(rawblock)
@@ -412,7 +420,7 @@ class BZ2File(io.BufferedIOBase):
self._read_all(return_data=False)
offset = self._size + offset
else:
- raise ValueError("Invalid value for whence: {}".format(whence))
+ raise ValueError("Invalid value for whence: %s" % (whence,))
# Make it so that offset is the number of bytes to skip forward.
if offset < self._pos:
@@ -436,20 +444,20 @@ def open(filename, mode="rb", compresslevel=9,
encoding=None, errors=None, newline=None):
"""Open a bzip2-compressed file in binary or text mode.
- The filename argument can be an actual filename (a str or bytes object), or
- an existing file object to read from or write to.
+ The filename argument can be an actual filename (a str or bytes
+ object), or an existing file object to read from or write to.
- The mode argument can be "r", "rb", "w", "wb", "a" or "ab" for binary mode,
- or "rt", "wt" or "at" for text mode. The default mode is "rb", and the
- default compresslevel is 9.
+ The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or
+ "ab" for binary mode, or "rt", "wt", "xt" or "at" for text mode.
+ The default mode is "rb", and the default compresslevel is 9.
- For binary mode, this function is equivalent to the BZ2File constructor:
- BZ2File(filename, mode, compresslevel). In this case, the encoding, errors
- and newline arguments must not be provided.
+ For binary mode, this function is equivalent to the BZ2File
+ constructor: BZ2File(filename, mode, compresslevel). In this case,
+ the encoding, errors and newline arguments must not be provided.
For text mode, a BZ2File object is created, and wrapped in an
- io.TextIOWrapper instance with the specified encoding, error handling
- behavior, and line ending(s).
+ io.TextIOWrapper instance with the specified encoding, error
+ handling behavior, and line ending(s).
"""
if "t" in mode:
diff --git a/Lib/cProfile.py b/Lib/cProfile.py
index c24d45bab4..1184385ae1 100755
--- a/Lib/cProfile.py
+++ b/Lib/cProfile.py
@@ -7,54 +7,20 @@
__all__ = ["run", "runctx", "Profile"]
import _lsprof
+import profile as _pyprofile
# ____________________________________________________________
# Simple interface
def run(statement, filename=None, sort=-1):
- """Run statement under profiler optionally saving results in filename
-
- This function takes a single argument that can be passed to the
- "exec" statement, and an optional file name. In all cases this
- routine attempts to "exec" its first argument and gather profiling
- statistics from the execution. If no file name is present, then this
- function automatically prints a simple profiling report, sorted by the
- standard name string (file/line/function-name) that is presented in
- each line.
- """
- prof = Profile()
- result = None
- try:
- try:
- prof = prof.run(statement)
- except SystemExit:
- pass
- finally:
- if filename is not None:
- prof.dump_stats(filename)
- else:
- result = prof.print_stats(sort)
- return result
+ return _pyprofile._Utils(Profile).run(statement, filename, sort)
def runctx(statement, globals, locals, filename=None, sort=-1):
- """Run statement under profiler, supplying your own globals and locals,
- optionally saving results in filename.
+ return _pyprofile._Utils(Profile).runctx(statement, globals, locals,
+ filename, sort)
- statement and filename have the same semantics as profile.run
- """
- prof = Profile()
- result = None
- try:
- try:
- prof = prof.runctx(statement, globals, locals)
- except SystemExit:
- pass
- finally:
- if filename is not None:
- prof.dump_stats(filename)
- else:
- result = prof.print_stats(sort)
- return result
+run.__doc__ = _pyprofile.run.__doc__
+runctx.__doc__ = _pyprofile.runctx.__doc__
# ____________________________________________________________
@@ -77,10 +43,9 @@ class Profile(_lsprof.Profiler):
def dump_stats(self, file):
import marshal
- f = open(file, 'wb')
- self.create_stats()
- marshal.dump(self.stats, f)
- f.close()
+ with open(file, 'wb') as f:
+ self.create_stats()
+ marshal.dump(self.stats, f)
def create_stats(self):
self.disable()
diff --git a/Lib/cgi.py b/Lib/cgi.py
index 06e03b5858..1d8040cc7a 100755
--- a/Lib/cgi.py
+++ b/Lib/cgi.py
@@ -80,7 +80,7 @@ def initlog(*allargs):
if logfile and not logfp:
try:
logfp = open(logfile, "a")
- except IOError:
+ except OSError:
pass
if not logfp:
log = nolog
@@ -552,6 +552,12 @@ class FieldStorage:
else:
self.read_single()
+ def __del__(self):
+ try:
+ self.file.close()
+ except AttributeError:
+ pass
+
def __repr__(self):
"""Return a printable representation."""
return "FieldStorage(%r, %r, %r)" % (
@@ -670,7 +676,6 @@ class FieldStorage:
encoding=self.encoding, errors=self.errors)
for key, value in query:
self.list.append(MiniFieldStorage(key, value))
- FieldStorageClass = None
klass = self.FieldStorageClass or self.__class__
first_line = self.fp.readline() # bytes
@@ -958,8 +963,8 @@ def print_directory():
print("<H3>Current Working Directory:</H3>")
try:
pwd = os.getcwd()
- except os.error as msg:
- print("os.error:", html.escape(str(msg)))
+ except OSError as msg:
+ print("OSError:", html.escape(str(msg)))
else:
print(html.escape(pwd))
print()
diff --git a/Lib/chunk.py b/Lib/chunk.py
index 5863ed0846..dc90a7522e 100644
--- a/Lib/chunk.py
+++ b/Lib/chunk.py
@@ -70,7 +70,7 @@ class Chunk:
self.size_read = 0
try:
self.offset = self.file.tell()
- except (AttributeError, IOError):
+ except (AttributeError, OSError):
self.seekable = False
else:
self.seekable = True
@@ -102,7 +102,7 @@ class Chunk:
if self.closed:
raise ValueError("I/O operation on closed file")
if not self.seekable:
- raise IOError("cannot seek")
+ raise OSError("cannot seek")
if whence == 1:
pos = pos + self.size_read
elif whence == 2:
@@ -158,7 +158,7 @@ class Chunk:
self.file.seek(n, 1)
self.size_read = self.size_read + n
return
- except IOError:
+ except OSError:
pass
while self.size_read < self.chunksize:
n = min(8192, self.chunksize - self.size_read)
diff --git a/Lib/code.py b/Lib/code.py
index 9020aab701..f8184b6c22 100644
--- a/Lib/code.py
+++ b/Lib/code.py
@@ -216,7 +216,7 @@ class InteractiveConsole(InteractiveInterpreter):
self.write("Python %s on %s\n%s\n(%s)\n" %
(sys.version, sys.platform, cprt,
self.__class__.__name__))
- else:
+ elif banner:
self.write("%s\n" % str(banner))
more = 0
while 1:
diff --git a/Lib/codecs.py b/Lib/codecs.py
index 6a6eb900c7..2e2e7555a4 100644
--- a/Lib/codecs.py
+++ b/Lib/codecs.py
@@ -73,9 +73,19 @@ BOM64_BE = BOM_UTF32_BE
### Codec base classes (defining the API)
class CodecInfo(tuple):
+ """Codec details when looking up the codec registry"""
+
+ # Private API to allow Python 3.4 to blacklist the known non-Unicode
+ # codecs in the standard library. A more general mechanism to
+ # reliably distinguish test encodings from other codecs will hopefully
+ # be defined for Python 3.5
+ #
+ # See http://bugs.python.org/issue19619
+ _is_text_encoding = True # Assume codecs are text encodings by default
def __new__(cls, encode, decode, streamreader=None, streamwriter=None,
- incrementalencoder=None, incrementaldecoder=None, name=None):
+ incrementalencoder=None, incrementaldecoder=None, name=None,
+ *, _is_text_encoding=None):
self = tuple.__new__(cls, (encode, decode, streamreader, streamwriter))
self.name = name
self.encode = encode
@@ -84,6 +94,8 @@ class CodecInfo(tuple):
self.incrementaldecoder = incrementaldecoder
self.streamwriter = streamwriter
self.streamreader = streamreader
+ if _is_text_encoding is not None:
+ self._is_text_encoding = _is_text_encoding
return self
def __repr__(self):
diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py
index d737295dd1..02bdc57c9f 100644
--- a/Lib/collections/__init__.py
+++ b/Lib/collections/__init__.py
@@ -3,16 +3,16 @@ __all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList',
# For backwards compatibility, continue to make the collections ABCs
# available through the collections module.
-from collections.abc import *
-import collections.abc
-__all__ += collections.abc.__all__
+from _collections_abc import *
+import _collections_abc
+__all__ += _collections_abc.__all__
from _collections import deque, defaultdict
from operator import itemgetter as _itemgetter, eq as _eq
from keyword import iskeyword as _iskeyword
import sys as _sys
import heapq as _heapq
-from weakref import proxy as _proxy
+from _weakref import proxy as _proxy
from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
from reprlib import recursive_repr as _recursive_repr
@@ -199,13 +199,10 @@ class OrderedDict(dict):
def __reduce__(self):
'Return state information for pickling'
- items = [[k, self[k]] for k in self]
inst_dict = vars(self).copy()
for k in vars(OrderedDict()):
inst_dict.pop(k, None)
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
+ return self.__class__, (), inst_dict or None, None, iter(self.items())
def copy(self):
'od.copy() -> a shallow copy of od'
@@ -822,9 +819,14 @@ class ChainMap(MutableMapping):
__copy__ = copy
- def new_child(self): # like Django's Context.push()
- 'New ChainMap with a new dict followed by all previous maps.'
- return self.__class__({}, *self.maps)
+ def new_child(self, m=None): # like Django's Context.push()
+ '''
+ New ChainMap with a new map followed by all previous maps. If no
+ map is provided, an empty dict is used.
+ '''
+ if m is None:
+ m = {}
+ return self.__class__(m, *self.maps)
@property
def parents(self): # like Django's Context.pop()
diff --git a/Lib/collections/abc.py b/Lib/collections/abc.py
index 7939268c99..891600d16b 100644
--- a/Lib/collections/abc.py
+++ b/Lib/collections/abc.py
@@ -1,728 +1,2 @@
-# Copyright 2007 Google, Inc. All Rights Reserved.
-# Licensed to PSF under a Contributor Agreement.
-
-"""Abstract Base Classes (ABCs) for collections, according to PEP 3119.
-
-Unit tests are in test_collections.
-"""
-
-from abc import ABCMeta, abstractmethod
-import sys
-
-__all__ = ["Hashable", "Iterable", "Iterator",
- "Sized", "Container", "Callable",
- "Set", "MutableSet",
- "Mapping", "MutableMapping",
- "MappingView", "KeysView", "ItemsView", "ValuesView",
- "Sequence", "MutableSequence",
- "ByteString",
- ]
-
-# Private list of types that we want to register with the various ABCs
-# so that they will pass tests like:
-# it = iter(somebytearray)
-# assert isinstance(it, Iterable)
-# Note: in other implementations, these types many not be distinct
-# and they make have their own implementation specific types that
-# are not included on this list.
-bytes_iterator = type(iter(b''))
-bytearray_iterator = type(iter(bytearray()))
-#callable_iterator = ???
-dict_keyiterator = type(iter({}.keys()))
-dict_valueiterator = type(iter({}.values()))
-dict_itemiterator = type(iter({}.items()))
-list_iterator = type(iter([]))
-list_reverseiterator = type(iter(reversed([])))
-range_iterator = type(iter(range(0)))
-set_iterator = type(iter(set()))
-str_iterator = type(iter(""))
-tuple_iterator = type(iter(()))
-zip_iterator = type(iter(zip()))
-## views ##
-dict_keys = type({}.keys())
-dict_values = type({}.values())
-dict_items = type({}.items())
-## misc ##
-mappingproxy = type(type.__dict__)
-
-
-### ONE-TRICK PONIES ###
-
-class Hashable(metaclass=ABCMeta):
-
- __slots__ = ()
-
- @abstractmethod
- def __hash__(self):
- return 0
-
- @classmethod
- def __subclasshook__(cls, C):
- if cls is Hashable:
- for B in C.__mro__:
- if "__hash__" in B.__dict__:
- if B.__dict__["__hash__"]:
- return True
- break
- return NotImplemented
-
-
-class Iterable(metaclass=ABCMeta):
-
- __slots__ = ()
-
- @abstractmethod
- def __iter__(self):
- while False:
- yield None
-
- @classmethod
- def __subclasshook__(cls, C):
- if cls is Iterable:
- if any("__iter__" in B.__dict__ for B in C.__mro__):
- return True
- return NotImplemented
-
-
-class Iterator(Iterable):
-
- __slots__ = ()
-
- @abstractmethod
- def __next__(self):
- 'Return the next item from the iterator. When exhausted, raise StopIteration'
- raise StopIteration
-
- def __iter__(self):
- return self
-
- @classmethod
- def __subclasshook__(cls, C):
- if cls is Iterator:
- if (any("__next__" in B.__dict__ for B in C.__mro__) and
- any("__iter__" in B.__dict__ for B in C.__mro__)):
- return True
- return NotImplemented
-
-Iterator.register(bytes_iterator)
-Iterator.register(bytearray_iterator)
-#Iterator.register(callable_iterator)
-Iterator.register(dict_keyiterator)
-Iterator.register(dict_valueiterator)
-Iterator.register(dict_itemiterator)
-Iterator.register(list_iterator)
-Iterator.register(list_reverseiterator)
-Iterator.register(range_iterator)
-Iterator.register(set_iterator)
-Iterator.register(str_iterator)
-Iterator.register(tuple_iterator)
-Iterator.register(zip_iterator)
-
-class Sized(metaclass=ABCMeta):
-
- __slots__ = ()
-
- @abstractmethod
- def __len__(self):
- return 0
-
- @classmethod
- def __subclasshook__(cls, C):
- if cls is Sized:
- if any("__len__" in B.__dict__ for B in C.__mro__):
- return True
- return NotImplemented
-
-
-class Container(metaclass=ABCMeta):
-
- __slots__ = ()
-
- @abstractmethod
- def __contains__(self, x):
- return False
-
- @classmethod
- def __subclasshook__(cls, C):
- if cls is Container:
- if any("__contains__" in B.__dict__ for B in C.__mro__):
- return True
- return NotImplemented
-
-
-class Callable(metaclass=ABCMeta):
-
- __slots__ = ()
-
- @abstractmethod
- def __call__(self, *args, **kwds):
- return False
-
- @classmethod
- def __subclasshook__(cls, C):
- if cls is Callable:
- if any("__call__" in B.__dict__ for B in C.__mro__):
- return True
- return NotImplemented
-
-
-### SETS ###
-
-
-class Set(Sized, Iterable, Container):
-
- """A set is a finite, iterable container.
-
- This class provides concrete generic implementations of all
- methods except for __contains__, __iter__ and __len__.
-
- To override the comparisons (presumably for speed, as the
- semantics are fixed), all you have to do is redefine __le__ and
- then the other operations will automatically follow suit.
- """
-
- __slots__ = ()
-
- def __le__(self, other):
- if not isinstance(other, Set):
- return NotImplemented
- if len(self) > len(other):
- return False
- for elem in self:
- if elem not in other:
- return False
- return True
-
- def __lt__(self, other):
- if not isinstance(other, Set):
- return NotImplemented
- return len(self) < len(other) and self.__le__(other)
-
- def __gt__(self, other):
- if not isinstance(other, Set):
- return NotImplemented
- return other.__lt__(self)
-
- def __ge__(self, other):
- if not isinstance(other, Set):
- return NotImplemented
- return other.__le__(self)
-
- def __eq__(self, other):
- if not isinstance(other, Set):
- return NotImplemented
- return len(self) == len(other) and self.__le__(other)
-
- def __ne__(self, other):
- return not (self == other)
-
- @classmethod
- def _from_iterable(cls, it):
- '''Construct an instance of the class from any iterable input.
-
- Must override this method if the class constructor signature
- does not accept an iterable for an input.
- '''
- return cls(it)
-
- def __and__(self, other):
- if not isinstance(other, Iterable):
- return NotImplemented
- return self._from_iterable(value for value in other if value in self)
-
- def isdisjoint(self, other):
- 'Return True if two sets have a null intersection.'
- for value in other:
- if value in self:
- return False
- return True
-
- def __or__(self, other):
- if not isinstance(other, Iterable):
- return NotImplemented
- chain = (e for s in (self, other) for e in s)
- return self._from_iterable(chain)
-
- def __sub__(self, other):
- if not isinstance(other, Set):
- if not isinstance(other, Iterable):
- return NotImplemented
- other = self._from_iterable(other)
- return self._from_iterable(value for value in self
- if value not in other)
-
- def __xor__(self, other):
- if not isinstance(other, Set):
- if not isinstance(other, Iterable):
- return NotImplemented
- other = self._from_iterable(other)
- return (self - other) | (other - self)
-
- def _hash(self):
- """Compute the hash value of a set.
-
- Note that we don't define __hash__: not all sets are hashable.
- But if you define a hashable set type, its __hash__ should
- call this function.
-
- This must be compatible __eq__.
-
- All sets ought to compare equal if they contain the same
- elements, regardless of how they are implemented, and
- regardless of the order of the elements; so there's not much
- freedom for __eq__ or __hash__. We match the algorithm used
- by the built-in frozenset type.
- """
- MAX = sys.maxsize
- MASK = 2 * MAX + 1
- n = len(self)
- h = 1927868237 * (n + 1)
- h &= MASK
- for x in self:
- hx = hash(x)
- h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167
- h &= MASK
- h = h * 69069 + 907133923
- h &= MASK
- if h > MAX:
- h -= MASK + 1
- if h == -1:
- h = 590923713
- return h
-
-Set.register(frozenset)
-
-
-class MutableSet(Set):
- """A mutable set is a finite, iterable container.
-
- This class provides concrete generic implementations of all
- methods except for __contains__, __iter__, __len__,
- add(), and discard().
-
- To override the comparisons (presumably for speed, as the
- semantics are fixed), all you have to do is redefine __le__ and
- then the other operations will automatically follow suit.
- """
-
- __slots__ = ()
-
- @abstractmethod
- def add(self, value):
- """Add an element."""
- raise NotImplementedError
-
- @abstractmethod
- def discard(self, value):
- """Remove an element. Do not raise an exception if absent."""
- raise NotImplementedError
-
- def remove(self, value):
- """Remove an element. If not a member, raise a KeyError."""
- if value not in self:
- raise KeyError(value)
- self.discard(value)
-
- def pop(self):
- """Return the popped value. Raise KeyError if empty."""
- it = iter(self)
- try:
- value = next(it)
- except StopIteration:
- raise KeyError
- self.discard(value)
- return value
-
- def clear(self):
- """This is slow (creates N new iterators!) but effective."""
- try:
- while True:
- self.pop()
- except KeyError:
- pass
-
- def __ior__(self, it):
- for value in it:
- self.add(value)
- return self
-
- def __iand__(self, it):
- for value in (self - it):
- self.discard(value)
- return self
-
- def __ixor__(self, it):
- if it is self:
- self.clear()
- else:
- if not isinstance(it, Set):
- it = self._from_iterable(it)
- for value in it:
- if value in self:
- self.discard(value)
- else:
- self.add(value)
- return self
-
- def __isub__(self, it):
- if it is self:
- self.clear()
- else:
- for value in it:
- self.discard(value)
- return self
-
-MutableSet.register(set)
-
-
-### MAPPINGS ###
-
-
-class Mapping(Sized, Iterable, Container):
-
- __slots__ = ()
-
- """A Mapping is a generic container for associating key/value
- pairs.
-
- This class provides concrete generic implementations of all
- methods except for __getitem__, __iter__, and __len__.
-
- """
-
- @abstractmethod
- def __getitem__(self, key):
- raise KeyError
-
- def get(self, key, default=None):
- 'D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.'
- try:
- return self[key]
- except KeyError:
- return default
-
- def __contains__(self, key):
- try:
- self[key]
- except KeyError:
- return False
- else:
- return True
-
- def keys(self):
- "D.keys() -> a set-like object providing a view on D's keys"
- return KeysView(self)
-
- def items(self):
- "D.items() -> a set-like object providing a view on D's items"
- return ItemsView(self)
-
- def values(self):
- "D.values() -> an object providing a view on D's values"
- return ValuesView(self)
-
- def __eq__(self, other):
- if not isinstance(other, Mapping):
- return NotImplemented
- return dict(self.items()) == dict(other.items())
-
- def __ne__(self, other):
- return not (self == other)
-
-Mapping.register(mappingproxy)
-
-
-class MappingView(Sized):
-
- def __init__(self, mapping):
- self._mapping = mapping
-
- def __len__(self):
- return len(self._mapping)
-
- def __repr__(self):
- return '{0.__class__.__name__}({0._mapping!r})'.format(self)
-
-
-class KeysView(MappingView, Set):
-
- @classmethod
- def _from_iterable(self, it):
- return set(it)
-
- def __contains__(self, key):
- return key in self._mapping
-
- def __iter__(self):
- for key in self._mapping:
- yield key
-
-KeysView.register(dict_keys)
-
-
-class ItemsView(MappingView, Set):
-
- @classmethod
- def _from_iterable(self, it):
- return set(it)
-
- def __contains__(self, item):
- key, value = item
- try:
- v = self._mapping[key]
- except KeyError:
- return False
- else:
- return v == value
-
- def __iter__(self):
- for key in self._mapping:
- yield (key, self._mapping[key])
-
-ItemsView.register(dict_items)
-
-
-class ValuesView(MappingView):
-
- def __contains__(self, value):
- for key in self._mapping:
- if value == self._mapping[key]:
- return True
- return False
-
- def __iter__(self):
- for key in self._mapping:
- yield self._mapping[key]
-
-ValuesView.register(dict_values)
-
-
-class MutableMapping(Mapping):
-
- __slots__ = ()
-
- """A MutableMapping is a generic container for associating
- key/value pairs.
-
- This class provides concrete generic implementations of all
- methods except for __getitem__, __setitem__, __delitem__,
- __iter__, and __len__.
-
- """
-
- @abstractmethod
- def __setitem__(self, key, value):
- raise KeyError
-
- @abstractmethod
- def __delitem__(self, key):
- raise KeyError
-
- __marker = object()
-
- def pop(self, key, default=__marker):
- '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
- If key is not found, d is returned if given, otherwise KeyError is raised.
- '''
- try:
- value = self[key]
- except KeyError:
- if default is self.__marker:
- raise
- return default
- else:
- del self[key]
- return value
-
- def popitem(self):
- '''D.popitem() -> (k, v), remove and return some (key, value) pair
- as a 2-tuple; but raise KeyError if D is empty.
- '''
- try:
- key = next(iter(self))
- except StopIteration:
- raise KeyError
- value = self[key]
- del self[key]
- return key, value
-
- def clear(self):
- 'D.clear() -> None. Remove all items from D.'
- try:
- while True:
- self.popitem()
- except KeyError:
- pass
-
- def update(*args, **kwds):
- ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.
- If E present and has a .keys() method, does: for k in E: D[k] = E[k]
- If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
- In either case, this is followed by: for k, v in F.items(): D[k] = v
- '''
- if len(args) > 2:
- raise TypeError("update() takes at most 2 positional "
- "arguments ({} given)".format(len(args)))
- elif not args:
- raise TypeError("update() takes at least 1 argument (0 given)")
- self = args[0]
- other = args[1] if len(args) >= 2 else ()
-
- if isinstance(other, Mapping):
- for key in other:
- self[key] = other[key]
- elif hasattr(other, "keys"):
- for key in other.keys():
- self[key] = other[key]
- else:
- for key, value in other:
- self[key] = value
- for key, value in kwds.items():
- self[key] = value
-
- def setdefault(self, key, default=None):
- 'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
- try:
- return self[key]
- except KeyError:
- self[key] = default
- return default
-
-MutableMapping.register(dict)
-
-
-### SEQUENCES ###
-
-
-class Sequence(Sized, Iterable, Container):
-
- """All the operations on a read-only sequence.
-
- Concrete subclasses must override __new__ or __init__,
- __getitem__, and __len__.
- """
-
- __slots__ = ()
-
- @abstractmethod
- def __getitem__(self, index):
- raise IndexError
-
- def __iter__(self):
- i = 0
- try:
- while True:
- v = self[i]
- yield v
- i += 1
- except IndexError:
- return
-
- def __contains__(self, value):
- for v in self:
- if v == value:
- return True
- return False
-
- def __reversed__(self):
- for i in reversed(range(len(self))):
- yield self[i]
-
- def index(self, value):
- '''S.index(value) -> integer -- return first index of value.
- Raises ValueError if the value is not present.
- '''
- for i, v in enumerate(self):
- if v == value:
- return i
- raise ValueError
-
- def count(self, value):
- 'S.count(value) -> integer -- return number of occurrences of value'
- return sum(1 for v in self if v == value)
-
-Sequence.register(tuple)
-Sequence.register(str)
-Sequence.register(range)
-
-
-class ByteString(Sequence):
-
- """This unifies bytes and bytearray.
-
- XXX Should add all their methods.
- """
-
- __slots__ = ()
-
-ByteString.register(bytes)
-ByteString.register(bytearray)
-
-
-class MutableSequence(Sequence):
-
- __slots__ = ()
-
- """All the operations on a read-only sequence.
-
- Concrete subclasses must provide __new__ or __init__,
- __getitem__, __setitem__, __delitem__, __len__, and insert().
-
- """
-
- @abstractmethod
- def __setitem__(self, index, value):
- raise IndexError
-
- @abstractmethod
- def __delitem__(self, index):
- raise IndexError
-
- @abstractmethod
- def insert(self, index, value):
- 'S.insert(index, value) -- insert value before index'
- raise IndexError
-
- def append(self, value):
- 'S.append(value) -- append value to the end of the sequence'
- self.insert(len(self), value)
-
- def clear(self):
- 'S.clear() -> None -- remove all items from S'
- try:
- while True:
- self.pop()
- except IndexError:
- pass
-
- def reverse(self):
- 'S.reverse() -- reverse *IN PLACE*'
- n = len(self)
- for i in range(n//2):
- self[i], self[n-i-1] = self[n-i-1], self[i]
-
- def extend(self, values):
- 'S.extend(iterable) -- extend sequence by appending elements from the iterable'
- for v in values:
- self.append(v)
-
- def pop(self, index=-1):
- '''S.pop([index]) -> item -- remove and return item at index (default last).
- Raise IndexError if list is empty or index is out of range.
- '''
- v = self[index]
- del self[index]
- return v
-
- def remove(self, value):
- '''S.remove(value) -- remove first occurrence of value.
- Raise ValueError if the value is not present.
- '''
- del self[self.index(value)]
-
- def __iadd__(self, values):
- self.extend(values)
- return self
-
-MutableSequence.register(list)
-MutableSequence.register(bytearray) # Multiply inheriting, see ByteString
+from _collections_abc import *
+from _collections_abc import __all__
diff --git a/Lib/colorsys.py b/Lib/colorsys.py
index a6c0cf6a46..b93e384406 100644
--- a/Lib/colorsys.py
+++ b/Lib/colorsys.py
@@ -33,17 +33,25 @@ TWO_THIRD = 2.0/3.0
# YIQ: used by composite video signals (linear combinations of RGB)
# Y: perceived grey level (0.0 == black, 1.0 == white)
# I, Q: color components
+#
+# There are a great many versions of the constants used in these formulae.
+# The ones in this library uses constants from the FCC version of NTSC.
def rgb_to_yiq(r, g, b):
y = 0.30*r + 0.59*g + 0.11*b
- i = 0.60*r - 0.28*g - 0.32*b
- q = 0.21*r - 0.52*g + 0.31*b
+ i = 0.74*(r-y) - 0.27*(b-y)
+ q = 0.48*(r-y) + 0.41*(b-y)
return (y, i, q)
def yiq_to_rgb(y, i, q):
- r = y + 0.948262*i + 0.624013*q
- g = y - 0.276066*i - 0.639810*q
- b = y - 1.105450*i + 1.729860*q
+ # r = y + (0.27*q + 0.41*i) / (0.74*0.41 + 0.27*0.48)
+ # b = y + (0.74*q - 0.48*i) / (0.74*0.41 + 0.27*0.48)
+ # g = y - (0.30*(r-y) + 0.11*(b-y)) / 0.59
+
+ r = y + 0.9468822170900693*i + 0.6235565819861433*q
+ g = y - 0.27478764629897834*i - 0.6356910791873801*q
+ b = y - 1.1085450346420322*i + 1.7090069284064666*q
+
if r < 0.0:
r = 0.0
if g < 0.0:
diff --git a/Lib/compileall.py b/Lib/compileall.py
index d3cff6a98a..475dc1c8a0 100644
--- a/Lib/compileall.py
+++ b/Lib/compileall.py
@@ -13,7 +13,7 @@ See module py_compile for details of the actual byte-compilation.
import os
import sys
import errno
-import imp
+import importlib.util
import py_compile
import struct
@@ -38,7 +38,7 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
print('Listing {!r}...'.format(dir))
try:
names = os.listdir(dir)
- except os.error:
+ except OSError:
print("Can't list {!r}".format(dir))
names = []
names.sort()
@@ -91,22 +91,23 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=False,
cfile = fullname + ('c' if __debug__ else 'o')
else:
if optimize >= 0:
- cfile = imp.cache_from_source(fullname,
- debug_override=not optimize)
+ cfile = importlib.util.cache_from_source(
+ fullname, debug_override=not optimize)
else:
- cfile = imp.cache_from_source(fullname)
+ cfile = importlib.util.cache_from_source(fullname)
cache_dir = os.path.dirname(cfile)
head, tail = name[:-3], name[-3:]
if tail == '.py':
if not force:
try:
mtime = int(os.stat(fullname).st_mtime)
- expect = struct.pack('<4sl', imp.get_magic(), mtime)
+ expect = struct.pack('<4sl', importlib.util.MAGIC_NUMBER,
+ mtime)
with open(cfile, 'rb') as chandle:
actual = chandle.read(8)
if expect == actual:
return success
- except IOError:
+ except OSError:
pass
if not quiet:
print('Compiling {!r}...'.format(fullname))
@@ -124,7 +125,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=False,
msg = msg.decode(sys.stdout.encoding)
print(msg)
success = 0
- except (SyntaxError, UnicodeError, IOError) as e:
+ except (SyntaxError, UnicodeError, OSError) as e:
if quiet:
print('*** Error compiling {!r}...'.format(fullname))
else:
@@ -209,7 +210,7 @@ def main():
with (sys.stdin if args.flist=='-' else open(args.flist)) as f:
for line in f:
compile_dests.append(line.strip())
- except EnvironmentError:
+ except OSError:
print("Error reading file list {}".format(args.flist))
return False
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
index ca3aebd9c7..3d0328049f 100644
--- a/Lib/concurrent/futures/_base.py
+++ b/Lib/concurrent/futures/_base.py
@@ -198,8 +198,7 @@ def as_completed(fs, timeout=None):
waiter = _create_and_install_waiters(fs, _AS_COMPLETED)
try:
- for future in finished:
- yield future
+ yield from finished
while pending:
if timeout is None:
diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py
index adf2ab4759..07b5225d1d 100644
--- a/Lib/concurrent/futures/process.py
+++ b/Lib/concurrent/futures/process.py
@@ -40,7 +40,7 @@ Local worker thread:
Process #1..n:
- reads _CallItems from "Call Q", executes the calls, and puts the resulting
- _ResultItems in "Request Q"
+ _ResultItems in "Result Q"
"""
__author__ = 'Brian Quinlan (brian@sweetapp.com)'
@@ -49,8 +49,9 @@ import atexit
import os
from concurrent.futures import _base
import queue
+from queue import Full
import multiprocessing
-from multiprocessing.queues import SimpleQueue, Full
+from multiprocessing import SimpleQueue
from multiprocessing.connection import wait
import threading
import weakref
@@ -240,6 +241,8 @@ def _queue_management_worker(executor_reference,
"terminated abruptly while the future was "
"running or pending."
))
+ # Delete references to object. See issue16284
+ del work_item
pending_work_items.clear()
# Terminate remaining workers forcibly: the queues or their
# locks may be in a dirty state and block forever.
@@ -264,6 +267,8 @@ def _queue_management_worker(executor_reference,
work_item.future.set_exception(result_item.exception)
else:
work_item.future.set_result(result_item.result)
+ # Delete references to object. See issue16284
+ del work_item
# Check whether we should start shutting down.
executor = executor_reference()
# No more work items can be added if:
@@ -327,7 +332,7 @@ class ProcessPoolExecutor(_base.Executor):
_check_system_limits()
if max_workers is None:
- self._max_workers = multiprocessing.cpu_count()
+ self._max_workers = os.cpu_count() or 1
else:
self._max_workers = max_workers
diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py
index 95bb682565..f9beb0f7f7 100644
--- a/Lib/concurrent/futures/thread.py
+++ b/Lib/concurrent/futures/thread.py
@@ -63,6 +63,8 @@ def _worker(executor_reference, work_queue):
work_item = work_queue.get(block=True)
if work_item is not None:
work_item.run()
+ # Delete references to object. See issue16284
+ del work_item
continue
executor = executor_reference()
# Exit if:
diff --git a/Lib/configparser.py b/Lib/configparser.py
index fde1c12440..794f857c3f 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -687,7 +687,7 @@ class RawConfigParser(MutableMapping):
try:
with open(filename, encoding=encoding) as fp:
self._read(fp, filename)
- except IOError:
+ except OSError:
continue
read_ok.append(filename)
return read_ok
diff --git a/Lib/contextlib.py b/Lib/contextlib.py
index f8e026b7e2..d3219f6c15 100644
--- a/Lib/contextlib.py
+++ b/Lib/contextlib.py
@@ -4,7 +4,8 @@ import sys
from collections import deque
from functools import wraps
-__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack"]
+__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack",
+ "redirect_stdout", "suppress"]
class ContextDecorator(object):
@@ -36,6 +37,16 @@ class _GeneratorContextManager(ContextDecorator):
def __init__(self, func, *args, **kwds):
self.gen = func(*args, **kwds)
self.func, self.args, self.kwds = func, args, kwds
+ # Issue 19330: ensure context manager instances have good docstrings
+ doc = getattr(func, "__doc__", None)
+ if doc is None:
+ doc = type(self).__doc__
+ self.__doc__ = doc
+ # Unfortunately, this still doesn't provide good help output when
+ # inspecting the created context manager instances, since pydoc
+ # currently bypasses the instance docstring and shows the docstring
+ # for the class instead.
+ # See http://bugs.python.org/issue19404 for more details.
def _recreate_cm(self):
# _GCM instances are one-shot context managers, so the
@@ -47,7 +58,7 @@ class _GeneratorContextManager(ContextDecorator):
try:
return next(self.gen)
except StopIteration:
- raise RuntimeError("generator didn't yield")
+ raise RuntimeError("generator didn't yield") from None
def __exit__(self, type, value, traceback):
if type is None:
@@ -140,6 +151,62 @@ class closing(object):
def __exit__(self, *exc_info):
self.thing.close()
+class redirect_stdout:
+ """Context manager for temporarily redirecting stdout to another file
+
+ # How to send help() to stderr
+ with redirect_stdout(sys.stderr):
+ help(dir)
+
+ # How to write help() to a file
+ with open('help.txt', 'w') as f:
+ with redirect_stdout(f):
+ help(pow)
+ """
+
+ def __init__(self, new_target):
+ self._new_target = new_target
+ # We use a list of old targets to make this CM re-entrant
+ self._old_targets = []
+
+ def __enter__(self):
+ self._old_targets.append(sys.stdout)
+ sys.stdout = self._new_target
+ return self._new_target
+
+ def __exit__(self, exctype, excinst, exctb):
+ sys.stdout = self._old_targets.pop()
+
+
+class suppress:
+ """Context manager to suppress specified exceptions
+
+ After the exception is suppressed, execution proceeds with the next
+ statement following the with statement.
+
+ with suppress(FileNotFoundError):
+ os.remove(somefile)
+ # Execution still resumes here if the file was already removed
+ """
+
+ def __init__(self, *exceptions):
+ self._exceptions = exceptions
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, exctype, excinst, exctb):
+ # Unlike isinstance and issubclass, CPython exception handling
+ # currently only looks at the concrete type hierarchy (ignoring
+ # the instance and subclass checking hooks). While Guido considers
+ # that a bug rather than a feature, it's a fairly hard one to fix
+ # due to various internal implementation details. suppress provides
+ # the simpler issubclass based semantics, rather than trying to
+ # exactly reproduce the limitations of the CPython interpreter.
+ #
+ # See http://bugs.python.org/issue12029 for more details
+ return exctype is not None and issubclass(exctype, self._exceptions)
+
# Inspired by discussions on http://bugs.python.org/issue13585
class ExitStack(object):
diff --git a/Lib/copyreg.py b/Lib/copyreg.py
index 66c0f8a64d..67f5bb029e 100644
--- a/Lib/copyreg.py
+++ b/Lib/copyreg.py
@@ -87,6 +87,12 @@ def _reduce_ex(self, proto):
def __newobj__(cls, *args):
return cls.__new__(cls, *args)
+def __newobj_ex__(cls, args, kwargs):
+ """Used by pickle protocol 4, instead of __newobj__ to allow classes with
+ keyword-only arguments to be pickled correctly.
+ """
+ return cls.__new__(cls, *args, **kwargs)
+
def _slotnames(cls):
"""Return a list of slot names for a given class.
diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
index c92e130976..e34c646e2d 100644
--- a/Lib/ctypes/__init__.py
+++ b/Lib/ctypes/__init__.py
@@ -34,17 +34,15 @@ from _ctypes import FUNCFLAG_CDECL as _FUNCFLAG_CDECL, \
FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \
FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR
-"""
-WINOLEAPI -> HRESULT
-WINOLEAPI_(type)
-
-STDMETHODCALLTYPE
-
-STDMETHOD(name)
-STDMETHOD_(type, name)
-
-STDAPICALLTYPE
-"""
+# WINOLEAPI -> HRESULT
+# WINOLEAPI_(type)
+#
+# STDMETHODCALLTYPE
+#
+# STDMETHOD(name)
+# STDMETHOD_(type, name)
+#
+# STDAPICALLTYPE
def create_string_buffer(init, size=None):
"""create_string_buffer(aBytes) -> character array
@@ -395,7 +393,7 @@ if _os.name in ("nt", "ce"):
_type_ = "l"
# _check_retval_ is called with the function's result when it
# is used as restype. It checks for the FAILED bit, and
- # raises a WindowsError if it is set.
+ # raises an OSError if it is set.
#
# The _check_retval_ method is implemented in C, so that the
# method definition itself is not included in the traceback
@@ -407,7 +405,7 @@ if _os.name in ("nt", "ce"):
class OleDLL(CDLL):
"""This class represents a dll exporting functions using the
Windows stdcall calling convention, and returning HRESULT.
- HRESULT error values are automatically raised as WindowsError
+ HRESULT error values are automatically raised as OSError
exceptions.
"""
_func_flags_ = _FUNCFLAG_STDCALL
@@ -456,7 +454,7 @@ if _os.name in ("nt", "ce"):
code = GetLastError()
if descr is None:
descr = FormatError(code).strip()
- return WindowsError(None, descr, None, code)
+ return OSError(None, descr, None, code)
if sizeof(c_uint) == sizeof(c_void_p):
c_size_t = c_uint
diff --git a/Lib/ctypes/test/__init__.py b/Lib/ctypes/test/__init__.py
index cc5fe02d1b..7c72210496 100644
--- a/Lib/ctypes/test/__init__.py
+++ b/Lib/ctypes/test/__init__.py
@@ -37,7 +37,7 @@ def requires(resource, msg=None):
def find_package_modules(package, mask):
import fnmatch
- if (hasattr(package, "__loader__") and
+ if (package.__loader__ is not None and
hasattr(package.__loader__, '_files')):
path = package.__name__.replace(".", os.path.sep)
mask = os.path.join(path, mask)
diff --git a/Lib/ctypes/test/test_checkretval.py b/Lib/ctypes/test/test_checkretval.py
index 01ccc57686..19bb8135b3 100644
--- a/Lib/ctypes/test/test_checkretval.py
+++ b/Lib/ctypes/test/test_checkretval.py
@@ -31,7 +31,7 @@ class Test(unittest.TestCase):
pass
else:
def test_oledll(self):
- self.assertRaises(WindowsError,
+ self.assertRaises(OSError,
oledll.oleaut32.CreateTypeLib2,
0, None, None)
diff --git a/Lib/ctypes/test/test_internals.py b/Lib/ctypes/test/test_internals.py
index cbf2e0589a..271e3f57f8 100644
--- a/Lib/ctypes/test/test_internals.py
+++ b/Lib/ctypes/test/test_internals.py
@@ -5,17 +5,14 @@ from sys import getrefcount as grc
# XXX This test must be reviewed for correctness!!!
-"""
-ctypes' types are container types.
-
-They have an internal memory block, which only consists of some bytes,
-but it has to keep references to other objects as well. This is not
-really needed for trivial C types like int or char, but it is important
-for aggregate types like strings or pointers in particular.
-
-What about pointers?
-
-"""
+# ctypes' types are container types.
+#
+# They have an internal memory block, which only consists of some bytes,
+# but it has to keep references to other objects as well. This is not
+# really needed for trivial C types like int or char, but it is important
+# for aggregate types like strings or pointers in particular.
+#
+# What about pointers?
class ObjectsTestCase(unittest.TestCase):
def assertSame(self, a, b):
diff --git a/Lib/ctypes/test/test_macholib.py b/Lib/ctypes/test/test_macholib.py
index eda846d30a..fd2683764c 100644
--- a/Lib/ctypes/test/test_macholib.py
+++ b/Lib/ctypes/test/test_macholib.py
@@ -3,35 +3,33 @@ import sys
import unittest
# Bob Ippolito:
-"""
-Ok.. the code to find the filename for __getattr__ should look
-something like:
-
-import os
-from macholib.dyld import dyld_find
-
-def find_lib(name):
- possible = ['lib'+name+'.dylib', name+'.dylib',
- name+'.framework/'+name]
- for dylib in possible:
- try:
- return os.path.realpath(dyld_find(dylib))
- except ValueError:
- pass
- raise ValueError, "%s not found" % (name,)
-
-It'll have output like this:
-
- >>> find_lib('pthread')
-'/usr/lib/libSystem.B.dylib'
- >>> find_lib('z')
-'/usr/lib/libz.1.dylib'
- >>> find_lib('IOKit')
-'/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit'
-
--bob
-
-"""
+#
+# Ok.. the code to find the filename for __getattr__ should look
+# something like:
+#
+# import os
+# from macholib.dyld import dyld_find
+#
+# def find_lib(name):
+# possible = ['lib'+name+'.dylib', name+'.dylib',
+# name+'.framework/'+name]
+# for dylib in possible:
+# try:
+# return os.path.realpath(dyld_find(dylib))
+# except ValueError:
+# pass
+# raise ValueError, "%s not found" % (name,)
+#
+# It'll have output like this:
+#
+# >>> find_lib('pthread')
+# '/usr/lib/libSystem.B.dylib'
+# >>> find_lib('z')
+# '/usr/lib/libz.1.dylib'
+# >>> find_lib('IOKit')
+# '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit'
+#
+# -bob
from ctypes.macholib.dyld import dyld_find
diff --git a/Lib/ctypes/test/test_win32.py b/Lib/ctypes/test/test_win32.py
index da21336682..91ad3149a7 100644
--- a/Lib/ctypes/test/test_win32.py
+++ b/Lib/ctypes/test/test_win32.py
@@ -41,7 +41,7 @@ if sys.platform == "win32":
# Call functions with invalid arguments, and make sure
# that access violations are trapped and raise an
# exception.
- self.assertRaises(WindowsError, windll.kernel32.GetModuleHandleA, 32)
+ self.assertRaises(OSError, windll.kernel32.GetModuleHandleA, 32)
def test_noargs(self):
# This is a special case on win32 x64
diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
index 054c51158e..0cf20766cc 100644
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -102,9 +102,8 @@ elif os.name == "posix":
finally:
try:
os.unlink(ccout)
- except OSError as e:
- if e.errno != errno.ENOENT:
- raise
+ except FileNotFoundError:
+ pass
if rv == 10:
raise OSError('gcc or cc command not found')
res = re.search(expr, trace)
@@ -133,8 +132,10 @@ elif os.name == "posix":
cmd = 'if ! type objdump >/dev/null 2>&1; then exit 10; fi;' \
"objdump -p -j .dynamic 2>/dev/null " + f
f = os.popen(cmd)
- dump = f.read()
- rv = f.close()
+ try:
+ dump = f.read()
+ finally:
+ rv = f.close()
if rv == 10:
raise OSError('objdump command not found')
res = re.search(r'\sSONAME\s+([^\s]+)', dump)
@@ -177,10 +178,11 @@ elif os.name == "posix":
else:
cmd = 'env LC_ALL=C /usr/bin/crle 2>/dev/null'
- for line in os.popen(cmd).readlines():
- line = line.strip()
- if line.startswith('Default Library Path (ELF):'):
- paths = line.split()[4]
+ with contextlib.closing(os.popen(cmd)) as f:
+ for line in f.readlines():
+ line = line.strip()
+ if line.startswith('Default Library Path (ELF):'):
+ paths = line.split()[4]
if not paths:
return None
diff --git a/Lib/datetime.py b/Lib/datetime.py
index d1f353be10..1789714034 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -23,9 +23,10 @@ _MAXORDINAL = 3652059 # date.max.toordinal()
# for all computations. See the book for algorithms for converting between
# proleptic Gregorian ordinals and many other calendar systems.
-_DAYS_IN_MONTH = [None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
+# -1 is a placeholder for indexing purposes.
+_DAYS_IN_MONTH = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-_DAYS_BEFORE_MONTH = [None]
+_DAYS_BEFORE_MONTH = [-1] # -1 is a placeholder for indexing purposes.
dbm = 0
for dim in _DAYS_IN_MONTH[1:]:
_DAYS_BEFORE_MONTH.append(dbm)
@@ -1917,203 +1918,203 @@ timezone.utc = timezone._create(timedelta(0))
timezone.min = timezone._create(timezone._minoffset)
timezone.max = timezone._create(timezone._maxoffset)
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
-"""
-Some time zone algebra. For a datetime x, let
- x.n = x stripped of its timezone -- its naive time.
- x.o = x.utcoffset(), and assuming that doesn't raise an exception or
- return None
- x.d = x.dst(), and assuming that doesn't raise an exception or
- return None
- x.s = x's standard offset, x.o - x.d
-
-Now some derived rules, where k is a duration (timedelta).
-
-1. x.o = x.s + x.d
- This follows from the definition of x.s.
-
-2. If x and y have the same tzinfo member, x.s = y.s.
- This is actually a requirement, an assumption we need to make about
- sane tzinfo classes.
-
-3. The naive UTC time corresponding to x is x.n - x.o.
- This is again a requirement for a sane tzinfo class.
-
-4. (x+k).s = x.s
- This follows from #2, and that datimetimetz+timedelta preserves tzinfo.
-
-5. (x+k).n = x.n + k
- Again follows from how arithmetic is defined.
-
-Now we can explain tz.fromutc(x). Let's assume it's an interesting case
-(meaning that the various tzinfo methods exist, and don't blow up or return
-None when called).
-
-The function wants to return a datetime y with timezone tz, equivalent to x.
-x is already in UTC.
-
-By #3, we want
-
- y.n - y.o = x.n [1]
-
-The algorithm starts by attaching tz to x.n, and calling that y. So
-x.n = y.n at the start. Then it wants to add a duration k to y, so that [1]
-becomes true; in effect, we want to solve [2] for k:
-
- (y+k).n - (y+k).o = x.n [2]
-
-By #1, this is the same as
-
- (y+k).n - ((y+k).s + (y+k).d) = x.n [3]
-
-By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start.
-Substituting that into [3],
-
- x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving
- k - (y+k).s - (y+k).d = 0; rearranging,
- k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so
- k = y.s - (y+k).d
-
-On the RHS, (y+k).d can't be computed directly, but y.s can be, and we
-approximate k by ignoring the (y+k).d term at first. Note that k can't be
-very large, since all offset-returning methods return a duration of magnitude
-less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must
-be 0, so ignoring it has no consequence then.
-
-In any case, the new value is
- z = y + y.s [4]
+# Some time zone algebra. For a datetime x, let
+# x.n = x stripped of its timezone -- its naive time.
+# x.o = x.utcoffset(), and assuming that doesn't raise an exception or
+# return None
+# x.d = x.dst(), and assuming that doesn't raise an exception or
+# return None
+# x.s = x's standard offset, x.o - x.d
+#
+# Now some derived rules, where k is a duration (timedelta).
+#
+# 1. x.o = x.s + x.d
+# This follows from the definition of x.s.
+#
+# 2. If x and y have the same tzinfo member, x.s = y.s.
+# This is actually a requirement, an assumption we need to make about
+# sane tzinfo classes.
+#
+# 3. The naive UTC time corresponding to x is x.n - x.o.
+# This is again a requirement for a sane tzinfo class.
+#
+# 4. (x+k).s = x.s
+# This follows from #2, and that datimetimetz+timedelta preserves tzinfo.
+#
+# 5. (x+k).n = x.n + k
+# Again follows from how arithmetic is defined.
+#
+# Now we can explain tz.fromutc(x). Let's assume it's an interesting case
+# (meaning that the various tzinfo methods exist, and don't blow up or return
+# None when called).
+#
+# The function wants to return a datetime y with timezone tz, equivalent to x.
+# x is already in UTC.
+#
+# By #3, we want
+#
+# y.n - y.o = x.n [1]
+#
+# The algorithm starts by attaching tz to x.n, and calling that y. So
+# x.n = y.n at the start. Then it wants to add a duration k to y, so that [1]
+# becomes true; in effect, we want to solve [2] for k:
+#
+# (y+k).n - (y+k).o = x.n [2]
+#
+# By #1, this is the same as
+#
+# (y+k).n - ((y+k).s + (y+k).d) = x.n [3]
+#
+# By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start.
+# Substituting that into [3],
+#
+# x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving
+# k - (y+k).s - (y+k).d = 0; rearranging,
+# k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so
+# k = y.s - (y+k).d
+#
+# On the RHS, (y+k).d can't be computed directly, but y.s can be, and we
+# approximate k by ignoring the (y+k).d term at first. Note that k can't be
+# very large, since all offset-returning methods return a duration of magnitude
+# less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must
+# be 0, so ignoring it has no consequence then.
+#
+# In any case, the new value is
+#
+# z = y + y.s [4]
+#
+# It's helpful to step back at look at [4] from a higher level: it's simply
+# mapping from UTC to tz's standard time.
+#
+# At this point, if
+#
+# z.n - z.o = x.n [5]
+#
+# we have an equivalent time, and are almost done. The insecurity here is
+# at the start of daylight time. Picture US Eastern for concreteness. The wall
+# time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good
+# sense then. The docs ask that an Eastern tzinfo class consider such a time to
+# be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST
+# on the day DST starts. We want to return the 1:MM EST spelling because that's
+# the only spelling that makes sense on the local wall clock.
+#
+# In fact, if [5] holds at this point, we do have the standard-time spelling,
+# but that takes a bit of proof. We first prove a stronger result. What's the
+# difference between the LHS and RHS of [5]? Let
+#
+# diff = x.n - (z.n - z.o) [6]
+#
+# Now
+# z.n = by [4]
+# (y + y.s).n = by #5
+# y.n + y.s = since y.n = x.n
+# x.n + y.s = since z and y are have the same tzinfo member,
+# y.s = z.s by #2
+# x.n + z.s
+#
+# Plugging that back into [6] gives
+#
+# diff =
+# x.n - ((x.n + z.s) - z.o) = expanding
+# x.n - x.n - z.s + z.o = cancelling
+# - z.s + z.o = by #2
+# z.d
+#
+# So diff = z.d.
+#
+# If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time
+# spelling we wanted in the endcase described above. We're done. Contrarily,
+# if z.d = 0, then we have a UTC equivalent, and are also done.
+#
+# If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to
+# add to z (in effect, z is in tz's standard time, and we need to shift the
+# local clock into tz's daylight time).
+#
+# Let
+#
+# z' = z + z.d = z + diff [7]
+#
+# and we can again ask whether
+#
+# z'.n - z'.o = x.n [8]
+#
+# If so, we're done. If not, the tzinfo class is insane, according to the
+# assumptions we've made. This also requires a bit of proof. As before, let's
+# compute the difference between the LHS and RHS of [8] (and skipping some of
+# the justifications for the kinds of substitutions we've done several times
+# already):
+#
+# diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7]
+# x.n - (z.n + diff - z'.o) = replacing diff via [6]
+# x.n - (z.n + x.n - (z.n - z.o) - z'.o) =
+# x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n
+# - z.n + z.n - z.o + z'.o = cancel z.n
+# - z.o + z'.o = #1 twice
+# -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo
+# z'.d - z.d
+#
+# So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal,
+# we've found the UTC-equivalent so are done. In fact, we stop with [7] and
+# return z', not bothering to compute z'.d.
+#
+# How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by
+# a dst() offset, and starting *from* a time already in DST (we know z.d != 0),
+# would have to change the result dst() returns: we start in DST, and moving
+# a little further into it takes us out of DST.
+#
+# There isn't a sane case where this can happen. The closest it gets is at
+# the end of DST, where there's an hour in UTC with no spelling in a hybrid
+# tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During
+# that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM
+# UTC) because the docs insist on that, but 0:MM is taken as being in daylight
+# time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local
+# clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in
+# standard time. Since that's what the local clock *does*, we want to map both
+# UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous
+# in local time, but so it goes -- it's the way the local clock works.
+#
+# When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0,
+# so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going.
+# z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8]
+# (correctly) concludes that z' is not UTC-equivalent to x.
+#
+# Because we know z.d said z was in daylight time (else [5] would have held and
+# we would have stopped then), and we know z.d != z'.d (else [8] would have held
+# and we have stopped then), and there are only 2 possible values dst() can
+# return in Eastern, it follows that z'.d must be 0 (which it is in the example,
+# but the reasoning doesn't depend on the example -- it depends on there being
+# two possible dst() outcomes, one zero and the other non-zero). Therefore
+# z' must be in standard time, and is the spelling we want in this case.
+#
+# Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is
+# concerned (because it takes z' as being in standard time rather than the
+# daylight time we intend here), but returning it gives the real-life "local
+# clock repeats an hour" behavior when mapping the "unspellable" UTC hour into
+# tz.
+#
+# When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with
+# the 1:MM standard time spelling we want.
+#
+# So how can this break? One of the assumptions must be violated. Two
+# possibilities:
+#
+# 1) [2] effectively says that y.s is invariant across all y belong to a given
+# time zone. This isn't true if, for political reasons or continental drift,
+# a region decides to change its base offset from UTC.
+#
+# 2) There may be versions of "double daylight" time where the tail end of
+# the analysis gives up a step too early. I haven't thought about that
+# enough to say.
+#
+# In any case, it's clear that the default fromutc() is strong enough to handle
+# "almost all" time zones: so long as the standard offset is invariant, it
+# doesn't matter if daylight time transition points change from year to year, or
+# if daylight time is skipped in some years; it doesn't matter how large or
+# small dst() may get within its bounds; and it doesn't even matter if some
+# perverse time zone returns a negative dst()). So a breaking case must be
+# pretty bizarre, and a tzinfo subclass can override fromutc() if it is.
-It's helpful to step back at look at [4] from a higher level: it's simply
-mapping from UTC to tz's standard time.
-
-At this point, if
-
- z.n - z.o = x.n [5]
-
-we have an equivalent time, and are almost done. The insecurity here is
-at the start of daylight time. Picture US Eastern for concreteness. The wall
-time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good
-sense then. The docs ask that an Eastern tzinfo class consider such a time to
-be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST
-on the day DST starts. We want to return the 1:MM EST spelling because that's
-the only spelling that makes sense on the local wall clock.
-
-In fact, if [5] holds at this point, we do have the standard-time spelling,
-but that takes a bit of proof. We first prove a stronger result. What's the
-difference between the LHS and RHS of [5]? Let
-
- diff = x.n - (z.n - z.o) [6]
-
-Now
- z.n = by [4]
- (y + y.s).n = by #5
- y.n + y.s = since y.n = x.n
- x.n + y.s = since z and y are have the same tzinfo member,
- y.s = z.s by #2
- x.n + z.s
-
-Plugging that back into [6] gives
-
- diff =
- x.n - ((x.n + z.s) - z.o) = expanding
- x.n - x.n - z.s + z.o = cancelling
- - z.s + z.o = by #2
- z.d
-
-So diff = z.d.
-
-If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time
-spelling we wanted in the endcase described above. We're done. Contrarily,
-if z.d = 0, then we have a UTC equivalent, and are also done.
-
-If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to
-add to z (in effect, z is in tz's standard time, and we need to shift the
-local clock into tz's daylight time).
-
-Let
-
- z' = z + z.d = z + diff [7]
-
-and we can again ask whether
-
- z'.n - z'.o = x.n [8]
-
-If so, we're done. If not, the tzinfo class is insane, according to the
-assumptions we've made. This also requires a bit of proof. As before, let's
-compute the difference between the LHS and RHS of [8] (and skipping some of
-the justifications for the kinds of substitutions we've done several times
-already):
-
- diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7]
- x.n - (z.n + diff - z'.o) = replacing diff via [6]
- x.n - (z.n + x.n - (z.n - z.o) - z'.o) =
- x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n
- - z.n + z.n - z.o + z'.o = cancel z.n
- - z.o + z'.o = #1 twice
- -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo
- z'.d - z.d
-
-So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal,
-we've found the UTC-equivalent so are done. In fact, we stop with [7] and
-return z', not bothering to compute z'.d.
-
-How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by
-a dst() offset, and starting *from* a time already in DST (we know z.d != 0),
-would have to change the result dst() returns: we start in DST, and moving
-a little further into it takes us out of DST.
-
-There isn't a sane case where this can happen. The closest it gets is at
-the end of DST, where there's an hour in UTC with no spelling in a hybrid
-tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During
-that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM
-UTC) because the docs insist on that, but 0:MM is taken as being in daylight
-time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local
-clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in
-standard time. Since that's what the local clock *does*, we want to map both
-UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous
-in local time, but so it goes -- it's the way the local clock works.
-
-When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0,
-so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going.
-z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8]
-(correctly) concludes that z' is not UTC-equivalent to x.
-
-Because we know z.d said z was in daylight time (else [5] would have held and
-we would have stopped then), and we know z.d != z'.d (else [8] would have held
-and we have stopped then), and there are only 2 possible values dst() can
-return in Eastern, it follows that z'.d must be 0 (which it is in the example,
-but the reasoning doesn't depend on the example -- it depends on there being
-two possible dst() outcomes, one zero and the other non-zero). Therefore
-z' must be in standard time, and is the spelling we want in this case.
-
-Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is
-concerned (because it takes z' as being in standard time rather than the
-daylight time we intend here), but returning it gives the real-life "local
-clock repeats an hour" behavior when mapping the "unspellable" UTC hour into
-tz.
-
-When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with
-the 1:MM standard time spelling we want.
-
-So how can this break? One of the assumptions must be violated. Two
-possibilities:
-
-1) [2] effectively says that y.s is invariant across all y belong to a given
- time zone. This isn't true if, for political reasons or continental drift,
- a region decides to change its base offset from UTC.
-
-2) There may be versions of "double daylight" time where the tail end of
- the analysis gives up a step too early. I haven't thought about that
- enough to say.
-
-In any case, it's clear that the default fromutc() is strong enough to handle
-"almost all" time zones: so long as the standard offset is invariant, it
-doesn't matter if daylight time transition points change from year to year, or
-if daylight time is skipped in some years; it doesn't matter how large or
-small dst() may get within its bounds; and it doesn't even matter if some
-perverse time zone returns a negative dst()). So a breaking case must be
-pretty bizarre, and a tzinfo subclass can override fromutc() if it is.
-"""
try:
from _datetime import *
except ImportError:
diff --git a/Lib/dbm/__init__.py b/Lib/dbm/__init__.py
index a783fde464..5f4664a7c6 100644
--- a/Lib/dbm/__init__.py
+++ b/Lib/dbm/__init__.py
@@ -42,7 +42,7 @@ _names = ['dbm.gnu', 'dbm.ndbm', 'dbm.dumb']
_defaultmod = None
_modules = {}
-error = (error, IOError)
+error = (error, OSError)
try:
from dbm import ndbm
@@ -111,12 +111,10 @@ def whichdb(filename):
try:
f = io.open(filename + ".pag", "rb")
f.close()
- # dbm linked with gdbm on OS/2 doesn't have .dir file
- if not (ndbm.library == "GNU gdbm" and sys.platform == "os2emx"):
- f = io.open(filename + ".dir", "rb")
- f.close()
+ f = io.open(filename + ".dir", "rb")
+ f.close()
return "dbm.ndbm"
- except IOError:
+ except OSError:
# some dbm emulations based on Berkeley DB generate a .db file
# some do not, but they should be caught by the bsd checks
try:
@@ -129,7 +127,7 @@ def whichdb(filename):
d = ndbm.open(filename)
d.close()
return "dbm.ndbm"
- except IOError:
+ except OSError:
pass
# Check for dumbdbm next -- this has a .dir and a .dat file
@@ -146,13 +144,13 @@ def whichdb(filename):
return "dbm.dumb"
finally:
f.close()
- except (OSError, IOError):
+ except OSError:
pass
# See if the file exists, return None if not
try:
f = io.open(filename, "rb")
- except IOError:
+ except OSError:
return None
# Read the start of the file -- the magic number
diff --git a/Lib/dbm/dumb.py b/Lib/dbm/dumb.py
index cfb912371c..ba6a20d0c9 100644
--- a/Lib/dbm/dumb.py
+++ b/Lib/dbm/dumb.py
@@ -29,7 +29,7 @@ __all__ = ["error", "open"]
_BLOCKSIZE = 512
-error = IOError
+error = OSError
class _Database(collections.MutableMapping):
@@ -67,7 +67,7 @@ class _Database(collections.MutableMapping):
# Mod by Jack: create data file if needed
try:
f = _io.open(self._datfile, 'r', encoding="Latin-1")
- except IOError:
+ except OSError:
f = _io.open(self._datfile, 'w', encoding="Latin-1")
self._chmod(self._datfile)
f.close()
@@ -78,7 +78,7 @@ class _Database(collections.MutableMapping):
self._index = {}
try:
f = _io.open(self._dirfile, 'r', encoding="Latin-1")
- except IOError:
+ except OSError:
pass
else:
for line in f:
@@ -100,12 +100,12 @@ class _Database(collections.MutableMapping):
try:
self._os.unlink(self._bakfile)
- except self._os.error:
+ except OSError:
pass
try:
self._os.rename(self._dirfile, self._bakfile)
- except self._os.error:
+ except OSError:
pass
f = self._io.open(self._dirfile, 'w', encoding="Latin-1")
@@ -236,6 +236,12 @@ class _Database(collections.MutableMapping):
if hasattr(self._os, 'chmod'):
self._os.chmod(file, self._mode)
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
def open(file, flag=None, mode=0o666):
"""Open the database file, filename, and return corresponding object.
diff --git a/Lib/decimal.py b/Lib/decimal.py
index 9f37e4fa48..7bc1c943b5 100644
--- a/Lib/decimal.py
+++ b/Lib/decimal.py
@@ -704,8 +704,7 @@ class Decimal(object):
raise TypeError("Cannot convert %r to Decimal" % value)
- # @classmethod, but @decorator is not valid Python 2.3 syntax, so
- # don't use it (see notes on Py2.3 compatibility at top of file)
+ @classmethod
def from_float(cls, f):
"""Converts a float to a decimal number, exactly.
@@ -744,7 +743,6 @@ class Decimal(object):
return result
else:
return cls(result)
- from_float = classmethod(from_float)
def _isnan(self):
"""Returns whether the number is not actually one.
diff --git a/Lib/difflib.py b/Lib/difflib.py
index fe94cc47ab..159cbe4dfd 100644
--- a/Lib/difflib.py
+++ b/Lib/difflib.py
@@ -336,20 +336,6 @@ class SequenceMatcher:
for elt in popular: # ditto; as fast for 1% deletion
del b2j[elt]
- def isbjunk(self, item):
- "Deprecated; use 'item in SequenceMatcher().bjunk'."
- warnings.warn("'SequenceMatcher().isbjunk(item)' is deprecated;\n"
- "use 'item in SMinstance.bjunk' instead.",
- DeprecationWarning, 2)
- return item in self.bjunk
-
- def isbpopular(self, item):
- "Deprecated; use 'item in SequenceMatcher().bpopular'."
- warnings.warn("'SequenceMatcher().isbpopular(item)' is deprecated;\n"
- "use 'item in SMinstance.bpopular' instead.",
- DeprecationWarning, 2)
- return item in self.bpopular
-
def find_longest_match(self, alo, ahi, blo, bhi):
"""Find longest matching block in a[alo:ahi] and b[blo:bhi].
@@ -922,8 +908,7 @@ class Differ:
else:
raise ValueError('unknown tag %r' % (tag,))
- for line in g:
- yield line
+ yield from g
def _dump(self, tag, x, lo, hi):
"""Generate comparison results for a same-tagged range."""
@@ -942,8 +927,7 @@ class Differ:
second = self._dump('+', b, blo, bhi)
for g in first, second:
- for line in g:
- yield line
+ yield from g
def _fancy_replace(self, a, alo, ahi, b, blo, bhi):
r"""
@@ -997,8 +981,7 @@ class Differ:
# no non-identical "pretty close" pair
if eqi is None:
# no identical pair either -- treat it as a straight replace
- for line in self._plain_replace(a, alo, ahi, b, blo, bhi):
- yield line
+ yield from self._plain_replace(a, alo, ahi, b, blo, bhi)
return
# no close pair, but an identical pair -- synch up on that
best_i, best_j, best_ratio = eqi, eqj, 1.0
@@ -1010,8 +993,7 @@ class Differ:
# identical
# pump out diffs from before the synch point
- for line in self._fancy_helper(a, alo, best_i, b, blo, best_j):
- yield line
+ yield from self._fancy_helper(a, alo, best_i, b, blo, best_j)
# do intraline marking on the synch pair
aelt, belt = a[best_i], b[best_j]
@@ -1033,15 +1015,13 @@ class Differ:
btags += ' ' * lb
else:
raise ValueError('unknown tag %r' % (tag,))
- for line in self._qformat(aelt, belt, atags, btags):
- yield line
+ yield from self._qformat(aelt, belt, atags, btags)
else:
# the synch pair is identical
yield ' ' + aelt
# pump out diffs from after the synch point
- for line in self._fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi):
- yield line
+ yield from self._fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi)
def _fancy_helper(self, a, alo, ahi, b, blo, bhi):
g = []
@@ -1053,8 +1033,7 @@ class Differ:
elif blo < bhi:
g = self._dump('+', b, blo, bhi)
- for line in g:
- yield line
+ yield from g
def _qformat(self, aline, bline, atags, btags):
r"""
diff --git a/Lib/dis.py b/Lib/dis.py
index 543fdc7ed0..81cbe7f4f9 100644
--- a/Lib/dis.py
+++ b/Lib/dis.py
@@ -2,12 +2,15 @@
import sys
import types
+import collections
+import io
from opcode import *
from opcode import __all__ as _opcodes_all
__all__ = ["code_info", "dis", "disassemble", "distb", "disco",
- "findlinestarts", "findlabels", "show_code"] + _opcodes_all
+ "findlinestarts", "findlabels", "show_code",
+ "get_instructions", "Instruction", "Bytecode"] + _opcodes_all
del _opcodes_all
_have_code = (types.MethodType, types.FunctionType, types.CodeType, type)
@@ -25,14 +28,14 @@ def _try_compile(source, name):
c = compile(source, name, 'exec')
return c
-def dis(x=None):
+def dis(x=None, *, file=None):
"""Disassemble classes, methods, functions, or code.
With no argument, disassemble the last traceback.
"""
if x is None:
- distb()
+ distb(file=file)
return
if hasattr(x, '__func__'): # Method
x = x.__func__
@@ -42,23 +45,23 @@ def dis(x=None):
items = sorted(x.__dict__.items())
for name, x1 in items:
if isinstance(x1, _have_code):
- print("Disassembly of %s:" % name)
+ print("Disassembly of %s:" % name, file=file)
try:
- dis(x1)
+ dis(x1, file=file)
except TypeError as msg:
- print("Sorry:", msg)
- print()
+ print("Sorry:", msg, file=file)
+ print(file=file)
elif hasattr(x, 'co_code'): # Code object
- disassemble(x)
+ disassemble(x, file=file)
elif isinstance(x, (bytes, bytearray)): # Raw bytecode
- _disassemble_bytes(x)
+ _disassemble_bytes(x, file=file)
elif isinstance(x, str): # Source code
- _disassemble_str(x)
+ _disassemble_str(x, file=file)
else:
raise TypeError("don't know how to disassemble %s objects" %
type(x).__name__)
-def distb(tb=None):
+def distb(tb=None, *, file=None):
"""Disassemble a traceback (default: last traceback)."""
if tb is None:
try:
@@ -66,7 +69,7 @@ def distb(tb=None):
except AttributeError:
raise RuntimeError("no last traceback to disassemble")
while tb.tb_next: tb = tb.tb_next
- disassemble(tb.tb_frame.f_code, tb.tb_lasti)
+ disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file)
# The inspect module interrogates this dictionary to build its
# list of CO_* constants. It is also used by pretty_flags to
@@ -95,19 +98,22 @@ def pretty_flags(flags):
names.append(hex(flags))
return ", ".join(names)
-def code_info(x):
- """Formatted details of methods, functions, or code."""
+def _get_code_object(x):
+ """Helper to handle methods, functions, strings and raw code objects"""
if hasattr(x, '__func__'): # Method
x = x.__func__
if hasattr(x, '__code__'): # Function
x = x.__code__
if isinstance(x, str): # Source code
- x = _try_compile(x, "<code_info>")
+ x = _try_compile(x, "<disassembly>")
if hasattr(x, 'co_code'): # Code object
- return _format_code_info(x)
- else:
- raise TypeError("don't know how to disassemble %s objects" %
- type(x).__name__)
+ return x
+ raise TypeError("don't know how to disassemble %s objects" %
+ type(x).__name__)
+
+def code_info(x):
+ """Formatted details of methods, functions, or code."""
+ return _format_code_info(_get_code_object(x))
def _format_code_info(co):
lines = []
@@ -140,106 +146,206 @@ def _format_code_info(co):
lines.append("%4d: %s" % i_n)
return "\n".join(lines)
-def show_code(co):
- """Print details of methods, functions, or code to stdout."""
- print(code_info(co))
+def show_code(co, *, file=None):
+ """Print details of methods, functions, or code to *file*.
-def disassemble(co, lasti=-1):
- """Disassemble a code object."""
- code = co.co_code
- labels = findlabels(code)
+ If *file* is not provided, the output is printed on stdout.
+ """
+ print(code_info(co), file=file)
+
+_Instruction = collections.namedtuple("_Instruction",
+ "opname opcode arg argval argrepr offset starts_line is_jump_target")
+
+class Instruction(_Instruction):
+ """Details for a bytecode operation
+
+ Defined fields:
+ opname - human readable name for operation
+ opcode - numeric code for operation
+ arg - numeric argument to operation (if any), otherwise None
+ argval - resolved arg value (if known), otherwise same as arg
+ argrepr - human readable description of operation argument
+ offset - start index of operation within bytecode sequence
+ starts_line - line started by this opcode (if any), otherwise None
+ is_jump_target - True if other code jumps to here, otherwise False
+ """
+
+ def _disassemble(self, lineno_width=3, mark_as_current=False):
+ """Format instruction details for inclusion in disassembly output
+
+ *lineno_width* sets the width of the line number field (0 omits it)
+ *mark_as_current* inserts a '-->' marker arrow as part of the line
+ """
+ fields = []
+ # Column: Source code line number
+ if lineno_width:
+ if self.starts_line is not None:
+ lineno_fmt = "%%%dd" % lineno_width
+ fields.append(lineno_fmt % self.starts_line)
+ else:
+ fields.append(' ' * lineno_width)
+ # Column: Current instruction indicator
+ if mark_as_current:
+ fields.append('-->')
+ else:
+ fields.append(' ')
+ # Column: Jump target marker
+ if self.is_jump_target:
+ fields.append('>>')
+ else:
+ fields.append(' ')
+ # Column: Instruction offset from start of code sequence
+ fields.append(repr(self.offset).rjust(4))
+ # Column: Opcode name
+ fields.append(self.opname.ljust(20))
+ # Column: Opcode argument
+ if self.arg is not None:
+ fields.append(repr(self.arg).rjust(5))
+ # Column: Opcode argument details
+ if self.argrepr:
+ fields.append('(' + self.argrepr + ')')
+ return ' '.join(fields).rstrip()
+
+
+def get_instructions(x, *, first_line=None):
+ """Iterator for the opcodes in methods, functions or code
+
+ Generates a series of Instruction named tuples giving the details of
+ each operations in the supplied code.
+
+ If *first_line* is not None, it indicates the line number that should
+ be reported for the first source line in the disassembled code.
+ Otherwise, the source line information (if any) is taken directly from
+ the disassembled code object.
+ """
+ co = _get_code_object(x)
+ cell_names = co.co_cellvars + co.co_freevars
linestarts = dict(findlinestarts(co))
- n = len(code)
- i = 0
+ if first_line is not None:
+ line_offset = first_line - co.co_firstlineno
+ else:
+ line_offset = 0
+ return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names,
+ co.co_consts, cell_names, linestarts,
+ line_offset)
+
+def _get_const_info(const_index, const_list):
+ """Helper to get optional details about const references
+
+ Returns the dereferenced constant and its repr if the constant
+ list is defined.
+ Otherwise returns the constant index and its repr().
+ """
+ argval = const_index
+ if const_list is not None:
+ argval = const_list[const_index]
+ return argval, repr(argval)
+
+def _get_name_info(name_index, name_list):
+ """Helper to get optional details about named references
+
+ Returns the dereferenced name as both value and repr if the name
+ list is defined.
+ Otherwise returns the name index and its repr().
+ """
+ argval = name_index
+ if name_list is not None:
+ argval = name_list[name_index]
+ argrepr = argval
+ else:
+ argrepr = repr(argval)
+ return argval, argrepr
+
+
+def _get_instructions_bytes(code, varnames=None, names=None, constants=None,
+ cells=None, linestarts=None, line_offset=0):
+ """Iterate over the instructions in a bytecode string.
+
+ Generates a sequence of Instruction namedtuples giving the details of each
+ opcode. Additional information about the code's runtime environment
+ (e.g. variable names, constants) can be specified using optional
+ arguments.
+
+ """
+ labels = findlabels(code)
extended_arg = 0
+ starts_line = None
free = None
+ # enumerate() is not an option, since we sometimes process
+ # multiple elements on a single pass through the loop
+ n = len(code)
+ i = 0
while i < n:
op = code[i]
- if i in linestarts:
- if i > 0:
- print()
- print("%3d" % linestarts[i], end=' ')
- else:
- print(' ', end=' ')
-
- if i == lasti: print('-->', end=' ')
- else: print(' ', end=' ')
- if i in labels: print('>>', end=' ')
- else: print(' ', end=' ')
- print(repr(i).rjust(4), end=' ')
- print(opname[op].ljust(20), end=' ')
+ offset = i
+ if linestarts is not None:
+ starts_line = linestarts.get(i, None)
+ if starts_line is not None:
+ starts_line += line_offset
+ is_jump_target = i in labels
i = i+1
+ arg = None
+ argval = None
+ argrepr = ''
if op >= HAVE_ARGUMENT:
- oparg = code[i] + code[i+1]*256 + extended_arg
+ arg = code[i] + code[i+1]*256 + extended_arg
extended_arg = 0
i = i+2
if op == EXTENDED_ARG:
- extended_arg = oparg*65536
- print(repr(oparg).rjust(5), end=' ')
+ extended_arg = arg*65536
+ # Set argval to the dereferenced value of the argument when
+ # availabe, and argrepr to the string representation of argval.
+ # _disassemble_bytes needs the string repr of the
+ # raw name index for LOAD_GLOBAL, LOAD_CONST, etc.
+ argval = arg
if op in hasconst:
- print('(' + repr(co.co_consts[oparg]) + ')', end=' ')
+ argval, argrepr = _get_const_info(arg, constants)
elif op in hasname:
- print('(' + co.co_names[oparg] + ')', end=' ')
+ argval, argrepr = _get_name_info(arg, names)
elif op in hasjrel:
- print('(to ' + repr(i + oparg) + ')', end=' ')
+ argval = i + arg
+ argrepr = "to " + repr(argval)
elif op in haslocal:
- print('(' + co.co_varnames[oparg] + ')', end=' ')
+ argval, argrepr = _get_name_info(arg, varnames)
elif op in hascompare:
- print('(' + cmp_op[oparg] + ')', end=' ')
+ argval = cmp_op[arg]
+ argrepr = argval
elif op in hasfree:
- if free is None:
- free = co.co_cellvars + co.co_freevars
- print('(' + free[oparg] + ')', end=' ')
+ argval, argrepr = _get_name_info(arg, cells)
elif op in hasnargs:
- print('(%d positional, %d keyword pair)'
- % (code[i-2], code[i-1]), end=' ')
- print()
+ argrepr = "%d positional, %d keyword pair" % (code[i-2], code[i-1])
+ yield Instruction(opname[op], op,
+ arg, argval, argrepr,
+ offset, starts_line, is_jump_target)
+
+def disassemble(co, lasti=-1, *, file=None):
+ """Disassemble a code object."""
+ cell_names = co.co_cellvars + co.co_freevars
+ linestarts = dict(findlinestarts(co))
+ _disassemble_bytes(co.co_code, lasti, co.co_varnames, co.co_names,
+ co.co_consts, cell_names, linestarts, file=file)
def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
- constants=None):
- labels = findlabels(code)
- n = len(code)
- i = 0
- while i < n:
- op = code[i]
- if i == lasti: print('-->', end=' ')
- else: print(' ', end=' ')
- if i in labels: print('>>', end=' ')
- else: print(' ', end=' ')
- print(repr(i).rjust(4), end=' ')
- print(opname[op].ljust(15), end=' ')
- i = i+1
- if op >= HAVE_ARGUMENT:
- oparg = code[i] + code[i+1]*256
- i = i+2
- print(repr(oparg).rjust(5), end=' ')
- if op in hasconst:
- if constants:
- print('(' + repr(constants[oparg]) + ')', end=' ')
- else:
- print('(%d)'%oparg, end=' ')
- elif op in hasname:
- if names is not None:
- print('(' + names[oparg] + ')', end=' ')
- else:
- print('(%d)'%oparg, end=' ')
- elif op in hasjrel:
- print('(to ' + repr(i + oparg) + ')', end=' ')
- elif op in haslocal:
- if varnames:
- print('(' + varnames[oparg] + ')', end=' ')
- else:
- print('(%d)' % oparg, end=' ')
- elif op in hascompare:
- print('(' + cmp_op[oparg] + ')', end=' ')
- elif op in hasnargs:
- print('(%d positional, %d keyword pair)'
- % (code[i-2], code[i-1]), end=' ')
- print()
+ constants=None, cells=None, linestarts=None,
+ *, file=None, line_offset=0):
+ # Omit the line number column entirely if we have no line number info
+ show_lineno = linestarts is not None
+ # TODO?: Adjust width upwards if max(linestarts.values()) >= 1000?
+ lineno_width = 3 if show_lineno else 0
+ for instr in _get_instructions_bytes(code, varnames, names,
+ constants, cells, linestarts,
+ line_offset=line_offset):
+ new_source_line = (show_lineno and
+ instr.starts_line is not None and
+ instr.offset > 0)
+ if new_source_line:
+ print(file=file)
+ is_current_instr = instr.offset == lasti
+ print(instr._disassemble(lineno_width, is_current_instr), file=file)
-def _disassemble_str(source):
+def _disassemble_str(source, *, file=None):
"""Compile the source string, then disassemble the code object."""
- disassemble(_try_compile(source, '<dis>'))
+ disassemble(_try_compile(source, '<dis>'), file=file)
disco = disassemble # XXX For backwards compatibility
@@ -250,19 +356,21 @@ def findlabels(code):
"""
labels = []
+ # enumerate() is not an option, since we sometimes process
+ # multiple elements on a single pass through the loop
n = len(code)
i = 0
while i < n:
op = code[i]
i = i+1
if op >= HAVE_ARGUMENT:
- oparg = code[i] + code[i+1]*256
+ arg = code[i] + code[i+1]*256
i = i+2
label = -1
if op in hasjrel:
- label = i+oparg
+ label = i+arg
elif op in hasjabs:
- label = oparg
+ label = arg
if label >= 0:
if label not in labels:
labels.append(label)
@@ -290,27 +398,77 @@ def findlinestarts(code):
if lineno != lastlineno:
yield (addr, lineno)
+class Bytecode:
+ """The bytecode operations of a piece of code
+
+ Instantiate this with a function, method, string of code, or a code object
+ (as returned by compile()).
+
+ Iterating over this yields the bytecode operations as Instruction instances.
+ """
+ def __init__(self, x, *, first_line=None, current_offset=None):
+ self.codeobj = co = _get_code_object(x)
+ if first_line is None:
+ self.first_line = co.co_firstlineno
+ self._line_offset = 0
+ else:
+ self.first_line = first_line
+ self._line_offset = first_line - co.co_firstlineno
+ self._cell_names = co.co_cellvars + co.co_freevars
+ self._linestarts = dict(findlinestarts(co))
+ self._original_object = x
+ self.current_offset = current_offset
+
+ def __iter__(self):
+ co = self.codeobj
+ return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names,
+ co.co_consts, self._cell_names,
+ self._linestarts,
+ line_offset=self._line_offset)
+
+ def __repr__(self):
+ return "{}({!r})".format(self.__class__.__name__,
+ self._original_object)
+
+ @classmethod
+ def from_traceback(cls, tb):
+ """ Construct a Bytecode from the given traceback """
+ while tb.tb_next:
+ tb = tb.tb_next
+ return cls(tb.tb_frame.f_code, current_offset=tb.tb_lasti)
+
+ def info(self):
+ """Return formatted information about the code object."""
+ return _format_code_info(self.codeobj)
+
+ def dis(self):
+ """Return a formatted view of the bytecode operations."""
+ co = self.codeobj
+ if self.current_offset is not None:
+ offset = self.current_offset
+ else:
+ offset = -1
+ with io.StringIO() as output:
+ _disassemble_bytes(co.co_code, varnames=co.co_varnames,
+ names=co.co_names, constants=co.co_consts,
+ cells=self._cell_names,
+ linestarts=self._linestarts,
+ line_offset=self._line_offset,
+ file=output,
+ lasti=offset)
+ return output.getvalue()
+
+
def _test():
"""Simple test program to disassemble a file."""
- if sys.argv[1:]:
- if sys.argv[2:]:
- sys.stderr.write("usage: python dis.py [-|file]\n")
- sys.exit(2)
- fn = sys.argv[1]
- if not fn or fn == "-":
- fn = None
- else:
- fn = None
- if fn is None:
- f = sys.stdin
- else:
- f = open(fn)
- source = f.read()
- if fn is not None:
- f.close()
- else:
- fn = "<stdin>"
- code = compile(source, fn, "exec")
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('infile', type=argparse.FileType(), nargs='?', default='-')
+ args = parser.parse_args()
+ with args.infile as infile:
+ source = infile.read()
+ code = compile(source, args.infile.name, "exec")
dis(code)
if __name__ == "__main__":
diff --git a/Lib/distutils/__init__.py b/Lib/distutils/__init__.py
index 6747ab8677..38a50869ea 100644
--- a/Lib/distutils/__init__.py
+++ b/Lib/distutils/__init__.py
@@ -13,5 +13,5 @@ used from a setup script as
# Updated automatically by the Python release process.
#
#--start constants--
-__version__ = "3.3.3"
+__version__ = "3.4.0b1"
#--end constants--
diff --git a/Lib/distutils/archive_util.py b/Lib/distutils/archive_util.py
index fcda08e20a..4470bb0222 100644
--- a/Lib/distutils/archive_util.py
+++ b/Lib/distutils/archive_util.py
@@ -18,15 +18,55 @@ from distutils.spawn import spawn
from distutils.dir_util import mkpath
from distutils import log
-def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0):
+try:
+ from pwd import getpwnam
+except ImportError:
+ getpwnam = None
+
+try:
+ from grp import getgrnam
+except ImportError:
+ getgrnam = None
+
+def _get_gid(name):
+ """Returns a gid, given a group name."""
+ if getgrnam is None or name is None:
+ return None
+ try:
+ result = getgrnam(name)
+ except KeyError:
+ result = None
+ if result is not None:
+ return result[2]
+ return None
+
+def _get_uid(name):
+ """Returns an uid, given a user name."""
+ if getpwnam is None or name is None:
+ return None
+ try:
+ result = getpwnam(name)
+ except KeyError:
+ result = None
+ if result is not None:
+ return result[2]
+ return None
+
+def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
+ owner=None, group=None):
"""Create a (possibly compressed) tar file from all the files under
'base_dir'.
'compress' must be "gzip" (the default), "compress", "bzip2", or None.
- Both "tar" and the compression utility named by 'compress' must be on
- the default program search path, so this is probably Unix-specific.
+ (compress will be deprecated in Python 3.2)
+
+ 'owner' and 'group' can be used to define an owner and a group for the
+ archive that is being built. If not provided, the current owner and group
+ will be used.
+
The output tar file will be named 'base_dir' + ".tar", possibly plus
the appropriate compression extension (".gz", ".bz2" or ".Z").
+
Returns the output filename.
"""
tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
@@ -48,10 +88,23 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0):
import tarfile # late import so Python build itself doesn't break
log.info('Creating tar archive')
+
+ uid = _get_uid(owner)
+ gid = _get_gid(group)
+
+ def _set_uid_gid(tarinfo):
+ if gid is not None:
+ tarinfo.gid = gid
+ tarinfo.gname = group
+ if uid is not None:
+ tarinfo.uid = uid
+ tarinfo.uname = owner
+ return tarinfo
+
if not dry_run:
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
try:
- tar.add(base_dir)
+ tar.add(base_dir, filter=_set_uid_gid)
finally:
tar.close()
@@ -140,7 +193,7 @@ def check_archive_formats(formats):
return None
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
- dry_run=0):
+ dry_run=0, owner=None, group=None):
"""Create an archive file (eg. zip or tar).
'base_name' is the name of the file to create, minus any format-specific
@@ -153,6 +206,9 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
ie. 'base_dir' will be the common prefix of all files and
directories in the archive. 'root_dir' and 'base_dir' both default
to the current directory. Returns the name of the archive file.
+
+ 'owner' and 'group' are used when creating a tar archive. By default,
+ uses the current owner and group.
"""
save_cwd = os.getcwd()
if root_dir is not None:
@@ -174,6 +230,11 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
func = format_info[0]
for arg, val in format_info[1]:
kwargs[arg] = val
+
+ if format != 'zip':
+ kwargs['owner'] = owner
+ kwargs['group'] = group
+
try:
filename = func(base_name, base_dir, **kwargs)
finally:
diff --git a/Lib/distutils/ccompiler.py b/Lib/distutils/ccompiler.py
index c795c958fe..911e84dd3b 100644
--- a/Lib/distutils/ccompiler.py
+++ b/Lib/distutils/ccompiler.py
@@ -351,7 +351,7 @@ class CCompiler:
return macros, objects, extra, pp_opts, build
def _get_cc_args(self, pp_opts, debug, before):
- # works for unixccompiler, emxccompiler, cygwinccompiler
+ # works for unixccompiler, cygwinccompiler
cc_args = pp_opts + ['-c']
if debug:
cc_args[:0] = ['-g']
@@ -926,7 +926,6 @@ _default_compilers = (
# on a cygwin built python we can use gcc like an ordinary UNIXish
# compiler
('cygwin.*', 'unix'),
- ('os2emx', 'emx'),
# OS name mappings
('posix', 'unix'),
@@ -968,8 +967,6 @@ compiler_class = { 'unix': ('unixccompiler', 'UnixCCompiler',
"Mingw32 port of GNU C Compiler for Win32"),
'bcpp': ('bcppcompiler', 'BCPPCompiler',
"Borland C++ Compiler"),
- 'emx': ('emxccompiler', 'EMXCCompiler',
- "EMX port of GNU C Compiler for OS/2"),
}
def show_compilers():
diff --git a/Lib/distutils/cmd.py b/Lib/distutils/cmd.py
index 3ea08101ac..c89d5efc45 100644
--- a/Lib/distutils/cmd.py
+++ b/Lib/distutils/cmd.py
@@ -365,9 +365,11 @@ class Command:
from distutils.spawn import spawn
spawn(cmd, search_path, dry_run=self.dry_run)
- def make_archive(self, base_name, format, root_dir=None, base_dir=None):
+ def make_archive(self, base_name, format, root_dir=None, base_dir=None,
+ owner=None, group=None):
return archive_util.make_archive(base_name, format, root_dir, base_dir,
- dry_run=self.dry_run)
+ dry_run=self.dry_run,
+ owner=owner, group=group)
def make_file(self, infiles, outfile, func, args,
exec_msg=None, skip_msg=None, level=1):
diff --git a/Lib/distutils/command/bdist.py b/Lib/distutils/command/bdist.py
index c5188eb371..6814a1c382 100644
--- a/Lib/distutils/command/bdist.py
+++ b/Lib/distutils/command/bdist.py
@@ -37,6 +37,12 @@ class bdist(Command):
"[default: dist]"),
('skip-build', None,
"skip rebuilding everything (for testing/debugging)"),
+ ('owner=', 'u',
+ "Owner name used when creating a tar file"
+ " [default: current user]"),
+ ('group=', 'g',
+ "Group name used when creating a tar file"
+ " [default: current group]"),
]
boolean_options = ['skip-build']
@@ -52,8 +58,7 @@ class bdist(Command):
# This won't do in reality: will need to distinguish RPM-ish Linux,
# Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS.
default_format = {'posix': 'gztar',
- 'nt': 'zip',
- 'os2': 'zip'}
+ 'nt': 'zip'}
# Establish the preferred order (for the --help-formats option).
format_commands = ['rpm', 'gztar', 'bztar', 'ztar', 'tar',
@@ -78,6 +83,8 @@ class bdist(Command):
self.formats = None
self.dist_dir = None
self.skip_build = 0
+ self.group = None
+ self.owner = None
def finalize_options(self):
# have to finalize 'plat_name' before 'bdist_base'
@@ -123,6 +130,11 @@ class bdist(Command):
if cmd_name not in self.no_format_option:
sub_cmd.format = self.formats[i]
+ # passing the owner and group names for tar archiving
+ if cmd_name == 'bdist_dumb':
+ sub_cmd.owner = self.owner
+ sub_cmd.group = self.group
+
# If we're going to need to run this command again, tell it to
# keep its temporary files around so subsequent runs go faster.
if cmd_name in commands[i+1:]:
diff --git a/Lib/distutils/command/bdist_dumb.py b/Lib/distutils/command/bdist_dumb.py
index 1ab09d1616..4405d12c05 100644
--- a/Lib/distutils/command/bdist_dumb.py
+++ b/Lib/distutils/command/bdist_dumb.py
@@ -33,13 +33,18 @@ class bdist_dumb(Command):
('relative', None,
"build the archive using relative paths"
"(default: false)"),
+ ('owner=', 'u',
+ "Owner name used when creating a tar file"
+ " [default: current user]"),
+ ('group=', 'g',
+ "Group name used when creating a tar file"
+ " [default: current group]"),
]
boolean_options = ['keep-temp', 'skip-build', 'relative']
default_format = { 'posix': 'gztar',
- 'nt': 'zip',
- 'os2': 'zip' }
+ 'nt': 'zip' }
def initialize_options(self):
self.bdist_dir = None
@@ -49,6 +54,8 @@ class bdist_dumb(Command):
self.dist_dir = None
self.skip_build = None
self.relative = 0
+ self.owner = None
+ self.group = None
def finalize_options(self):
if self.bdist_dir is None:
@@ -85,11 +92,6 @@ class bdist_dumb(Command):
archive_basename = "%s.%s" % (self.distribution.get_fullname(),
self.plat_name)
- # OS/2 objects to any ":" characters in a filename (such as when
- # a timestamp is used in a version) so change them to hyphens.
- if os.name == "os2":
- archive_basename = archive_basename.replace(":", "-")
-
pseudoinstall_root = os.path.join(self.dist_dir, archive_basename)
if not self.relative:
archive_root = self.bdist_dir
@@ -107,7 +109,8 @@ class bdist_dumb(Command):
# Make the archive
filename = self.make_archive(pseudoinstall_root,
- self.format, root_dir=archive_root)
+ self.format, root_dir=archive_root,
+ owner=self.owner, group=self.group)
if self.distribution.has_ext_modules():
pyversion = get_python_version()
else:
diff --git a/Lib/distutils/command/build_ext.py b/Lib/distutils/command/build_ext.py
index bc6a23f1b6..80689b6357 100644
--- a/Lib/distutils/command/build_ext.py
+++ b/Lib/distutils/command/build_ext.py
@@ -230,11 +230,6 @@ class build_ext(Command):
self.library_dirs.append(os.path.join(sys.exec_prefix,
'PC', 'VC6'))
- # OS/2 (EMX) doesn't support Debug vs Release builds, but has the
- # import libraries in its "Config" subdirectory
- if os.name == 'os2':
- self.library_dirs.append(os.path.join(sys.exec_prefix, 'Config'))
-
# for extensions under Cygwin and AtheOS Python's library directory must be
# appended to library_dirs
if sys.platform[:6] == 'cygwin' or sys.platform[:6] == 'atheos':
@@ -619,9 +614,6 @@ class build_ext(Command):
return fn
else:
return "swig.exe"
- elif os.name == "os2":
- # assume swig available in the PATH.
- return "swig.exe"
else:
raise DistutilsPlatformError(
"I don't know how to find (much less run) SWIG "
@@ -672,9 +664,6 @@ class build_ext(Command):
"""
from distutils.sysconfig import get_config_var
ext_path = ext_name.split('.')
- # OS/2 has an 8 character module (extension) limit :-(
- if os.name == "os2":
- ext_path[len(ext_path) - 1] = ext_path[len(ext_path) - 1][:8]
# extensions in debug_mode are named 'module_d.pyd' under windows
ext_suffix = get_config_var('EXT_SUFFIX')
if os.name == 'nt' and self.debug:
@@ -695,7 +684,7 @@ class build_ext(Command):
def get_libraries(self, ext):
"""Return the list of libraries to link against when building a
shared extension. On most platforms, this is just 'ext.libraries';
- on Windows and OS/2, we add the Python library (eg. python20.dll).
+ on Windows, we add the Python library (eg. python20.dll).
"""
# The python library is always needed on Windows. For MSVC, this
# is redundant, since the library is mentioned in a pragma in
@@ -715,19 +704,6 @@ class build_ext(Command):
return ext.libraries + [pythonlib]
else:
return ext.libraries
- elif sys.platform == "os2emx":
- # EMX/GCC requires the python library explicitly, and I
- # believe VACPP does as well (though not confirmed) - AIM Apr01
- template = "python%d%d"
- # debug versions of the main DLL aren't supported, at least
- # not at this time - AIM Apr01
- #if self.debug:
- # template = template + '_d'
- pythonlib = (template %
- (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff))
- # don't extend ext.libraries, it may be shared with other
- # extensions, it is a reference to the original list
- return ext.libraries + [pythonlib]
elif sys.platform[:6] == "cygwin":
template = "python%d.%d"
pythonlib = (template %
diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py
index d48eb69900..9100b96a2a 100644
--- a/Lib/distutils/command/build_py.py
+++ b/Lib/distutils/command/build_py.py
@@ -3,7 +3,7 @@
Implements the Distutils 'build_py' command."""
import os
-import imp
+import importlib.util
import sys
from glob import glob
@@ -313,11 +313,11 @@ class build_py (Command):
outputs.append(filename)
if include_bytecode:
if self.compile:
- outputs.append(imp.cache_from_source(filename,
- debug_override=True))
+ outputs.append(importlib.util.cache_from_source(
+ filename, debug_override=True))
if self.optimize > 0:
- outputs.append(imp.cache_from_source(filename,
- debug_override=False))
+ outputs.append(importlib.util.cache_from_source(
+ filename, debug_override=False))
outputs += [
os.path.join(build_dir, filename)
diff --git a/Lib/distutils/command/build_scripts.py b/Lib/distutils/command/build_scripts.py
index 4b5b22ec20..90a8380a04 100644
--- a/Lib/distutils/command/build_scripts.py
+++ b/Lib/distutils/command/build_scripts.py
@@ -74,7 +74,7 @@ class build_scripts(Command):
# script.
try:
f = open(script, "rb")
- except IOError:
+ except OSError:
if not self.dry_run:
raise
f = None
diff --git a/Lib/distutils/command/install.py b/Lib/distutils/command/install.py
index 3c675d1182..456511cdfb 100644
--- a/Lib/distutils/command/install.py
+++ b/Lib/distutils/command/install.py
@@ -58,13 +58,6 @@ INSTALL_SCHEMES = {
'data' : '$base',
},
'nt': WINDOWS_SCHEME,
- 'os2': {
- 'purelib': '$base/Lib/site-packages',
- 'platlib': '$base/Lib/site-packages',
- 'headers': '$base/Include/$dist_name',
- 'scripts': '$base/Scripts',
- 'data' : '$base',
- },
}
# user site schemes
@@ -86,14 +79,6 @@ if HAS_USER_SITE:
'data' : '$userbase',
}
- INSTALL_SCHEMES['os2_home'] = {
- 'purelib': '$usersite',
- 'platlib': '$usersite',
- 'headers': '$userbase/include/python$py_version_short/$dist_name',
- 'scripts': '$userbase/bin',
- 'data' : '$userbase',
- }
-
# The keys to an installation scheme; if any new types of files are to be
# installed, be sure to add an entry to every installation scheme above,
# and to SCHEME_KEYS here.
diff --git a/Lib/distutils/command/install_lib.py b/Lib/distutils/command/install_lib.py
index 15c08f1249..215813ba97 100644
--- a/Lib/distutils/command/install_lib.py
+++ b/Lib/distutils/command/install_lib.py
@@ -4,7 +4,7 @@ Implements the Distutils 'install_lib' command
(install all Python modules)."""
import os
-import imp
+import importlib.util
import sys
from distutils.core import Command
@@ -165,10 +165,10 @@ class install_lib(Command):
if ext != PYTHON_SOURCE_EXTENSION:
continue
if self.compile:
- bytecode_files.append(imp.cache_from_source(
+ bytecode_files.append(importlib.util.cache_from_source(
py_file, debug_override=True))
if self.optimize > 0:
- bytecode_files.append(imp.cache_from_source(
+ bytecode_files.append(importlib.util.cache_from_source(
py_file, debug_override=False))
return bytecode_files
diff --git a/Lib/distutils/command/sdist.py b/Lib/distutils/command/sdist.py
index 116f67ef9b..7ea3d5fa27 100644
--- a/Lib/distutils/command/sdist.py
+++ b/Lib/distutils/command/sdist.py
@@ -74,6 +74,10 @@ class sdist(Command):
('metadata-check', None,
"Ensure that all required elements of meta-data "
"are supplied. Warn if any missing. [default]"),
+ ('owner=', 'u',
+ "Owner name used when creating a tar file [default: current user]"),
+ ('group=', 'g',
+ "Group name used when creating a tar file [default: current group]"),
]
boolean_options = ['use-defaults', 'prune',
@@ -113,6 +117,8 @@ class sdist(Command):
self.archive_files = None
self.metadata_check = 1
+ self.owner = None
+ self.group = None
def finalize_options(self):
if self.manifest is None:
@@ -444,7 +450,8 @@ class sdist(Command):
self.formats.append(self.formats.pop(self.formats.index('tar')))
for fmt in self.formats:
- file = self.make_archive(base_name, fmt, base_dir=base_dir)
+ file = self.make_archive(base_name, fmt, base_dir=base_dir,
+ owner=self.owner, group=self.group)
archive_files.append(file)
self.distribution.dist_files.append(('sdist', '', file))
diff --git a/Lib/distutils/command/upload.py b/Lib/distutils/command/upload.py
index d2bc82cea3..287158343b 100644
--- a/Lib/distutils/command/upload.py
+++ b/Lib/distutils/command/upload.py
@@ -182,7 +182,7 @@ class upload(PyPIRCCommand):
result = urlopen(request)
status = result.getcode()
reason = result.msg
- except socket.error as e:
+ except OSError as e:
self.announce(str(e), log.ERROR)
return
except HTTPError as e:
diff --git a/Lib/distutils/config.py b/Lib/distutils/config.py
index 1fd53346e9..7439a83a4f 100644
--- a/Lib/distutils/config.py
+++ b/Lib/distutils/config.py
@@ -21,7 +21,7 @@ password:%s
class PyPIRCCommand(Command):
"""Base command that knows how to handle the .pypirc file
"""
- DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
+ DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi'
DEFAULT_REALM = 'pypi'
repository = None
realm = None
@@ -83,6 +83,15 @@ class PyPIRCCommand(Command):
current[key] = config.get(server, key)
else:
current[key] = default
+
+ # work around people having "repository" for the "pypi"
+ # section of their config set to the HTTP (rather than
+ # HTTPS) URL
+ if (server == 'pypi' and
+ repository in (self.DEFAULT_REPOSITORY, 'pypi')):
+ current['repository'] = self.DEFAULT_REPOSITORY
+ return current
+
if (current['server'] == repository or
current['repository'] == repository):
return current
diff --git a/Lib/distutils/core.py b/Lib/distutils/core.py
index 260332a2ac..c811d5bd9c 100644
--- a/Lib/distutils/core.py
+++ b/Lib/distutils/core.py
@@ -128,8 +128,9 @@ def setup (**attrs):
if _setup_stop_after == "config":
return dist
- # Parse the command line; any command-line errors are the end user's
- # fault, so turn them into SystemExit to suppress tracebacks.
+ # Parse the command line and override config files; any
+ # command-line errors are the end user's fault, so turn them into
+ # SystemExit to suppress tracebacks.
try:
ok = dist.parse_command_line()
except DistutilsArgError as msg:
@@ -148,7 +149,7 @@ def setup (**attrs):
dist.run_commands()
except KeyboardInterrupt:
raise SystemExit("interrupted")
- except (IOError, os.error) as exc:
+ except OSError as exc:
error = grok_environment_error(exc)
if DEBUG:
diff --git a/Lib/distutils/cygwinccompiler.py b/Lib/distutils/cygwinccompiler.py
index e0074a18f3..d28b1b368a 100644
--- a/Lib/distutils/cygwinccompiler.py
+++ b/Lib/distutils/cygwinccompiler.py
@@ -54,7 +54,8 @@ import re
from distutils.ccompiler import gen_preprocess_options, gen_lib_options
from distutils.unixccompiler import UnixCCompiler
from distutils.file_util import write_file
-from distutils.errors import DistutilsExecError, CompileError, UnknownFileError
+from distutils.errors import (DistutilsExecError, CCompilerError,
+ CompileError, UnknownFileError)
from distutils import log
from distutils.version import LooseVersion
from distutils.spawn import find_executable
@@ -294,18 +295,17 @@ class Mingw32CCompiler(CygwinCCompiler):
else:
entry_point = ''
- if self.gcc_version < '4' or is_cygwingcc():
- no_cygwin = ' -mno-cygwin'
- else:
- no_cygwin = ''
-
- self.set_executables(compiler='gcc%s -O -Wall' % no_cygwin,
- compiler_so='gcc%s -mdll -O -Wall' % no_cygwin,
- compiler_cxx='g++%s -O -Wall' % no_cygwin,
- linker_exe='gcc%s' % no_cygwin,
- linker_so='%s%s %s %s'
- % (self.linker_dll, no_cygwin,
- shared_option, entry_point))
+ if is_cygwingcc():
+ raise CCompilerError(
+ 'Cygwin gcc cannot be used with --compiler=mingw32')
+
+ self.set_executables(compiler='gcc -O -Wall',
+ compiler_so='gcc -mdll -O -Wall',
+ compiler_cxx='g++ -O -Wall',
+ linker_exe='gcc',
+ linker_so='%s %s %s'
+ % (self.linker_dll, shared_option,
+ entry_point))
# Maybe we should also append -mthreads, but then the finished
# dlls need another dll (mingwm10.dll see Mingw32 docs)
# (-mthreads: Support thread-safe exception handling on `Mingw32')
@@ -364,7 +364,7 @@ def check_config_h():
return CONFIG_H_NOTOK, "'%s' does not mention '__GNUC__'" % fn
finally:
config_h.close()
- except IOError as exc:
+ except OSError as exc:
return (CONFIG_H_UNCERTAIN,
"couldn't read '%s': %s" % (fn, exc.strerror))
diff --git a/Lib/distutils/dir_util.py b/Lib/distutils/dir_util.py
index 2826ff805d..2b35aa318e 100644
--- a/Lib/distutils/dir_util.py
+++ b/Lib/distutils/dir_util.py
@@ -124,7 +124,7 @@ def copy_tree(src, dst, preserve_mode=1, preserve_times=1,
"cannot copy tree '%s': not a directory" % src)
try:
names = os.listdir(src)
- except os.error as e:
+ except OSError as e:
(errno, errstr) = e
if dry_run:
names = []
@@ -198,7 +198,7 @@ def remove_tree(directory, verbose=1, dry_run=0):
abspath = os.path.abspath(cmd[1])
if abspath in _path_created:
del _path_created[abspath]
- except (IOError, OSError) as exc:
+ except OSError as exc:
log.warn(grok_environment_error(
exc, "error removing %s: " % directory))
diff --git a/Lib/distutils/dist.py b/Lib/distutils/dist.py
index 11a210279e..7eb04bc3f5 100644
--- a/Lib/distutils/dist.py
+++ b/Lib/distutils/dist.py
@@ -52,7 +52,9 @@ class Distribution:
('quiet', 'q', "run quietly (turns verbosity off)"),
('dry-run', 'n', "don't actually do anything"),
('help', 'h', "show detailed help message"),
- ]
+ ('no-user-cfg', None,
+ 'ignore pydistutils.cfg in your home directory'),
+ ]
# 'common_usage' is a short (2-3 line) string describing the common
# usage of the setup script.
@@ -259,6 +261,22 @@ Common commands: (see '--help-commands' for more)
else:
sys.stderr.write(msg + "\n")
+ # no-user-cfg is handled before other command line args
+ # because other args override the config files, and this
+ # one is needed before we can load the config files.
+ # If attrs['script_args'] wasn't passed, assume false.
+ #
+ # This also make sure we just look at the global options
+ self.want_user_cfg = True
+
+ if self.script_args is not None:
+ for arg in self.script_args:
+ if not arg.startswith('-'):
+ break
+ if arg == '--no-user-cfg':
+ self.want_user_cfg = False
+ break
+
self.finalize_options()
def get_option_dict(self, command):
@@ -310,7 +328,10 @@ Common commands: (see '--help-commands' for more)
Distutils installation directory (ie. where the top-level
Distutils __inst__.py file lives), a file in the user's home
directory named .pydistutils.cfg on Unix and pydistutils.cfg
- on Windows/Mac, and setup.cfg in the current directory.
+ on Windows/Mac; and setup.cfg in the current directory.
+
+ The file in the user's home directory can be disabled with the
+ --no-user-cfg option.
"""
files = []
check_environ()
@@ -330,15 +351,19 @@ Common commands: (see '--help-commands' for more)
user_filename = "pydistutils.cfg"
# And look for the user config file
- user_file = os.path.join(os.path.expanduser('~'), user_filename)
- if os.path.isfile(user_file):
- files.append(user_file)
+ if self.want_user_cfg:
+ user_file = os.path.join(os.path.expanduser('~'), user_filename)
+ if os.path.isfile(user_file):
+ files.append(user_file)
# All platforms support local setup.cfg
local_file = "setup.cfg"
if os.path.isfile(local_file):
files.append(local_file)
+ if DEBUG:
+ self.announce("using config files: %s" % ', '.join(files))
+
return files
def parse_config_files(self, filenames=None):
diff --git a/Lib/distutils/emxccompiler.py b/Lib/distutils/emxccompiler.py
deleted file mode 100644
index 3675f8df9c..0000000000
--- a/Lib/distutils/emxccompiler.py
+++ /dev/null
@@ -1,315 +0,0 @@
-"""distutils.emxccompiler
-
-Provides the EMXCCompiler class, a subclass of UnixCCompiler that
-handles the EMX port of the GNU C compiler to OS/2.
-"""
-
-# issues:
-#
-# * OS/2 insists that DLLs can have names no longer than 8 characters
-# We put export_symbols in a def-file, as though the DLL can have
-# an arbitrary length name, but truncate the output filename.
-#
-# * only use OMF objects and use LINK386 as the linker (-Zomf)
-#
-# * always build for multithreading (-Zmt) as the accompanying OS/2 port
-# of Python is only distributed with threads enabled.
-#
-# tested configurations:
-#
-# * EMX gcc 2.81/EMX 0.9d fix03
-
-import os,sys,copy
-from distutils.ccompiler import gen_preprocess_options, gen_lib_options
-from distutils.unixccompiler import UnixCCompiler
-from distutils.file_util import write_file
-from distutils.errors import DistutilsExecError, CompileError, UnknownFileError
-from distutils import log
-
-class EMXCCompiler (UnixCCompiler):
-
- compiler_type = 'emx'
- obj_extension = ".obj"
- static_lib_extension = ".lib"
- shared_lib_extension = ".dll"
- static_lib_format = "%s%s"
- shared_lib_format = "%s%s"
- res_extension = ".res" # compiled resource file
- exe_extension = ".exe"
-
- def __init__ (self,
- verbose=0,
- dry_run=0,
- force=0):
-
- UnixCCompiler.__init__ (self, verbose, dry_run, force)
-
- (status, details) = check_config_h()
- self.debug_print("Python's GCC status: %s (details: %s)" %
- (status, details))
- if status is not CONFIG_H_OK:
- self.warn(
- "Python's pyconfig.h doesn't seem to support your compiler. " +
- ("Reason: %s." % details) +
- "Compiling may fail because of undefined preprocessor macros.")
-
- (self.gcc_version, self.ld_version) = \
- get_versions()
- self.debug_print(self.compiler_type + ": gcc %s, ld %s\n" %
- (self.gcc_version,
- self.ld_version) )
-
- # Hard-code GCC because that's what this is all about.
- # XXX optimization, warnings etc. should be customizable.
- self.set_executables(compiler='gcc -Zomf -Zmt -O3 -fomit-frame-pointer -mprobe -Wall',
- compiler_so='gcc -Zomf -Zmt -O3 -fomit-frame-pointer -mprobe -Wall',
- linker_exe='gcc -Zomf -Zmt -Zcrtdll',
- linker_so='gcc -Zomf -Zmt -Zcrtdll -Zdll')
-
- # want the gcc library statically linked (so that we don't have
- # to distribute a version dependent on the compiler we have)
- self.dll_libraries=["gcc"]
-
- # __init__ ()
-
- def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
- if ext == '.rc':
- # gcc requires '.rc' compiled to binary ('.res') files !!!
- try:
- self.spawn(["rc", "-r", src])
- except DistutilsExecError as msg:
- raise CompileError(msg)
- else: # for other files use the C-compiler
- try:
- self.spawn(self.compiler_so + cc_args + [src, '-o', obj] +
- extra_postargs)
- except DistutilsExecError as msg:
- raise CompileError(msg)
-
- def link (self,
- target_desc,
- objects,
- output_filename,
- output_dir=None,
- libraries=None,
- library_dirs=None,
- runtime_library_dirs=None,
- export_symbols=None,
- debug=0,
- extra_preargs=None,
- extra_postargs=None,
- build_temp=None,
- target_lang=None):
-
- # use separate copies, so we can modify the lists
- extra_preargs = copy.copy(extra_preargs or [])
- libraries = copy.copy(libraries or [])
- objects = copy.copy(objects or [])
-
- # Additional libraries
- libraries.extend(self.dll_libraries)
-
- # handle export symbols by creating a def-file
- # with executables this only works with gcc/ld as linker
- if ((export_symbols is not None) and
- (target_desc != self.EXECUTABLE)):
- # (The linker doesn't do anything if output is up-to-date.
- # So it would probably better to check if we really need this,
- # but for this we had to insert some unchanged parts of
- # UnixCCompiler, and this is not what we want.)
-
- # we want to put some files in the same directory as the
- # object files are, build_temp doesn't help much
- # where are the object files
- temp_dir = os.path.dirname(objects[0])
- # name of dll to give the helper files the same base name
- (dll_name, dll_extension) = os.path.splitext(
- os.path.basename(output_filename))
-
- # generate the filenames for these files
- def_file = os.path.join(temp_dir, dll_name + ".def")
-
- # Generate .def file
- contents = [
- "LIBRARY %s INITINSTANCE TERMINSTANCE" % \
- os.path.splitext(os.path.basename(output_filename))[0],
- "DATA MULTIPLE NONSHARED",
- "EXPORTS"]
- for sym in export_symbols:
- contents.append(' "%s"' % sym)
- self.execute(write_file, (def_file, contents),
- "writing %s" % def_file)
-
- # next add options for def-file and to creating import libraries
- # for gcc/ld the def-file is specified as any other object files
- objects.append(def_file)
-
- #end: if ((export_symbols is not None) and
- # (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")):
-
- # who wants symbols and a many times larger output file
- # should explicitly switch the debug mode on
- # otherwise we let dllwrap/ld strip the output file
- # (On my machine: 10KB < stripped_file < ??100KB
- # unstripped_file = stripped_file + XXX KB
- # ( XXX=254 for a typical python extension))
- if not debug:
- extra_preargs.append("-s")
-
- UnixCCompiler.link(self,
- target_desc,
- objects,
- output_filename,
- output_dir,
- libraries,
- library_dirs,
- runtime_library_dirs,
- None, # export_symbols, we do this in our def-file
- debug,
- extra_preargs,
- extra_postargs,
- build_temp,
- target_lang)
-
- # link ()
-
- # -- Miscellaneous methods -----------------------------------------
-
- # override the object_filenames method from CCompiler to
- # support rc and res-files
- def object_filenames (self,
- source_filenames,
- strip_dir=0,
- output_dir=''):
- if output_dir is None: output_dir = ''
- obj_names = []
- for src_name in source_filenames:
- # use normcase to make sure '.rc' is really '.rc' and not '.RC'
- (base, ext) = os.path.splitext (os.path.normcase(src_name))
- if ext not in (self.src_extensions + ['.rc']):
- raise UnknownFileError("unknown file type '%s' (from '%s')" % \
- (ext, src_name))
- if strip_dir:
- base = os.path.basename (base)
- if ext == '.rc':
- # these need to be compiled to object files
- obj_names.append (os.path.join (output_dir,
- base + self.res_extension))
- else:
- obj_names.append (os.path.join (output_dir,
- base + self.obj_extension))
- return obj_names
-
- # object_filenames ()
-
- # override the find_library_file method from UnixCCompiler
- # to deal with file naming/searching differences
- def find_library_file(self, dirs, lib, debug=0):
- shortlib = '%s.lib' % lib
- longlib = 'lib%s.lib' % lib # this form very rare
-
- # get EMX's default library directory search path
- try:
- emx_dirs = os.environ['LIBRARY_PATH'].split(';')
- except KeyError:
- emx_dirs = []
-
- for dir in dirs + emx_dirs:
- shortlibp = os.path.join(dir, shortlib)
- longlibp = os.path.join(dir, longlib)
- if os.path.exists(shortlibp):
- return shortlibp
- elif os.path.exists(longlibp):
- return longlibp
-
- # Oops, didn't find it in *any* of 'dirs'
- return None
-
-# class EMXCCompiler
-
-
-# Because these compilers aren't configured in Python's pyconfig.h file by
-# default, we should at least warn the user if he is using a unmodified
-# version.
-
-CONFIG_H_OK = "ok"
-CONFIG_H_NOTOK = "not ok"
-CONFIG_H_UNCERTAIN = "uncertain"
-
-def check_config_h():
-
- """Check if the current Python installation (specifically, pyconfig.h)
- appears amenable to building extensions with GCC. Returns a tuple
- (status, details), where 'status' is one of the following constants:
- CONFIG_H_OK
- all is well, go ahead and compile
- CONFIG_H_NOTOK
- doesn't look good
- CONFIG_H_UNCERTAIN
- not sure -- unable to read pyconfig.h
- 'details' is a human-readable string explaining the situation.
-
- Note there are two ways to conclude "OK": either 'sys.version' contains
- the string "GCC" (implying that this Python was built with GCC), or the
- installed "pyconfig.h" contains the string "__GNUC__".
- """
-
- # XXX since this function also checks sys.version, it's not strictly a
- # "pyconfig.h" check -- should probably be renamed...
-
- from distutils import sysconfig
- # if sys.version contains GCC then python was compiled with
- # GCC, and the pyconfig.h file should be OK
- if sys.version.find("GCC") >= 0:
- return (CONFIG_H_OK, "sys.version mentions 'GCC'")
-
- fn = sysconfig.get_config_h_filename()
- try:
- # It would probably better to read single lines to search.
- # But we do this only once, and it is fast enough
- f = open(fn)
- try:
- s = f.read()
- finally:
- f.close()
-
- except IOError as exc:
- # if we can't read this file, we cannot say it is wrong
- # the compiler will complain later about this file as missing
- return (CONFIG_H_UNCERTAIN,
- "couldn't read '%s': %s" % (fn, exc.strerror))
-
- else:
- # "pyconfig.h" contains an "#ifdef __GNUC__" or something similar
- if s.find("__GNUC__") >= 0:
- return (CONFIG_H_OK, "'%s' mentions '__GNUC__'" % fn)
- else:
- return (CONFIG_H_NOTOK, "'%s' does not mention '__GNUC__'" % fn)
-
-
-def get_versions():
- """ Try to find out the versions of gcc and ld.
- If not possible it returns None for it.
- """
- from distutils.version import StrictVersion
- from distutils.spawn import find_executable
- import re
-
- gcc_exe = find_executable('gcc')
- if gcc_exe:
- out = os.popen(gcc_exe + ' -dumpversion','r')
- try:
- out_string = out.read()
- finally:
- out.close()
- result = re.search('(\d+\.\d+\.\d+)', out_string, re.ASCII)
- if result:
- gcc_version = StrictVersion(result.group(1))
- else:
- gcc_version = None
- else:
- gcc_version = None
- # EMX ld has no way of reporting version number, and we use GCC
- # anyway - so we can link OMF DLLs
- ld_version = None
- return (gcc_version, ld_version)
diff --git a/Lib/distutils/errors.py b/Lib/distutils/errors.py
index eb13c983e9..8b93059e19 100644
--- a/Lib/distutils/errors.py
+++ b/Lib/distutils/errors.py
@@ -35,8 +35,8 @@ class DistutilsArgError (DistutilsError):
class DistutilsFileError (DistutilsError):
"""Any problems in the filesystem: expected file not found, etc.
- Typically this is for problems that we detect before IOError or
- OSError could be raised."""
+ Typically this is for problems that we detect before OSError
+ could be raised."""
pass
class DistutilsOptionError (DistutilsError):
diff --git a/Lib/distutils/file_util.py b/Lib/distutils/file_util.py
index 9bdd14e42e..f6ed290f13 100644
--- a/Lib/distutils/file_util.py
+++ b/Lib/distutils/file_util.py
@@ -27,26 +27,26 @@ def _copy_file_contents(src, dst, buffer_size=16*1024):
try:
try:
fsrc = open(src, 'rb')
- except os.error as e:
+ except OSError as e:
raise DistutilsFileError("could not open '%s': %s" % (src, e.strerror))
if os.path.exists(dst):
try:
os.unlink(dst)
- except os.error as e:
+ except OSError as e:
raise DistutilsFileError(
"could not delete '%s': %s" % (dst, e.strerror))
try:
fdst = open(dst, 'wb')
- except os.error as e:
+ except OSError as e:
raise DistutilsFileError(
"could not create '%s': %s" % (dst, e.strerror))
while True:
try:
buf = fsrc.read(buffer_size)
- except os.error as e:
+ except OSError as e:
raise DistutilsFileError(
"could not read from '%s': %s" % (src, e.strerror))
@@ -55,7 +55,7 @@ def _copy_file_contents(src, dst, buffer_size=16*1024):
try:
fdst.write(buf)
- except os.error as e:
+ except OSError as e:
raise DistutilsFileError(
"could not write to '%s': %s" % (dst, e.strerror))
finally:
@@ -193,7 +193,7 @@ def move_file (src, dst,
copy_it = False
try:
os.rename(src, dst)
- except os.error as e:
+ except OSError as e:
(num, msg) = e
if num == errno.EXDEV:
copy_it = True
@@ -205,11 +205,11 @@ def move_file (src, dst,
copy_file(src, dst, verbose=verbose)
try:
os.unlink(src)
- except os.error as e:
+ except OSError as e:
(num, msg) = e
try:
os.unlink(dst)
- except os.error:
+ except OSError:
pass
raise DistutilsFileError(
"couldn't move '%s' to '%s' by copy/delete: "
diff --git a/Lib/distutils/msvc9compiler.py b/Lib/distutils/msvc9compiler.py
index b3f6ce10a8..9688f20019 100644
--- a/Lib/distutils/msvc9compiler.py
+++ b/Lib/distutils/msvc9compiler.py
@@ -729,7 +729,7 @@ class MSVCCompiler(CCompiler) :
return manifest_file
finally:
manifest_f.close()
- except IOError:
+ except OSError:
pass
# -- Miscellaneous methods -----------------------------------------
diff --git a/Lib/distutils/spawn.py b/Lib/distutils/spawn.py
index f58c55f902..b1c5a442a5 100644
--- a/Lib/distutils/spawn.py
+++ b/Lib/distutils/spawn.py
@@ -32,8 +32,6 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0):
_spawn_posix(cmd, search_path, dry_run=dry_run)
elif os.name == 'nt':
_spawn_nt(cmd, search_path, dry_run=dry_run)
- elif os.name == 'os2':
- _spawn_os2(cmd, search_path, dry_run=dry_run)
else:
raise DistutilsPlatformError(
"don't know how to spawn programs on platform '%s'" % os.name)
@@ -74,26 +72,6 @@ def _spawn_nt(cmd, search_path=1, verbose=0, dry_run=0):
raise DistutilsExecError(
"command '%s' failed with exit status %d" % (cmd[0], rc))
-def _spawn_os2(cmd, search_path=1, verbose=0, dry_run=0):
- executable = cmd[0]
- if search_path:
- # either we find one or it stays the same
- executable = find_executable(executable) or executable
- log.info(' '.join([executable] + cmd[1:]))
- if not dry_run:
- # spawnv for OS/2 EMX requires a full path to the .exe
- try:
- rc = os.spawnv(os.P_WAIT, executable, cmd)
- except OSError as exc:
- # this seems to happen when the command isn't found
- raise DistutilsExecError(
- "command '%s' failed: %s" % (cmd[0], exc.args[-1]))
- if rc != 0:
- # and this reflects the command running but failing
- log.debug("command '%s' failed with exit status %d" % (cmd[0], rc))
- raise DistutilsExecError(
- "command '%s' failed with exit status %d" % (cmd[0], rc))
-
if sys.platform == 'darwin':
from distutils import sysconfig
_cfg_target = None
@@ -180,7 +158,7 @@ def find_executable(executable, path=None):
paths = path.split(os.pathsep)
base, ext = os.path.splitext(executable)
- if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
+ if (sys.platform == 'win32') and (ext != '.exe'):
executable = executable + '.exe'
if not os.path.isfile(executable):
diff --git a/Lib/distutils/sysconfig.py b/Lib/distutils/sysconfig.py
index b9479889f4..75537db8d0 100644
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -114,8 +114,6 @@ def get_python_inc(plat_specific=0, prefix=None):
return os.path.join(prefix, "include", python_dir)
elif os.name == "nt":
return os.path.join(prefix, "include")
- elif os.name == "os2":
- return os.path.join(prefix, "Include")
else:
raise DistutilsPlatformError(
"I don't know where Python installs its C header files "
@@ -157,11 +155,6 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
return prefix
else:
return os.path.join(prefix, "Lib", "site-packages")
- elif os.name == "os2":
- if standard_lib:
- return os.path.join(prefix, "Lib")
- else:
- return os.path.join(prefix, "Lib", "site-packages")
else:
raise DistutilsPlatformError(
"I don't know where Python installs its library "
@@ -442,7 +435,7 @@ def _init_posix():
try:
filename = get_makefile_filename()
parse_makefile(filename, g)
- except IOError as msg:
+ except OSError as msg:
my_msg = "invalid Python installation: unable to open %s" % filename
if hasattr(msg, "strerror"):
my_msg = my_msg + " (%s)" % msg.strerror
@@ -454,7 +447,7 @@ def _init_posix():
filename = get_config_h_filename()
with open(filename) as file:
parse_config_h(file, g)
- except IOError as msg:
+ except OSError as msg:
my_msg = "invalid Python installation: unable to open %s" % filename
if hasattr(msg, "strerror"):
my_msg = my_msg + " (%s)" % msg.strerror
@@ -492,7 +485,6 @@ def _init_nt():
# XXX hmmm.. a normal install puts include files here
g['INCLUDEPY'] = get_python_inc(plat_specific=0)
- g['SO'] = '.pyd'
g['EXT_SUFFIX'] = '.pyd'
g['EXE'] = ".exe"
g['VERSION'] = get_python_version().replace(".", "")
@@ -502,24 +494,6 @@ def _init_nt():
_config_vars = g
-def _init_os2():
- """Initialize the module as appropriate for OS/2"""
- g = {}
- # set basic install directories
- g['LIBDEST'] = get_python_lib(plat_specific=0, standard_lib=1)
- g['BINLIBDEST'] = get_python_lib(plat_specific=1, standard_lib=1)
-
- # XXX hmmm.. a normal install puts include files here
- g['INCLUDEPY'] = get_python_inc(plat_specific=0)
-
- g['SO'] = '.pyd'
- g['EXT_SUFFIX'] = '.pyd'
- g['EXE'] = ".exe"
-
- global _config_vars
- _config_vars = g
-
-
def get_config_vars(*args):
"""With no arguments, return a dictionary of all configuration
variables relevant for the current platform. Generally this includes
@@ -544,6 +518,11 @@ def get_config_vars(*args):
_config_vars['prefix'] = PREFIX
_config_vars['exec_prefix'] = EXEC_PREFIX
+ # For backward compatibility, see issue19555
+ SO = _config_vars.get('EXT_SUFFIX')
+ if SO is not None:
+ _config_vars['SO'] = SO
+
# Always convert srcdir to an absolute path
srcdir = _config_vars.get('srcdir', project_base)
if os.name == 'posix':
@@ -594,4 +573,7 @@ def get_config_var(name):
returned by 'get_config_vars()'. Equivalent to
get_config_vars().get(name)
"""
+ if name == 'SO':
+ import warnings
+ warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2)
return get_config_vars().get(name)
diff --git a/Lib/distutils/tests/test_archive_util.py b/Lib/distutils/tests/test_archive_util.py
index d3fb24ad56..6b42c5a213 100644
--- a/Lib/distutils/tests/test_archive_util.py
+++ b/Lib/distutils/tests/test_archive_util.py
@@ -16,6 +16,13 @@ from distutils.tests import support
from test.support import check_warnings, run_unittest, patch
try:
+ import grp
+ import pwd
+ UID_GID_SUPPORT = True
+except ImportError:
+ UID_GID_SUPPORT = False
+
+try:
import zipfile
ZIP_SUPPORT = True
except ImportError:
@@ -77,7 +84,7 @@ class ArchiveUtilTestCase(support.TempdirManager,
tmpdir2 = self.mkdtemp()
unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0],
- "Source and target should be on same drive")
+ "source and target should be on same drive")
base_name = os.path.join(tmpdir2, target_name)
@@ -275,6 +282,58 @@ class ArchiveUtilTestCase(support.TempdirManager,
finally:
del ARCHIVE_FORMATS['xxx']
+ def test_make_archive_owner_group(self):
+ # testing make_archive with owner and group, with various combinations
+ # this works even if there's not gid/uid support
+ if UID_GID_SUPPORT:
+ group = grp.getgrgid(0)[0]
+ owner = pwd.getpwuid(0)[0]
+ else:
+ group = owner = 'root'
+
+ base_dir, root_dir, base_name = self._create_files()
+ base_name = os.path.join(self.mkdtemp() , 'archive')
+ res = make_archive(base_name, 'zip', root_dir, base_dir, owner=owner,
+ group=group)
+ self.assertTrue(os.path.exists(res))
+
+ res = make_archive(base_name, 'zip', root_dir, base_dir)
+ self.assertTrue(os.path.exists(res))
+
+ res = make_archive(base_name, 'tar', root_dir, base_dir,
+ owner=owner, group=group)
+ self.assertTrue(os.path.exists(res))
+
+ res = make_archive(base_name, 'tar', root_dir, base_dir,
+ owner='kjhkjhkjg', group='oihohoh')
+ self.assertTrue(os.path.exists(res))
+
+ @unittest.skipUnless(zlib, "Requires zlib")
+ @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
+ def test_tarfile_root_owner(self):
+ tmpdir, tmpdir2, base_name = self._create_files()
+ old_dir = os.getcwd()
+ os.chdir(tmpdir)
+ group = grp.getgrgid(0)[0]
+ owner = pwd.getpwuid(0)[0]
+ try:
+ archive_name = make_tarball(base_name, 'dist', compress=None,
+ owner=owner, group=group)
+ finally:
+ os.chdir(old_dir)
+
+ # check if the compressed tarball was created
+ self.assertTrue(os.path.exists(archive_name))
+
+ # now checks the rights
+ archive = tarfile.open(archive_name)
+ try:
+ for member in archive.getmembers():
+ self.assertEqual(member.uid, 0)
+ self.assertEqual(member.gid, 0)
+ finally:
+ archive.close()
+
def test_suite():
return unittest.makeSuite(ArchiveUtilTestCase)
diff --git a/Lib/distutils/tests/test_bdist_dumb.py b/Lib/distutils/tests/test_bdist_dumb.py
index 0ad32d421e..c8ccdc2383 100644
--- a/Lib/distutils/tests/test_bdist_dumb.py
+++ b/Lib/distutils/tests/test_bdist_dumb.py
@@ -1,7 +1,6 @@
"""Tests for distutils.command.bdist_dumb."""
import os
-import imp
import sys
import zipfile
import unittest
@@ -75,8 +74,6 @@ class BuildDumbTestCase(support.TempdirManager,
# see what we have
dist_created = os.listdir(os.path.join(pkg_dir, 'dist'))
base = "%s.%s.zip" % (dist.get_fullname(), cmd.plat_name)
- if os.name == 'os2':
- base = base.replace(':', '-')
self.assertEqual(dist_created, [base])
@@ -90,7 +87,7 @@ class BuildDumbTestCase(support.TempdirManager,
contents = sorted(os.path.basename(fn) for fn in contents)
wanted = ['foo-0.1-py%s.%s.egg-info' % sys.version_info[:2], 'foo.py']
if not sys.dont_write_bytecode:
- wanted.append('foo.%s.pyc' % imp.get_tag())
+ wanted.append('foo.%s.pyc' % sys.implementation.cache_tag)
self.assertEqual(contents, sorted(wanted))
def test_suite():
diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py
index 2ce9d4492d..c8f6b89919 100644
--- a/Lib/distutils/tests/test_build_py.py
+++ b/Lib/distutils/tests/test_build_py.py
@@ -2,7 +2,6 @@
import os
import sys
-import imp
import unittest
from distutils.command.build_py import build_py
@@ -63,7 +62,8 @@ class BuildPyTestCase(support.TempdirManager,
self.assertFalse(os.path.exists(pycache_dir))
else:
pyc_files = os.listdir(pycache_dir)
- self.assertIn("__init__.%s.pyc" % imp.get_tag(), pyc_files)
+ self.assertIn("__init__.%s.pyc" % sys.implementation.cache_tag,
+ pyc_files)
def test_empty_package_dir(self):
# See bugs #1668596/#1720897
@@ -102,7 +102,8 @@ class BuildPyTestCase(support.TempdirManager,
found = os.listdir(cmd.build_lib)
self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py'])
found = os.listdir(os.path.join(cmd.build_lib, '__pycache__'))
- self.assertEqual(found, ['boiledeggs.%s.pyc' % imp.get_tag()])
+ self.assertEqual(found,
+ ['boiledeggs.%s.pyc' % sys.implementation.cache_tag])
@unittest.skipIf(sys.dont_write_bytecode, 'byte-compile disabled')
def test_byte_compile_optimized(self):
@@ -119,7 +120,8 @@ class BuildPyTestCase(support.TempdirManager,
found = os.listdir(cmd.build_lib)
self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py'])
found = os.listdir(os.path.join(cmd.build_lib, '__pycache__'))
- self.assertEqual(sorted(found), ['boiledeggs.%s.pyo' % imp.get_tag()])
+ self.assertEqual(sorted(found),
+ ['boiledeggs.%s.pyo' % sys.implementation.cache_tag])
def test_dir_in_package_data(self):
"""
diff --git a/Lib/distutils/tests/test_config.py b/Lib/distutils/tests/test_config.py
index 0e8d65e271..4de825a81e 100644
--- a/Lib/distutils/tests/test_config.py
+++ b/Lib/distutils/tests/test_config.py
@@ -87,7 +87,7 @@ class PyPIRCCommandTestCase(support.TempdirManager,
config = list(sorted(config.items()))
waited = [('password', 'secret'), ('realm', 'pypi'),
- ('repository', 'http://pypi.python.org/pypi'),
+ ('repository', 'https://pypi.python.org/pypi'),
('server', 'server1'), ('username', 'me')]
self.assertEqual(config, waited)
@@ -96,7 +96,7 @@ class PyPIRCCommandTestCase(support.TempdirManager,
config = cmd._read_pypirc()
config = list(sorted(config.items()))
waited = [('password', 'secret'), ('realm', 'pypi'),
- ('repository', 'http://pypi.python.org/pypi'),
+ ('repository', 'https://pypi.python.org/pypi'),
('server', 'server-login'), ('username', 'tarek')]
self.assertEqual(config, waited)
diff --git a/Lib/distutils/tests/test_dist.py b/Lib/distutils/tests/test_dist.py
index 9a8ca19902..b7fd3fbf90 100644
--- a/Lib/distutils/tests/test_dist.py
+++ b/Lib/distutils/tests/test_dist.py
@@ -39,6 +39,7 @@ class TestDistribution(Distribution):
class DistributionTestCase(support.LoggingSilencer,
+ support.TempdirManager,
support.EnvironGuard,
unittest.TestCase):
@@ -213,6 +214,34 @@ class DistributionTestCase(support.LoggingSilencer,
self.assertRaises(ValueError, dist.announce, args, kwargs)
+ def test_find_config_files_disable(self):
+ # Ticket #1180: Allow user to disable their home config file.
+ temp_home = self.mkdtemp()
+ if os.name == 'posix':
+ user_filename = os.path.join(temp_home, ".pydistutils.cfg")
+ else:
+ user_filename = os.path.join(temp_home, "pydistutils.cfg")
+
+ with open(user_filename, 'w') as f:
+ f.write('[distutils]\n')
+
+ def _expander(path):
+ return temp_home
+
+ old_expander = os.path.expanduser
+ os.path.expanduser = _expander
+ try:
+ d = Distribution()
+ all_files = d.find_config_files()
+
+ d = Distribution(attrs={'script_args': ['--no-user-cfg']})
+ files = d.find_config_files()
+ finally:
+ os.path.expanduser = old_expander
+
+ # make sure --no-user-cfg disables the user cfg file
+ self.assertEqual(len(all_files)-1, len(files))
+
class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
unittest.TestCase):
@@ -406,14 +435,14 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
PKG_INFO.seek(0)
metadata.read_pkg_file(PKG_INFO)
- self.assertEquals(metadata.name, "package")
- self.assertEquals(metadata.version, "1.0")
- self.assertEquals(metadata.description, "xxx")
- self.assertEquals(metadata.download_url, 'http://example.com')
- self.assertEquals(metadata.keywords, ['one', 'two'])
- self.assertEquals(metadata.platforms, ['UNKNOWN'])
- self.assertEquals(metadata.obsoletes, None)
- self.assertEquals(metadata.requires, ['foo'])
+ self.assertEqual(metadata.name, "package")
+ self.assertEqual(metadata.version, "1.0")
+ self.assertEqual(metadata.description, "xxx")
+ self.assertEqual(metadata.download_url, 'http://example.com')
+ self.assertEqual(metadata.keywords, ['one', 'two'])
+ self.assertEqual(metadata.platforms, ['UNKNOWN'])
+ self.assertEqual(metadata.obsoletes, None)
+ self.assertEqual(metadata.requires, ['foo'])
def test_suite():
suite = unittest.TestSuite()
diff --git a/Lib/distutils/tests/test_install.py b/Lib/distutils/tests/test_install.py
index ede88e5d90..18e1e57505 100644
--- a/Lib/distutils/tests/test_install.py
+++ b/Lib/distutils/tests/test_install.py
@@ -1,7 +1,6 @@
"""Tests for distutils.command.install."""
import os
-import imp
import sys
import unittest
import site
@@ -94,7 +93,7 @@ class InstallTestCase(support.TempdirManager,
self.addCleanup(cleanup)
- for key in ('nt_user', 'unix_user', 'os2_home'):
+ for key in ('nt_user', 'unix_user'):
self.assertIn(key, INSTALL_SCHEMES)
dist = Distribution({'name': 'xx'})
@@ -193,7 +192,8 @@ class InstallTestCase(support.TempdirManager,
f.close()
found = [os.path.basename(line) for line in content.splitlines()]
- expected = ['hello.py', 'hello.%s.pyc' % imp.get_tag(), 'sayhi',
+ expected = ['hello.py', 'hello.%s.pyc' % sys.implementation.cache_tag,
+ 'sayhi',
'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2]]
self.assertEqual(found, expected)
diff --git a/Lib/distutils/tests/test_install_lib.py b/Lib/distutils/tests/test_install_lib.py
index d0dfca00d7..40dd1a95a6 100644
--- a/Lib/distutils/tests/test_install_lib.py
+++ b/Lib/distutils/tests/test_install_lib.py
@@ -1,7 +1,7 @@
"""Tests for distutils.command.install_data."""
import sys
import os
-import imp
+import importlib.util
import unittest
from distutils.command.install_lib import install_lib
@@ -44,8 +44,10 @@ class InstallLibTestCase(support.TempdirManager,
f = os.path.join(project_dir, 'foo.py')
self.write_file(f, '# python file')
cmd.byte_compile([f])
- pyc_file = imp.cache_from_source('foo.py', debug_override=True)
- pyo_file = imp.cache_from_source('foo.py', debug_override=False)
+ pyc_file = importlib.util.cache_from_source('foo.py',
+ debug_override=True)
+ pyo_file = importlib.util.cache_from_source('foo.py',
+ debug_override=False)
self.assertTrue(os.path.exists(pyc_file))
self.assertTrue(os.path.exists(pyo_file))
diff --git a/Lib/distutils/tests/test_sdist.py b/Lib/distutils/tests/test_sdist.py
index e6359d6a8a..164586b784 100644
--- a/Lib/distutils/tests/test_sdist.py
+++ b/Lib/distutils/tests/test_sdist.py
@@ -14,6 +14,12 @@ try:
except ImportError:
ZLIB_SUPPORT = False
+try:
+ import grp
+ import pwd
+ UID_GID_SUPPORT = True
+except ImportError:
+ UID_GID_SUPPORT = False
from distutils.command.sdist import sdist, show_formats
from distutils.core import Distribution
@@ -425,6 +431,56 @@ class SDistTestCase(PyPIRCCommandTestCase):
self.assertEqual(sorted(filenames), ['fake-1.0', 'fake-1.0/PKG-INFO',
'fake-1.0/README.manual'])
+ @unittest.skipUnless(zlib, "requires zlib")
+ @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
+ def test_make_distribution_owner_group(self):
+
+ # check if tar and gzip are installed
+ if (find_executable('tar') is None or
+ find_executable('gzip') is None):
+ return
+
+ # now building a sdist
+ dist, cmd = self.get_cmd()
+
+ # creating a gztar and specifying the owner+group
+ cmd.formats = ['gztar']
+ cmd.owner = pwd.getpwuid(0)[0]
+ cmd.group = grp.getgrgid(0)[0]
+ cmd.ensure_finalized()
+ cmd.run()
+
+ # making sure we have the good rights
+ archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
+ archive = tarfile.open(archive_name)
+ try:
+ for member in archive.getmembers():
+ self.assertEqual(member.uid, 0)
+ self.assertEqual(member.gid, 0)
+ finally:
+ archive.close()
+
+ # building a sdist again
+ dist, cmd = self.get_cmd()
+
+ # creating a gztar
+ cmd.formats = ['gztar']
+ cmd.ensure_finalized()
+ cmd.run()
+
+ # making sure we have the good rights
+ archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
+ archive = tarfile.open(archive_name)
+
+ # note that we are not testing the group ownership here
+ # because, depending on the platforms and the container
+ # rights (see #7408)
+ try:
+ for member in archive.getmembers():
+ self.assertEqual(member.uid, os.getuid())
+ finally:
+ archive.close()
+
def test_suite():
return unittest.makeSuite(SDistTestCase)
diff --git a/Lib/distutils/tests/test_sysconfig.py b/Lib/distutils/tests/test_sysconfig.py
index 07812d80fc..b5fdc98dc9 100644
--- a/Lib/distutils/tests/test_sysconfig.py
+++ b/Lib/distutils/tests/test_sysconfig.py
@@ -1,16 +1,14 @@
"""Tests for distutils.sysconfig."""
import os
import shutil
-import test
import unittest
from distutils import sysconfig
from distutils.ccompiler import get_default_compiler
from distutils.tests import support
-from test.support import TESTFN, run_unittest
+from test.support import TESTFN, run_unittest, check_warnings
-class SysconfigTestCase(support.EnvironGuard,
- unittest.TestCase):
+class SysconfigTestCase(support.EnvironGuard, unittest.TestCase):
def setUp(self):
super(SysconfigTestCase, self).setUp()
self.makefile = None
@@ -32,7 +30,6 @@ class SysconfigTestCase(support.EnvironGuard,
self.assertTrue(os.path.isfile(config_h), config_h)
def test_get_python_lib(self):
- lib_dir = sysconfig.get_python_lib()
# XXX doesn't work on Linux when Python was never installed before
#self.assertTrue(os.path.isdir(lib_dir), lib_dir)
# test for pythonxx.lib?
@@ -67,8 +64,9 @@ class SysconfigTestCase(support.EnvironGuard,
self.assertTrue(os.path.exists(Python_h), Python_h)
self.assertTrue(sysconfig._is_python_source_dir(srcdir))
elif os.name == 'posix':
- self.assertEqual(os.path.dirname(sysconfig.get_makefile_filename()),
- srcdir)
+ self.assertEqual(
+ os.path.dirname(sysconfig.get_makefile_filename()),
+ srcdir)
def test_srcdir_independent_of_cwd(self):
# srcdir should be independent of the current working directory
@@ -129,10 +127,13 @@ class SysconfigTestCase(support.EnvironGuard,
def test_sysconfig_module(self):
import sysconfig as global_sysconfig
- self.assertEqual(global_sysconfig.get_config_var('CFLAGS'), sysconfig.get_config_var('CFLAGS'))
- self.assertEqual(global_sysconfig.get_config_var('LDFLAGS'), sysconfig.get_config_var('LDFLAGS'))
+ self.assertEqual(global_sysconfig.get_config_var('CFLAGS'),
+ sysconfig.get_config_var('CFLAGS'))
+ self.assertEqual(global_sysconfig.get_config_var('LDFLAGS'),
+ sysconfig.get_config_var('LDFLAGS'))
- @unittest.skipIf(sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'),'compiler flags customized')
+ @unittest.skipIf(sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'),
+ 'compiler flags customized')
def test_sysconfig_compiler_vars(self):
# On OS X, binary installers support extension module building on
# various levels of the operating system with differing Xcode
@@ -151,9 +152,30 @@ class SysconfigTestCase(support.EnvironGuard,
import sysconfig as global_sysconfig
if sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'):
return
- self.assertEqual(global_sysconfig.get_config_var('LDSHARED'), sysconfig.get_config_var('LDSHARED'))
- self.assertEqual(global_sysconfig.get_config_var('CC'), sysconfig.get_config_var('CC'))
-
+ self.assertEqual(global_sysconfig.get_config_var('LDSHARED'),
+ sysconfig.get_config_var('LDSHARED'))
+ self.assertEqual(global_sysconfig.get_config_var('CC'),
+ sysconfig.get_config_var('CC'))
+
+ @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None,
+ 'EXT_SUFFIX required for this test')
+ def test_SO_deprecation(self):
+ self.assertWarns(DeprecationWarning,
+ sysconfig.get_config_var, 'SO')
+
+ @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None,
+ 'EXT_SUFFIX required for this test')
+ def test_SO_value(self):
+ with check_warnings(('', DeprecationWarning)):
+ self.assertEqual(sysconfig.get_config_var('SO'),
+ sysconfig.get_config_var('EXT_SUFFIX'))
+
+ @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None,
+ 'EXT_SUFFIX required for this test')
+ def test_SO_in_vars(self):
+ vars = sysconfig.get_config_vars()
+ self.assertIsNotNone(vars['SO'])
+ self.assertEqual(vars['SO'], vars['EXT_SUFFIX'])
def test_suite():
diff --git a/Lib/distutils/tests/test_upload.py b/Lib/distutils/tests/test_upload.py
index a474596e33..1fcba889b2 100644
--- a/Lib/distutils/tests/test_upload.py
+++ b/Lib/distutils/tests/test_upload.py
@@ -77,7 +77,7 @@ class uploadTestCase(PyPIRCCommandTestCase):
cmd.finalize_options()
for attr, waited in (('username', 'me'), ('password', 'secret'),
('realm', 'pypi'),
- ('repository', 'http://pypi.python.org/pypi')):
+ ('repository', 'https://pypi.python.org/pypi')):
self.assertEqual(getattr(cmd, attr), waited)
def test_saved_password(self):
@@ -114,11 +114,12 @@ class uploadTestCase(PyPIRCCommandTestCase):
# what did we send ?
headers = dict(self.last_open.req.headers)
self.assertEqual(headers['Content-length'], '2087')
- self.assert_(headers['Content-type'].startswith('multipart/form-data'))
- self.assertEquals(self.last_open.req.get_method(), 'POST')
- self.assertEquals(self.last_open.req.get_full_url(),
- 'http://pypi.python.org/pypi')
- self.assert_(b'xxx' in self.last_open.req.data)
+ content_type = headers['Content-type']
+ self.assertTrue(content_type.startswith('multipart/form-data'))
+ self.assertEqual(self.last_open.req.get_method(), 'POST')
+ expected_url = 'https://pypi.python.org/pypi'
+ self.assertEqual(self.last_open.req.get_full_url(), expected_url)
+ self.assertTrue(b'xxx' in self.last_open.req.data)
def test_suite():
return unittest.makeSuite(uploadTestCase)
diff --git a/Lib/distutils/tests/test_util.py b/Lib/distutils/tests/test_util.py
index 00219cfdba..548865c02b 100644
--- a/Lib/distutils/tests/test_util.py
+++ b/Lib/distutils/tests/test_util.py
@@ -236,7 +236,7 @@ class UtilTestCase(support.EnvironGuard, unittest.TestCase):
self.assertRaises(DistutilsPlatformError,
change_root, 'c:\\root', 'its\\here')
- # XXX platforms to be covered: os2, mac
+ # XXX platforms to be covered: mac
def test_check_environ(self):
util._environ_checked = 0
diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py
index 67d8166349..efb3834cf5 100644
--- a/Lib/distutils/util.py
+++ b/Lib/distutils/util.py
@@ -6,7 +6,7 @@ one of the other *util.py modules.
import os
import re
-import imp
+import importlib.util
import sys
import string
from distutils.errors import DistutilsPlatformError
@@ -154,12 +154,6 @@ def change_root (new_root, pathname):
path = path[1:]
return os.path.join(new_root, path)
- elif os.name == 'os2':
- (drive, path) = os.path.splitdrive(pathname)
- if path[0] == os.sep:
- path = path[1:]
- return os.path.join(new_root, path)
-
else:
raise DistutilsPlatformError("nothing known about platform '%s'" % os.name)
@@ -213,8 +207,8 @@ def subst_vars (s, local_vars):
def grok_environment_error (exc, prefix="error: "):
- """Generate a useful error message from an EnvironmentError (IOError or
- OSError) exception object. Handles Python 1.5.1 and 1.5.2 styles, and
+ """Generate a useful error message from an OSError
+ exception object. Handles Python 1.5.1 and 1.5.2 styles, and
does what it can to deal with exception objects that don't have a
filename (which happens when the error is due to a two-file operation,
such as 'rename()' or 'link()'. Returns the error message as a string
@@ -459,9 +453,10 @@ byte_compile(files, optimize=%r, force=%r,
# cfile - byte-compiled file
# dfile - purported source filename (same as 'file' by default)
if optimize >= 0:
- cfile = imp.cache_from_source(file, debug_override=not optimize)
+ cfile = importlib.util.cache_from_source(
+ file, debug_override=not optimize)
else:
- cfile = imp.cache_from_source(file)
+ cfile = importlib.util.cache_from_source(file)
dfile = file
if prefix:
if file[:len(prefix)] != prefix:
diff --git a/Lib/doctest.py b/Lib/doctest.py
index 0b485f1f74..ee4e068784 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -62,6 +62,7 @@ __all__ = [
'REPORT_NDIFF',
'REPORT_ONLY_FIRST_FAILURE',
'REPORTING_FLAGS',
+ 'FAIL_FAST',
# 1. Utility Functions
# 2. Example & DocTest
'Example',
@@ -92,6 +93,7 @@ __all__ = [
]
import __future__
+import argparse
import difflib
import inspect
import linecache
@@ -150,11 +152,13 @@ REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
REPORT_NDIFF = register_optionflag('REPORT_NDIFF')
REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE')
+FAIL_FAST = register_optionflag('FAIL_FAST')
REPORTING_FLAGS = (REPORT_UDIFF |
REPORT_CDIFF |
REPORT_NDIFF |
- REPORT_ONLY_FIRST_FAILURE)
+ REPORT_ONLY_FIRST_FAILURE |
+ FAIL_FAST)
# Special string markers for use in `want` strings:
BLANKLINE_MARKER = '<BLANKLINE>'
@@ -212,7 +216,7 @@ def _load_testfile(filename, package, module_relative, encoding):
if module_relative:
package = _normalize_module(package, 3)
filename = _module_relative_path(package, filename)
- if hasattr(package, '__loader__'):
+ if getattr(package, '__loader__', None) is not None:
if hasattr(package.__loader__, 'get_data'):
file_contents = package.__loader__.get_data(filename)
file_contents = file_contents.decode(encoding)
@@ -914,6 +918,8 @@ class DocTestFinder:
return module is inspect.getmodule(object)
elif inspect.isfunction(object):
return module.__dict__ is object.__globals__
+ elif inspect.ismethoddescriptor(object):
+ return module.__name__ == object.__objclass__.__module__
elif inspect.isclass(object):
return module.__name__ == object.__module__
elif hasattr(object, '__module__'):
@@ -946,7 +952,7 @@ class DocTestFinder:
for valname, val in obj.__dict__.items():
valname = '%s.%s' % (name, valname)
# Recurse to functions & classes.
- if ((inspect.isfunction(val) or inspect.isclass(val)) and
+ if ((inspect.isroutine(val) or inspect.isclass(val)) and
self._from_module(module, val)):
self._find(tests, val, valname, module, source_lines,
globs, seen)
@@ -958,9 +964,8 @@ class DocTestFinder:
raise ValueError("DocTestFinder.find: __test__ keys "
"must be strings: %r" %
(type(valname),))
- if not (inspect.isfunction(val) or inspect.isclass(val) or
- inspect.ismethod(val) or inspect.ismodule(val) or
- isinstance(val, str)):
+ if not (inspect.isroutine(val) or inspect.isclass(val) or
+ inspect.ismodule(val) or isinstance(val, str)):
raise ValueError("DocTestFinder.find: __test__ values "
"must be strings, functions, methods, "
"classes, or modules: %r" %
@@ -979,7 +984,7 @@ class DocTestFinder:
val = getattr(obj, valname).__func__
# Recurse to methods, properties, and nested classes.
- if ((inspect.isfunction(val) or inspect.isclass(val) or
+ if ((inspect.isroutine(val) or inspect.isclass(val) or
isinstance(val, property)) and
self._from_module(module, val)):
valname = '%s.%s' % (name, valname)
@@ -1342,6 +1347,9 @@ class DocTestRunner:
else:
assert False, ("unknown outcome", outcome)
+ if failures and self.optionflags & FAIL_FAST:
+ break
+
# Restore the option flags (in case they were modified)
self.optionflags = original_optionflags
@@ -2283,6 +2291,12 @@ class SkipDocTestCase(DocTestCase):
__str__ = shortDescription
+class _DocTestSuite(unittest.TestSuite):
+
+ def _removeTestAtIndex(self, index):
+ pass
+
+
def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
**options):
"""
@@ -2328,7 +2342,7 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
if not tests and sys.flags.optimize >=2:
# Skip doctests when running with -O2
- suite = unittest.TestSuite()
+ suite = _DocTestSuite()
suite.addTest(SkipDocTestCase(module))
return suite
elif not tests:
@@ -2342,7 +2356,7 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
raise ValueError(module, "has no docstrings")
tests.sort()
- suite = unittest.TestSuite()
+ suite = _DocTestSuite()
for test in tests:
if len(test.examples) == 0:
@@ -2452,7 +2466,7 @@ def DocFileSuite(*paths, **kw):
encoding
An encoding that will be used to convert the files to unicode.
"""
- suite = unittest.TestSuite()
+ suite = _DocTestSuite()
# We do this here so that _normalize_module is called at the right
# level. If it were called in DocFileTest, then this function
@@ -2702,13 +2716,30 @@ __test__ = {"_TestClass": _TestClass,
def _test():
- testfiles = [arg for arg in sys.argv[1:] if arg and arg[0] != '-']
- if not testfiles:
- name = os.path.basename(sys.argv[0])
- if '__loader__' in globals(): # python -m
- name, _ = os.path.splitext(name)
- print("usage: {0} [-v] file ...".format(name))
- return 2
+ parser = argparse.ArgumentParser(description="doctest runner")
+ parser.add_argument('-v', '--verbose', action='store_true', default=False,
+ help='print very verbose output for all tests')
+ parser.add_argument('-o', '--option', action='append',
+ choices=OPTIONFLAGS_BY_NAME.keys(), default=[],
+ help=('specify a doctest option flag to apply'
+ ' to the test run; may be specified more'
+ ' than once to apply multiple options'))
+ parser.add_argument('-f', '--fail-fast', action='store_true',
+ help=('stop running tests after first failure (this'
+ ' is a shorthand for -o FAIL_FAST, and is'
+ ' in addition to any other -o options)'))
+ parser.add_argument('file', nargs='+',
+ help='file containing the tests to run')
+ args = parser.parse_args()
+ testfiles = args.file
+ # Verbose used to be handled by the "inspect argv" magic in DocTestRunner,
+ # but since we are using argparse we are passing it manually now.
+ verbose = args.verbose
+ options = 0
+ for option in args.option:
+ options |= OPTIONFLAGS_BY_NAME[option]
+ if args.fail_fast:
+ options |= FAIL_FAST
for filename in testfiles:
if filename.endswith(".py"):
# It is a module -- insert its dir into sys.path and try to
@@ -2718,9 +2749,10 @@ def _test():
sys.path.insert(0, dirname)
m = __import__(filename[:-3])
del sys.path[0]
- failures, _ = testmod(m)
+ failures, _ = testmod(m, verbose=verbose, optionflags=options)
else:
- failures, _ = testfile(filename, module_relative=False)
+ failures, _ = testfile(filename, module_relative=False,
+ verbose=verbose, optionflags=options)
if failures:
return 1
return 0
diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py
index 291437c586..039237936c 100644
--- a/Lib/email/_header_value_parser.py
+++ b/Lib/email/_header_value_parser.py
@@ -368,8 +368,7 @@ class TokenList(list):
yield (indent + ' !! invalid element in token '
'list: {!r}'.format(token))
else:
- for line in token._pp(indent+' '):
- yield line
+ yield from token._pp(indent+' ')
if self.defects:
extra = ' Defects: {}'.format(self.defects)
else:
@@ -1315,24 +1314,22 @@ RouteComponentMarker = ValueTerminal('@', 'route-component-marker')
# Parser
#
-"""Parse strings according to RFC822/2047/2822/5322 rules.
-
-This is a stateless parser. Each get_XXX function accepts a string and
-returns either a Terminal or a TokenList representing the RFC object named
-by the method and a string containing the remaining unparsed characters
-from the input. Thus a parser method consumes the next syntactic construct
-of a given type and returns a token representing the construct plus the
-unparsed remainder of the input string.
-
-For example, if the first element of a structured header is a 'phrase',
-then:
-
- phrase, value = get_phrase(value)
-
-returns the complete phrase from the start of the string value, plus any
-characters left in the string after the phrase is removed.
-
-"""
+# Parse strings according to RFC822/2047/2822/5322 rules.
+#
+# This is a stateless parser. Each get_XXX function accepts a string and
+# returns either a Terminal or a TokenList representing the RFC object named
+# by the method and a string containing the remaining unparsed characters
+# from the input. Thus a parser method consumes the next syntactic construct
+# of a given type and returns a token representing the construct plus the
+# unparsed remainder of the input string.
+#
+# For example, if the first element of a structured header is a 'phrase',
+# then:
+#
+# phrase, value = get_phrase(value)
+#
+# returns the complete phrase from the start of the string value, plus any
+# characters left in the string after the phrase is removed.
_wsp_splitter = re.compile(r'([{}]+)'.format(''.join(WSP))).split
_non_atom_end_matcher = re.compile(r"[^{}]+".format(
diff --git a/Lib/email/contentmanager.py b/Lib/email/contentmanager.py
new file mode 100644
index 0000000000..d3636529b6
--- /dev/null
+++ b/Lib/email/contentmanager.py
@@ -0,0 +1,249 @@
+import binascii
+import email.charset
+import email.message
+import email.errors
+from email import quoprimime
+
+class ContentManager:
+
+ def __init__(self):
+ self.get_handlers = {}
+ self.set_handlers = {}
+
+ def add_get_handler(self, key, handler):
+ self.get_handlers[key] = handler
+
+ def get_content(self, msg, *args, **kw):
+ content_type = msg.get_content_type()
+ if content_type in self.get_handlers:
+ return self.get_handlers[content_type](msg, *args, **kw)
+ maintype = msg.get_content_maintype()
+ if maintype in self.get_handlers:
+ return self.get_handlers[maintype](msg, *args, **kw)
+ if '' in self.get_handlers:
+ return self.get_handlers[''](msg, *args, **kw)
+ raise KeyError(content_type)
+
+ def add_set_handler(self, typekey, handler):
+ self.set_handlers[typekey] = handler
+
+ def set_content(self, msg, obj, *args, **kw):
+ if msg.get_content_maintype() == 'multipart':
+ # XXX: is this error a good idea or not? We can remove it later,
+ # but we can't add it later, so do it for now.
+ raise TypeError("set_content not valid on multipart")
+ handler = self._find_set_handler(msg, obj)
+ msg.clear_content()
+ handler(msg, obj, *args, **kw)
+
+ def _find_set_handler(self, msg, obj):
+ full_path_for_error = None
+ for typ in type(obj).__mro__:
+ if typ in self.set_handlers:
+ return self.set_handlers[typ]
+ qname = typ.__qualname__
+ modname = getattr(typ, '__module__', '')
+ full_path = '.'.join((modname, qname)) if modname else qname
+ if full_path_for_error is None:
+ full_path_for_error = full_path
+ if full_path in self.set_handlers:
+ return self.set_handlers[full_path]
+ if qname in self.set_handlers:
+ return self.set_handlers[qname]
+ name = typ.__name__
+ if name in self.set_handlers:
+ return self.set_handlers[name]
+ if None in self.set_handlers:
+ return self.set_handlers[None]
+ raise KeyError(full_path_for_error)
+
+
+raw_data_manager = ContentManager()
+
+
+def get_text_content(msg, errors='replace'):
+ content = msg.get_payload(decode=True)
+ charset = msg.get_param('charset', 'ASCII')
+ return content.decode(charset, errors=errors)
+raw_data_manager.add_get_handler('text', get_text_content)
+
+
+def get_non_text_content(msg):
+ return msg.get_payload(decode=True)
+for maintype in 'audio image video application'.split():
+ raw_data_manager.add_get_handler(maintype, get_non_text_content)
+
+
+def get_message_content(msg):
+ return msg.get_payload(0)
+for subtype in 'rfc822 external-body'.split():
+ raw_data_manager.add_get_handler('message/'+subtype, get_message_content)
+
+
+def get_and_fixup_unknown_message_content(msg):
+ # If we don't understand a message subtype, we are supposed to treat it as
+ # if it were application/octet-stream, per
+ # tools.ietf.org/html/rfc2046#section-5.2.4. Feedparser doesn't do that,
+ # so do our best to fix things up. Note that it is *not* appropriate to
+ # model message/partial content as Message objects, so they are handled
+ # here as well. (How to reassemble them is out of scope for this comment :)
+ return bytes(msg.get_payload(0))
+raw_data_manager.add_get_handler('message',
+ get_and_fixup_unknown_message_content)
+
+
+def _prepare_set(msg, maintype, subtype, headers):
+ msg['Content-Type'] = '/'.join((maintype, subtype))
+ if headers:
+ if not hasattr(headers[0], 'name'):
+ mp = msg.policy
+ headers = [mp.header_factory(*mp.header_source_parse([header]))
+ for header in headers]
+ try:
+ for header in headers:
+ if header.defects:
+ raise header.defects[0]
+ msg[header.name] = header
+ except email.errors.HeaderDefect as exc:
+ raise ValueError("Invalid header: {}".format(
+ header.fold(policy=msg.policy))) from exc
+
+
+def _finalize_set(msg, disposition, filename, cid, params):
+ if disposition is None and filename is not None:
+ disposition = 'attachment'
+ if disposition is not None:
+ msg['Content-Disposition'] = disposition
+ if filename is not None:
+ msg.set_param('filename',
+ filename,
+ header='Content-Disposition',
+ replace=True)
+ if cid is not None:
+ msg['Content-ID'] = cid
+ if params is not None:
+ for key, value in params.items():
+ msg.set_param(key, value)
+
+
+# XXX: This is a cleaned-up version of base64mime.body_encode. It would
+# be nice to drop both this and quoprimime.body_encode in favor of
+# enhanced binascii routines that accepted a max_line_length parameter.
+def _encode_base64(data, max_line_length):
+ encoded_lines = []
+ unencoded_bytes_per_line = max_line_length * 3 // 4
+ for i in range(0, len(data), unencoded_bytes_per_line):
+ thisline = data[i:i+unencoded_bytes_per_line]
+ encoded_lines.append(binascii.b2a_base64(thisline).decode('ascii'))
+ return ''.join(encoded_lines)
+
+
+def _encode_text(string, charset, cte, policy):
+ lines = string.encode(charset).splitlines()
+ linesep = policy.linesep.encode('ascii')
+ def embeded_body(lines): return linesep.join(lines) + linesep
+ def normal_body(lines): return b'\n'.join(lines) + b'\n'
+ if cte==None:
+ # Use heuristics to decide on the "best" encoding.
+ try:
+ return '7bit', normal_body(lines).decode('ascii')
+ except UnicodeDecodeError:
+ pass
+ if (policy.cte_type == '8bit' and
+ max(len(x) for x in lines) <= policy.max_line_length):
+ return '8bit', normal_body(lines).decode('ascii', 'surrogateescape')
+ sniff = embeded_body(lines[:10])
+ sniff_qp = quoprimime.body_encode(sniff.decode('latin-1'),
+ policy.max_line_length)
+ sniff_base64 = binascii.b2a_base64(sniff)
+ # This is a little unfair to qp; it includes lineseps, base64 doesn't.
+ if len(sniff_qp) > len(sniff_base64):
+ cte = 'base64'
+ else:
+ cte = 'quoted-printable'
+ if len(lines) <= 10:
+ return cte, sniff_qp
+ if cte == '7bit':
+ data = normal_body(lines).decode('ascii')
+ elif cte == '8bit':
+ data = normal_body(lines).decode('ascii', 'surrogateescape')
+ elif cte == 'quoted-printable':
+ data = quoprimime.body_encode(normal_body(lines).decode('latin-1'),
+ policy.max_line_length)
+ elif cte == 'base64':
+ data = _encode_base64(embeded_body(lines), policy.max_line_length)
+ else:
+ raise ValueError("Unknown content transfer encoding {}".format(cte))
+ return cte, data
+
+
+def set_text_content(msg, string, subtype="plain", charset='utf-8', cte=None,
+ disposition=None, filename=None, cid=None,
+ params=None, headers=None):
+ _prepare_set(msg, 'text', subtype, headers)
+ cte, payload = _encode_text(string, charset, cte, msg.policy)
+ msg.set_payload(payload)
+ msg.set_param('charset',
+ email.charset.ALIASES.get(charset, charset),
+ replace=True)
+ msg['Content-Transfer-Encoding'] = cte
+ _finalize_set(msg, disposition, filename, cid, params)
+raw_data_manager.add_set_handler(str, set_text_content)
+
+
+def set_message_content(msg, message, subtype="rfc822", cte=None,
+ disposition=None, filename=None, cid=None,
+ params=None, headers=None):
+ if subtype == 'partial':
+ raise ValueError("message/partial is not supported for Message objects")
+ if subtype == 'rfc822':
+ if cte not in (None, '7bit', '8bit', 'binary'):
+ # http://tools.ietf.org/html/rfc2046#section-5.2.1 mandate.
+ raise ValueError(
+ "message/rfc822 parts do not support cte={}".format(cte))
+ # 8bit will get coerced on serialization if policy.cte_type='7bit'. We
+ # may end up claiming 8bit when it isn't needed, but the only negative
+ # result of that should be a gateway that needs to coerce to 7bit
+ # having to look through the whole embedded message to discover whether
+ # or not it actually has to do anything.
+ cte = '8bit' if cte is None else cte
+ elif subtype == 'external-body':
+ if cte not in (None, '7bit'):
+ # http://tools.ietf.org/html/rfc2046#section-5.2.3 mandate.
+ raise ValueError(
+ "message/external-body parts do not support cte={}".format(cte))
+ cte = '7bit'
+ elif cte is None:
+ # http://tools.ietf.org/html/rfc2046#section-5.2.4 says all future
+ # subtypes should be restricted to 7bit, so assume that.
+ cte = '7bit'
+ _prepare_set(msg, 'message', subtype, headers)
+ msg.set_payload([message])
+ msg['Content-Transfer-Encoding'] = cte
+ _finalize_set(msg, disposition, filename, cid, params)
+raw_data_manager.add_set_handler(email.message.Message, set_message_content)
+
+
+def set_bytes_content(msg, data, maintype, subtype, cte='base64',
+ disposition=None, filename=None, cid=None,
+ params=None, headers=None):
+ _prepare_set(msg, maintype, subtype, headers)
+ if cte == 'base64':
+ data = _encode_base64(data, max_line_length=msg.policy.max_line_length)
+ elif cte == 'quoted-printable':
+ # XXX: quoprimime.body_encode won't encode newline characters in data,
+ # so we can't use it. This means max_line_length is ignored. Another
+ # bug to fix later. (Note: encoders.quopri is broken on line ends.)
+ data = binascii.b2a_qp(data, istext=False, header=False, quotetabs=True)
+ data = data.decode('ascii')
+ elif cte == '7bit':
+ # Make sure it really is only ASCII. The early warning here seems
+ # worth the overhead...if you care write your own content manager :).
+ data.encode('ascii')
+ elif cte in ('8bit', 'binary'):
+ data = data.decode('ascii', 'surrogateescape')
+ msg.set_payload(data)
+ msg['Content-Transfer-Encoding'] = cte
+ _finalize_set(msg, disposition, filename, cid, params)
+for typ in (bytes, bytearray, memoryview):
+ raw_data_manager.add_set_handler(typ, set_bytes_content)
diff --git a/Lib/email/feedparser.py b/Lib/email/feedparser.py
index ea41e9571d..eb75fe3579 100644
--- a/Lib/email/feedparser.py
+++ b/Lib/email/feedparser.py
@@ -98,24 +98,15 @@ class BufferedSubFile(object):
"""Push some new data into this object."""
# Handle any previous leftovers
data, self._partial = self._partial + data, ''
- # Crack into lines, but preserve the newlines on the end of each
- parts = NLCRE_crack.split(data)
- # The *ahem* interesting behaviour of re.split when supplied grouping
- # parentheses is that the last element of the resulting list is the
- # data after the final RE. In the case of a NL/CR terminated string,
- # this is the empty string.
- self._partial = parts.pop()
- #GAN 29Mar09 bugs 1555570, 1721862 Confusion at 8K boundary ending with \r:
- # is there a \n to follow later?
- if not self._partial and parts and parts[-1].endswith('\r'):
- self._partial = parts.pop(-2)+parts.pop()
- # parts is a list of strings, alternating between the line contents
- # and the eol character(s). Gather up a list of lines after
- # re-attaching the newlines.
- lines = []
- for i in range(len(parts) // 2):
- lines.append(parts[i*2] + parts[i*2+1])
- self.pushlines(lines)
+ # Crack into lines, but preserve the linesep characters on the end of each
+ parts = data.splitlines(True)
+ # If the last element of the list does not end in a newline, then treat
+ # it as a partial line. We only check for '\n' here because a line
+ # ending with '\r' might be a line that was split in the middle of a
+ # '\r\n' sequence (see bugs 1555570 and 1721862).
+ if parts and not parts[-1].endswith('\n'):
+ self._partial = parts.pop()
+ self.pushlines(parts)
def pushlines(self, lines):
# Reverse and insert at the front of the lines.
diff --git a/Lib/email/iterators.py b/Lib/email/iterators.py
index 3adc4a04ba..b5502ee975 100644
--- a/Lib/email/iterators.py
+++ b/Lib/email/iterators.py
@@ -26,8 +26,7 @@ def walk(self):
yield self
if self.is_multipart():
for subpart in self.get_payload():
- for subsubpart in subpart.walk():
- yield subsubpart
+ yield from subpart.walk()
@@ -40,8 +39,7 @@ def body_line_iterator(msg, decode=False):
for subpart in msg.walk():
payload = subpart.get_payload(decode=decode)
if isinstance(payload, str):
- for line in StringIO(payload):
- yield line
+ yield from StringIO(payload)
def typed_subpart_iterator(msg, maintype='text', subtype=None):
diff --git a/Lib/email/message.py b/Lib/email/message.py
index f43a3809be..ce673b0b92 100644
--- a/Lib/email/message.py
+++ b/Lib/email/message.py
@@ -8,8 +8,6 @@ __all__ = ['Message']
import re
import uu
-import base64
-import binascii
from io import BytesIO, StringIO
# Intrapackage imports
@@ -132,22 +130,50 @@ class Message:
def __str__(self):
"""Return the entire formatted message as a string.
- This includes the headers, body, and envelope header.
"""
return self.as_string()
- def as_string(self, unixfrom=False, maxheaderlen=0):
+ def as_string(self, unixfrom=False, maxheaderlen=0, policy=None):
"""Return the entire formatted message as a string.
- Optional `unixfrom' when True, means include the Unix From_ envelope
- header.
- This is a convenience method and may not generate the message exactly
- as you intend. For more flexibility, use the flatten() method of a
- Generator instance.
+ Optional 'unixfrom', when true, means include the Unix From_ envelope
+ header. For backward compatibility reasons, if maxheaderlen is
+ not specified it defaults to 0, so you must override it explicitly
+ if you want a different maxheaderlen. 'policy' is passed to the
+ Generator instance used to serialize the mesasge; if it is not
+ specified the policy associated with the message instance is used.
+
+ If the message object contains binary data that is not encoded
+ according to RFC standards, the non-compliant data will be replaced by
+ unicode "unknown character" code points.
"""
from email.generator import Generator
+ policy = self.policy if policy is None else policy
fp = StringIO()
- g = Generator(fp, mangle_from_=False, maxheaderlen=maxheaderlen)
+ g = Generator(fp,
+ mangle_from_=False,
+ maxheaderlen=maxheaderlen,
+ policy=policy)
+ g.flatten(self, unixfrom=unixfrom)
+ return fp.getvalue()
+
+ def __bytes__(self):
+ """Return the entire formatted message as a bytes object.
+ """
+ return self.as_bytes()
+
+ def as_bytes(self, unixfrom=False, policy=None):
+ """Return the entire formatted message as a bytes object.
+
+ Optional 'unixfrom', when true, means include the Unix From_ envelope
+ header. 'policy' is passed to the BytesGenerator instance used to
+ serialize the message; if not specified the policy associated with
+ the message instance is used.
+ """
+ from email.generator import BytesGenerator
+ policy = self.policy if policy is None else policy
+ fp = BytesIO()
+ g = BytesGenerator(fp, mangle_from_=False, policy=policy)
g.flatten(self, unixfrom=unixfrom)
return fp.getvalue()
@@ -651,7 +677,7 @@ class Message:
return failobj
def set_param(self, param, value, header='Content-Type', requote=True,
- charset=None, language=''):
+ charset=None, language='', replace=False):
"""Set a parameter in the Content-Type header.
If the parameter already exists in the header, its value will be
@@ -695,8 +721,11 @@ class Message:
else:
ctype = SEMISPACE.join([ctype, append_param])
if ctype != self.get(header):
- del self[header]
- self[header] = ctype
+ if replace:
+ self.replace_header(header, ctype)
+ else:
+ del self[header]
+ self[header] = ctype
def del_param(self, param, header='content-type', requote=True):
"""Remove the given parameter completely from the Content-Type header.
@@ -877,3 +906,208 @@ class Message:
# I.e. def walk(self): ...
from email.iterators import walk
+
+
+class MIMEPart(Message):
+
+ def __init__(self, policy=None):
+ if policy is None:
+ from email.policy import default
+ policy = default
+ Message.__init__(self, policy)
+
+ @property
+ def is_attachment(self):
+ c_d = self.get('content-disposition')
+ if c_d is None:
+ return False
+ return c_d.lower() == 'attachment'
+
+ def _find_body(self, part, preferencelist):
+ if part.is_attachment:
+ return
+ maintype, subtype = part.get_content_type().split('/')
+ if maintype == 'text':
+ if subtype in preferencelist:
+ yield (preferencelist.index(subtype), part)
+ return
+ if maintype != 'multipart':
+ return
+ if subtype != 'related':
+ for subpart in part.iter_parts():
+ yield from self._find_body(subpart, preferencelist)
+ return
+ if 'related' in preferencelist:
+ yield (preferencelist.index('related'), part)
+ candidate = None
+ start = part.get_param('start')
+ if start:
+ for subpart in part.iter_parts():
+ if subpart['content-id'] == start:
+ candidate = subpart
+ break
+ if candidate is None:
+ subparts = part.get_payload()
+ candidate = subparts[0] if subparts else None
+ if candidate is not None:
+ yield from self._find_body(candidate, preferencelist)
+
+ def get_body(self, preferencelist=('related', 'html', 'plain')):
+ """Return best candidate mime part for display as 'body' of message.
+
+ Do a depth first search, starting with self, looking for the first part
+ matching each of the items in preferencelist, and return the part
+ corresponding to the first item that has a match, or None if no items
+ have a match. If 'related' is not included in preferencelist, consider
+ the root part of any multipart/related encountered as a candidate
+ match. Ignore parts with 'Content-Disposition: attachment'.
+ """
+ best_prio = len(preferencelist)
+ body = None
+ for prio, part in self._find_body(self, preferencelist):
+ if prio < best_prio:
+ best_prio = prio
+ body = part
+ if prio == 0:
+ break
+ return body
+
+ _body_types = {('text', 'plain'),
+ ('text', 'html'),
+ ('multipart', 'related'),
+ ('multipart', 'alternative')}
+ def iter_attachments(self):
+ """Return an iterator over the non-main parts of a multipart.
+
+ Skip the first of each occurrence of text/plain, text/html,
+ multipart/related, or multipart/alternative in the multipart (unless
+ they have a 'Content-Disposition: attachment' header) and include all
+ remaining subparts in the returned iterator. When applied to a
+ multipart/related, return all parts except the root part. Return an
+ empty iterator when applied to a multipart/alternative or a
+ non-multipart.
+ """
+ maintype, subtype = self.get_content_type().split('/')
+ if maintype != 'multipart' or subtype == 'alternative':
+ return
+ parts = self.get_payload()
+ if maintype == 'multipart' and subtype == 'related':
+ # For related, we treat everything but the root as an attachment.
+ # The root may be indicated by 'start'; if there's no start or we
+ # can't find the named start, treat the first subpart as the root.
+ start = self.get_param('start')
+ if start:
+ found = False
+ attachments = []
+ for part in parts:
+ if part.get('content-id') == start:
+ found = True
+ else:
+ attachments.append(part)
+ if found:
+ yield from attachments
+ return
+ parts.pop(0)
+ yield from parts
+ return
+ # Otherwise we more or less invert the remaining logic in get_body.
+ # This only really works in edge cases (ex: non-text relateds or
+ # alternatives) if the sending agent sets content-disposition.
+ seen = [] # Only skip the first example of each candidate type.
+ for part in parts:
+ maintype, subtype = part.get_content_type().split('/')
+ if ((maintype, subtype) in self._body_types and
+ not part.is_attachment and subtype not in seen):
+ seen.append(subtype)
+ continue
+ yield part
+
+ def iter_parts(self):
+ """Return an iterator over all immediate subparts of a multipart.
+
+ Return an empty iterator for a non-multipart.
+ """
+ if self.get_content_maintype() == 'multipart':
+ yield from self.get_payload()
+
+ def get_content(self, *args, content_manager=None, **kw):
+ if content_manager is None:
+ content_manager = self.policy.content_manager
+ return content_manager.get_content(self, *args, **kw)
+
+ def set_content(self, *args, content_manager=None, **kw):
+ if content_manager is None:
+ content_manager = self.policy.content_manager
+ content_manager.set_content(self, *args, **kw)
+
+ def _make_multipart(self, subtype, disallowed_subtypes, boundary):
+ if self.get_content_maintype() == 'multipart':
+ existing_subtype = self.get_content_subtype()
+ disallowed_subtypes = disallowed_subtypes + (subtype,)
+ if existing_subtype in disallowed_subtypes:
+ raise ValueError("Cannot convert {} to {}".format(
+ existing_subtype, subtype))
+ keep_headers = []
+ part_headers = []
+ for name, value in self._headers:
+ if name.lower().startswith('content-'):
+ part_headers.append((name, value))
+ else:
+ keep_headers.append((name, value))
+ if part_headers:
+ # There is existing content, move it to the first subpart.
+ part = type(self)(policy=self.policy)
+ part._headers = part_headers
+ part._payload = self._payload
+ self._payload = [part]
+ else:
+ self._payload = []
+ self._headers = keep_headers
+ self['Content-Type'] = 'multipart/' + subtype
+ if boundary is not None:
+ self.set_param('boundary', boundary)
+
+ def make_related(self, boundary=None):
+ self._make_multipart('related', ('alternative', 'mixed'), boundary)
+
+ def make_alternative(self, boundary=None):
+ self._make_multipart('alternative', ('mixed',), boundary)
+
+ def make_mixed(self, boundary=None):
+ self._make_multipart('mixed', (), boundary)
+
+ def _add_multipart(self, _subtype, *args, _disp=None, **kw):
+ if (self.get_content_maintype() != 'multipart' or
+ self.get_content_subtype() != _subtype):
+ getattr(self, 'make_' + _subtype)()
+ part = type(self)(policy=self.policy)
+ part.set_content(*args, **kw)
+ if _disp and 'content-disposition' not in part:
+ part['Content-Disposition'] = _disp
+ self.attach(part)
+
+ def add_related(self, *args, **kw):
+ self._add_multipart('related', *args, _disp='inline', **kw)
+
+ def add_alternative(self, *args, **kw):
+ self._add_multipart('alternative', *args, **kw)
+
+ def add_attachment(self, *args, **kw):
+ self._add_multipart('mixed', *args, _disp='attachment', **kw)
+
+ def clear(self):
+ self._headers = []
+ self._payload = None
+
+ def clear_content(self):
+ self._headers = [(n, v) for n, v in self._headers
+ if not n.lower().startswith('content-')]
+ self._payload = None
+
+
+class EmailMessage(MIMEPart):
+
+ def set_content(self, *args, **kw):
+ super().set_content(*args, **kw)
+ if 'MIME-Version' not in self:
+ self['MIME-Version'] = '1.0'
diff --git a/Lib/email/parser.py b/Lib/email/parser.py
index 752bf35a6e..f49d31d43d 100644
--- a/Lib/email/parser.py
+++ b/Lib/email/parser.py
@@ -4,7 +4,8 @@
"""A parser of RFC 2822 and MIME email messages."""
-__all__ = ['Parser', 'HeaderParser', 'BytesParser', 'BytesHeaderParser']
+__all__ = ['Parser', 'HeaderParser', 'BytesParser', 'BytesHeaderParser',
+ 'FeedParser', 'BytesFeedParser']
import warnings
from io import StringIO, TextIOWrapper
diff --git a/Lib/email/policy.py b/Lib/email/policy.py
index 38e88afe1d..f0b20f4b19 100644
--- a/Lib/email/policy.py
+++ b/Lib/email/policy.py
@@ -5,6 +5,7 @@ code that adds all the email6 features.
from email._policybase import Policy, Compat32, compat32, _extend_docstrings
from email.utils import _has_surrogates
from email.headerregistry import HeaderRegistry as HeaderRegistry
+from email.contentmanager import raw_data_manager
__all__ = [
'Compat32',
@@ -58,10 +59,22 @@ class EmailPolicy(Policy):
special treatment, while all other fields are
treated as unstructured. This list will be
completed before the extension is marked stable.)
+
+ content_manager -- an object with at least two methods: get_content
+ and set_content. When the get_content or
+ set_content method of a Message object is called,
+ it calls the corresponding method of this object,
+ passing it the message object as its first argument,
+ and any arguments or keywords that were passed to
+ it as additional arguments. The default
+ content_manager is
+ :data:`~email.contentmanager.raw_data_manager`.
+
"""
refold_source = 'long'
header_factory = HeaderRegistry()
+ content_manager = raw_data_manager
def __init__(self, **kw):
# Ensure that each new instance gets a unique header factory
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
index 93a625c8b4..25b0d561f4 100644
--- a/Lib/email/utils.py
+++ b/Lib/email/utils.py
@@ -54,17 +54,27 @@ TICK = "'"
specialsre = re.compile(r'[][\\()<>@,:;".]')
escapesre = re.compile(r'[\\"]')
-# How to figure out if we are processing strings that come from a byte
-# source with undecodable characters.
-_has_surrogates = re.compile(
- '([^\ud800-\udbff]|\A)[\udc00-\udfff]([^\udc00-\udfff]|\Z)').search
+def _has_surrogates(s):
+ """Return True if s contains surrogate-escaped binary data."""
+ # This check is based on the fact that unless there are surrogates, utf8
+ # (Python's default encoding) can encode any string. This is the fastest
+ # way to check for surrogates, see issue 11454 for timings.
+ try:
+ s.encode()
+ return False
+ except UnicodeEncodeError:
+ return True
# How to deal with a string containing bytes before handing it to the
# application through the 'normal' interface.
def _sanitize(string):
- # Turn any escaped bytes into unicode 'unknown' char.
- original_bytes = string.encode('ascii', 'surrogateescape')
- return original_bytes.decode('ascii', 'replace')
+ # Turn any escaped bytes into unicode 'unknown' char. If the escaped
+ # bytes happen to be utf-8 they will instead get decoded, even if they
+ # were invalid in the charset the source was supposed to be in. This
+ # seems like it is not a bad thing; a defect was still registered.
+ original_bytes = string.encode('utf-8', 'surrogateescape')
+ return original_bytes.decode('utf-8', 'replace')
+
# Helpers
diff --git a/Lib/encodings/aliases.py b/Lib/encodings/aliases.py
index 235deb5c30..5461aa053a 100644
--- a/Lib/encodings/aliases.py
+++ b/Lib/encodings/aliases.py
@@ -33,9 +33,9 @@ aliases = {
'us' : 'ascii',
'us_ascii' : 'ascii',
- ## base64_codec codec
- #'base64' : 'base64_codec',
- #'base_64' : 'base64_codec',
+ # base64_codec codec
+ 'base64' : 'base64_codec',
+ 'base_64' : 'base64_codec',
# big5 codec
'big5_tw' : 'big5',
@@ -45,8 +45,8 @@ aliases = {
'big5_hkscs' : 'big5hkscs',
'hkscs' : 'big5hkscs',
- ## bz2_codec codec
- #'bz2' : 'bz2_codec',
+ # bz2_codec codec
+ 'bz2' : 'bz2_codec',
# cp037 codec
'037' : 'cp037',
@@ -63,6 +63,12 @@ aliases = {
'csibm1026' : 'cp1026',
'ibm1026' : 'cp1026',
+ # cp1125 codec
+ '1125' : 'cp1125',
+ 'ibm1125' : 'cp1125',
+ 'cp866u' : 'cp1125',
+ 'ruscii' : 'cp1125',
+
# cp1140 codec
'1140' : 'cp1140',
'ibm1140' : 'cp1140',
@@ -248,8 +254,8 @@ aliases = {
'cp936' : 'gbk',
'ms936' : 'gbk',
- ## hex_codec codec
- #'hex' : 'hex_codec',
+ # hex_codec codec
+ 'hex' : 'hex_codec',
# hp_roman8 codec
'roman8' : 'hp_roman8',
@@ -450,13 +456,13 @@ aliases = {
'cp154' : 'ptcp154',
'cyrillic_asian' : 'ptcp154',
- ## quopri_codec codec
- #'quopri' : 'quopri_codec',
- #'quoted_printable' : 'quopri_codec',
- #'quotedprintable' : 'quopri_codec',
+ # quopri_codec codec
+ 'quopri' : 'quopri_codec',
+ 'quoted_printable' : 'quopri_codec',
+ 'quotedprintable' : 'quopri_codec',
- ## rot_13 codec
- #'rot13' : 'rot_13',
+ # rot_13 codec
+ 'rot13' : 'rot_13',
# shift_jis codec
'csshiftjis' : 'shift_jis',
@@ -518,12 +524,12 @@ aliases = {
'utf8_ucs2' : 'utf_8',
'utf8_ucs4' : 'utf_8',
- ## uu_codec codec
- #'uu' : 'uu_codec',
+ # uu_codec codec
+ 'uu' : 'uu_codec',
- ## zlib_codec codec
- #'zip' : 'zlib_codec',
- #'zlib' : 'zlib_codec',
+ # zlib_codec codec
+ 'zip' : 'zlib_codec',
+ 'zlib' : 'zlib_codec',
# temporary mac CJK aliases, will be replaced by proper codecs in 3.1
'x_mac_japanese' : 'shift_jis',
diff --git a/Lib/encodings/base64_codec.py b/Lib/encodings/base64_codec.py
index 321a961782..881d1ba0be 100644
--- a/Lib/encodings/base64_codec.py
+++ b/Lib/encodings/base64_codec.py
@@ -52,4 +52,5 @@ def getregentry():
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
+ _is_text_encoding=False,
)
diff --git a/Lib/encodings/bz2_codec.py b/Lib/encodings/bz2_codec.py
index e65d226bfd..fd9495e341 100644
--- a/Lib/encodings/bz2_codec.py
+++ b/Lib/encodings/bz2_codec.py
@@ -74,4 +74,5 @@ def getregentry():
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
+ _is_text_encoding=False,
)
diff --git a/Lib/encodings/cp037.py b/Lib/encodings/cp037.py
index bfe2c1ed17..4edd708f3d 100644
--- a/Lib/encodings/cp037.py
+++ b/Lib/encodings/cp037.py
@@ -301,7 +301,6 @@ decoding_table = (
'\xd9' # 0xFD -> LATIN CAPITAL LETTER U WITH GRAVE
'\xda' # 0xFE -> LATIN CAPITAL LETTER U WITH ACUTE
'\x9f' # 0xFF -> CONTROL
- '\ufffe' ## Widen to UCS2 for optimization
)
### Encoding table
diff --git a/Lib/encodings/cp1125.py b/Lib/encodings/cp1125.py
new file mode 100644
index 0000000000..b1fd69deb0
--- /dev/null
+++ b/Lib/encodings/cp1125.py
@@ -0,0 +1,698 @@
+""" Python Character Mapping Codec for CP1125
+
+"""#"
+
+import codecs
+
+### Codec APIs
+
+class Codec(codecs.Codec):
+
+ def encode(self,input,errors='strict'):
+ return codecs.charmap_encode(input,errors,encoding_map)
+
+ def decode(self,input,errors='strict'):
+ return codecs.charmap_decode(input,errors,decoding_table)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ return codecs.charmap_encode(input,self.errors,encoding_map)[0]
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ return codecs.charmap_decode(input,self.errors,decoding_table)[0]
+
+class StreamWriter(Codec,codecs.StreamWriter):
+ pass
+
+class StreamReader(Codec,codecs.StreamReader):
+ pass
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='cp1125',
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamreader=StreamReader,
+ streamwriter=StreamWriter,
+ )
+
+### Decoding Map
+
+decoding_map = codecs.make_identity_dict(range(256))
+decoding_map.update({
+ 0x0080: 0x0410, # CYRILLIC CAPITAL LETTER A
+ 0x0081: 0x0411, # CYRILLIC CAPITAL LETTER BE
+ 0x0082: 0x0412, # CYRILLIC CAPITAL LETTER VE
+ 0x0083: 0x0413, # CYRILLIC CAPITAL LETTER GHE
+ 0x0084: 0x0414, # CYRILLIC CAPITAL LETTER DE
+ 0x0085: 0x0415, # CYRILLIC CAPITAL LETTER IE
+ 0x0086: 0x0416, # CYRILLIC CAPITAL LETTER ZHE
+ 0x0087: 0x0417, # CYRILLIC CAPITAL LETTER ZE
+ 0x0088: 0x0418, # CYRILLIC CAPITAL LETTER I
+ 0x0089: 0x0419, # CYRILLIC CAPITAL LETTER SHORT I
+ 0x008a: 0x041a, # CYRILLIC CAPITAL LETTER KA
+ 0x008b: 0x041b, # CYRILLIC CAPITAL LETTER EL
+ 0x008c: 0x041c, # CYRILLIC CAPITAL LETTER EM
+ 0x008d: 0x041d, # CYRILLIC CAPITAL LETTER EN
+ 0x008e: 0x041e, # CYRILLIC CAPITAL LETTER O
+ 0x008f: 0x041f, # CYRILLIC CAPITAL LETTER PE
+ 0x0090: 0x0420, # CYRILLIC CAPITAL LETTER ER
+ 0x0091: 0x0421, # CYRILLIC CAPITAL LETTER ES
+ 0x0092: 0x0422, # CYRILLIC CAPITAL LETTER TE
+ 0x0093: 0x0423, # CYRILLIC CAPITAL LETTER U
+ 0x0094: 0x0424, # CYRILLIC CAPITAL LETTER EF
+ 0x0095: 0x0425, # CYRILLIC CAPITAL LETTER HA
+ 0x0096: 0x0426, # CYRILLIC CAPITAL LETTER TSE
+ 0x0097: 0x0427, # CYRILLIC CAPITAL LETTER CHE
+ 0x0098: 0x0428, # CYRILLIC CAPITAL LETTER SHA
+ 0x0099: 0x0429, # CYRILLIC CAPITAL LETTER SHCHA
+ 0x009a: 0x042a, # CYRILLIC CAPITAL LETTER HARD SIGN
+ 0x009b: 0x042b, # CYRILLIC CAPITAL LETTER YERU
+ 0x009c: 0x042c, # CYRILLIC CAPITAL LETTER SOFT SIGN
+ 0x009d: 0x042d, # CYRILLIC CAPITAL LETTER E
+ 0x009e: 0x042e, # CYRILLIC CAPITAL LETTER YU
+ 0x009f: 0x042f, # CYRILLIC CAPITAL LETTER YA
+ 0x00a0: 0x0430, # CYRILLIC SMALL LETTER A
+ 0x00a1: 0x0431, # CYRILLIC SMALL LETTER BE
+ 0x00a2: 0x0432, # CYRILLIC SMALL LETTER VE
+ 0x00a3: 0x0433, # CYRILLIC SMALL LETTER GHE
+ 0x00a4: 0x0434, # CYRILLIC SMALL LETTER DE
+ 0x00a5: 0x0435, # CYRILLIC SMALL LETTER IE
+ 0x00a6: 0x0436, # CYRILLIC SMALL LETTER ZHE
+ 0x00a7: 0x0437, # CYRILLIC SMALL LETTER ZE
+ 0x00a8: 0x0438, # CYRILLIC SMALL LETTER I
+ 0x00a9: 0x0439, # CYRILLIC SMALL LETTER SHORT I
+ 0x00aa: 0x043a, # CYRILLIC SMALL LETTER KA
+ 0x00ab: 0x043b, # CYRILLIC SMALL LETTER EL
+ 0x00ac: 0x043c, # CYRILLIC SMALL LETTER EM
+ 0x00ad: 0x043d, # CYRILLIC SMALL LETTER EN
+ 0x00ae: 0x043e, # CYRILLIC SMALL LETTER O
+ 0x00af: 0x043f, # CYRILLIC SMALL LETTER PE
+ 0x00b0: 0x2591, # LIGHT SHADE
+ 0x00b1: 0x2592, # MEDIUM SHADE
+ 0x00b2: 0x2593, # DARK SHADE
+ 0x00b3: 0x2502, # BOX DRAWINGS LIGHT VERTICAL
+ 0x00b4: 0x2524, # BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ 0x00b5: 0x2561, # BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+ 0x00b6: 0x2562, # BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+ 0x00b7: 0x2556, # BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+ 0x00b8: 0x2555, # BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+ 0x00b9: 0x2563, # BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ 0x00ba: 0x2551, # BOX DRAWINGS DOUBLE VERTICAL
+ 0x00bb: 0x2557, # BOX DRAWINGS DOUBLE DOWN AND LEFT
+ 0x00bc: 0x255d, # BOX DRAWINGS DOUBLE UP AND LEFT
+ 0x00bd: 0x255c, # BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+ 0x00be: 0x255b, # BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+ 0x00bf: 0x2510, # BOX DRAWINGS LIGHT DOWN AND LEFT
+ 0x00c0: 0x2514, # BOX DRAWINGS LIGHT UP AND RIGHT
+ 0x00c1: 0x2534, # BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ 0x00c2: 0x252c, # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ 0x00c3: 0x251c, # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ 0x00c4: 0x2500, # BOX DRAWINGS LIGHT HORIZONTAL
+ 0x00c5: 0x253c, # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ 0x00c6: 0x255e, # BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+ 0x00c7: 0x255f, # BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+ 0x00c8: 0x255a, # BOX DRAWINGS DOUBLE UP AND RIGHT
+ 0x00c9: 0x2554, # BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ 0x00ca: 0x2569, # BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ 0x00cb: 0x2566, # BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ 0x00cc: 0x2560, # BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ 0x00cd: 0x2550, # BOX DRAWINGS DOUBLE HORIZONTAL
+ 0x00ce: 0x256c, # BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ 0x00cf: 0x2567, # BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+ 0x00d0: 0x2568, # BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+ 0x00d1: 0x2564, # BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+ 0x00d2: 0x2565, # BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+ 0x00d3: 0x2559, # BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+ 0x00d4: 0x2558, # BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+ 0x00d5: 0x2552, # BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+ 0x00d6: 0x2553, # BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+ 0x00d7: 0x256b, # BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+ 0x00d8: 0x256a, # BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+ 0x00d9: 0x2518, # BOX DRAWINGS LIGHT UP AND LEFT
+ 0x00da: 0x250c, # BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 0x00db: 0x2588, # FULL BLOCK
+ 0x00dc: 0x2584, # LOWER HALF BLOCK
+ 0x00dd: 0x258c, # LEFT HALF BLOCK
+ 0x00de: 0x2590, # RIGHT HALF BLOCK
+ 0x00df: 0x2580, # UPPER HALF BLOCK
+ 0x00e0: 0x0440, # CYRILLIC SMALL LETTER ER
+ 0x00e1: 0x0441, # CYRILLIC SMALL LETTER ES
+ 0x00e2: 0x0442, # CYRILLIC SMALL LETTER TE
+ 0x00e3: 0x0443, # CYRILLIC SMALL LETTER U
+ 0x00e4: 0x0444, # CYRILLIC SMALL LETTER EF
+ 0x00e5: 0x0445, # CYRILLIC SMALL LETTER HA
+ 0x00e6: 0x0446, # CYRILLIC SMALL LETTER TSE
+ 0x00e7: 0x0447, # CYRILLIC SMALL LETTER CHE
+ 0x00e8: 0x0448, # CYRILLIC SMALL LETTER SHA
+ 0x00e9: 0x0449, # CYRILLIC SMALL LETTER SHCHA
+ 0x00ea: 0x044a, # CYRILLIC SMALL LETTER HARD SIGN
+ 0x00eb: 0x044b, # CYRILLIC SMALL LETTER YERU
+ 0x00ec: 0x044c, # CYRILLIC SMALL LETTER SOFT SIGN
+ 0x00ed: 0x044d, # CYRILLIC SMALL LETTER E
+ 0x00ee: 0x044e, # CYRILLIC SMALL LETTER YU
+ 0x00ef: 0x044f, # CYRILLIC SMALL LETTER YA
+ 0x00f0: 0x0401, # CYRILLIC CAPITAL LETTER IO
+ 0x00f1: 0x0451, # CYRILLIC SMALL LETTER IO
+ 0x00f2: 0x0490, # CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+ 0x00f3: 0x0491, # CYRILLIC SMALL LETTER GHE WITH UPTURN
+ 0x00f4: 0x0404, # CYRILLIC CAPITAL LETTER UKRAINIAN IE
+ 0x00f5: 0x0454, # CYRILLIC SMALL LETTER UKRAINIAN IE
+ 0x00f6: 0x0406, # CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+ 0x00f7: 0x0456, # CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ 0x00f8: 0x0407, # CYRILLIC CAPITAL LETTER YI
+ 0x00f9: 0x0457, # CYRILLIC SMALL LETTER YI
+ 0x00fa: 0x00b7, # MIDDLE DOT
+ 0x00fb: 0x221a, # SQUARE ROOT
+ 0x00fc: 0x2116, # NUMERO SIGN
+ 0x00fd: 0x00a4, # CURRENCY SIGN
+ 0x00fe: 0x25a0, # BLACK SQUARE
+ 0x00ff: 0x00a0, # NO-BREAK SPACE
+})
+
+### Decoding Table
+
+decoding_table = (
+ '\x00' # 0x0000 -> NULL
+ '\x01' # 0x0001 -> START OF HEADING
+ '\x02' # 0x0002 -> START OF TEXT
+ '\x03' # 0x0003 -> END OF TEXT
+ '\x04' # 0x0004 -> END OF TRANSMISSION
+ '\x05' # 0x0005 -> ENQUIRY
+ '\x06' # 0x0006 -> ACKNOWLEDGE
+ '\x07' # 0x0007 -> BELL
+ '\x08' # 0x0008 -> BACKSPACE
+ '\t' # 0x0009 -> HORIZONTAL TABULATION
+ '\n' # 0x000a -> LINE FEED
+ '\x0b' # 0x000b -> VERTICAL TABULATION
+ '\x0c' # 0x000c -> FORM FEED
+ '\r' # 0x000d -> CARRIAGE RETURN
+ '\x0e' # 0x000e -> SHIFT OUT
+ '\x0f' # 0x000f -> SHIFT IN
+ '\x10' # 0x0010 -> DATA LINK ESCAPE
+ '\x11' # 0x0011 -> DEVICE CONTROL ONE
+ '\x12' # 0x0012 -> DEVICE CONTROL TWO
+ '\x13' # 0x0013 -> DEVICE CONTROL THREE
+ '\x14' # 0x0014 -> DEVICE CONTROL FOUR
+ '\x15' # 0x0015 -> NEGATIVE ACKNOWLEDGE
+ '\x16' # 0x0016 -> SYNCHRONOUS IDLE
+ '\x17' # 0x0017 -> END OF TRANSMISSION BLOCK
+ '\x18' # 0x0018 -> CANCEL
+ '\x19' # 0x0019 -> END OF MEDIUM
+ '\x1a' # 0x001a -> SUBSTITUTE
+ '\x1b' # 0x001b -> ESCAPE
+ '\x1c' # 0x001c -> FILE SEPARATOR
+ '\x1d' # 0x001d -> GROUP SEPARATOR
+ '\x1e' # 0x001e -> RECORD SEPARATOR
+ '\x1f' # 0x001f -> UNIT SEPARATOR
+ ' ' # 0x0020 -> SPACE
+ '!' # 0x0021 -> EXCLAMATION MARK
+ '"' # 0x0022 -> QUOTATION MARK
+ '#' # 0x0023 -> NUMBER SIGN
+ '$' # 0x0024 -> DOLLAR SIGN
+ '%' # 0x0025 -> PERCENT SIGN
+ '&' # 0x0026 -> AMPERSAND
+ "'" # 0x0027 -> APOSTROPHE
+ '(' # 0x0028 -> LEFT PARENTHESIS
+ ')' # 0x0029 -> RIGHT PARENTHESIS
+ '*' # 0x002a -> ASTERISK
+ '+' # 0x002b -> PLUS SIGN
+ ',' # 0x002c -> COMMA
+ '-' # 0x002d -> HYPHEN-MINUS
+ '.' # 0x002e -> FULL STOP
+ '/' # 0x002f -> SOLIDUS
+ '0' # 0x0030 -> DIGIT ZERO
+ '1' # 0x0031 -> DIGIT ONE
+ '2' # 0x0032 -> DIGIT TWO
+ '3' # 0x0033 -> DIGIT THREE
+ '4' # 0x0034 -> DIGIT FOUR
+ '5' # 0x0035 -> DIGIT FIVE
+ '6' # 0x0036 -> DIGIT SIX
+ '7' # 0x0037 -> DIGIT SEVEN
+ '8' # 0x0038 -> DIGIT EIGHT
+ '9' # 0x0039 -> DIGIT NINE
+ ':' # 0x003a -> COLON
+ ';' # 0x003b -> SEMICOLON
+ '<' # 0x003c -> LESS-THAN SIGN
+ '=' # 0x003d -> EQUALS SIGN
+ '>' # 0x003e -> GREATER-THAN SIGN
+ '?' # 0x003f -> QUESTION MARK
+ '@' # 0x0040 -> COMMERCIAL AT
+ 'A' # 0x0041 -> LATIN CAPITAL LETTER A
+ 'B' # 0x0042 -> LATIN CAPITAL LETTER B
+ 'C' # 0x0043 -> LATIN CAPITAL LETTER C
+ 'D' # 0x0044 -> LATIN CAPITAL LETTER D
+ 'E' # 0x0045 -> LATIN CAPITAL LETTER E
+ 'F' # 0x0046 -> LATIN CAPITAL LETTER F
+ 'G' # 0x0047 -> LATIN CAPITAL LETTER G
+ 'H' # 0x0048 -> LATIN CAPITAL LETTER H
+ 'I' # 0x0049 -> LATIN CAPITAL LETTER I
+ 'J' # 0x004a -> LATIN CAPITAL LETTER J
+ 'K' # 0x004b -> LATIN CAPITAL LETTER K
+ 'L' # 0x004c -> LATIN CAPITAL LETTER L
+ 'M' # 0x004d -> LATIN CAPITAL LETTER M
+ 'N' # 0x004e -> LATIN CAPITAL LETTER N
+ 'O' # 0x004f -> LATIN CAPITAL LETTER O
+ 'P' # 0x0050 -> LATIN CAPITAL LETTER P
+ 'Q' # 0x0051 -> LATIN CAPITAL LETTER Q
+ 'R' # 0x0052 -> LATIN CAPITAL LETTER R
+ 'S' # 0x0053 -> LATIN CAPITAL LETTER S
+ 'T' # 0x0054 -> LATIN CAPITAL LETTER T
+ 'U' # 0x0055 -> LATIN CAPITAL LETTER U
+ 'V' # 0x0056 -> LATIN CAPITAL LETTER V
+ 'W' # 0x0057 -> LATIN CAPITAL LETTER W
+ 'X' # 0x0058 -> LATIN CAPITAL LETTER X
+ 'Y' # 0x0059 -> LATIN CAPITAL LETTER Y
+ 'Z' # 0x005a -> LATIN CAPITAL LETTER Z
+ '[' # 0x005b -> LEFT SQUARE BRACKET
+ '\\' # 0x005c -> REVERSE SOLIDUS
+ ']' # 0x005d -> RIGHT SQUARE BRACKET
+ '^' # 0x005e -> CIRCUMFLEX ACCENT
+ '_' # 0x005f -> LOW LINE
+ '`' # 0x0060 -> GRAVE ACCENT
+ 'a' # 0x0061 -> LATIN SMALL LETTER A
+ 'b' # 0x0062 -> LATIN SMALL LETTER B
+ 'c' # 0x0063 -> LATIN SMALL LETTER C
+ 'd' # 0x0064 -> LATIN SMALL LETTER D
+ 'e' # 0x0065 -> LATIN SMALL LETTER E
+ 'f' # 0x0066 -> LATIN SMALL LETTER F
+ 'g' # 0x0067 -> LATIN SMALL LETTER G
+ 'h' # 0x0068 -> LATIN SMALL LETTER H
+ 'i' # 0x0069 -> LATIN SMALL LETTER I
+ 'j' # 0x006a -> LATIN SMALL LETTER J
+ 'k' # 0x006b -> LATIN SMALL LETTER K
+ 'l' # 0x006c -> LATIN SMALL LETTER L
+ 'm' # 0x006d -> LATIN SMALL LETTER M
+ 'n' # 0x006e -> LATIN SMALL LETTER N
+ 'o' # 0x006f -> LATIN SMALL LETTER O
+ 'p' # 0x0070 -> LATIN SMALL LETTER P
+ 'q' # 0x0071 -> LATIN SMALL LETTER Q
+ 'r' # 0x0072 -> LATIN SMALL LETTER R
+ 's' # 0x0073 -> LATIN SMALL LETTER S
+ 't' # 0x0074 -> LATIN SMALL LETTER T
+ 'u' # 0x0075 -> LATIN SMALL LETTER U
+ 'v' # 0x0076 -> LATIN SMALL LETTER V
+ 'w' # 0x0077 -> LATIN SMALL LETTER W
+ 'x' # 0x0078 -> LATIN SMALL LETTER X
+ 'y' # 0x0079 -> LATIN SMALL LETTER Y
+ 'z' # 0x007a -> LATIN SMALL LETTER Z
+ '{' # 0x007b -> LEFT CURLY BRACKET
+ '|' # 0x007c -> VERTICAL LINE
+ '}' # 0x007d -> RIGHT CURLY BRACKET
+ '~' # 0x007e -> TILDE
+ '\x7f' # 0x007f -> DELETE
+ '\u0410' # 0x0080 -> CYRILLIC CAPITAL LETTER A
+ '\u0411' # 0x0081 -> CYRILLIC CAPITAL LETTER BE
+ '\u0412' # 0x0082 -> CYRILLIC CAPITAL LETTER VE
+ '\u0413' # 0x0083 -> CYRILLIC CAPITAL LETTER GHE
+ '\u0414' # 0x0084 -> CYRILLIC CAPITAL LETTER DE
+ '\u0415' # 0x0085 -> CYRILLIC CAPITAL LETTER IE
+ '\u0416' # 0x0086 -> CYRILLIC CAPITAL LETTER ZHE
+ '\u0417' # 0x0087 -> CYRILLIC CAPITAL LETTER ZE
+ '\u0418' # 0x0088 -> CYRILLIC CAPITAL LETTER I
+ '\u0419' # 0x0089 -> CYRILLIC CAPITAL LETTER SHORT I
+ '\u041a' # 0x008a -> CYRILLIC CAPITAL LETTER KA
+ '\u041b' # 0x008b -> CYRILLIC CAPITAL LETTER EL
+ '\u041c' # 0x008c -> CYRILLIC CAPITAL LETTER EM
+ '\u041d' # 0x008d -> CYRILLIC CAPITAL LETTER EN
+ '\u041e' # 0x008e -> CYRILLIC CAPITAL LETTER O
+ '\u041f' # 0x008f -> CYRILLIC CAPITAL LETTER PE
+ '\u0420' # 0x0090 -> CYRILLIC CAPITAL LETTER ER
+ '\u0421' # 0x0091 -> CYRILLIC CAPITAL LETTER ES
+ '\u0422' # 0x0092 -> CYRILLIC CAPITAL LETTER TE
+ '\u0423' # 0x0093 -> CYRILLIC CAPITAL LETTER U
+ '\u0424' # 0x0094 -> CYRILLIC CAPITAL LETTER EF
+ '\u0425' # 0x0095 -> CYRILLIC CAPITAL LETTER HA
+ '\u0426' # 0x0096 -> CYRILLIC CAPITAL LETTER TSE
+ '\u0427' # 0x0097 -> CYRILLIC CAPITAL LETTER CHE
+ '\u0428' # 0x0098 -> CYRILLIC CAPITAL LETTER SHA
+ '\u0429' # 0x0099 -> CYRILLIC CAPITAL LETTER SHCHA
+ '\u042a' # 0x009a -> CYRILLIC CAPITAL LETTER HARD SIGN
+ '\u042b' # 0x009b -> CYRILLIC CAPITAL LETTER YERU
+ '\u042c' # 0x009c -> CYRILLIC CAPITAL LETTER SOFT SIGN
+ '\u042d' # 0x009d -> CYRILLIC CAPITAL LETTER E
+ '\u042e' # 0x009e -> CYRILLIC CAPITAL LETTER YU
+ '\u042f' # 0x009f -> CYRILLIC CAPITAL LETTER YA
+ '\u0430' # 0x00a0 -> CYRILLIC SMALL LETTER A
+ '\u0431' # 0x00a1 -> CYRILLIC SMALL LETTER BE
+ '\u0432' # 0x00a2 -> CYRILLIC SMALL LETTER VE
+ '\u0433' # 0x00a3 -> CYRILLIC SMALL LETTER GHE
+ '\u0434' # 0x00a4 -> CYRILLIC SMALL LETTER DE
+ '\u0435' # 0x00a5 -> CYRILLIC SMALL LETTER IE
+ '\u0436' # 0x00a6 -> CYRILLIC SMALL LETTER ZHE
+ '\u0437' # 0x00a7 -> CYRILLIC SMALL LETTER ZE
+ '\u0438' # 0x00a8 -> CYRILLIC SMALL LETTER I
+ '\u0439' # 0x00a9 -> CYRILLIC SMALL LETTER SHORT I
+ '\u043a' # 0x00aa -> CYRILLIC SMALL LETTER KA
+ '\u043b' # 0x00ab -> CYRILLIC SMALL LETTER EL
+ '\u043c' # 0x00ac -> CYRILLIC SMALL LETTER EM
+ '\u043d' # 0x00ad -> CYRILLIC SMALL LETTER EN
+ '\u043e' # 0x00ae -> CYRILLIC SMALL LETTER O
+ '\u043f' # 0x00af -> CYRILLIC SMALL LETTER PE
+ '\u2591' # 0x00b0 -> LIGHT SHADE
+ '\u2592' # 0x00b1 -> MEDIUM SHADE
+ '\u2593' # 0x00b2 -> DARK SHADE
+ '\u2502' # 0x00b3 -> BOX DRAWINGS LIGHT VERTICAL
+ '\u2524' # 0x00b4 -> BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ '\u2561' # 0x00b5 -> BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+ '\u2562' # 0x00b6 -> BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+ '\u2556' # 0x00b7 -> BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+ '\u2555' # 0x00b8 -> BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+ '\u2563' # 0x00b9 -> BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ '\u2551' # 0x00ba -> BOX DRAWINGS DOUBLE VERTICAL
+ '\u2557' # 0x00bb -> BOX DRAWINGS DOUBLE DOWN AND LEFT
+ '\u255d' # 0x00bc -> BOX DRAWINGS DOUBLE UP AND LEFT
+ '\u255c' # 0x00bd -> BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+ '\u255b' # 0x00be -> BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+ '\u2510' # 0x00bf -> BOX DRAWINGS LIGHT DOWN AND LEFT
+ '\u2514' # 0x00c0 -> BOX DRAWINGS LIGHT UP AND RIGHT
+ '\u2534' # 0x00c1 -> BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ '\u252c' # 0x00c2 -> BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ '\u251c' # 0x00c3 -> BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ '\u2500' # 0x00c4 -> BOX DRAWINGS LIGHT HORIZONTAL
+ '\u253c' # 0x00c5 -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ '\u255e' # 0x00c6 -> BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+ '\u255f' # 0x00c7 -> BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+ '\u255a' # 0x00c8 -> BOX DRAWINGS DOUBLE UP AND RIGHT
+ '\u2554' # 0x00c9 -> BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ '\u2569' # 0x00ca -> BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ '\u2566' # 0x00cb -> BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ '\u2560' # 0x00cc -> BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ '\u2550' # 0x00cd -> BOX DRAWINGS DOUBLE HORIZONTAL
+ '\u256c' # 0x00ce -> BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ '\u2567' # 0x00cf -> BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+ '\u2568' # 0x00d0 -> BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+ '\u2564' # 0x00d1 -> BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+ '\u2565' # 0x00d2 -> BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+ '\u2559' # 0x00d3 -> BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+ '\u2558' # 0x00d4 -> BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+ '\u2552' # 0x00d5 -> BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+ '\u2553' # 0x00d6 -> BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+ '\u256b' # 0x00d7 -> BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+ '\u256a' # 0x00d8 -> BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+ '\u2518' # 0x00d9 -> BOX DRAWINGS LIGHT UP AND LEFT
+ '\u250c' # 0x00da -> BOX DRAWINGS LIGHT DOWN AND RIGHT
+ '\u2588' # 0x00db -> FULL BLOCK
+ '\u2584' # 0x00dc -> LOWER HALF BLOCK
+ '\u258c' # 0x00dd -> LEFT HALF BLOCK
+ '\u2590' # 0x00de -> RIGHT HALF BLOCK
+ '\u2580' # 0x00df -> UPPER HALF BLOCK
+ '\u0440' # 0x00e0 -> CYRILLIC SMALL LETTER ER
+ '\u0441' # 0x00e1 -> CYRILLIC SMALL LETTER ES
+ '\u0442' # 0x00e2 -> CYRILLIC SMALL LETTER TE
+ '\u0443' # 0x00e3 -> CYRILLIC SMALL LETTER U
+ '\u0444' # 0x00e4 -> CYRILLIC SMALL LETTER EF
+ '\u0445' # 0x00e5 -> CYRILLIC SMALL LETTER HA
+ '\u0446' # 0x00e6 -> CYRILLIC SMALL LETTER TSE
+ '\u0447' # 0x00e7 -> CYRILLIC SMALL LETTER CHE
+ '\u0448' # 0x00e8 -> CYRILLIC SMALL LETTER SHA
+ '\u0449' # 0x00e9 -> CYRILLIC SMALL LETTER SHCHA
+ '\u044a' # 0x00ea -> CYRILLIC SMALL LETTER HARD SIGN
+ '\u044b' # 0x00eb -> CYRILLIC SMALL LETTER YERU
+ '\u044c' # 0x00ec -> CYRILLIC SMALL LETTER SOFT SIGN
+ '\u044d' # 0x00ed -> CYRILLIC SMALL LETTER E
+ '\u044e' # 0x00ee -> CYRILLIC SMALL LETTER YU
+ '\u044f' # 0x00ef -> CYRILLIC SMALL LETTER YA
+ '\u0401' # 0x00f0 -> CYRILLIC CAPITAL LETTER IO
+ '\u0451' # 0x00f1 -> CYRILLIC SMALL LETTER IO
+ '\u0490' # 0x00f2 -> CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+ '\u0491' # 0x00f3 -> CYRILLIC SMALL LETTER GHE WITH UPTURN
+ '\u0404' # 0x00f4 -> CYRILLIC CAPITAL LETTER UKRAINIAN IE
+ '\u0454' # 0x00f5 -> CYRILLIC SMALL LETTER UKRAINIAN IE
+ '\u0406' # 0x00f6 -> CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+ '\u0456' # 0x00f7 -> CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ '\u0407' # 0x00f8 -> CYRILLIC CAPITAL LETTER YI
+ '\u0457' # 0x00f9 -> CYRILLIC SMALL LETTER YI
+ '\xb7' # 0x00fa -> MIDDLE DOT
+ '\u221a' # 0x00fb -> SQUARE ROOT
+ '\u2116' # 0x00fc -> NUMERO SIGN
+ '\xa4' # 0x00fd -> CURRENCY SIGN
+ '\u25a0' # 0x00fe -> BLACK SQUARE
+ '\xa0' # 0x00ff -> NO-BREAK SPACE
+)
+
+### Encoding Map
+
+encoding_map = {
+ 0x0000: 0x0000, # NULL
+ 0x0001: 0x0001, # START OF HEADING
+ 0x0002: 0x0002, # START OF TEXT
+ 0x0003: 0x0003, # END OF TEXT
+ 0x0004: 0x0004, # END OF TRANSMISSION
+ 0x0005: 0x0005, # ENQUIRY
+ 0x0006: 0x0006, # ACKNOWLEDGE
+ 0x0007: 0x0007, # BELL
+ 0x0008: 0x0008, # BACKSPACE
+ 0x0009: 0x0009, # HORIZONTAL TABULATION
+ 0x000a: 0x000a, # LINE FEED
+ 0x000b: 0x000b, # VERTICAL TABULATION
+ 0x000c: 0x000c, # FORM FEED
+ 0x000d: 0x000d, # CARRIAGE RETURN
+ 0x000e: 0x000e, # SHIFT OUT
+ 0x000f: 0x000f, # SHIFT IN
+ 0x0010: 0x0010, # DATA LINK ESCAPE
+ 0x0011: 0x0011, # DEVICE CONTROL ONE
+ 0x0012: 0x0012, # DEVICE CONTROL TWO
+ 0x0013: 0x0013, # DEVICE CONTROL THREE
+ 0x0014: 0x0014, # DEVICE CONTROL FOUR
+ 0x0015: 0x0015, # NEGATIVE ACKNOWLEDGE
+ 0x0016: 0x0016, # SYNCHRONOUS IDLE
+ 0x0017: 0x0017, # END OF TRANSMISSION BLOCK
+ 0x0018: 0x0018, # CANCEL
+ 0x0019: 0x0019, # END OF MEDIUM
+ 0x001a: 0x001a, # SUBSTITUTE
+ 0x001b: 0x001b, # ESCAPE
+ 0x001c: 0x001c, # FILE SEPARATOR
+ 0x001d: 0x001d, # GROUP SEPARATOR
+ 0x001e: 0x001e, # RECORD SEPARATOR
+ 0x001f: 0x001f, # UNIT SEPARATOR
+ 0x0020: 0x0020, # SPACE
+ 0x0021: 0x0021, # EXCLAMATION MARK
+ 0x0022: 0x0022, # QUOTATION MARK
+ 0x0023: 0x0023, # NUMBER SIGN
+ 0x0024: 0x0024, # DOLLAR SIGN
+ 0x0025: 0x0025, # PERCENT SIGN
+ 0x0026: 0x0026, # AMPERSAND
+ 0x0027: 0x0027, # APOSTROPHE
+ 0x0028: 0x0028, # LEFT PARENTHESIS
+ 0x0029: 0x0029, # RIGHT PARENTHESIS
+ 0x002a: 0x002a, # ASTERISK
+ 0x002b: 0x002b, # PLUS SIGN
+ 0x002c: 0x002c, # COMMA
+ 0x002d: 0x002d, # HYPHEN-MINUS
+ 0x002e: 0x002e, # FULL STOP
+ 0x002f: 0x002f, # SOLIDUS
+ 0x0030: 0x0030, # DIGIT ZERO
+ 0x0031: 0x0031, # DIGIT ONE
+ 0x0032: 0x0032, # DIGIT TWO
+ 0x0033: 0x0033, # DIGIT THREE
+ 0x0034: 0x0034, # DIGIT FOUR
+ 0x0035: 0x0035, # DIGIT FIVE
+ 0x0036: 0x0036, # DIGIT SIX
+ 0x0037: 0x0037, # DIGIT SEVEN
+ 0x0038: 0x0038, # DIGIT EIGHT
+ 0x0039: 0x0039, # DIGIT NINE
+ 0x003a: 0x003a, # COLON
+ 0x003b: 0x003b, # SEMICOLON
+ 0x003c: 0x003c, # LESS-THAN SIGN
+ 0x003d: 0x003d, # EQUALS SIGN
+ 0x003e: 0x003e, # GREATER-THAN SIGN
+ 0x003f: 0x003f, # QUESTION MARK
+ 0x0040: 0x0040, # COMMERCIAL AT
+ 0x0041: 0x0041, # LATIN CAPITAL LETTER A
+ 0x0042: 0x0042, # LATIN CAPITAL LETTER B
+ 0x0043: 0x0043, # LATIN CAPITAL LETTER C
+ 0x0044: 0x0044, # LATIN CAPITAL LETTER D
+ 0x0045: 0x0045, # LATIN CAPITAL LETTER E
+ 0x0046: 0x0046, # LATIN CAPITAL LETTER F
+ 0x0047: 0x0047, # LATIN CAPITAL LETTER G
+ 0x0048: 0x0048, # LATIN CAPITAL LETTER H
+ 0x0049: 0x0049, # LATIN CAPITAL LETTER I
+ 0x004a: 0x004a, # LATIN CAPITAL LETTER J
+ 0x004b: 0x004b, # LATIN CAPITAL LETTER K
+ 0x004c: 0x004c, # LATIN CAPITAL LETTER L
+ 0x004d: 0x004d, # LATIN CAPITAL LETTER M
+ 0x004e: 0x004e, # LATIN CAPITAL LETTER N
+ 0x004f: 0x004f, # LATIN CAPITAL LETTER O
+ 0x0050: 0x0050, # LATIN CAPITAL LETTER P
+ 0x0051: 0x0051, # LATIN CAPITAL LETTER Q
+ 0x0052: 0x0052, # LATIN CAPITAL LETTER R
+ 0x0053: 0x0053, # LATIN CAPITAL LETTER S
+ 0x0054: 0x0054, # LATIN CAPITAL LETTER T
+ 0x0055: 0x0055, # LATIN CAPITAL LETTER U
+ 0x0056: 0x0056, # LATIN CAPITAL LETTER V
+ 0x0057: 0x0057, # LATIN CAPITAL LETTER W
+ 0x0058: 0x0058, # LATIN CAPITAL LETTER X
+ 0x0059: 0x0059, # LATIN CAPITAL LETTER Y
+ 0x005a: 0x005a, # LATIN CAPITAL LETTER Z
+ 0x005b: 0x005b, # LEFT SQUARE BRACKET
+ 0x005c: 0x005c, # REVERSE SOLIDUS
+ 0x005d: 0x005d, # RIGHT SQUARE BRACKET
+ 0x005e: 0x005e, # CIRCUMFLEX ACCENT
+ 0x005f: 0x005f, # LOW LINE
+ 0x0060: 0x0060, # GRAVE ACCENT
+ 0x0061: 0x0061, # LATIN SMALL LETTER A
+ 0x0062: 0x0062, # LATIN SMALL LETTER B
+ 0x0063: 0x0063, # LATIN SMALL LETTER C
+ 0x0064: 0x0064, # LATIN SMALL LETTER D
+ 0x0065: 0x0065, # LATIN SMALL LETTER E
+ 0x0066: 0x0066, # LATIN SMALL LETTER F
+ 0x0067: 0x0067, # LATIN SMALL LETTER G
+ 0x0068: 0x0068, # LATIN SMALL LETTER H
+ 0x0069: 0x0069, # LATIN SMALL LETTER I
+ 0x006a: 0x006a, # LATIN SMALL LETTER J
+ 0x006b: 0x006b, # LATIN SMALL LETTER K
+ 0x006c: 0x006c, # LATIN SMALL LETTER L
+ 0x006d: 0x006d, # LATIN SMALL LETTER M
+ 0x006e: 0x006e, # LATIN SMALL LETTER N
+ 0x006f: 0x006f, # LATIN SMALL LETTER O
+ 0x0070: 0x0070, # LATIN SMALL LETTER P
+ 0x0071: 0x0071, # LATIN SMALL LETTER Q
+ 0x0072: 0x0072, # LATIN SMALL LETTER R
+ 0x0073: 0x0073, # LATIN SMALL LETTER S
+ 0x0074: 0x0074, # LATIN SMALL LETTER T
+ 0x0075: 0x0075, # LATIN SMALL LETTER U
+ 0x0076: 0x0076, # LATIN SMALL LETTER V
+ 0x0077: 0x0077, # LATIN SMALL LETTER W
+ 0x0078: 0x0078, # LATIN SMALL LETTER X
+ 0x0079: 0x0079, # LATIN SMALL LETTER Y
+ 0x007a: 0x007a, # LATIN SMALL LETTER Z
+ 0x007b: 0x007b, # LEFT CURLY BRACKET
+ 0x007c: 0x007c, # VERTICAL LINE
+ 0x007d: 0x007d, # RIGHT CURLY BRACKET
+ 0x007e: 0x007e, # TILDE
+ 0x007f: 0x007f, # DELETE
+ 0x00a0: 0x00ff, # NO-BREAK SPACE
+ 0x00a4: 0x00fd, # CURRENCY SIGN
+ 0x00b7: 0x00fa, # MIDDLE DOT
+ 0x0401: 0x00f0, # CYRILLIC CAPITAL LETTER IO
+ 0x0404: 0x00f4, # CYRILLIC CAPITAL LETTER UKRAINIAN IE
+ 0x0406: 0x00f6, # CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+ 0x0407: 0x00f8, # CYRILLIC CAPITAL LETTER YI
+ 0x0410: 0x0080, # CYRILLIC CAPITAL LETTER A
+ 0x0411: 0x0081, # CYRILLIC CAPITAL LETTER BE
+ 0x0412: 0x0082, # CYRILLIC CAPITAL LETTER VE
+ 0x0413: 0x0083, # CYRILLIC CAPITAL LETTER GHE
+ 0x0414: 0x0084, # CYRILLIC CAPITAL LETTER DE
+ 0x0415: 0x0085, # CYRILLIC CAPITAL LETTER IE
+ 0x0416: 0x0086, # CYRILLIC CAPITAL LETTER ZHE
+ 0x0417: 0x0087, # CYRILLIC CAPITAL LETTER ZE
+ 0x0418: 0x0088, # CYRILLIC CAPITAL LETTER I
+ 0x0419: 0x0089, # CYRILLIC CAPITAL LETTER SHORT I
+ 0x041a: 0x008a, # CYRILLIC CAPITAL LETTER KA
+ 0x041b: 0x008b, # CYRILLIC CAPITAL LETTER EL
+ 0x041c: 0x008c, # CYRILLIC CAPITAL LETTER EM
+ 0x041d: 0x008d, # CYRILLIC CAPITAL LETTER EN
+ 0x041e: 0x008e, # CYRILLIC CAPITAL LETTER O
+ 0x041f: 0x008f, # CYRILLIC CAPITAL LETTER PE
+ 0x0420: 0x0090, # CYRILLIC CAPITAL LETTER ER
+ 0x0421: 0x0091, # CYRILLIC CAPITAL LETTER ES
+ 0x0422: 0x0092, # CYRILLIC CAPITAL LETTER TE
+ 0x0423: 0x0093, # CYRILLIC CAPITAL LETTER U
+ 0x0424: 0x0094, # CYRILLIC CAPITAL LETTER EF
+ 0x0425: 0x0095, # CYRILLIC CAPITAL LETTER HA
+ 0x0426: 0x0096, # CYRILLIC CAPITAL LETTER TSE
+ 0x0427: 0x0097, # CYRILLIC CAPITAL LETTER CHE
+ 0x0428: 0x0098, # CYRILLIC CAPITAL LETTER SHA
+ 0x0429: 0x0099, # CYRILLIC CAPITAL LETTER SHCHA
+ 0x042a: 0x009a, # CYRILLIC CAPITAL LETTER HARD SIGN
+ 0x042b: 0x009b, # CYRILLIC CAPITAL LETTER YERU
+ 0x042c: 0x009c, # CYRILLIC CAPITAL LETTER SOFT SIGN
+ 0x042d: 0x009d, # CYRILLIC CAPITAL LETTER E
+ 0x042e: 0x009e, # CYRILLIC CAPITAL LETTER YU
+ 0x042f: 0x009f, # CYRILLIC CAPITAL LETTER YA
+ 0x0430: 0x00a0, # CYRILLIC SMALL LETTER A
+ 0x0431: 0x00a1, # CYRILLIC SMALL LETTER BE
+ 0x0432: 0x00a2, # CYRILLIC SMALL LETTER VE
+ 0x0433: 0x00a3, # CYRILLIC SMALL LETTER GHE
+ 0x0434: 0x00a4, # CYRILLIC SMALL LETTER DE
+ 0x0435: 0x00a5, # CYRILLIC SMALL LETTER IE
+ 0x0436: 0x00a6, # CYRILLIC SMALL LETTER ZHE
+ 0x0437: 0x00a7, # CYRILLIC SMALL LETTER ZE
+ 0x0438: 0x00a8, # CYRILLIC SMALL LETTER I
+ 0x0439: 0x00a9, # CYRILLIC SMALL LETTER SHORT I
+ 0x043a: 0x00aa, # CYRILLIC SMALL LETTER KA
+ 0x043b: 0x00ab, # CYRILLIC SMALL LETTER EL
+ 0x043c: 0x00ac, # CYRILLIC SMALL LETTER EM
+ 0x043d: 0x00ad, # CYRILLIC SMALL LETTER EN
+ 0x043e: 0x00ae, # CYRILLIC SMALL LETTER O
+ 0x043f: 0x00af, # CYRILLIC SMALL LETTER PE
+ 0x0440: 0x00e0, # CYRILLIC SMALL LETTER ER
+ 0x0441: 0x00e1, # CYRILLIC SMALL LETTER ES
+ 0x0442: 0x00e2, # CYRILLIC SMALL LETTER TE
+ 0x0443: 0x00e3, # CYRILLIC SMALL LETTER U
+ 0x0444: 0x00e4, # CYRILLIC SMALL LETTER EF
+ 0x0445: 0x00e5, # CYRILLIC SMALL LETTER HA
+ 0x0446: 0x00e6, # CYRILLIC SMALL LETTER TSE
+ 0x0447: 0x00e7, # CYRILLIC SMALL LETTER CHE
+ 0x0448: 0x00e8, # CYRILLIC SMALL LETTER SHA
+ 0x0449: 0x00e9, # CYRILLIC SMALL LETTER SHCHA
+ 0x044a: 0x00ea, # CYRILLIC SMALL LETTER HARD SIGN
+ 0x044b: 0x00eb, # CYRILLIC SMALL LETTER YERU
+ 0x044c: 0x00ec, # CYRILLIC SMALL LETTER SOFT SIGN
+ 0x044d: 0x00ed, # CYRILLIC SMALL LETTER E
+ 0x044e: 0x00ee, # CYRILLIC SMALL LETTER YU
+ 0x044f: 0x00ef, # CYRILLIC SMALL LETTER YA
+ 0x0451: 0x00f1, # CYRILLIC SMALL LETTER IO
+ 0x0454: 0x00f5, # CYRILLIC SMALL LETTER UKRAINIAN IE
+ 0x0456: 0x00f7, # CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ 0x0457: 0x00f9, # CYRILLIC SMALL LETTER YI
+ 0x0490: 0x00f2, # CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+ 0x0491: 0x00f3, # CYRILLIC SMALL LETTER GHE WITH UPTURN
+ 0x2116: 0x00fc, # NUMERO SIGN
+ 0x221a: 0x00fb, # SQUARE ROOT
+ 0x2500: 0x00c4, # BOX DRAWINGS LIGHT HORIZONTAL
+ 0x2502: 0x00b3, # BOX DRAWINGS LIGHT VERTICAL
+ 0x250c: 0x00da, # BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 0x2510: 0x00bf, # BOX DRAWINGS LIGHT DOWN AND LEFT
+ 0x2514: 0x00c0, # BOX DRAWINGS LIGHT UP AND RIGHT
+ 0x2518: 0x00d9, # BOX DRAWINGS LIGHT UP AND LEFT
+ 0x251c: 0x00c3, # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ 0x2524: 0x00b4, # BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ 0x252c: 0x00c2, # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ 0x2534: 0x00c1, # BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ 0x253c: 0x00c5, # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ 0x2550: 0x00cd, # BOX DRAWINGS DOUBLE HORIZONTAL
+ 0x2551: 0x00ba, # BOX DRAWINGS DOUBLE VERTICAL
+ 0x2552: 0x00d5, # BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+ 0x2553: 0x00d6, # BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+ 0x2554: 0x00c9, # BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ 0x2555: 0x00b8, # BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+ 0x2556: 0x00b7, # BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+ 0x2557: 0x00bb, # BOX DRAWINGS DOUBLE DOWN AND LEFT
+ 0x2558: 0x00d4, # BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+ 0x2559: 0x00d3, # BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+ 0x255a: 0x00c8, # BOX DRAWINGS DOUBLE UP AND RIGHT
+ 0x255b: 0x00be, # BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+ 0x255c: 0x00bd, # BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+ 0x255d: 0x00bc, # BOX DRAWINGS DOUBLE UP AND LEFT
+ 0x255e: 0x00c6, # BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+ 0x255f: 0x00c7, # BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+ 0x2560: 0x00cc, # BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ 0x2561: 0x00b5, # BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+ 0x2562: 0x00b6, # BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+ 0x2563: 0x00b9, # BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ 0x2564: 0x00d1, # BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+ 0x2565: 0x00d2, # BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+ 0x2566: 0x00cb, # BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ 0x2567: 0x00cf, # BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+ 0x2568: 0x00d0, # BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+ 0x2569: 0x00ca, # BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ 0x256a: 0x00d8, # BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+ 0x256b: 0x00d7, # BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+ 0x256c: 0x00ce, # BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ 0x2580: 0x00df, # UPPER HALF BLOCK
+ 0x2584: 0x00dc, # LOWER HALF BLOCK
+ 0x2588: 0x00db, # FULL BLOCK
+ 0x258c: 0x00dd, # LEFT HALF BLOCK
+ 0x2590: 0x00de, # RIGHT HALF BLOCK
+ 0x2591: 0x00b0, # LIGHT SHADE
+ 0x2592: 0x00b1, # MEDIUM SHADE
+ 0x2593: 0x00b2, # DARK SHADE
+ 0x25a0: 0x00fe, # BLACK SQUARE
+}
diff --git a/Lib/encodings/cp273.py b/Lib/encodings/cp273.py
new file mode 100644
index 0000000000..69c6d778c5
--- /dev/null
+++ b/Lib/encodings/cp273.py
@@ -0,0 +1,307 @@
+""" Python Character Mapping Codec cp273 generated from 'python-mappings/CP273.TXT' with gencodec.py.
+
+"""#"
+
+import codecs
+
+### Codec APIs
+
+class Codec(codecs.Codec):
+
+ def encode(self,input,errors='strict'):
+ return codecs.charmap_encode(input,errors,encoding_table)
+
+ def decode(self,input,errors='strict'):
+ return codecs.charmap_decode(input,errors,decoding_table)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ return codecs.charmap_encode(input,self.errors,encoding_table)[0]
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ return codecs.charmap_decode(input,self.errors,decoding_table)[0]
+
+class StreamWriter(Codec,codecs.StreamWriter):
+ pass
+
+class StreamReader(Codec,codecs.StreamReader):
+ pass
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='cp273',
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamreader=StreamReader,
+ streamwriter=StreamWriter,
+ )
+
+
+### Decoding Table
+
+decoding_table = (
+ '\x00' # 0x00 -> NULL (NUL)
+ '\x01' # 0x01 -> START OF HEADING (SOH)
+ '\x02' # 0x02 -> START OF TEXT (STX)
+ '\x03' # 0x03 -> END OF TEXT (ETX)
+ '\x9c' # 0x04 -> STRING TERMINATOR (ST)
+ '\t' # 0x05 -> CHARACTER TABULATION (HT)
+ '\x86' # 0x06 -> START OF SELECTED AREA (SSA)
+ '\x7f' # 0x07 -> DELETE (DEL)
+ '\x97' # 0x08 -> END OF GUARDED AREA (EPA)
+ '\x8d' # 0x09 -> REVERSE LINE FEED (RI)
+ '\x8e' # 0x0A -> SINGLE-SHIFT TWO (SS2)
+ '\x0b' # 0x0B -> LINE TABULATION (VT)
+ '\x0c' # 0x0C -> FORM FEED (FF)
+ '\r' # 0x0D -> CARRIAGE RETURN (CR)
+ '\x0e' # 0x0E -> SHIFT OUT (SO)
+ '\x0f' # 0x0F -> SHIFT IN (SI)
+ '\x10' # 0x10 -> DATALINK ESCAPE (DLE)
+ '\x11' # 0x11 -> DEVICE CONTROL ONE (DC1)
+ '\x12' # 0x12 -> DEVICE CONTROL TWO (DC2)
+ '\x13' # 0x13 -> DEVICE CONTROL THREE (DC3)
+ '\x9d' # 0x14 -> OPERATING SYSTEM COMMAND (OSC)
+ '\x85' # 0x15 -> NEXT LINE (NEL)
+ '\x08' # 0x16 -> BACKSPACE (BS)
+ '\x87' # 0x17 -> END OF SELECTED AREA (ESA)
+ '\x18' # 0x18 -> CANCEL (CAN)
+ '\x19' # 0x19 -> END OF MEDIUM (EM)
+ '\x92' # 0x1A -> PRIVATE USE TWO (PU2)
+ '\x8f' # 0x1B -> SINGLE-SHIFT THREE (SS3)
+ '\x1c' # 0x1C -> FILE SEPARATOR (IS4)
+ '\x1d' # 0x1D -> GROUP SEPARATOR (IS3)
+ '\x1e' # 0x1E -> RECORD SEPARATOR (IS2)
+ '\x1f' # 0x1F -> UNIT SEPARATOR (IS1)
+ '\x80' # 0x20 -> PADDING CHARACTER (PAD)
+ '\x81' # 0x21 -> HIGH OCTET PRESET (HOP)
+ '\x82' # 0x22 -> BREAK PERMITTED HERE (BPH)
+ '\x83' # 0x23 -> NO BREAK HERE (NBH)
+ '\x84' # 0x24 -> INDEX (IND)
+ '\n' # 0x25 -> LINE FEED (LF)
+ '\x17' # 0x26 -> END OF TRANSMISSION BLOCK (ETB)
+ '\x1b' # 0x27 -> ESCAPE (ESC)
+ '\x88' # 0x28 -> CHARACTER TABULATION SET (HTS)
+ '\x89' # 0x29 -> CHARACTER TABULATION WITH JUSTIFICATION (HTJ)
+ '\x8a' # 0x2A -> LINE TABULATION SET (VTS)
+ '\x8b' # 0x2B -> PARTIAL LINE FORWARD (PLD)
+ '\x8c' # 0x2C -> PARTIAL LINE BACKWARD (PLU)
+ '\x05' # 0x2D -> ENQUIRY (ENQ)
+ '\x06' # 0x2E -> ACKNOWLEDGE (ACK)
+ '\x07' # 0x2F -> BELL (BEL)
+ '\x90' # 0x30 -> DEVICE CONTROL STRING (DCS)
+ '\x91' # 0x31 -> PRIVATE USE ONE (PU1)
+ '\x16' # 0x32 -> SYNCHRONOUS IDLE (SYN)
+ '\x93' # 0x33 -> SET TRANSMIT STATE (STS)
+ '\x94' # 0x34 -> CANCEL CHARACTER (CCH)
+ '\x95' # 0x35 -> MESSAGE WAITING (MW)
+ '\x96' # 0x36 -> START OF GUARDED AREA (SPA)
+ '\x04' # 0x37 -> END OF TRANSMISSION (EOT)
+ '\x98' # 0x38 -> START OF STRING (SOS)
+ '\x99' # 0x39 -> SINGLE GRAPHIC CHARACTER INTRODUCER (SGCI)
+ '\x9a' # 0x3A -> SINGLE CHARACTER INTRODUCER (SCI)
+ '\x9b' # 0x3B -> CONTROL SEQUENCE INTRODUCER (CSI)
+ '\x14' # 0x3C -> DEVICE CONTROL FOUR (DC4)
+ '\x15' # 0x3D -> NEGATIVE ACKNOWLEDGE (NAK)
+ '\x9e' # 0x3E -> PRIVACY MESSAGE (PM)
+ '\x1a' # 0x3F -> SUBSTITUTE (SUB)
+ ' ' # 0x40 -> SPACE
+ '\xa0' # 0x41 -> NO-BREAK SPACE
+ '\xe2' # 0x42 -> LATIN SMALL LETTER A WITH CIRCUMFLEX
+ '{' # 0x43 -> LEFT CURLY BRACKET
+ '\xe0' # 0x44 -> LATIN SMALL LETTER A WITH GRAVE
+ '\xe1' # 0x45 -> LATIN SMALL LETTER A WITH ACUTE
+ '\xe3' # 0x46 -> LATIN SMALL LETTER A WITH TILDE
+ '\xe5' # 0x47 -> LATIN SMALL LETTER A WITH RING ABOVE
+ '\xe7' # 0x48 -> LATIN SMALL LETTER C WITH CEDILLA
+ '\xf1' # 0x49 -> LATIN SMALL LETTER N WITH TILDE
+ '\xc4' # 0x4A -> LATIN CAPITAL LETTER A WITH DIAERESIS
+ '.' # 0x4B -> FULL STOP
+ '<' # 0x4C -> LESS-THAN SIGN
+ '(' # 0x4D -> LEFT PARENTHESIS
+ '+' # 0x4E -> PLUS SIGN
+ '!' # 0x4F -> EXCLAMATION MARK
+ '&' # 0x50 -> AMPERSAND
+ '\xe9' # 0x51 -> LATIN SMALL LETTER E WITH ACUTE
+ '\xea' # 0x52 -> LATIN SMALL LETTER E WITH CIRCUMFLEX
+ '\xeb' # 0x53 -> LATIN SMALL LETTER E WITH DIAERESIS
+ '\xe8' # 0x54 -> LATIN SMALL LETTER E WITH GRAVE
+ '\xed' # 0x55 -> LATIN SMALL LETTER I WITH ACUTE
+ '\xee' # 0x56 -> LATIN SMALL LETTER I WITH CIRCUMFLEX
+ '\xef' # 0x57 -> LATIN SMALL LETTER I WITH DIAERESIS
+ '\xec' # 0x58 -> LATIN SMALL LETTER I WITH GRAVE
+ '~' # 0x59 -> TILDE
+ '\xdc' # 0x5A -> LATIN CAPITAL LETTER U WITH DIAERESIS
+ '$' # 0x5B -> DOLLAR SIGN
+ '*' # 0x5C -> ASTERISK
+ ')' # 0x5D -> RIGHT PARENTHESIS
+ ';' # 0x5E -> SEMICOLON
+ '^' # 0x5F -> CIRCUMFLEX ACCENT
+ '-' # 0x60 -> HYPHEN-MINUS
+ '/' # 0x61 -> SOLIDUS
+ '\xc2' # 0x62 -> LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ '[' # 0x63 -> LEFT SQUARE BRACKET
+ '\xc0' # 0x64 -> LATIN CAPITAL LETTER A WITH GRAVE
+ '\xc1' # 0x65 -> LATIN CAPITAL LETTER A WITH ACUTE
+ '\xc3' # 0x66 -> LATIN CAPITAL LETTER A WITH TILDE
+ '\xc5' # 0x67 -> LATIN CAPITAL LETTER A WITH RING ABOVE
+ '\xc7' # 0x68 -> LATIN CAPITAL LETTER C WITH CEDILLA
+ '\xd1' # 0x69 -> LATIN CAPITAL LETTER N WITH TILDE
+ '\xf6' # 0x6A -> LATIN SMALL LETTER O WITH DIAERESIS
+ ',' # 0x6B -> COMMA
+ '%' # 0x6C -> PERCENT SIGN
+ '_' # 0x6D -> LOW LINE
+ '>' # 0x6E -> GREATER-THAN SIGN
+ '?' # 0x6F -> QUESTION MARK
+ '\xf8' # 0x70 -> LATIN SMALL LETTER O WITH STROKE
+ '\xc9' # 0x71 -> LATIN CAPITAL LETTER E WITH ACUTE
+ '\xca' # 0x72 -> LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ '\xcb' # 0x73 -> LATIN CAPITAL LETTER E WITH DIAERESIS
+ '\xc8' # 0x74 -> LATIN CAPITAL LETTER E WITH GRAVE
+ '\xcd' # 0x75 -> LATIN CAPITAL LETTER I WITH ACUTE
+ '\xce' # 0x76 -> LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ '\xcf' # 0x77 -> LATIN CAPITAL LETTER I WITH DIAERESIS
+ '\xcc' # 0x78 -> LATIN CAPITAL LETTER I WITH GRAVE
+ '`' # 0x79 -> GRAVE ACCENT
+ ':' # 0x7A -> COLON
+ '#' # 0x7B -> NUMBER SIGN
+ '\xa7' # 0x7C -> SECTION SIGN
+ "'" # 0x7D -> APOSTROPHE
+ '=' # 0x7E -> EQUALS SIGN
+ '"' # 0x7F -> QUOTATION MARK
+ '\xd8' # 0x80 -> LATIN CAPITAL LETTER O WITH STROKE
+ 'a' # 0x81 -> LATIN SMALL LETTER A
+ 'b' # 0x82 -> LATIN SMALL LETTER B
+ 'c' # 0x83 -> LATIN SMALL LETTER C
+ 'd' # 0x84 -> LATIN SMALL LETTER D
+ 'e' # 0x85 -> LATIN SMALL LETTER E
+ 'f' # 0x86 -> LATIN SMALL LETTER F
+ 'g' # 0x87 -> LATIN SMALL LETTER G
+ 'h' # 0x88 -> LATIN SMALL LETTER H
+ 'i' # 0x89 -> LATIN SMALL LETTER I
+ '\xab' # 0x8A -> LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ '\xbb' # 0x8B -> RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ '\xf0' # 0x8C -> LATIN SMALL LETTER ETH (Icelandic)
+ '\xfd' # 0x8D -> LATIN SMALL LETTER Y WITH ACUTE
+ '\xfe' # 0x8E -> LATIN SMALL LETTER THORN (Icelandic)
+ '\xb1' # 0x8F -> PLUS-MINUS SIGN
+ '\xb0' # 0x90 -> DEGREE SIGN
+ 'j' # 0x91 -> LATIN SMALL LETTER J
+ 'k' # 0x92 -> LATIN SMALL LETTER K
+ 'l' # 0x93 -> LATIN SMALL LETTER L
+ 'm' # 0x94 -> LATIN SMALL LETTER M
+ 'n' # 0x95 -> LATIN SMALL LETTER N
+ 'o' # 0x96 -> LATIN SMALL LETTER O
+ 'p' # 0x97 -> LATIN SMALL LETTER P
+ 'q' # 0x98 -> LATIN SMALL LETTER Q
+ 'r' # 0x99 -> LATIN SMALL LETTER R
+ '\xaa' # 0x9A -> FEMININE ORDINAL INDICATOR
+ '\xba' # 0x9B -> MASCULINE ORDINAL INDICATOR
+ '\xe6' # 0x9C -> LATIN SMALL LETTER AE
+ '\xb8' # 0x9D -> CEDILLA
+ '\xc6' # 0x9E -> LATIN CAPITAL LETTER AE
+ '\xa4' # 0x9F -> CURRENCY SIGN
+ '\xb5' # 0xA0 -> MICRO SIGN
+ '\xdf' # 0xA1 -> LATIN SMALL LETTER SHARP S (German)
+ 's' # 0xA2 -> LATIN SMALL LETTER S
+ 't' # 0xA3 -> LATIN SMALL LETTER T
+ 'u' # 0xA4 -> LATIN SMALL LETTER U
+ 'v' # 0xA5 -> LATIN SMALL LETTER V
+ 'w' # 0xA6 -> LATIN SMALL LETTER W
+ 'x' # 0xA7 -> LATIN SMALL LETTER X
+ 'y' # 0xA8 -> LATIN SMALL LETTER Y
+ 'z' # 0xA9 -> LATIN SMALL LETTER Z
+ '\xa1' # 0xAA -> INVERTED EXCLAMATION MARK
+ '\xbf' # 0xAB -> INVERTED QUESTION MARK
+ '\xd0' # 0xAC -> LATIN CAPITAL LETTER ETH (Icelandic)
+ '\xdd' # 0xAD -> LATIN CAPITAL LETTER Y WITH ACUTE
+ '\xde' # 0xAE -> LATIN CAPITAL LETTER THORN (Icelandic)
+ '\xae' # 0xAF -> REGISTERED SIGN
+ '\xa2' # 0xB0 -> CENT SIGN
+ '\xa3' # 0xB1 -> POUND SIGN
+ '\xa5' # 0xB2 -> YEN SIGN
+ '\xb7' # 0xB3 -> MIDDLE DOT
+ '\xa9' # 0xB4 -> COPYRIGHT SIGN
+ '@' # 0xB5 -> COMMERCIAL AT
+ '\xb6' # 0xB6 -> PILCROW SIGN
+ '\xbc' # 0xB7 -> VULGAR FRACTION ONE QUARTER
+ '\xbd' # 0xB8 -> VULGAR FRACTION ONE HALF
+ '\xbe' # 0xB9 -> VULGAR FRACTION THREE QUARTERS
+ '\xac' # 0xBA -> NOT SIGN
+ '|' # 0xBB -> VERTICAL LINE
+ '\u203e' # 0xBC -> OVERLINE
+ '\xa8' # 0xBD -> DIAERESIS
+ '\xb4' # 0xBE -> ACUTE ACCENT
+ '\xd7' # 0xBF -> MULTIPLICATION SIGN
+ '\xe4' # 0xC0 -> LATIN SMALL LETTER A WITH DIAERESIS
+ 'A' # 0xC1 -> LATIN CAPITAL LETTER A
+ 'B' # 0xC2 -> LATIN CAPITAL LETTER B
+ 'C' # 0xC3 -> LATIN CAPITAL LETTER C
+ 'D' # 0xC4 -> LATIN CAPITAL LETTER D
+ 'E' # 0xC5 -> LATIN CAPITAL LETTER E
+ 'F' # 0xC6 -> LATIN CAPITAL LETTER F
+ 'G' # 0xC7 -> LATIN CAPITAL LETTER G
+ 'H' # 0xC8 -> LATIN CAPITAL LETTER H
+ 'I' # 0xC9 -> LATIN CAPITAL LETTER I
+ '\xad' # 0xCA -> SOFT HYPHEN
+ '\xf4' # 0xCB -> LATIN SMALL LETTER O WITH CIRCUMFLEX
+ '\xa6' # 0xCC -> BROKEN BAR
+ '\xf2' # 0xCD -> LATIN SMALL LETTER O WITH GRAVE
+ '\xf3' # 0xCE -> LATIN SMALL LETTER O WITH ACUTE
+ '\xf5' # 0xCF -> LATIN SMALL LETTER O WITH TILDE
+ '\xfc' # 0xD0 -> LATIN SMALL LETTER U WITH DIAERESIS
+ 'J' # 0xD1 -> LATIN CAPITAL LETTER J
+ 'K' # 0xD2 -> LATIN CAPITAL LETTER K
+ 'L' # 0xD3 -> LATIN CAPITAL LETTER L
+ 'M' # 0xD4 -> LATIN CAPITAL LETTER M
+ 'N' # 0xD5 -> LATIN CAPITAL LETTER N
+ 'O' # 0xD6 -> LATIN CAPITAL LETTER O
+ 'P' # 0xD7 -> LATIN CAPITAL LETTER P
+ 'Q' # 0xD8 -> LATIN CAPITAL LETTER Q
+ 'R' # 0xD9 -> LATIN CAPITAL LETTER R
+ '\xb9' # 0xDA -> SUPERSCRIPT ONE
+ '\xfb' # 0xDB -> LATIN SMALL LETTER U WITH CIRCUMFLEX
+ '}' # 0xDC -> RIGHT CURLY BRACKET
+ '\xf9' # 0xDD -> LATIN SMALL LETTER U WITH GRAVE
+ '\xfa' # 0xDE -> LATIN SMALL LETTER U WITH ACUTE
+ '\xff' # 0xDF -> LATIN SMALL LETTER Y WITH DIAERESIS
+ '\xd6' # 0xE0 -> LATIN CAPITAL LETTER O WITH DIAERESIS
+ '\xf7' # 0xE1 -> DIVISION SIGN
+ 'S' # 0xE2 -> LATIN CAPITAL LETTER S
+ 'T' # 0xE3 -> LATIN CAPITAL LETTER T
+ 'U' # 0xE4 -> LATIN CAPITAL LETTER U
+ 'V' # 0xE5 -> LATIN CAPITAL LETTER V
+ 'W' # 0xE6 -> LATIN CAPITAL LETTER W
+ 'X' # 0xE7 -> LATIN CAPITAL LETTER X
+ 'Y' # 0xE8 -> LATIN CAPITAL LETTER Y
+ 'Z' # 0xE9 -> LATIN CAPITAL LETTER Z
+ '\xb2' # 0xEA -> SUPERSCRIPT TWO
+ '\xd4' # 0xEB -> LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ '\\' # 0xEC -> REVERSE SOLIDUS
+ '\xd2' # 0xED -> LATIN CAPITAL LETTER O WITH GRAVE
+ '\xd3' # 0xEE -> LATIN CAPITAL LETTER O WITH ACUTE
+ '\xd5' # 0xEF -> LATIN CAPITAL LETTER O WITH TILDE
+ '0' # 0xF0 -> DIGIT ZERO
+ '1' # 0xF1 -> DIGIT ONE
+ '2' # 0xF2 -> DIGIT TWO
+ '3' # 0xF3 -> DIGIT THREE
+ '4' # 0xF4 -> DIGIT FOUR
+ '5' # 0xF5 -> DIGIT FIVE
+ '6' # 0xF6 -> DIGIT SIX
+ '7' # 0xF7 -> DIGIT SEVEN
+ '8' # 0xF8 -> DIGIT EIGHT
+ '9' # 0xF9 -> DIGIT NINE
+ '\xb3' # 0xFA -> SUPERSCRIPT THREE
+ '\xdb' # 0xFB -> LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ ']' # 0xFC -> RIGHT SQUARE BRACKET
+ '\xd9' # 0xFD -> LATIN CAPITAL LETTER U WITH GRAVE
+ '\xda' # 0xFE -> LATIN CAPITAL LETTER U WITH ACUTE
+ '\x9f' # 0xFF -> APPLICATION PROGRAM COMMAND (APC)
+)
+
+### Encoding table
+encoding_table=codecs.charmap_build(decoding_table)
diff --git a/Lib/encodings/cp500.py b/Lib/encodings/cp500.py
index a975be7b8d..5f61535f82 100644
--- a/Lib/encodings/cp500.py
+++ b/Lib/encodings/cp500.py
@@ -301,7 +301,6 @@ decoding_table = (
'\xd9' # 0xFD -> LATIN CAPITAL LETTER U WITH GRAVE
'\xda' # 0xFE -> LATIN CAPITAL LETTER U WITH ACUTE
'\x9f' # 0xFF -> CONTROL
- '\ufffe' ## Widen to UCS2 for optimization
)
### Encoding table
diff --git a/Lib/encodings/hex_codec.py b/Lib/encodings/hex_codec.py
index e003fc3ff9..f2ed0a7658 100644
--- a/Lib/encodings/hex_codec.py
+++ b/Lib/encodings/hex_codec.py
@@ -52,4 +52,5 @@ def getregentry():
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
+ _is_text_encoding=False,
)
diff --git a/Lib/encodings/iso8859_1.py b/Lib/encodings/iso8859_1.py
index d9cc516718..8cfc01fe14 100644
--- a/Lib/encodings/iso8859_1.py
+++ b/Lib/encodings/iso8859_1.py
@@ -301,7 +301,6 @@ decoding_table = (
'\xfd' # 0xFD -> LATIN SMALL LETTER Y WITH ACUTE
'\xfe' # 0xFE -> LATIN SMALL LETTER THORN (Icelandic)
'\xff' # 0xFF -> LATIN SMALL LETTER Y WITH DIAERESIS
- '\ufffe' ## Widen to UCS2 for optimization
)
### Encoding table
diff --git a/Lib/encodings/quopri_codec.py b/Lib/encodings/quopri_codec.py
index 9243fc443b..70f7083793 100644
--- a/Lib/encodings/quopri_codec.py
+++ b/Lib/encodings/quopri_codec.py
@@ -53,4 +53,5 @@ def getregentry():
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
+ _is_text_encoding=False,
)
diff --git a/Lib/encodings/rot_13.py b/Lib/encodings/rot_13.py
index 3140c1432d..fff9153b4c 100755
--- a/Lib/encodings/rot_13.py
+++ b/Lib/encodings/rot_13.py
@@ -43,6 +43,7 @@ def getregentry():
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
+ _is_text_encoding=False,
)
### Map
diff --git a/Lib/encodings/uu_codec.py b/Lib/encodings/uu_codec.py
index 69c6f17c7f..e3269e40cd 100644
--- a/Lib/encodings/uu_codec.py
+++ b/Lib/encodings/uu_codec.py
@@ -96,4 +96,5 @@ def getregentry():
incrementaldecoder=IncrementalDecoder,
streamreader=StreamReader,
streamwriter=StreamWriter,
+ _is_text_encoding=False,
)
diff --git a/Lib/encodings/zlib_codec.py b/Lib/encodings/zlib_codec.py
index e0b9cdadbc..4c81ca115a 100644
--- a/Lib/encodings/zlib_codec.py
+++ b/Lib/encodings/zlib_codec.py
@@ -74,4 +74,5 @@ def getregentry():
incrementaldecoder=IncrementalDecoder,
streamreader=StreamReader,
streamwriter=StreamWriter,
+ _is_text_encoding=False,
)
diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py
new file mode 100644
index 0000000000..63013aec3d
--- /dev/null
+++ b/Lib/ensurepip/__init__.py
@@ -0,0 +1,114 @@
+import os
+import os.path
+import pkgutil
+import sys
+import tempfile
+
+# TODO: Remove the --pre flag when a pip 1.5 final copy is available
+
+
+__all__ = ["version", "bootstrap"]
+
+
+_SETUPTOOLS_VERSION = "1.3.2"
+
+_PIP_VERSION = "1.5rc1"
+
+_PROJECTS = [
+ ("setuptools", _SETUPTOOLS_VERSION),
+ ("pip", _PIP_VERSION),
+]
+
+
+def _run_pip(args, additional_paths=None):
+ # Add our bundled software to the sys.path so we can import it
+ if additional_paths is not None:
+ sys.path = additional_paths + sys.path
+
+ # Install the bundled software
+ import pip
+ pip.main(args)
+
+
+def version():
+ """
+ Returns a string specifying the bundled version of pip.
+ """
+ return _PIP_VERSION
+
+
+def bootstrap(*, root=None, upgrade=False, user=False,
+ altinstall=False, default_pip=False,
+ verbosity=0):
+ """
+ Bootstrap pip into the current Python installation (or the given root
+ directory).
+ """
+ if altinstall and default_pip:
+ raise ValueError("Cannot use altinstall and default_pip together")
+
+ # By default, installing pip and setuptools installs all of the
+ # following scripts (X.Y == running Python version):
+ #
+ # pip, pipX, pipX.Y, easy_install, easy_install-X.Y
+ #
+ # pip 1.5+ allows ensurepip to request that some of those be left out
+ if altinstall:
+ # omit pip, pipX and easy_install
+ os.environ["ENSUREPIP_OPTIONS"] = "altinstall"
+ elif not default_pip:
+ # omit pip and easy_install
+ os.environ["ENSUREPIP_OPTIONS"] = "install"
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ # Put our bundled wheels into a temporary directory and construct the
+ # additional paths that need added to sys.path
+ additional_paths = []
+ for project, version in _PROJECTS:
+ wheel_name = "{}-{}-py2.py3-none-any.whl".format(project, version)
+ whl = pkgutil.get_data(
+ "ensurepip",
+ "_bundled/{}".format(wheel_name),
+ )
+ with open(os.path.join(tmpdir, wheel_name), "wb") as fp:
+ fp.write(whl)
+
+ additional_paths.append(os.path.join(tmpdir, wheel_name))
+
+ # Construct the arguments to be passed to the pip command
+ args = [
+ "install", "--no-index", "--find-links", tmpdir,
+ # Temporary until pip 1.5 is final
+ "--pre",
+ ]
+ if root:
+ args += ["--root", root]
+ if upgrade:
+ args += ["--upgrade"]
+ if user:
+ args += ["--user"]
+ if verbosity:
+ args += ["-" + "v" * verbosity]
+
+ _run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
+
+def _uninstall(*, verbosity=0):
+ """Helper to support a clean default uninstall process on Windows"""
+ # Nothing to do if pip was never installed, or has been removed
+ try:
+ import pip
+ except ImportError:
+ return
+
+ # If the pip version doesn't match the bundled one, leave it alone
+ if pip.__version__ != _PIP_VERSION:
+ msg = ("ensurepip will only uninstall a matching pip "
+ "({!r} installed, {!r} bundled)")
+ raise RuntimeError(msg.format(pip.__version__, _PIP_VERSION))
+
+ # Construct the arguments to be passed to the pip command
+ args = ["uninstall", "-y"]
+ if verbosity:
+ args += ["-" + "v" * verbosity]
+
+ _run_pip(args + [p[0] for p in reversed(_PROJECTS)])
diff --git a/Lib/ensurepip/__main__.py b/Lib/ensurepip/__main__.py
new file mode 100644
index 0000000000..53e84595b3
--- /dev/null
+++ b/Lib/ensurepip/__main__.py
@@ -0,0 +1,66 @@
+import argparse
+import ensurepip
+
+
+def main():
+ parser = argparse.ArgumentParser(prog="python -m ensurepip")
+ parser.add_argument(
+ "--version",
+ action="version",
+ version="pip {}".format(ensurepip.version()),
+ help="Show the version of pip that is bundled with this Python.",
+ )
+ parser.add_argument(
+ "-v", "--verbose",
+ action="count",
+ default=0,
+ dest="verbosity",
+ help=("Give more output. Option is additive, and can be used up to 3 "
+ "times."),
+ )
+ parser.add_argument(
+ "-U", "--upgrade",
+ action="store_true",
+ default=False,
+ help="Upgrade pip and dependencies, even if already installed.",
+ )
+ parser.add_argument(
+ "--user",
+ action="store_true",
+ default=False,
+ help="Install using the user scheme.",
+ )
+ parser.add_argument(
+ "--root",
+ default=None,
+ help="Install everything relative to this alternate root directory.",
+ )
+ parser.add_argument(
+ "--altinstall",
+ action="store_true",
+ default=False,
+ help=("Make an alternate install, installing only the X.Y versioned"
+ "scripts (Default: pipX, pipX.Y, easy_install-X.Y)"),
+ )
+ parser.add_argument(
+ "--default-pip",
+ action="store_true",
+ default=False,
+ help=("Make a default pip install, installing the unqualified pip "
+ "and easy_install in addition to the versioned scripts"),
+ )
+
+ args = parser.parse_args()
+
+ ensurepip.bootstrap(
+ root=args.root,
+ upgrade=args.upgrade,
+ user=args.user,
+ verbosity=args.verbosity,
+ altinstall=args.altinstall,
+ default_pip=args.default_pip,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/Lib/ensurepip/_bundled/pip-1.5rc1-py2.py3-none-any.whl b/Lib/ensurepip/_bundled/pip-1.5rc1-py2.py3-none-any.whl
new file mode 100644
index 0000000000..6418895a58
--- /dev/null
+++ b/Lib/ensurepip/_bundled/pip-1.5rc1-py2.py3-none-any.whl
Binary files differ
diff --git a/Lib/ensurepip/_bundled/setuptools-1.3.2-py2.py3-none-any.whl b/Lib/ensurepip/_bundled/setuptools-1.3.2-py2.py3-none-any.whl
new file mode 100644
index 0000000000..81962b1081
--- /dev/null
+++ b/Lib/ensurepip/_bundled/setuptools-1.3.2-py2.py3-none-any.whl
Binary files differ
diff --git a/Lib/ensurepip/_uninstall.py b/Lib/ensurepip/_uninstall.py
new file mode 100644
index 0000000000..38c486b0a2
--- /dev/null
+++ b/Lib/ensurepip/_uninstall.py
@@ -0,0 +1,30 @@
+"""Basic pip uninstallation support, helper for the Windows uninstaller"""
+
+import argparse
+import ensurepip
+
+
+def main():
+ parser = argparse.ArgumentParser(prog="python -m ensurepip._uninstall")
+ parser.add_argument(
+ "--version",
+ action="version",
+ version="pip {}".format(ensurepip.version()),
+ help="Show the version of pip this will attempt to uninstall.",
+ )
+ parser.add_argument(
+ "-v", "--verbose",
+ action="count",
+ default=0,
+ dest="verbosity",
+ help=("Give more output. Option is additive, and can be used up to 3 "
+ "times."),
+ )
+
+ args = parser.parse_args()
+
+ ensurepip._uninstall(verbosity=args.verbosity)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/Lib/enum.py b/Lib/enum.py
new file mode 100644
index 0000000000..7d58f8d17a
--- /dev/null
+++ b/Lib/enum.py
@@ -0,0 +1,504 @@
+import sys
+from collections import OrderedDict
+from types import MappingProxyType, DynamicClassAttribute
+
+__all__ = ['Enum', 'IntEnum', 'unique']
+
+
+def _is_descriptor(obj):
+ """Returns True if obj is a descriptor, False otherwise."""
+ return (
+ hasattr(obj, '__get__') or
+ hasattr(obj, '__set__') or
+ hasattr(obj, '__delete__'))
+
+
+def _is_dunder(name):
+ """Returns True if a __dunder__ name, False otherwise."""
+ return (name[:2] == name[-2:] == '__' and
+ name[2:3] != '_' and
+ name[-3:-2] != '_' and
+ len(name) > 4)
+
+
+def _is_sunder(name):
+ """Returns True if a _sunder_ name, False otherwise."""
+ return (name[0] == name[-1] == '_' and
+ name[1:2] != '_' and
+ name[-2:-1] != '_' and
+ len(name) > 2)
+
+
+def _make_class_unpicklable(cls):
+ """Make the given class un-picklable."""
+ def _break_on_call_reduce(self):
+ raise TypeError('%r cannot be pickled' % self)
+ cls.__reduce__ = _break_on_call_reduce
+ cls.__module__ = '<unknown>'
+
+
+class _EnumDict(dict):
+ """Track enum member order and ensure member names are not reused.
+
+ EnumMeta will use the names found in self._member_names as the
+ enumeration member names.
+
+ """
+ def __init__(self):
+ super().__init__()
+ self._member_names = []
+
+ def __setitem__(self, key, value):
+ """Changes anything not dundered or not a descriptor.
+
+ If an enum member name is used twice, an error is raised; duplicate
+ values are not checked for.
+
+ Single underscore (sunder) names are reserved.
+
+ """
+ if _is_sunder(key):
+ raise ValueError('_names_ are reserved for future Enum use')
+ elif _is_dunder(key):
+ pass
+ elif key in self._member_names:
+ # descriptor overwriting an enum?
+ raise TypeError('Attempted to reuse key: %r' % key)
+ elif not _is_descriptor(value):
+ if key in self:
+ # enum overwriting a descriptor?
+ raise TypeError('Key already defined as: %r' % self[key])
+ self._member_names.append(key)
+ super().__setitem__(key, value)
+
+
+
+# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
+# until EnumMeta finishes running the first time the Enum class doesn't exist.
+# This is also why there are checks in EnumMeta like `if Enum is not None`
+Enum = None
+
+
+class EnumMeta(type):
+ """Metaclass for Enum"""
+ @classmethod
+ def __prepare__(metacls, cls, bases):
+ return _EnumDict()
+
+ def __new__(metacls, cls, bases, classdict):
+ # an Enum class is final once enumeration items have been defined; it
+ # cannot be mixed with other types (int, float, etc.) if it has an
+ # inherited __new__ unless a new __new__ is defined (or the resulting
+ # class will fail).
+ member_type, first_enum = metacls._get_mixins_(bases)
+ __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
+ first_enum)
+
+ # save enum items into separate mapping so they don't get baked into
+ # the new class
+ members = {k: classdict[k] for k in classdict._member_names}
+ for name in classdict._member_names:
+ del classdict[name]
+
+ # check for illegal enum names (any others?)
+ invalid_names = set(members) & {'mro', }
+ if invalid_names:
+ raise ValueError('Invalid enum member name: {0}'.format(
+ ','.join(invalid_names)))
+
+ # create our new Enum type
+ enum_class = super().__new__(metacls, cls, bases, classdict)
+ enum_class._member_names_ = [] # names in definition order
+ enum_class._member_map_ = OrderedDict() # name->value map
+ enum_class._member_type_ = member_type
+
+ # Reverse value->name map for hashable values.
+ enum_class._value2member_map_ = {}
+
+ # check for a __getnewargs__, and if not present sabotage
+ # pickling, since it won't work anyway
+ if (member_type is not object and
+ member_type.__dict__.get('__getnewargs__') is None
+ ):
+ _make_class_unpicklable(enum_class)
+
+ # instantiate them, checking for duplicates as we go
+ # we instantiate first instead of checking for duplicates first in case
+ # a custom __new__ is doing something funky with the values -- such as
+ # auto-numbering ;)
+ for member_name in classdict._member_names:
+ value = members[member_name]
+ if not isinstance(value, tuple):
+ args = (value, )
+ else:
+ args = value
+ if member_type is tuple: # special case for tuple enums
+ args = (args, ) # wrap it one more time
+ if not use_args:
+ enum_member = __new__(enum_class)
+ if not hasattr(enum_member, '_value_'):
+ enum_member._value_ = value
+ else:
+ enum_member = __new__(enum_class, *args)
+ if not hasattr(enum_member, '_value_'):
+ enum_member._value_ = member_type(*args)
+ value = enum_member._value_
+ enum_member._name_ = member_name
+ enum_member.__objclass__ = enum_class
+ enum_member.__init__(*args)
+ # If another member with the same value was already defined, the
+ # new member becomes an alias to the existing one.
+ for name, canonical_member in enum_class._member_map_.items():
+ if canonical_member.value == enum_member._value_:
+ enum_member = canonical_member
+ break
+ else:
+ # Aliases don't appear in member names (only in __members__).
+ enum_class._member_names_.append(member_name)
+ enum_class._member_map_[member_name] = enum_member
+ try:
+ # This may fail if value is not hashable. We can't add the value
+ # to the map, and by-value lookups for this value will be
+ # linear.
+ enum_class._value2member_map_[value] = enum_member
+ except TypeError:
+ pass
+
+ # double check that repr and friends are not the mixin's or various
+ # things break (such as pickle)
+ for name in ('__repr__', '__str__', '__format__', '__getnewargs__'):
+ class_method = getattr(enum_class, name)
+ obj_method = getattr(member_type, name, None)
+ enum_method = getattr(first_enum, name, None)
+ if obj_method is not None and obj_method is class_method:
+ setattr(enum_class, name, enum_method)
+
+ # replace any other __new__ with our own (as long as Enum is not None,
+ # anyway) -- again, this is to support pickle
+ if Enum is not None:
+ # if the user defined their own __new__, save it before it gets
+ # clobbered in case they subclass later
+ if save_new:
+ enum_class.__new_member__ = __new__
+ enum_class.__new__ = Enum.__new__
+ return enum_class
+
+ def __call__(cls, value, names=None, *, module=None, type=None):
+ """Either returns an existing member, or creates a new enum class.
+
+ This method is used both when an enum class is given a value to match
+ to an enumeration member (i.e. Color(3)) and for the functional API
+ (i.e. Color = Enum('Color', names='red green blue')).
+
+ When used for the functional API: `module`, if set, will be stored in
+ the new class' __module__ attribute; `type`, if set, will be mixed in
+ as the first base class.
+
+ Note: if `module` is not set this routine will attempt to discover the
+ calling module by walking the frame stack; if this is unsuccessful
+ the resulting class will not be pickleable.
+
+ """
+ if names is None: # simple value lookup
+ return cls.__new__(cls, value)
+ # otherwise, functional API: we're creating a new Enum type
+ return cls._create_(value, names, module=module, type=type)
+
+ def __contains__(cls, member):
+ return isinstance(member, cls) and member.name in cls._member_map_
+
+ def __delattr__(cls, attr):
+ # nicer error message when someone tries to delete an attribute
+ # (see issue19025).
+ if attr in cls._member_map_:
+ raise AttributeError(
+ "%s: cannot delete Enum member." % cls.__name__)
+ super().__delattr__(attr)
+
+ def __dir__(self):
+ return (['__class__', '__doc__', '__members__', '__module__'] +
+ self._member_names_)
+
+ def __getattr__(cls, name):
+ """Return the enum member matching `name`
+
+ We use __getattr__ instead of descriptors or inserting into the enum
+ class' __dict__ in order to support `name` and `value` being both
+ properties for enum members (which live in the class' __dict__) and
+ enum members themselves.
+
+ """
+ if _is_dunder(name):
+ raise AttributeError(name)
+ try:
+ return cls._member_map_[name]
+ except KeyError:
+ raise AttributeError(name) from None
+
+ def __getitem__(cls, name):
+ return cls._member_map_[name]
+
+ def __iter__(cls):
+ return (cls._member_map_[name] for name in cls._member_names_)
+
+ def __len__(cls):
+ return len(cls._member_names_)
+
+ @property
+ def __members__(cls):
+ """Returns a mapping of member name->value.
+
+ This mapping lists all enum members, including aliases. Note that this
+ is a read-only view of the internal mapping.
+
+ """
+ return MappingProxyType(cls._member_map_)
+
+ def __repr__(cls):
+ return "<enum %r>" % cls.__name__
+
+ def __reversed__(cls):
+ return (cls._member_map_[name] for name in reversed(cls._member_names_))
+
+ def __setattr__(cls, name, value):
+ """Block attempts to reassign Enum members.
+
+ A simple assignment to the class namespace only changes one of the
+ several possible ways to get an Enum member from the Enum class,
+ resulting in an inconsistent Enumeration.
+
+ """
+ member_map = cls.__dict__.get('_member_map_', {})
+ if name in member_map:
+ raise AttributeError('Cannot reassign members.')
+ super().__setattr__(name, value)
+
+ def _create_(cls, class_name, names=None, *, module=None, type=None):
+ """Convenience method to create a new Enum class.
+
+ `names` can be:
+
+ * A string containing member names, separated either with spaces or
+ commas. Values are auto-numbered from 1.
+ * An iterable of member names. Values are auto-numbered from 1.
+ * An iterable of (member name, value) pairs.
+ * A mapping of member name -> value.
+
+ """
+ metacls = cls.__class__
+ bases = (cls, ) if type is None else (type, cls)
+ classdict = metacls.__prepare__(class_name, bases)
+
+ # special processing needed for names?
+ if isinstance(names, str):
+ names = names.replace(',', ' ').split()
+ if isinstance(names, (tuple, list)) and isinstance(names[0], str):
+ names = [(e, i) for (i, e) in enumerate(names, 1)]
+
+ # Here, names is either an iterable of (name, value) or a mapping.
+ for item in names:
+ if isinstance(item, str):
+ member_name, member_value = item, names[item]
+ else:
+ member_name, member_value = item
+ classdict[member_name] = member_value
+ enum_class = metacls.__new__(metacls, class_name, bases, classdict)
+
+ # TODO: replace the frame hack if a blessed way to know the calling
+ # module is ever developed
+ if module is None:
+ try:
+ module = sys._getframe(2).f_globals['__name__']
+ except (AttributeError, ValueError) as exc:
+ pass
+ if module is None:
+ _make_class_unpicklable(enum_class)
+ else:
+ enum_class.__module__ = module
+
+ return enum_class
+
+ @staticmethod
+ def _get_mixins_(bases):
+ """Returns the type for creating enum members, and the first inherited
+ enum class.
+
+ bases: the tuple of bases that was given to __new__
+
+ """
+ if not bases:
+ return object, Enum
+
+ # double check that we are not subclassing a class with existing
+ # enumeration members; while we're at it, see if any other data
+ # type has been mixed in so we can use the correct __new__
+ member_type = first_enum = None
+ for base in bases:
+ if (base is not Enum and
+ issubclass(base, Enum) and
+ base._member_names_):
+ raise TypeError("Cannot extend enumerations")
+ # base is now the last base in bases
+ if not issubclass(base, Enum):
+ raise TypeError("new enumerations must be created as "
+ "`ClassName([mixin_type,] enum_type)`")
+
+ # get correct mix-in type (either mix-in type of Enum subclass, or
+ # first base if last base is Enum)
+ if not issubclass(bases[0], Enum):
+ member_type = bases[0] # first data type
+ first_enum = bases[-1] # enum type
+ else:
+ for base in bases[0].__mro__:
+ # most common: (IntEnum, int, Enum, object)
+ # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
+ # <class 'int'>, <Enum 'Enum'>,
+ # <class 'object'>)
+ if issubclass(base, Enum):
+ if first_enum is None:
+ first_enum = base
+ else:
+ if member_type is None:
+ member_type = base
+
+ return member_type, first_enum
+
+ @staticmethod
+ def _find_new_(classdict, member_type, first_enum):
+ """Returns the __new__ to be used for creating the enum members.
+
+ classdict: the class dictionary given to __new__
+ member_type: the data type whose __new__ will be used by default
+ first_enum: enumeration to check for an overriding __new__
+
+ """
+ # now find the correct __new__, checking to see of one was defined
+ # by the user; also check earlier enum classes in case a __new__ was
+ # saved as __new_member__
+ __new__ = classdict.get('__new__', None)
+
+ # should __new__ be saved as __new_member__ later?
+ save_new = __new__ is not None
+
+ if __new__ is None:
+ # check all possibles for __new_member__ before falling back to
+ # __new__
+ for method in ('__new_member__', '__new__'):
+ for possible in (member_type, first_enum):
+ target = getattr(possible, method, None)
+ if target not in {
+ None,
+ None.__new__,
+ object.__new__,
+ Enum.__new__,
+ }:
+ __new__ = target
+ break
+ if __new__ is not None:
+ break
+ else:
+ __new__ = object.__new__
+
+ # if a non-object.__new__ is used then whatever value/tuple was
+ # assigned to the enum member name will be passed to __new__ and to the
+ # new enum member's __init__
+ if __new__ is object.__new__:
+ use_args = False
+ else:
+ use_args = True
+
+ return __new__, save_new, use_args
+
+
+class Enum(metaclass=EnumMeta):
+ """Generic enumeration.
+
+ Derive from this class to define new enumerations.
+
+ """
+ def __new__(cls, value):
+ # all enum instances are actually created during class construction
+ # without calling this method; this method is called by the metaclass'
+ # __call__ (i.e. Color(3) ), and by pickle
+ if type(value) is cls:
+ # For lookups like Color(Color.red)
+ return value
+ # by-value search for a matching enum member
+ # see if it's in the reverse mapping (for hashable values)
+ try:
+ if value in cls._value2member_map_:
+ return cls._value2member_map_[value]
+ except TypeError:
+ # not there, now do long search -- O(n) behavior
+ for member in cls._member_map_.values():
+ if member.value == value:
+ return member
+ raise ValueError("%s is not a valid %s" % (value, cls.__name__))
+
+ def __repr__(self):
+ return "<%s.%s: %r>" % (
+ self.__class__.__name__, self._name_, self._value_)
+
+ def __str__(self):
+ return "%s.%s" % (self.__class__.__name__, self._name_)
+
+ def __dir__(self):
+ added_behavior = [m for m in self.__class__.__dict__ if m[0] != '_']
+ return (['__class__', '__doc__', '__module__', 'name', 'value'] +
+ added_behavior)
+
+ def __format__(self, format_spec):
+ # mixed-in Enums should use the mixed-in type's __format__, otherwise
+ # we can get strange results with the Enum name showing up instead of
+ # the value
+
+ # pure Enum branch
+ if self._member_type_ is object:
+ cls = str
+ val = str(self)
+ # mix-in branch
+ else:
+ cls = self._member_type_
+ val = self.value
+ return cls.__format__(val, format_spec)
+
+ def __getnewargs__(self):
+ return (self._value_, )
+
+ def __hash__(self):
+ return hash(self._name_)
+
+ # DynamicClassAttribute is used to provide access to the `name` and
+ # `value` properties of enum members while keeping some measure of
+ # protection from modification, while still allowing for an enumeration
+ # to have members named `name` and `value`. This works because enumeration
+ # members are not set directly on the enum class -- __getattr__ is
+ # used to look them up.
+
+ @DynamicClassAttribute
+ def name(self):
+ """The name of the Enum member."""
+ return self._name_
+
+ @DynamicClassAttribute
+ def value(self):
+ """The value of the Enum member."""
+ return self._value_
+
+
+class IntEnum(int, Enum):
+ """Enum where members are also (and must be) ints"""
+
+
+def unique(enumeration):
+ """Class decorator for enumerations ensuring unique member values."""
+ duplicates = []
+ for name, member in enumeration.__members__.items():
+ if name != member.name:
+ duplicates.append((name, member.name))
+ if duplicates:
+ alias_details = ', '.join(
+ ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
+ raise ValueError('duplicate values found in %r: %s' %
+ (enumeration, alias_details))
+ return enumeration
diff --git a/Lib/filecmp.py b/Lib/filecmp.py
index f5cea1de6f..328528871a 100644
--- a/Lib/filecmp.py
+++ b/Lib/filecmp.py
@@ -6,6 +6,7 @@ Classes:
Functions:
cmp(f1, f2, shallow=True) -> int
cmpfiles(a, b, common) -> ([], [], [])
+ clear_cache()
"""
@@ -13,11 +14,18 @@ import os
import stat
from itertools import filterfalse
-__all__ = ["cmp", "dircmp", "cmpfiles"]
+__all__ = ['clear_cache', 'cmp', 'dircmp', 'cmpfiles', 'DEFAULT_IGNORES']
_cache = {}
BUFSIZE = 8*1024
+DEFAULT_IGNORES = [
+ 'RCS', 'CVS', 'tags', '.git', '.hg', '.bzr', '_darcs', '__pycache__']
+
+def clear_cache():
+ """Clear the filecmp cache."""
+ _cache.clear()
+
def cmp(f1, f2, shallow=True):
"""Compare two files.
@@ -35,7 +43,8 @@ def cmp(f1, f2, shallow=True):
True if the files are the same, False otherwise.
This function uses a cache for past comparisons and the results,
- with a cache invalidation mechanism relying on stale signatures.
+ with a cache invalidation mechanism relying on stale signatures
+ or by explicitly calling clear_cache().
"""
@@ -52,7 +61,7 @@ def cmp(f1, f2, shallow=True):
if outcome is None:
outcome = _do_cmp(f1, f2)
if len(_cache) > 100: # limit the maximum size of the cache
- _cache.clear()
+ clear_cache()
_cache[f1, f2, s1, s2] = outcome
return outcome
@@ -80,7 +89,7 @@ class dircmp:
dircmp(a, b, ignore=None, hide=None)
A and B are directories.
IGNORE is a list of names to ignore,
- defaults to ['RCS', 'CVS', 'tags'].
+ defaults to DEFAULT_IGNORES.
HIDE is a list of names to hide,
defaults to [os.curdir, os.pardir].
@@ -116,7 +125,7 @@ class dircmp:
else:
self.hide = hide
if ignore is None:
- self.ignore = ['RCS', 'CVS', 'tags'] # Names ignored in comparison
+ self.ignore = DEFAULT_IGNORES
else:
self.ignore = ignore
@@ -147,12 +156,12 @@ class dircmp:
ok = 1
try:
a_stat = os.stat(a_path)
- except os.error as why:
+ except OSError as why:
# print('Can\'t stat', a_path, ':', why.args[1])
ok = 0
try:
b_stat = os.stat(b_path)
- except os.error as why:
+ except OSError as why:
# print('Can\'t stat', b_path, ':', why.args[1])
ok = 0
@@ -268,7 +277,7 @@ def cmpfiles(a, b, common, shallow=True):
def _cmp(a, b, sh, abs=abs, cmp=cmp):
try:
return not abs(cmp(a, b, sh))
- except os.error:
+ except OSError:
return 2
diff --git a/Lib/fileinput.py b/Lib/fileinput.py
index 879a0fdaab..de2951844a 100644
--- a/Lib/fileinput.py
+++ b/Lib/fileinput.py
@@ -30,7 +30,7 @@ pertaining to the last line read; nextfile() has no effect.
All files are opened in text mode by default, you can override this by
setting the mode parameter to input() or FileInput.__init__().
-If an I/O error occurs during opening or reading a file, the IOError
+If an I/O error occurs during opening or reading a file, the OSError
exception is raised.
If sys.stdin is used more than once, the second and further use will
@@ -222,6 +222,10 @@ class FileInput:
if mode not in ('r', 'rU', 'U', 'rb'):
raise ValueError("FileInput opening mode must be one of "
"'r', 'rU', 'U' and 'rb'")
+ if 'U' in mode:
+ import warnings
+ warnings.warn("'U' mode is deprecated",
+ DeprecationWarning, 2)
self._mode = mode
if openhook:
if inplace:
@@ -322,9 +326,11 @@ class FileInput:
if self._inplace:
self._backupfilename = (
self._filename + (self._backup or ".bak"))
- try: os.unlink(self._backupfilename)
- except os.error: pass
- # The next few lines may raise IOError
+ try:
+ os.unlink(self._backupfilename)
+ except OSError:
+ pass
+ # The next few lines may raise OSError
os.rename(self._filename, self._backupfilename)
self._file = open(self._backupfilename, self._mode)
try:
@@ -346,7 +352,7 @@ class FileInput:
self._savestdout = sys.stdout
sys.stdout = self._output
else:
- # This may raise IOError
+ # This may raise OSError
if self._openhook:
self._file = self._openhook(self._filename, self._mode)
else:
diff --git a/Lib/formatter.py b/Lib/formatter.py
index 60e60f1160..d8cca52e30 100644
--- a/Lib/formatter.py
+++ b/Lib/formatter.py
@@ -19,6 +19,9 @@ manage and inserting data into the output.
"""
import sys
+import warnings
+warnings.warn('the formatter module is deprecated and will be removed in '
+ 'Python 3.6', PendingDeprecationWarning)
AS_IS = None
diff --git a/Lib/fractions.py b/Lib/fractions.py
index 8be52d2db8..79e83ff2c0 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -182,8 +182,10 @@ class Fraction(numbers.Rational):
elif not isinstance(f, float):
raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
(cls.__name__, f, type(f).__name__))
- if math.isnan(f) or math.isinf(f):
- raise TypeError("Cannot convert %r to %s." % (f, cls.__name__))
+ if math.isnan(f):
+ raise ValueError("Cannot convert %r to %s." % (f, cls.__name__))
+ if math.isinf(f):
+ raise OverflowError("Cannot convert %r to %s." % (f, cls.__name__))
return cls(*f.as_integer_ratio())
@classmethod
@@ -196,9 +198,11 @@ class Fraction(numbers.Rational):
raise TypeError(
"%s.from_decimal() only takes Decimals, not %r (%s)" %
(cls.__name__, dec, type(dec).__name__))
- if not dec.is_finite():
- # Catches infinities and nans.
- raise TypeError("Cannot convert %s to %s." % (dec, cls.__name__))
+ if dec.is_infinite():
+ raise OverflowError(
+ "Cannot convert %s to %s." % (dec, cls.__name__))
+ if dec.is_nan():
+ raise ValueError("Cannot convert %s to %s." % (dec, cls.__name__))
sign, digits, exp = dec.as_tuple()
digits = int(''.join(map(str, digits)))
if sign:
diff --git a/Lib/ftplib.py b/Lib/ftplib.py
index 5e75e6d422..2cc470258e 100644
--- a/Lib/ftplib.py
+++ b/Lib/ftplib.py
@@ -39,9 +39,10 @@ python ftplib.py -d localhost -l -p -l
import os
import sys
import socket
+import warnings
from socket import _GLOBAL_DEFAULT_TIMEOUT
-__all__ = ["FTP","Netrc"]
+__all__ = ["FTP", "Netrc"]
# Magic number from <socket.h>
MSG_OOB = 0x1 # Process data out of band
@@ -63,7 +64,7 @@ class error_proto(Error): pass # response does not begin with [1-5]
# All exceptions (hopefully) that may be raised here and that aren't
# (always) programming errors on our side
-all_errors = (Error, IOError, EOFError)
+all_errors = (Error, OSError, EOFError)
# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
@@ -126,7 +127,7 @@ class FTP:
if self.sock is not None:
try:
self.quit()
- except (socket.error, EOFError):
+ except (OSError, EOFError):
pass
finally:
if self.sock is not None:
@@ -136,6 +137,7 @@ class FTP:
'''Connect to host. Arguments are:
- host: hostname to connect to (string, default previous host)
- port: port to connect to (integer, default previous port)
+ - timeout: the timeout to set against the ftp socket(s)
- source_address: a 2-tuple (host, port) for the socket to bind
to as its source address before connecting.
'''
@@ -186,7 +188,8 @@ class FTP:
# Internal: send one line to the server, appending CRLF
def putline(self, line):
line = line + CRLF
- if self.debugging > 1: print('*put*', self.sanitize(line))
+ if self.debugging > 1:
+ print('*put*', self.sanitize(line))
self.sock.sendall(line.encode(self.encoding))
# Internal: send one command to the server (through putline())
@@ -202,9 +205,12 @@ class FTP:
raise Error("got more than %d bytes" % self.maxline)
if self.debugging > 1:
print('*get*', self.sanitize(line))
- if not line: raise EOFError
- if line[-2:] == CRLF: line = line[:-2]
- elif line[-1:] in CRLF: line = line[:-1]
+ if not line:
+ raise EOFError
+ if line[-2:] == CRLF:
+ line = line[:-2]
+ elif line[-1:] in CRLF:
+ line = line[:-1]
return line
# Internal: get a response from the server, which may possibly
@@ -227,7 +233,8 @@ class FTP:
# Raise various errors if the response indicates an error
def getresp(self):
resp = self.getmultiline()
- if self.debugging: print('*resp*', self.sanitize(resp))
+ if self.debugging:
+ print('*resp*', self.sanitize(resp))
self.lastresp = resp[:3]
c = resp[:1]
if c in {'1', '2', '3'}:
@@ -251,7 +258,8 @@ class FTP:
IP and Synch; that doesn't seem to work with the servers I've
tried. Instead, just send the ABOR command as OOB data.'''
line = b'ABOR' + B_CRLF
- if self.debugging > 1: print('*put urgent*', self.sanitize(line))
+ if self.debugging > 1:
+ print('*put urgent*', self.sanitize(line))
self.sock.sendall(line, MSG_OOB)
resp = self.getmultiline()
if resp[:3] not in {'426', '225', '226'}:
@@ -300,7 +308,7 @@ class FTP:
try:
sock = socket.socket(af, socktype, proto)
sock.bind(sa)
- except socket.error as _:
+ except OSError as _:
err = _
if sock:
sock.close()
@@ -311,8 +319,8 @@ class FTP:
if err is not None:
raise err
else:
- raise socket.error("getaddrinfo returns an empty list")
- raise socket.error(msg)
+ raise OSError("getaddrinfo returns an empty list")
+ raise OSError(msg)
sock.listen(1)
port = sock.getsockname()[1] # Get proper port
host = self.sock.getsockname()[0] # Get proper host
@@ -392,9 +400,12 @@ class FTP:
def login(self, user = '', passwd = '', acct = ''):
'''Login, default anonymous.'''
- if not user: user = 'anonymous'
- if not passwd: passwd = ''
- if not acct: acct = ''
+ if not user:
+ user = 'anonymous'
+ if not passwd:
+ passwd = ''
+ if not acct:
+ acct = ''
if user == 'anonymous' and passwd in {'', '-'}:
# If there is no anonymous ftp password specified
# then we'll just use anonymous@
@@ -405,8 +416,10 @@ class FTP:
# host or country.
passwd = passwd + 'anonymous@'
resp = self.sendcmd('USER ' + user)
- if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
- if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
+ if resp[0] == '3':
+ resp = self.sendcmd('PASS ' + passwd)
+ if resp[0] == '3':
+ resp = self.sendcmd('ACCT ' + acct)
if resp[0] != '2':
raise error_reply(resp)
return resp
@@ -432,6 +445,9 @@ class FTP:
if not data:
break
callback(data)
+ # shutdown ssl layer
+ if _SSLSocket is not None and isinstance(conn, _SSLSocket):
+ conn.unwrap()
return self.voidresp()
def retrlines(self, cmd, callback = None):
@@ -446,7 +462,8 @@ class FTP:
Returns:
The response code.
"""
- if callback is None: callback = print_line
+ if callback is None:
+ callback = print_line
resp = self.sendcmd('TYPE A')
with self.transfercmd(cmd) as conn, \
conn.makefile('r', encoding=self.encoding) as fp:
@@ -454,7 +471,8 @@ class FTP:
line = fp.readline(self.maxline + 1)
if len(line) > self.maxline:
raise Error("got more than %d bytes" % self.maxline)
- if self.debugging > 2: print('*retr*', repr(line))
+ if self.debugging > 2:
+ print('*retr*', repr(line))
if not line:
break
if line[-2:] == CRLF:
@@ -462,6 +480,9 @@ class FTP:
elif line[-1:] == '\n':
line = line[:-1]
callback(line)
+ # shutdown ssl layer
+ if _SSLSocket is not None and isinstance(conn, _SSLSocket):
+ conn.unwrap()
return self.voidresp()
def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
@@ -483,9 +504,14 @@ class FTP:
with self.transfercmd(cmd, rest) as conn:
while 1:
buf = fp.read(blocksize)
- if not buf: break
+ if not buf:
+ break
conn.sendall(buf)
- if callback: callback(buf)
+ if callback:
+ callback(buf)
+ # shutdown ssl layer
+ if _SSLSocket is not None and isinstance(conn, _SSLSocket):
+ conn.unwrap()
return self.voidresp()
def storlines(self, cmd, fp, callback=None):
@@ -506,12 +532,17 @@ class FTP:
buf = fp.readline(self.maxline + 1)
if len(buf) > self.maxline:
raise Error("got more than %d bytes" % self.maxline)
- if not buf: break
+ if not buf:
+ break
if buf[-2:] != B_CRLF:
if buf[-1] in B_CRLF: buf = buf[:-1]
buf = buf + B_CRLF
conn.sendall(buf)
- if callback: callback(buf)
+ if callback:
+ callback(buf)
+ # shutdown ssl layer
+ if _SSLSocket is not None and isinstance(conn, _SSLSocket):
+ conn.unwrap()
return self.voidresp()
def acct(self, password):
@@ -646,8 +677,10 @@ class FTP:
try:
import ssl
except ImportError:
- pass
+ _SSLSocket = None
else:
+ _SSLSocket = ssl.SSLSocket
+
class FTP_TLS(FTP):
'''A FTP subclass which adds TLS support to FTP as described
in RFC-4217.
@@ -694,6 +727,10 @@ else:
"exclusive")
self.keyfile = keyfile
self.certfile = certfile
+ if context is None:
+ context = ssl._create_stdlib_context(self.ssl_version,
+ certfile=certfile,
+ keyfile=keyfile)
self.context = context
self._prot_p = False
FTP.__init__(self, host, user, passwd, acct, timeout, source_address)
@@ -711,12 +748,9 @@ else:
resp = self.voidcmd('AUTH TLS')
else:
resp = self.voidcmd('AUTH SSL')
- if self.context is not None:
- self.sock = self.context.wrap_socket(self.sock)
- else:
- self.sock = ssl.wrap_socket(self.sock, self.keyfile,
- self.certfile,
- ssl_version=self.ssl_version)
+ server_hostname = self.host if ssl.HAS_SNI else None
+ self.sock = self.context.wrap_socket(self.sock,
+ server_hostname=server_hostname)
self.file = self.sock.makefile(mode='r', encoding=self.encoding)
return resp
@@ -755,80 +789,11 @@ else:
def ntransfercmd(self, cmd, rest=None):
conn, size = FTP.ntransfercmd(self, cmd, rest)
if self._prot_p:
- if self.context is not None:
- conn = self.context.wrap_socket(conn)
- else:
- conn = ssl.wrap_socket(conn, self.keyfile, self.certfile,
- ssl_version=self.ssl_version)
+ server_hostname = self.host if ssl.HAS_SNI else None
+ conn = self.context.wrap_socket(conn,
+ server_hostname=server_hostname)
return conn, size
- def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
- self.voidcmd('TYPE I')
- with self.transfercmd(cmd, rest) as conn:
- while 1:
- data = conn.recv(blocksize)
- if not data:
- break
- callback(data)
- # shutdown ssl layer
- if isinstance(conn, ssl.SSLSocket):
- conn.unwrap()
- return self.voidresp()
-
- def retrlines(self, cmd, callback = None):
- if callback is None: callback = print_line
- resp = self.sendcmd('TYPE A')
- conn = self.transfercmd(cmd)
- fp = conn.makefile('r', encoding=self.encoding)
- with fp, conn:
- while 1:
- line = fp.readline(self.maxline + 1)
- if len(line) > self.maxline:
- raise Error("got more than %d bytes" % self.maxline)
- if self.debugging > 2: print('*retr*', repr(line))
- if not line:
- break
- if line[-2:] == CRLF:
- line = line[:-2]
- elif line[-1:] == '\n':
- line = line[:-1]
- callback(line)
- # shutdown ssl layer
- if isinstance(conn, ssl.SSLSocket):
- conn.unwrap()
- return self.voidresp()
-
- def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
- self.voidcmd('TYPE I')
- with self.transfercmd(cmd, rest) as conn:
- while 1:
- buf = fp.read(blocksize)
- if not buf: break
- conn.sendall(buf)
- if callback: callback(buf)
- # shutdown ssl layer
- if isinstance(conn, ssl.SSLSocket):
- conn.unwrap()
- return self.voidresp()
-
- def storlines(self, cmd, fp, callback=None):
- self.voidcmd('TYPE A')
- with self.transfercmd(cmd) as conn:
- while 1:
- buf = fp.readline(self.maxline + 1)
- if len(buf) > self.maxline:
- raise Error("got more than %d bytes" % self.maxline)
- if not buf: break
- if buf[-2:] != B_CRLF:
- if buf[-1] in B_CRLF: buf = buf[:-1]
- buf = buf + B_CRLF
- conn.sendall(buf)
- if callback: callback(buf)
- # shutdown ssl layer
- if isinstance(conn, ssl.SSLSocket):
- conn.unwrap()
- return self.voidresp()
-
def abort(self):
# overridden as we can't pass MSG_OOB flag to sendall()
line = b'ABOR' + B_CRLF
@@ -839,7 +804,7 @@ else:
return resp
__all__.append('FTP_TLS')
- all_errors = (Error, IOError, EOFError, ssl.SSLError)
+ all_errors = (Error, OSError, EOFError, ssl.SSLError)
_150_re = None
@@ -936,7 +901,8 @@ def print_line(line):
def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
'''Copy file from one FTP-instance to another.'''
- if not targetname: targetname = sourcename
+ if not targetname:
+ targetname = sourcename
type = 'TYPE ' + type
source.voidcmd(type)
target.voidcmd(type)
@@ -946,9 +912,11 @@ def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
# transfer request.
# So: STOR before RETR, because here the target is a "user".
treply = target.sendcmd('STOR ' + targetname)
- if treply[:3] not in {'125', '150'}: raise error_proto # RFC 959
+ if treply[:3] not in {'125', '150'}:
+ raise error_proto # RFC 959
sreply = source.sendcmd('RETR ' + sourcename)
- if sreply[:3] not in {'125', '150'}: raise error_proto # RFC 959
+ if sreply[:3] not in {'125', '150'}:
+ raise error_proto # RFC 959
source.voidresp()
target.voidresp()
@@ -966,19 +934,22 @@ class Netrc:
__defacct = None
def __init__(self, filename=None):
+ warnings.warn("This class is deprecated, use the netrc module instead",
+ DeprecationWarning, 2)
if filename is None:
if "HOME" in os.environ:
filename = os.path.join(os.environ["HOME"],
".netrc")
else:
- raise IOError("specify file to load or set $HOME")
+ raise OSError("specify file to load or set $HOME")
self.__hosts = {}
self.__macros = {}
fp = open(filename, "r")
in_macro = 0
while 1:
line = fp.readline()
- if not line: break
+ if not line:
+ break
if in_macro and line.strip():
macro_lines.append(line)
continue
@@ -1087,7 +1058,7 @@ def test():
userid = passwd = acct = ''
try:
netrc = Netrc(rcfile)
- except IOError:
+ except OSError:
if rcfile is not None:
sys.stderr.write("Could not open account file"
" -- using anonymous login.")
diff --git a/Lib/functools.py b/Lib/functools.py
index 053e44e3e8..1e79b31140 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -3,16 +3,24 @@
# Python module wrapper for _functools C module
# to allow utilities written in Python to be added
# to the functools module.
-# Written by Nick Coghlan <ncoghlan at gmail.com>
-# and Raymond Hettinger <python at rcn.com>
-# Copyright (C) 2006-2010 Python Software Foundation.
+# Written by Nick Coghlan <ncoghlan at gmail.com>,
+# Raymond Hettinger <python at rcn.com>,
+# and Łukasz Langa <lukasz at langa.pl>.
+# Copyright (C) 2006-2013 Python Software Foundation.
# See C source code for _functools credits/copyright
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
- 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial']
+ 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial',
+ 'partialmethod', 'singledispatch']
-from _functools import partial, reduce
+try:
+ from _functools import reduce
+except ImportError:
+ pass
+from abc import get_cache_token
from collections import namedtuple
+from types import MappingProxyType, MethodType
+from weakref import WeakKeyDictionary
try:
from _thread import RLock
except:
@@ -47,7 +55,6 @@ def update_wrapper(wrapper,
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
- wrapper.__wrapped__ = wrapped
for attr in assigned:
try:
value = getattr(wrapped, attr)
@@ -57,6 +64,9 @@ def update_wrapper(wrapper,
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+ # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
+ # from the wrapped function when updating __dict__
+ wrapper.__wrapped__ = wrapped
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
@@ -79,21 +89,91 @@ def wraps(wrapped,
### total_ordering class decorator
################################################################################
+# The correct way to indicate that a comparison operation doesn't
+# recognise the other type is to return NotImplemented and let the
+# interpreter handle raising TypeError if both operands return
+# NotImplemented from their respective comparison methods
+#
+# This makes the implementation of total_ordering more complicated, since
+# we need to be careful not to trigger infinite recursion when two
+# different types that both use this decorator encounter each other.
+#
+# For example, if a type implements __lt__, it's natural to define
+# __gt__ as something like:
+#
+# lambda self, other: not self < other and not self == other
+#
+# However, using the operator syntax like that ends up invoking the full
+# type checking machinery again and means we can end up bouncing back and
+# forth between the two operands until we run out of stack space.
+#
+# The solution is to define helper functions that invoke the appropriate
+# magic methods directly, ensuring we only try each operand once, and
+# return NotImplemented immediately if it is returned from the
+# underlying user provided method. Using this scheme, the __gt__ derived
+# from a user provided __lt__ becomes:
+#
+# lambda self, other: _not_op_and_not_eq(self.__lt__, self, other))
+
+def _not_op(op, other):
+ # "not a < b" handles "a >= b"
+ # "not a <= b" handles "a > b"
+ # "not a >= b" handles "a < b"
+ # "not a > b" handles "a <= b"
+ op_result = op(other)
+ if op_result is NotImplemented:
+ return NotImplemented
+ return not op_result
+
+def _op_or_eq(op, self, other):
+ # "a < b or a == b" handles "a <= b"
+ # "a > b or a == b" handles "a >= b"
+ op_result = op(other)
+ if op_result is NotImplemented:
+ return NotImplemented
+ return op_result or self == other
+
+def _not_op_and_not_eq(op, self, other):
+ # "not (a < b or a == b)" handles "a > b"
+ # "not a < b and a != b" is equivalent
+ # "not (a > b or a == b)" handles "a < b"
+ # "not a > b and a != b" is equivalent
+ op_result = op(other)
+ if op_result is NotImplemented:
+ return NotImplemented
+ return not op_result and self != other
+
+def _not_op_or_eq(op, self, other):
+ # "not a <= b or a == b" handles "a >= b"
+ # "not a >= b or a == b" handles "a <= b"
+ op_result = op(other)
+ if op_result is NotImplemented:
+ return NotImplemented
+ return not op_result or self == other
+
+def _op_and_not_eq(op, self, other):
+ # "a <= b and not a == b" handles "a < b"
+ # "a >= b and not a == b" handles "a > b"
+ op_result = op(other)
+ if op_result is NotImplemented:
+ return NotImplemented
+ return op_result and self != other
+
def total_ordering(cls):
"""Class decorator that fills in missing ordering methods"""
convert = {
- '__lt__': [('__gt__', lambda self, other: not (self < other or self == other)),
- ('__le__', lambda self, other: self < other or self == other),
- ('__ge__', lambda self, other: not self < other)],
- '__le__': [('__ge__', lambda self, other: not self <= other or self == other),
- ('__lt__', lambda self, other: self <= other and not self == other),
- ('__gt__', lambda self, other: not self <= other)],
- '__gt__': [('__lt__', lambda self, other: not (self > other or self == other)),
- ('__ge__', lambda self, other: self > other or self == other),
- ('__le__', lambda self, other: not self > other)],
- '__ge__': [('__le__', lambda self, other: (not self >= other) or self == other),
- ('__gt__', lambda self, other: self >= other and not self == other),
- ('__lt__', lambda self, other: not self >= other)]
+ '__lt__': [('__gt__', lambda self, other: _not_op_and_not_eq(self.__lt__, self, other)),
+ ('__le__', lambda self, other: _op_or_eq(self.__lt__, self, other)),
+ ('__ge__', lambda self, other: _not_op(self.__lt__, other))],
+ '__le__': [('__ge__', lambda self, other: _not_op_or_eq(self.__le__, self, other)),
+ ('__lt__', lambda self, other: _op_and_not_eq(self.__le__, self, other)),
+ ('__gt__', lambda self, other: _not_op(self.__le__, other))],
+ '__gt__': [('__lt__', lambda self, other: _not_op_and_not_eq(self.__gt__, self, other)),
+ ('__ge__', lambda self, other: _op_or_eq(self.__gt__, self, other)),
+ ('__le__', lambda self, other: _not_op(self.__gt__, other))],
+ '__ge__': [('__le__', lambda self, other: _not_op_or_eq(self.__ge__, self, other)),
+ ('__gt__', lambda self, other: _op_and_not_eq(self.__ge__, self, other)),
+ ('__lt__', lambda self, other: _not_op(self.__ge__, other))]
}
# Find user-defined comparisons (not those inherited from object).
roots = [op for op in convert if getattr(cls, op, None) is not getattr(object, op, None)]
@@ -140,6 +220,103 @@ except ImportError:
################################################################################
+### partial() argument application
+################################################################################
+
+# Purely functional, no descriptor behaviour
+def partial(func, *args, **keywords):
+ """New function with partial application of the given arguments
+ and keywords.
+ """
+ def newfunc(*fargs, **fkeywords):
+ newkeywords = keywords.copy()
+ newkeywords.update(fkeywords)
+ return func(*(args + fargs), **newkeywords)
+ newfunc.func = func
+ newfunc.args = args
+ newfunc.keywords = keywords
+ return newfunc
+
+try:
+ from _functools import partial
+except ImportError:
+ pass
+
+# Descriptor version
+class partialmethod(object):
+ """Method descriptor with partial application of the given arguments
+ and keywords.
+
+ Supports wrapping existing descriptors and handles non-descriptor
+ callables as instance methods.
+ """
+
+ def __init__(self, func, *args, **keywords):
+ if not callable(func) and not hasattr(func, "__get__"):
+ raise TypeError("{!r} is not callable or a descriptor"
+ .format(func))
+
+ # func could be a descriptor like classmethod which isn't callable,
+ # so we can't inherit from partial (it verifies func is callable)
+ if isinstance(func, partialmethod):
+ # flattening is mandatory in order to place cls/self before all
+ # other arguments
+ # it's also more efficient since only one function will be called
+ self.func = func.func
+ self.args = func.args + args
+ self.keywords = func.keywords.copy()
+ self.keywords.update(keywords)
+ else:
+ self.func = func
+ self.args = args
+ self.keywords = keywords
+
+ def __repr__(self):
+ args = ", ".join(map(repr, self.args))
+ keywords = ", ".join("{}={!r}".format(k, v)
+ for k, v in self.keywords.items())
+ format_string = "{module}.{cls}({func}, {args}, {keywords})"
+ return format_string.format(module=self.__class__.__module__,
+ cls=self.__class__.__name__,
+ func=self.func,
+ args=args,
+ keywords=keywords)
+
+ def _make_unbound_method(self):
+ def _method(*args, **keywords):
+ call_keywords = self.keywords.copy()
+ call_keywords.update(keywords)
+ cls_or_self, *rest = args
+ call_args = (cls_or_self,) + self.args + tuple(rest)
+ return self.func(*call_args, **call_keywords)
+ _method.__isabstractmethod__ = self.__isabstractmethod__
+ return _method
+
+ def __get__(self, obj, cls):
+ get = getattr(self.func, "__get__", None)
+ result = None
+ if get is not None:
+ new_func = get(obj, cls)
+ if new_func is not self.func:
+ # Assume __get__ returning something new indicates the
+ # creation of an appropriate callable
+ result = partial(new_func, *self.args, **self.keywords)
+ try:
+ result.__self__ = new_func.__self__
+ except AttributeError:
+ pass
+ if result is None:
+ # If the underlying descriptor didn't do anything, treat this
+ # like an instance method
+ result = self._make_unbound_method().__get__(obj, cls)
+ return result
+
+ @property
+ def __isabstractmethod__(self):
+ return getattr(self.func, "__isabstractmethod__", False)
+
+
+################################################################################
### LRU Cache function decorator
################################################################################
@@ -220,7 +397,6 @@ def lru_cache(maxsize=128, typed=False):
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
def decorating_function(user_function):
-
cache = {}
hits = misses = 0
full = False
@@ -329,3 +505,210 @@ def lru_cache(maxsize=128, typed=False):
return update_wrapper(wrapper, user_function)
return decorating_function
+
+
+################################################################################
+### singledispatch() - single-dispatch generic function decorator
+################################################################################
+
+def _c3_merge(sequences):
+ """Merges MROs in *sequences* to a single MRO using the C3 algorithm.
+
+ Adapted from http://www.python.org/download/releases/2.3/mro/.
+
+ """
+ result = []
+ while True:
+ sequences = [s for s in sequences if s] # purge empty sequences
+ if not sequences:
+ return result
+ for s1 in sequences: # find merge candidates among seq heads
+ candidate = s1[0]
+ for s2 in sequences:
+ if candidate in s2[1:]:
+ candidate = None
+ break # reject the current head, it appears later
+ else:
+ break
+ if not candidate:
+ raise RuntimeError("Inconsistent hierarchy")
+ result.append(candidate)
+ # remove the chosen candidate
+ for seq in sequences:
+ if seq[0] == candidate:
+ del seq[0]
+
+def _c3_mro(cls, abcs=None):
+ """Computes the method resolution order using extended C3 linearization.
+
+ If no *abcs* are given, the algorithm works exactly like the built-in C3
+ linearization used for method resolution.
+
+ If given, *abcs* is a list of abstract base classes that should be inserted
+ into the resulting MRO. Unrelated ABCs are ignored and don't end up in the
+ result. The algorithm inserts ABCs where their functionality is introduced,
+ i.e. issubclass(cls, abc) returns True for the class itself but returns
+ False for all its direct base classes. Implicit ABCs for a given class
+ (either registered or inferred from the presence of a special method like
+ __len__) are inserted directly after the last ABC explicitly listed in the
+ MRO of said class. If two implicit ABCs end up next to each other in the
+ resulting MRO, their ordering depends on the order of types in *abcs*.
+
+ """
+ for i, base in enumerate(reversed(cls.__bases__)):
+ if hasattr(base, '__abstractmethods__'):
+ boundary = len(cls.__bases__) - i
+ break # Bases up to the last explicit ABC are considered first.
+ else:
+ boundary = 0
+ abcs = list(abcs) if abcs else []
+ explicit_bases = list(cls.__bases__[:boundary])
+ abstract_bases = []
+ other_bases = list(cls.__bases__[boundary:])
+ for base in abcs:
+ if issubclass(cls, base) and not any(
+ issubclass(b, base) for b in cls.__bases__
+ ):
+ # If *cls* is the class that introduces behaviour described by
+ # an ABC *base*, insert said ABC to its MRO.
+ abstract_bases.append(base)
+ for base in abstract_bases:
+ abcs.remove(base)
+ explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases]
+ abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases]
+ other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases]
+ return _c3_merge(
+ [[cls]] +
+ explicit_c3_mros + abstract_c3_mros + other_c3_mros +
+ [explicit_bases] + [abstract_bases] + [other_bases]
+ )
+
+def _compose_mro(cls, types):
+ """Calculates the method resolution order for a given class *cls*.
+
+ Includes relevant abstract base classes (with their respective bases) from
+ the *types* iterable. Uses a modified C3 linearization algorithm.
+
+ """
+ bases = set(cls.__mro__)
+ # Remove entries which are already present in the __mro__ or unrelated.
+ def is_related(typ):
+ return (typ not in bases and hasattr(typ, '__mro__')
+ and issubclass(cls, typ))
+ types = [n for n in types if is_related(n)]
+ # Remove entries which are strict bases of other entries (they will end up
+ # in the MRO anyway.
+ def is_strict_base(typ):
+ for other in types:
+ if typ != other and typ in other.__mro__:
+ return True
+ return False
+ types = [n for n in types if not is_strict_base(n)]
+ # Subclasses of the ABCs in *types* which are also implemented by
+ # *cls* can be used to stabilize ABC ordering.
+ type_set = set(types)
+ mro = []
+ for typ in types:
+ found = []
+ for sub in typ.__subclasses__():
+ if sub not in bases and issubclass(cls, sub):
+ found.append([s for s in sub.__mro__ if s in type_set])
+ if not found:
+ mro.append(typ)
+ continue
+ # Favor subclasses with the biggest number of useful bases
+ found.sort(key=len, reverse=True)
+ for sub in found:
+ for subcls in sub:
+ if subcls not in mro:
+ mro.append(subcls)
+ return _c3_mro(cls, abcs=mro)
+
+def _find_impl(cls, registry):
+ """Returns the best matching implementation from *registry* for type *cls*.
+
+ Where there is no registered implementation for a specific type, its method
+ resolution order is used to find a more generic implementation.
+
+ Note: if *registry* does not contain an implementation for the base
+ *object* type, this function may return None.
+
+ """
+ mro = _compose_mro(cls, registry.keys())
+ match = None
+ for t in mro:
+ if match is not None:
+ # If *match* is an implicit ABC but there is another unrelated,
+ # equally matching implicit ABC, refuse the temptation to guess.
+ if (t in registry and t not in cls.__mro__
+ and match not in cls.__mro__
+ and not issubclass(match, t)):
+ raise RuntimeError("Ambiguous dispatch: {} or {}".format(
+ match, t))
+ break
+ if t in registry:
+ match = t
+ return registry.get(match)
+
+def singledispatch(func):
+ """Single-dispatch generic function decorator.
+
+ Transforms a function into a generic function, which can have different
+ behaviours depending upon the type of its first argument. The decorated
+ function acts as the default implementation, and additional
+ implementations can be registered using the register() attribute of the
+ generic function.
+
+ """
+ registry = {}
+ dispatch_cache = WeakKeyDictionary()
+ cache_token = None
+
+ def dispatch(cls):
+ """generic_func.dispatch(cls) -> <function implementation>
+
+ Runs the dispatch algorithm to return the best available implementation
+ for the given *cls* registered on *generic_func*.
+
+ """
+ nonlocal cache_token
+ if cache_token is not None:
+ current_token = get_cache_token()
+ if cache_token != current_token:
+ dispatch_cache.clear()
+ cache_token = current_token
+ try:
+ impl = dispatch_cache[cls]
+ except KeyError:
+ try:
+ impl = registry[cls]
+ except KeyError:
+ impl = _find_impl(cls, registry)
+ dispatch_cache[cls] = impl
+ return impl
+
+ def register(cls, func=None):
+ """generic_func.register(cls, func) -> func
+
+ Registers a new implementation for the given *cls* on a *generic_func*.
+
+ """
+ nonlocal cache_token
+ if func is None:
+ return lambda f: register(cls, f)
+ registry[cls] = func
+ if cache_token is None and hasattr(cls, '__abstractmethods__'):
+ cache_token = get_cache_token()
+ dispatch_cache.clear()
+ return func
+
+ def wrapper(*args, **kw):
+ return dispatch(args[0].__class__)(*args, **kw)
+
+ registry[object] = func
+ wrapper.register = register
+ wrapper.dispatch = dispatch
+ wrapper.registry = MappingProxyType(registry)
+ wrapper._clear_cache = dispatch_cache.clear
+ update_wrapper(wrapper, func)
+ return wrapper
diff --git a/Lib/genericpath.py b/Lib/genericpath.py
index 340c00494d..ca4a5108fd 100644
--- a/Lib/genericpath.py
+++ b/Lib/genericpath.py
@@ -7,7 +7,8 @@ import os
import stat
__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
- 'getsize', 'isdir', 'isfile']
+ 'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile',
+ 'samestat']
# Does a path exist?
@@ -16,7 +17,7 @@ def exists(path):
"""Test whether a path exists. Returns False for broken symbolic links"""
try:
os.stat(path)
- except os.error:
+ except OSError:
return False
return True
@@ -27,7 +28,7 @@ def isfile(path):
"""Test whether a path is a regular file"""
try:
st = os.stat(path)
- except os.error:
+ except OSError:
return False
return stat.S_ISREG(st.st_mode)
@@ -39,7 +40,7 @@ def isdir(s):
"""Return true if the pathname refers to an existing directory."""
try:
st = os.stat(s)
- except os.error:
+ except OSError:
return False
return stat.S_ISDIR(st.st_mode)
@@ -75,6 +76,31 @@ def commonprefix(m):
return s1[:i]
return s1
+# Are two stat buffers (obtained from stat, fstat or lstat)
+# describing the same file?
+def samestat(s1, s2):
+ """Test whether two stat buffers reference the same file"""
+ return (s1.st_ino == s2.st_ino and
+ s1.st_dev == s2.st_dev)
+
+
+# Are two filenames really pointing to the same file?
+def samefile(f1, f2):
+ """Test whether two pathnames reference the same actual file"""
+ s1 = os.stat(f1)
+ s2 = os.stat(f2)
+ return samestat(s1, s2)
+
+
+# Are two open files really referencing the same file?
+# (Not necessarily the same file descriptor!)
+def sameopenfile(fp1, fp2):
+ """Test whether two open file objects reference the same file"""
+ s1 = os.fstat(fp1)
+ s2 = os.fstat(fp2)
+ return samestat(s1, s2)
+
+
# Split a path in root and extension.
# The extension is everything starting at the last dot in the last
# pathname component; the root is everything before that.
diff --git a/Lib/getpass.py b/Lib/getpass.py
index 0044742193..53c38b8897 100644
--- a/Lib/getpass.py
+++ b/Lib/getpass.py
@@ -15,7 +15,11 @@ On the Mac EasyDialogs.AskPassword is used, if available.
# Guido van Rossum (Windows support and cleanup)
# Gregory P. Smith (tty support & GetPassWarning)
-import os, sys, warnings
+import contextlib
+import io
+import os
+import sys
+import warnings
__all__ = ["getpass","getuser","GetPassWarning"]
@@ -38,52 +42,57 @@ def unix_getpass(prompt='Password: ', stream=None):
Always restores terminal settings before returning.
"""
- fd = None
- tty = None
- try:
- # Always try reading and writing directly on the tty first.
- fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
- tty = os.fdopen(fd, 'w+', 1)
- input = tty
- if not stream:
- stream = tty
- except EnvironmentError as e:
- # If that fails, see if stdin can be controlled.
+ passwd = None
+ with contextlib.ExitStack() as stack:
try:
- fd = sys.stdin.fileno()
- except (AttributeError, ValueError):
- passwd = fallback_getpass(prompt, stream)
- input = sys.stdin
- if not stream:
- stream = sys.stderr
-
- if fd is not None:
- passwd = None
- try:
- old = termios.tcgetattr(fd) # a copy to save
- new = old[:]
- new[3] &= ~termios.ECHO # 3 == 'lflags'
- tcsetattr_flags = termios.TCSAFLUSH
- if hasattr(termios, 'TCSASOFT'):
- tcsetattr_flags |= termios.TCSASOFT
+ # Always try reading and writing directly on the tty first.
+ fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
+ tty = io.FileIO(fd, 'w+')
+ stack.enter_context(tty)
+ input = io.TextIOWrapper(tty)
+ stack.enter_context(input)
+ if not stream:
+ stream = input
+ except OSError as e:
+ # If that fails, see if stdin can be controlled.
+ stack.close()
+ try:
+ fd = sys.stdin.fileno()
+ except (AttributeError, ValueError):
+ fd = None
+ passwd = fallback_getpass(prompt, stream)
+ input = sys.stdin
+ if not stream:
+ stream = sys.stderr
+
+ if fd is not None:
try:
- termios.tcsetattr(fd, tcsetattr_flags, new)
- passwd = _raw_input(prompt, stream, input=input)
- finally:
- termios.tcsetattr(fd, tcsetattr_flags, old)
- stream.flush() # issue7208
- except termios.error:
- if passwd is not None:
- # _raw_input succeeded. The final tcsetattr failed. Reraise
- # instead of leaving the terminal in an unknown state.
- raise
- # We can't control the tty or stdin. Give up and use normal IO.
- # fallback_getpass() raises an appropriate warning.
- del input, tty # clean up unused file objects before blocking
- passwd = fallback_getpass(prompt, stream)
-
- stream.write('\n')
- return passwd
+ old = termios.tcgetattr(fd) # a copy to save
+ new = old[:]
+ new[3] &= ~termios.ECHO # 3 == 'lflags'
+ tcsetattr_flags = termios.TCSAFLUSH
+ if hasattr(termios, 'TCSASOFT'):
+ tcsetattr_flags |= termios.TCSASOFT
+ try:
+ termios.tcsetattr(fd, tcsetattr_flags, new)
+ passwd = _raw_input(prompt, stream, input=input)
+ finally:
+ termios.tcsetattr(fd, tcsetattr_flags, old)
+ stream.flush() # issue7208
+ except termios.error:
+ if passwd is not None:
+ # _raw_input succeeded. The final tcsetattr failed. Reraise
+ # instead of leaving the terminal in an unknown state.
+ raise
+ # We can't control the tty or stdin. Give up and use normal IO.
+ # fallback_getpass() raises an appropriate warning.
+ if stream is not input:
+ # clean up unused file objects before blocking
+ stack.close()
+ passwd = fallback_getpass(prompt, stream)
+
+ stream.write('\n')
+ return passwd
def win_getpass(prompt='Password: ', stream=None):
diff --git a/Lib/gettext.py b/Lib/gettext.py
index e43f044cc7..05d9c1e5a3 100644
--- a/Lib/gettext.py
+++ b/Lib/gettext.py
@@ -244,7 +244,7 @@ class GNUTranslations(NullTranslations):
version, msgcount, masteridx, transidx = unpack('>4I', buf[4:20])
ii = '>II'
else:
- raise IOError(0, 'Bad magic number', filename)
+ raise OSError(0, 'Bad magic number', filename)
# Now put all messages from the .mo file buffer into the catalog
# dictionary.
for i in range(0, msgcount):
@@ -256,7 +256,7 @@ class GNUTranslations(NullTranslations):
msg = buf[moff:mend]
tmsg = buf[toff:tend]
else:
- raise IOError(0, 'File is corrupt', filename)
+ raise OSError(0, 'File is corrupt', filename)
# See if we're looking at GNU .mo conventions for metadata
if mlen == 0:
# Catalog description
@@ -398,7 +398,7 @@ def translation(domain, localedir=None, languages=None,
if not mofiles:
if fallback:
return NullTranslations()
- raise IOError(ENOENT, 'No translation file found for domain', domain)
+ raise OSError(ENOENT, 'No translation file found for domain', domain)
# Avoid opening, reading, and parsing the .mo file after it's been done
# once.
result = None
@@ -460,7 +460,7 @@ def dgettext(domain, message):
try:
t = translation(domain, _localedirs.get(domain, None),
codeset=_localecodesets.get(domain))
- except IOError:
+ except OSError:
return message
return t.gettext(message)
@@ -468,7 +468,7 @@ def ldgettext(domain, message):
try:
t = translation(domain, _localedirs.get(domain, None),
codeset=_localecodesets.get(domain))
- except IOError:
+ except OSError:
return message
return t.lgettext(message)
@@ -476,7 +476,7 @@ def dngettext(domain, msgid1, msgid2, n):
try:
t = translation(domain, _localedirs.get(domain, None),
codeset=_localecodesets.get(domain))
- except IOError:
+ except OSError:
if n == 1:
return msgid1
else:
@@ -487,7 +487,7 @@ def ldngettext(domain, msgid1, msgid2, n):
try:
t = translation(domain, _localedirs.get(domain, None),
codeset=_localecodesets.get(domain))
- except IOError:
+ except OSError:
if n == 1:
return msgid1
else:
diff --git a/Lib/glob.py b/Lib/glob.py
index 1f602656d0..e388b5f9c0 100644
--- a/Lib/glob.py
+++ b/Lib/glob.py
@@ -32,8 +32,7 @@ def iglob(pathname):
return
dirname, basename = os.path.split(pathname)
if not dirname:
- for name in glob1(None, basename):
- yield name
+ yield from glob1(None, basename)
return
# `os.path.split()` returns the argument itself as a dirname if it is a
# drive or UNC path. Prevent an infinite recursion if a drive or UNC path
@@ -62,7 +61,7 @@ def glob1(dirname, pattern):
dirname = os.curdir
try:
names = os.listdir(dirname)
- except os.error:
+ except OSError:
return []
if not _ishidden(pattern):
names = [x for x in names if not _ishidden(x)]
@@ -80,8 +79,8 @@ def glob0(dirname, basename):
return []
-magic_check = re.compile('[*?[]')
-magic_check_bytes = re.compile(b'[*?[]')
+magic_check = re.compile('([*?[])')
+magic_check_bytes = re.compile(b'([*?[])')
def has_magic(s):
if isinstance(s, bytes):
@@ -92,3 +91,15 @@ def has_magic(s):
def _ishidden(path):
return path[0] in ('.', b'.'[0])
+
+def escape(pathname):
+ """Escape all special characters.
+ """
+ # Escaping is done by wrapping any of "*?[" between square brackets.
+ # Metacharacters do not work in the drive part and shouldn't be escaped.
+ drive, pathname = os.path.splitdrive(pathname)
+ if isinstance(pathname, bytes):
+ pathname = magic_check_bytes.sub(br'[\1]', pathname)
+ else:
+ pathname = magic_check.sub(r'[\1]', pathname)
+ return drive + pathname
diff --git a/Lib/gzip.py b/Lib/gzip.py
index 2fd03fa747..8d21fe4bc2 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -23,9 +23,9 @@ def open(filename, mode="rb", compresslevel=9,
The filename argument can be an actual filename (a str or bytes object), or
an existing file object to read from or write to.
- The mode argument can be "r", "rb", "w", "wb", "a" or "ab" for binary mode,
- or "rt", "wt" or "at" for text mode. The default mode is "rb", and the
- default compresslevel is 9.
+ The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or "ab" for
+ binary mode, or "rt", "wt", "xt" or "at" for text mode. The default mode is
+ "rb", and the default compresslevel is 9.
For binary mode, this function is equivalent to the GzipFile constructor:
GzipFile(filename, mode, compresslevel). In this case, the encoding, errors
@@ -65,9 +65,6 @@ def write32u(output, value):
# or unsigned.
output.write(struct.pack("<L", value))
-def read32(input):
- return struct.unpack("<I", input.read(4))[0]
-
class _PaddedFile:
"""Minimal read-only file object that prepends a string to the contents
of an actual file. Shouldn't be used outside of gzip.py, as it lacks
@@ -154,11 +151,11 @@ class GzipFile(io.BufferedIOBase):
fileobj, if discernible; otherwise, it defaults to the empty string,
and in this case the original filename is not included in the header.
- The mode argument can be any of 'r', 'rb', 'a', 'ab', 'w', or 'wb',
- depending on whether the file will be read or written. The default
+ The mode argument can be any of 'r', 'rb', 'a', 'ab', 'w', 'wb', 'x', or
+ 'xb' depending on whether the file will be read or written. The default
is the mode of fileobj if discernible; otherwise, the default is 'rb'.
A mode of 'r' is equivalent to one of 'rb', and similarly for 'w' and
- 'wb', and 'a' and 'ab'.
+ 'wb', 'a' and 'ab', and 'x' and 'xb'.
The compresslevel argument is an integer from 0 to 9 controlling the
level of compression; 1 is fastest and produces the least compression,
@@ -204,7 +201,7 @@ class GzipFile(io.BufferedIOBase):
self.min_readsize = 100
fileobj = _PaddedFile(fileobj)
- elif mode.startswith(('w', 'a')):
+ elif mode.startswith(('w', 'a', 'x')):
self.mode = WRITE
self._init_write(filename)
self.compress = zlib.compressobj(compresslevel,
@@ -281,28 +278,32 @@ class GzipFile(io.BufferedIOBase):
self.crc = zlib.crc32(b"") & 0xffffffff
self.size = 0
+ def _read_exact(self, n):
+ data = self.fileobj.read(n)
+ while len(data) < n:
+ b = self.fileobj.read(n - len(data))
+ if not b:
+ raise EOFError("Compressed file ended before the "
+ "end-of-stream marker was reached")
+ data += b
+ return data
+
def _read_gzip_header(self):
magic = self.fileobj.read(2)
if magic == b'':
- raise EOFError("Reached EOF")
+ return False
if magic != b'\037\213':
- raise IOError('Not a gzipped file')
+ raise OSError('Not a gzipped file')
- method = ord( self.fileobj.read(1) )
+ method, flag, self.mtime = struct.unpack("<BBIxx", self._read_exact(8))
if method != 8:
- raise IOError('Unknown compression method')
- flag = ord( self.fileobj.read(1) )
- self.mtime = read32(self.fileobj)
- # extraflag = self.fileobj.read(1)
- # os = self.fileobj.read(1)
- self.fileobj.read(2)
+ raise OSError('Unknown compression method')
if flag & FEXTRA:
# Read & discard the extra field, if present
- xlen = ord(self.fileobj.read(1))
- xlen = xlen + 256*ord(self.fileobj.read(1))
- self.fileobj.read(xlen)
+ extra_len, = struct.unpack("<H", self._read_exact(2))
+ self._read_exact(extra_len)
if flag & FNAME:
# Read and discard a null-terminated string containing the filename
while True:
@@ -316,18 +317,19 @@ class GzipFile(io.BufferedIOBase):
if not s or s==b'\000':
break
if flag & FHCRC:
- self.fileobj.read(2) # Read & discard the 16-bit header CRC
+ self._read_exact(2) # Read & discard the 16-bit header CRC
unused = self.fileobj.unused()
if unused:
uncompress = self.decompress.decompress(unused)
self._add_read_data(uncompress)
+ return True
def write(self,data):
self._check_closed()
if self.mode != WRITE:
import errno
- raise IOError(errno.EBADF, "write() on read-only GzipFile object")
+ raise OSError(errno.EBADF, "write() on read-only GzipFile object")
if self.fileobj is None:
raise ValueError("write() on closed GzipFile object")
@@ -348,27 +350,23 @@ class GzipFile(io.BufferedIOBase):
self._check_closed()
if self.mode != READ:
import errno
- raise IOError(errno.EBADF, "read() on write-only GzipFile object")
+ raise OSError(errno.EBADF, "read() on write-only GzipFile object")
if self.extrasize <= 0 and self.fileobj is None:
return b''
readsize = 1024
if size < 0: # get the whole thing
- try:
- while True:
- self._read(readsize)
- readsize = min(self.max_read_chunk, readsize * 2)
- except EOFError:
- size = self.extrasize
+ while self._read(readsize):
+ readsize = min(self.max_read_chunk, readsize * 2)
+ size = self.extrasize
else: # just get some more of it
- try:
- while size > self.extrasize:
- self._read(readsize)
- readsize = min(self.max_read_chunk, readsize * 2)
- except EOFError:
- if size > self.extrasize:
- size = self.extrasize
+ while size > self.extrasize:
+ if not self._read(readsize):
+ if size > self.extrasize:
+ size = self.extrasize
+ break
+ readsize = min(self.max_read_chunk, readsize * 2)
offset = self.offset - self.extrastart
chunk = self.extrabuf[offset: offset + size]
@@ -381,17 +379,14 @@ class GzipFile(io.BufferedIOBase):
self._check_closed()
if self.mode != READ:
import errno
- raise IOError(errno.EBADF, "read1() on write-only GzipFile object")
+ raise OSError(errno.EBADF, "read1() on write-only GzipFile object")
if self.extrasize <= 0 and self.fileobj is None:
return b''
- try:
- # For certain input data, a single call to _read() may not return
- # any data. In this case, retry until we get some data or reach EOF.
- while self.extrasize <= 0:
- self._read()
- except EOFError:
+ # For certain input data, a single call to _read() may not return
+ # any data. In this case, retry until we get some data or reach EOF.
+ while self.extrasize <= 0 and self._read():
pass
if size < 0 or size > self.extrasize:
size = self.extrasize
@@ -405,7 +400,7 @@ class GzipFile(io.BufferedIOBase):
def peek(self, n):
if self.mode != READ:
import errno
- raise IOError(errno.EBADF, "peek() on write-only GzipFile object")
+ raise OSError(errno.EBADF, "peek() on write-only GzipFile object")
# Do not return ridiculously small buffers, for one common idiom
# is to call peek(1) and expect more bytes in return.
@@ -414,12 +409,9 @@ class GzipFile(io.BufferedIOBase):
if self.extrasize == 0:
if self.fileobj is None:
return b''
- try:
- # Ensure that we don't return b"" if we haven't reached EOF.
- while self.extrasize == 0:
- # 1024 is the same buffering heuristic used in read()
- self._read(max(n, 1024))
- except EOFError:
+ # Ensure that we don't return b"" if we haven't reached EOF.
+ # 1024 is the same buffering heuristic used in read()
+ while self.extrasize == 0 and self._read(max(n, 1024)):
pass
offset = self.offset - self.extrastart
remaining = self.extrasize
@@ -432,13 +424,14 @@ class GzipFile(io.BufferedIOBase):
def _read(self, size=1024):
if self.fileobj is None:
- raise EOFError("Reached EOF")
+ return False
if self._new_member:
# If the _new_member flag is set, we have to
# jump to the next member, if there is one.
self._init_read()
- self._read_gzip_header()
+ if not self._read_gzip_header():
+ return False
self.decompress = zlib.decompressobj(-zlib.MAX_WBITS)
self._new_member = False
@@ -455,7 +448,7 @@ class GzipFile(io.BufferedIOBase):
self.fileobj.prepend(self.decompress.unused_data, True)
self._read_eof()
self._add_read_data( uncompress )
- raise EOFError('Reached EOF')
+ return False
uncompress = self.decompress.decompress(buf)
self._add_read_data( uncompress )
@@ -471,6 +464,7 @@ class GzipFile(io.BufferedIOBase):
# a new member on the next call
self._read_eof()
self._new_member = True
+ return True
def _add_read_data(self, data):
self.crc = zlib.crc32(data, self.crc) & 0xffffffff
@@ -485,13 +479,12 @@ class GzipFile(io.BufferedIOBase):
# We check the that the computed CRC and size of the
# uncompressed data matches the stored values. Note that the size
# stored is the true file size mod 2**32.
- crc32 = read32(self.fileobj)
- isize = read32(self.fileobj) # may exceed 2GB
+ crc32, isize = struct.unpack("<II", self._read_exact(8))
if crc32 != self.crc:
- raise IOError("CRC check failed %s != %s" % (hex(crc32),
+ raise OSError("CRC check failed %s != %s" % (hex(crc32),
hex(self.crc)))
elif isize != (self.size & 0xffffffff):
- raise IOError("Incorrect length of data produced")
+ raise OSError("Incorrect length of data produced")
# Gzip files can be padded with zeroes and still have archives.
# Consume all zero bytes and set the file position to the first
@@ -540,7 +533,7 @@ class GzipFile(io.BufferedIOBase):
'''Return the uncompressed stream file position indicator to the
beginning of the file'''
if self.mode != READ:
- raise IOError("Can't rewind in write mode")
+ raise OSError("Can't rewind in write mode")
self.fileobj.seek(0)
self._new_member = True
self.extrabuf = b""
@@ -565,7 +558,7 @@ class GzipFile(io.BufferedIOBase):
raise ValueError('Seek from end not supported')
if self.mode == WRITE:
if offset < self.offset:
- raise IOError('Negative seek in write mode')
+ raise OSError('Negative seek in write mode')
count = offset - self.offset
chunk = bytes(1024)
for i in range(count // 1024):
diff --git a/Lib/hashlib.py b/Lib/hashlib.py
index 21454c7d30..77673f06eb 100644
--- a/Lib/hashlib.py
+++ b/Lib/hashlib.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org)
+#. Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org)
# Licensed to PSF under a Contributor Agreement.
#
@@ -54,40 +54,52 @@ More condensed:
# 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')
+__always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
+ 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512')
algorithms_guaranteed = set(__always_supported)
algorithms_available = set(__always_supported)
__all__ = __always_supported + ('new', 'algorithms_guaranteed',
- 'algorithms_available')
+ 'algorithms_available', 'pbkdf2_hmac')
+__builtin_constructor_cache = {}
+
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
- return _sha1.sha1
+ cache['SHA1'] = cache['sha1'] = _sha1.sha1
elif name in ('MD5', 'md5'):
import _md5
- return _md5.md5
+ cache['MD5'] = cache['md5'] = _md5.md5
elif name in ('SHA256', 'sha256', 'SHA224', 'sha224'):
import _sha256
- bs = name[3:]
- if bs == '256':
- return _sha256.sha256
- elif bs == '224':
- return _sha256.sha224
+ cache['SHA224'] = cache['sha224'] = _sha256.sha224
+ cache['SHA256'] = cache['sha256'] = _sha256.sha256
elif name in ('SHA512', 'sha512', 'SHA384', 'sha384'):
import _sha512
- bs = name[3:]
- if bs == '512':
- return _sha512.sha512
- elif bs == '384':
- return _sha512.sha384
+ cache['SHA384'] = cache['sha384'] = _sha512.sha384
+ cache['SHA512'] = cache['sha512'] = _sha512.sha512
+ elif name in {'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
+ 'SHA3_224', 'SHA3_256', 'SHA3_384', 'SHA3_512'}:
+ import _sha3
+ cache['SHA3_224'] = cache['sha3_224'] = _sha3.sha3_224
+ cache['SHA3_256'] = cache['sha3_256'] = _sha3.sha3_256
+ cache['SHA3_384'] = cache['sha3_384'] = _sha3.sha3_384
+ cache['SHA3_512'] = cache['sha3_512'] = _sha3.sha3_512
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)
@@ -134,6 +146,71 @@ except ImportError:
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:
+ _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.
+ """
+ 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 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, 'big'))
+ # endianess doesn't matter here as long to / from use the same
+ rkey = int.from_bytes(prev, 'big')
+ for i in range(iterations - 1):
+ prev = prf(prev)
+ # rkey = rkey ^ prev
+ rkey ^= from_bytes(prev, 'big')
+ loop += 1
+ dkey += rkey.to_bytes(inner.digest_size, 'big')
+
+ return dkey[:dklen]
+
+
for __func_name in __always_supported:
# try them all, some may not work due to the OpenSSL
# version not supporting that algorithm.
diff --git a/Lib/hmac.py b/Lib/hmac.py
index 4297a7171a..77785a2d9b 100644
--- a/Lib/hmac.py
+++ b/Lib/hmac.py
@@ -4,7 +4,8 @@ Implements the HMAC algorithm as described by RFC 2104.
"""
import warnings as _warnings
-from operator import _compare_digest as compare_digest
+from _operator import _compare_digest as compare_digest
+import hashlib as _hashlib
trans_5C = bytes((x ^ 0x5C) for x in range(256))
trans_36 = bytes((x ^ 0x36) for x in range(256))
@@ -28,21 +29,27 @@ class HMAC:
key: key for the keyed hash object.
msg: Initial input for the hash, if provided.
digestmod: A module supporting PEP 247. *OR*
- A hashlib constructor returning a new hash object.
+ A hashlib constructor returning a new hash object. *OR*
+ A hash name suitable for hashlib.new().
Defaults to hashlib.md5.
+ Implicit default to hashlib.md5 is deprecated and will be
+ removed in Python 3.6.
- Note: key and msg must be bytes objects.
+ Note: key and msg must be a bytes or bytearray objects.
"""
- if not isinstance(key, bytes):
- raise TypeError("key: expected bytes, but got %r" % type(key).__name__)
+ if not isinstance(key, (bytes, bytearray)):
+ raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
if digestmod is None:
- import hashlib
- digestmod = hashlib.md5
+ _warnings.warn("HMAC() without an explicit digestmod argument "
+ "is deprecated.", PendingDeprecationWarning, 2)
+ digestmod = _hashlib.md5
if callable(digestmod):
self.digest_cons = digestmod
+ elif isinstance(digestmod, str):
+ self.digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
else:
self.digest_cons = lambda d=b'': digestmod.new(d)
@@ -63,6 +70,10 @@ class HMAC:
RuntimeWarning, 2)
blocksize = self.blocksize
+ # self.blocksize is the default blocksize. self.block_size is
+ # effective block size as well as the public API attribute.
+ self.block_size = blocksize
+
if len(key) > blocksize:
key = self.digest_cons(key).digest()
@@ -72,11 +83,13 @@ class HMAC:
if msg is not None:
self.update(msg)
+ @property
+ def name(self):
+ return "hmac-" + self.inner.name
+
def update(self, msg):
"""Update this hashing object with the string msg.
"""
- if not isinstance(msg, bytes):
- raise TypeError("expected bytes, but got %r" % type(msg).__name__)
self.inner.update(msg)
def copy(self):
diff --git a/Lib/html/__init__.py b/Lib/html/__init__.py
index 02652ef73c..da0a0a3ce7 100644
--- a/Lib/html/__init__.py
+++ b/Lib/html/__init__.py
@@ -2,12 +2,12 @@
General functions for HTML manipulation.
"""
+import re as _re
+from html.entities import html5 as _html5
-_escape_map = {ord('&'): '&amp;', ord('<'): '&lt;', ord('>'): '&gt;'}
-_escape_map_full = {ord('&'): '&amp;', ord('<'): '&lt;', ord('>'): '&gt;',
- ord('"'): '&quot;', ord('\''): '&#x27;'}
-# NB: this is a candidate for a bytes/string polymorphic interface
+__all__ = ['escape', 'unescape']
+
def escape(s, quote=True):
"""
@@ -16,6 +16,117 @@ def escape(s, quote=True):
characters, both double quote (") and single quote (') characters are also
translated.
"""
+ s = s.replace("&", "&amp;") # Must be done first!
+ s = s.replace("<", "&lt;")
+ s = s.replace(">", "&gt;")
if quote:
- return s.translate(_escape_map_full)
- return s.translate(_escape_map)
+ s = s.replace('"', "&quot;")
+ s = s.replace('\'', "&#x27;")
+ return s
+
+
+# see http://www.w3.org/TR/html5/syntax.html#tokenizing-character-references
+
+_invalid_charrefs = {
+ 0x00: '\ufffd', # REPLACEMENT CHARACTER
+ 0x0d: '\r', # CARRIAGE RETURN
+ 0x80: '\u20ac', # EURO SIGN
+ 0x81: '\x81', # <control>
+ 0x82: '\u201a', # SINGLE LOW-9 QUOTATION MARK
+ 0x83: '\u0192', # LATIN SMALL LETTER F WITH HOOK
+ 0x84: '\u201e', # DOUBLE LOW-9 QUOTATION MARK
+ 0x85: '\u2026', # HORIZONTAL ELLIPSIS
+ 0x86: '\u2020', # DAGGER
+ 0x87: '\u2021', # DOUBLE DAGGER
+ 0x88: '\u02c6', # MODIFIER LETTER CIRCUMFLEX ACCENT
+ 0x89: '\u2030', # PER MILLE SIGN
+ 0x8a: '\u0160', # LATIN CAPITAL LETTER S WITH CARON
+ 0x8b: '\u2039', # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ 0x8c: '\u0152', # LATIN CAPITAL LIGATURE OE
+ 0x8d: '\x8d', # <control>
+ 0x8e: '\u017d', # LATIN CAPITAL LETTER Z WITH CARON
+ 0x8f: '\x8f', # <control>
+ 0x90: '\x90', # <control>
+ 0x91: '\u2018', # LEFT SINGLE QUOTATION MARK
+ 0x92: '\u2019', # RIGHT SINGLE QUOTATION MARK
+ 0x93: '\u201c', # LEFT DOUBLE QUOTATION MARK
+ 0x94: '\u201d', # RIGHT DOUBLE QUOTATION MARK
+ 0x95: '\u2022', # BULLET
+ 0x96: '\u2013', # EN DASH
+ 0x97: '\u2014', # EM DASH
+ 0x98: '\u02dc', # SMALL TILDE
+ 0x99: '\u2122', # TRADE MARK SIGN
+ 0x9a: '\u0161', # LATIN SMALL LETTER S WITH CARON
+ 0x9b: '\u203a', # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ 0x9c: '\u0153', # LATIN SMALL LIGATURE OE
+ 0x9d: '\x9d', # <control>
+ 0x9e: '\u017e', # LATIN SMALL LETTER Z WITH CARON
+ 0x9f: '\u0178', # LATIN CAPITAL LETTER Y WITH DIAERESIS
+}
+
+_invalid_codepoints = {
+ # 0x0001 to 0x0008
+ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
+ # 0x000E to 0x001F
+ 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
+ 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ # 0x007F to 0x009F
+ 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
+ 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
+ 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+ # 0xFDD0 to 0xFDEF
+ 0xfdd0, 0xfdd1, 0xfdd2, 0xfdd3, 0xfdd4, 0xfdd5, 0xfdd6, 0xfdd7, 0xfdd8,
+ 0xfdd9, 0xfdda, 0xfddb, 0xfddc, 0xfddd, 0xfdde, 0xfddf, 0xfde0, 0xfde1,
+ 0xfde2, 0xfde3, 0xfde4, 0xfde5, 0xfde6, 0xfde7, 0xfde8, 0xfde9, 0xfdea,
+ 0xfdeb, 0xfdec, 0xfded, 0xfdee, 0xfdef,
+ # others
+ 0xb, 0xfffe, 0xffff, 0x1fffe, 0x1ffff, 0x2fffe, 0x2ffff, 0x3fffe, 0x3ffff,
+ 0x4fffe, 0x4ffff, 0x5fffe, 0x5ffff, 0x6fffe, 0x6ffff, 0x7fffe, 0x7ffff,
+ 0x8fffe, 0x8ffff, 0x9fffe, 0x9ffff, 0xafffe, 0xaffff, 0xbfffe, 0xbffff,
+ 0xcfffe, 0xcffff, 0xdfffe, 0xdffff, 0xefffe, 0xeffff, 0xffffe, 0xfffff,
+ 0x10fffe, 0x10ffff
+}
+
+
+def _replace_charref(s):
+ s = s.group(1)
+ if s[0] == '#':
+ # numeric charref
+ if s[1] in 'xX':
+ num = int(s[2:].rstrip(';'), 16)
+ else:
+ num = int(s[1:].rstrip(';'))
+ if num in _invalid_charrefs:
+ return _invalid_charrefs[num]
+ if 0xD800 <= num <= 0xDFFF or num > 0x10FFFF:
+ return '\uFFFD'
+ if num in _invalid_codepoints:
+ return ''
+ return chr(num)
+ else:
+ # named charref
+ if s in _html5:
+ return _html5[s]
+ # find the longest matching name (as defined by the standard)
+ for x in range(len(s)-1, 1, -1):
+ if s[:x] in _html5:
+ return _html5[s[:x]] + s[x:]
+ else:
+ return '&' + s
+
+
+_charref = _re.compile(r'&(#[0-9]+;?'
+ r'|#[xX][0-9a-fA-F]+;?'
+ r'|[^\t\n\f <&#;]{1,32};?)')
+
+def unescape(s):
+ """
+ Convert all named and numeric character references (e.g. &gt;, &#62;,
+ &x3e;) in the string s to the corresponding unicode characters.
+ This function uses the rules defined by the HTML 5 standard
+ for both valid and invalid character references, and the list of
+ HTML 5 named character references defined in html.entities.html5.
+ """
+ if '&' not in s:
+ return s
+ return _charref.sub(_replace_charref, s)
diff --git a/Lib/html/parser.py b/Lib/html/parser.py
index 2d3bef351b..12c28b8339 100644
--- a/Lib/html/parser.py
+++ b/Lib/html/parser.py
@@ -8,9 +8,14 @@
# and CDATA (character data -- only end tags are special).
-import _markupbase
import re
import warnings
+import _markupbase
+
+from html import unescape
+
+
+__all__ = ['HTMLParser']
# Regular expressions used for parsing
@@ -92,6 +97,8 @@ class HTMLParseError(Exception):
return result
+_default_sentinel = object()
+
class HTMLParser(_markupbase.ParserBase):
"""Find tags and other markup and call handler functions.
@@ -105,26 +112,39 @@ class HTMLParser(_markupbase.ParserBase):
self.handle_startendtag(); end tags by self.handle_endtag(). The
data between tags is passed from the parser to the derived class
by calling self.handle_data() with the data as argument (the data
- may be split up in arbitrary chunks). Entity references are
- passed by calling self.handle_entityref() with the entity
- reference as the argument. Numeric character references are
- passed to self.handle_charref() with the string containing the
- reference as the argument.
+ may be split up in arbitrary chunks). If convert_charrefs is
+ True the character references are converted automatically to the
+ corresponding Unicode character (and self.handle_data() is no
+ longer split in chunks), otherwise they are passed by calling
+ self.handle_entityref() or self.handle_charref() with the string
+ containing respectively the named or numeric reference as the
+ argument.
"""
CDATA_CONTENT_ELEMENTS = ("script", "style")
- def __init__(self, strict=False):
+ def __init__(self, strict=_default_sentinel, *,
+ convert_charrefs=_default_sentinel):
"""Initialize and reset this instance.
+ If convert_charrefs is True (default: False), all character references
+ are automatically converted to the corresponding Unicode characters.
If strict is set to False (the default) the parser will parse invalid
markup, otherwise it will raise an error. Note that the strict mode
- is deprecated.
+ and argument are deprecated.
"""
- if strict:
- warnings.warn("The strict mode is deprecated.",
+ if strict is not _default_sentinel:
+ warnings.warn("The strict argument and mode are deprecated.",
DeprecationWarning, stacklevel=2)
+ else:
+ strict = False # default
self.strict = strict
+ if convert_charrefs is _default_sentinel:
+ convert_charrefs = False # default
+ warnings.warn("The value of convert_charrefs will become True in "
+ "3.5. You are encouraged to set the value explicitly.",
+ DeprecationWarning, stacklevel=2)
+ self.convert_charrefs = convert_charrefs
self.reset()
def reset(self):
@@ -149,6 +169,8 @@ class HTMLParser(_markupbase.ParserBase):
self.goahead(1)
def error(self, message):
+ warnings.warn("The 'error' method is deprecated.",
+ DeprecationWarning, stacklevel=2)
raise HTMLParseError(message, self.getpos())
__starttag_text = None
@@ -173,14 +195,25 @@ class HTMLParser(_markupbase.ParserBase):
i = 0
n = len(rawdata)
while i < n:
- match = self.interesting.search(rawdata, i) # < or &
- if match:
- j = match.start()
+ if self.convert_charrefs and not self.cdata_elem:
+ j = rawdata.find('<', i)
+ if j < 0:
+ if not end:
+ break # wait till we get all the text
+ j = n
else:
- if self.cdata_elem:
- break
- j = n
- if i < j: self.handle_data(rawdata[i:j])
+ match = self.interesting.search(rawdata, i) # < or &
+ if match:
+ j = match.start()
+ else:
+ if self.cdata_elem:
+ break
+ j = n
+ if i < j:
+ if self.convert_charrefs and not self.cdata_elem:
+ self.handle_data(unescape(rawdata[i:j]))
+ else:
+ self.handle_data(rawdata[i:j])
i = self.updatepos(i, j)
if i == n: break
startswith = rawdata.startswith
@@ -215,7 +248,10 @@ class HTMLParser(_markupbase.ParserBase):
k = i + 1
else:
k += 1
- self.handle_data(rawdata[i:k])
+ if self.convert_charrefs and not self.cdata_elem:
+ self.handle_data(unescape(rawdata[i:k]))
+ else:
+ self.handle_data(rawdata[i:k])
i = self.updatepos(i, k)
elif startswith("&#", i):
match = charref.match(rawdata, i)
@@ -266,7 +302,10 @@ class HTMLParser(_markupbase.ParserBase):
assert 0, "interesting.search() lied"
# end while
if end and i < n and not self.cdata_elem:
- self.handle_data(rawdata[i:n])
+ if self.convert_charrefs and not self.cdata_elem:
+ self.handle_data(unescape(rawdata[i:n]))
+ else:
+ self.handle_data(rawdata[i:n])
i = self.updatepos(i, n)
self.rawdata = rawdata[i:]
@@ -349,7 +388,7 @@ class HTMLParser(_markupbase.ParserBase):
attrvalue[:1] == '"' == attrvalue[-1:]:
attrvalue = attrvalue[1:-1]
if attrvalue:
- attrvalue = self.unescape(attrvalue)
+ attrvalue = unescape(attrvalue)
attrs.append((attrname.lower(), attrvalue))
k = m.end()
@@ -505,31 +544,7 @@ class HTMLParser(_markupbase.ParserBase):
# Internal -- helper to remove special character quoting
def unescape(self, s):
- if '&' not in s:
- return s
- def replaceEntities(s):
- s = s.groups()[0]
- try:
- if s[0] == "#":
- s = s[1:]
- if s[0] in ['x','X']:
- c = int(s[1:].rstrip(';'), 16)
- else:
- c = int(s.rstrip(';'))
- return chr(c)
- except ValueError:
- return '&#' + s
- else:
- from html.entities import html5
- if s in html5:
- return html5[s]
- elif s.endswith(';'):
- return '&' + s
- for x in range(2, len(s)):
- if s[:x] in html5:
- return html5[s[:x]] + s[x:]
- else:
- return '&' + s
-
- return re.sub(r"&(#?[xX]?(?:[0-9a-fA-F]+;|\w{1,32};?))",
- replaceEntities, s, flags=re.ASCII)
+ warnings.warn('The unescape method is deprecated and will be removed '
+ 'in 3.5, use html.unescape() instead.',
+ DeprecationWarning, stacklevel=2)
+ return unescape(s)
diff --git a/Lib/http/client.py b/Lib/http/client.py
index cc452e2f52..5c52ba349a 100644
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -271,8 +271,6 @@ def parse_headers(fp, _class=HTTPMessage):
return email.parser.Parser(_class=_class).parsestr(hstring)
-_strict_sentinel = object()
-
class HTTPResponse(io.RawIOBase):
# See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details.
@@ -282,7 +280,7 @@ class HTTPResponse(io.RawIOBase):
# text following RFC 2047. The basic status line parsing only
# accepts iso-8859-1.
- def __init__(self, sock, debuglevel=0, strict=_strict_sentinel, method=None, url=None):
+ def __init__(self, sock, debuglevel=0, method=None, url=None):
# If the response includes a content-length header, we need to
# make sure that the client doesn't read more than the
# specified number of bytes. If it does, it will block until
@@ -292,10 +290,6 @@ class HTTPResponse(io.RawIOBase):
# clients unless they know what they are doing.
self.fp = sock.makefile("rb")
self.debuglevel = debuglevel
- if strict is not _strict_sentinel:
- warnings.warn("the 'strict' argument isn't supported anymore; "
- "http.client now always assumes HTTP/1.x compliant servers.",
- DeprecationWarning, 2)
self._method = method
# The HTTPResponse object is returned via urllib. The clients
@@ -732,13 +726,17 @@ class HTTPConnection:
default_port = HTTP_PORT
auto_open = 1
debuglevel = 0
-
- def __init__(self, host, port=None, strict=_strict_sentinel,
- timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None):
- if strict is not _strict_sentinel:
- warnings.warn("the 'strict' argument isn't supported anymore; "
- "http.client now always assumes HTTP/1.x compliant servers.",
- DeprecationWarning, 2)
+ # TCP Maximum Segment Size (MSS) is determined by the TCP stack on
+ # a per-connection basis. There is no simple and efficient
+ # platform independent mechanism for determining the MSS, so
+ # instead a reasonable estimate is chosen. The getsockopt()
+ # interface using the TCP_MAXSEG parameter may be a suitable
+ # approach on some operating systems. A value of 16KiB is chosen
+ # as a reasonable estimate of the maximum MSS.
+ mss = 16384
+
+ def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
+ source_address=None):
self.timeout = timeout
self.source_address = source_address
self.sock = None
@@ -804,8 +802,8 @@ class HTTPConnection:
if code != 200:
self.close()
- raise socket.error("Tunnel connection failed: %d %s" % (code,
- message.strip()))
+ raise OSError("Tunnel connection failed: %d %s" % (code,
+ message.strip()))
while True:
line = response.fp.readline(_MAXLINE + 1)
if len(line) > _MAXLINE:
@@ -899,8 +897,11 @@ class HTTPConnection:
del self._buffer[:]
# If msg and message_body are sent in a single send() call,
# it will avoid performance problems caused by the interaction
- # between delayed ack and the Nagle algorithm.
- if isinstance(message_body, bytes):
+ # between delayed ack and the Nagle algorithm. However,
+ # there is no performance gain if the message is larger
+ # than MSS (and there is a memory penalty for the message
+ # copy).
+ if isinstance(message_body, bytes) and len(message_body) < self.mss:
msg += message_body
message_body = None
self.send(msg)
@@ -1170,16 +1171,15 @@ else:
# XXX Should key_file and cert_file be deprecated in favour of context?
def __init__(self, host, port=None, key_file=None, cert_file=None,
- strict=_strict_sentinel, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
- source_address=None, *, context=None, check_hostname=None):
- super(HTTPSConnection, self).__init__(host, port, strict, timeout,
+ timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
+ source_address=None, *, context=None,
+ check_hostname=None):
+ super(HTTPSConnection, self).__init__(host, port, timeout,
source_address)
self.key_file = key_file
self.cert_file = cert_file
if context is None:
- # Some reasonable defaults
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- context.options |= ssl.OP_NO_SSLv2
+ context = ssl._create_stdlib_context()
will_verify = context.verify_mode != ssl.CERT_NONE
if check_hostname is None:
check_hostname = will_verify
diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py
index ddb79c5020..be828eba69 100644
--- a/Lib/http/cookiejar.py
+++ b/Lib/http/cookiejar.py
@@ -1193,8 +1193,7 @@ def deepvalues(mapping):
pass
else:
mapping = True
- for subobj in deepvalues(obj):
- yield subobj
+ yield from deepvalues(obj)
if not mapping:
yield obj
@@ -1731,8 +1730,8 @@ class CookieJar:
return "<%s[%s]>" % (self.__class__, ", ".join(r))
-# derives from IOError for backwards-compatibility with Python 2.4.0
-class LoadError(IOError): pass
+# derives from OSError for backwards-compatibility with Python 2.4.0
+class LoadError(OSError): pass
class FileCookieJar(CookieJar):
"""CookieJar that can be loaded from and saved to a file."""
@@ -1762,17 +1761,14 @@ class FileCookieJar(CookieJar):
if self.filename is not None: filename = self.filename
else: raise ValueError(MISSING_FILENAME_TEXT)
- f = open(filename)
- try:
+ with open(filename) as f:
self._really_load(f, filename, ignore_discard, ignore_expires)
- finally:
- f.close()
def revert(self, filename=None,
ignore_discard=False, ignore_expires=False):
"""Clear all cookies and reload cookies from a saved file.
- Raises LoadError (or IOError) if reversion is not successful; the
+ Raises LoadError (or OSError) if reversion is not successful; the
object's state will not be altered if this happens.
"""
@@ -1787,7 +1783,7 @@ class FileCookieJar(CookieJar):
self._cookies = {}
try:
self.load(filename, ignore_discard, ignore_expires)
- except (LoadError, IOError):
+ except OSError:
self._cookies = old_state
raise
@@ -1857,15 +1853,12 @@ class LWPCookieJar(FileCookieJar):
if self.filename is not None: filename = self.filename
else: raise ValueError(MISSING_FILENAME_TEXT)
- f = open(filename, "w")
- try:
+ with open(filename, "w") as f:
# There really isn't an LWP Cookies 2.0 format, but this indicates
# that there is extra information in here (domain_dot and
# port_spec) while still being compatible with libwww-perl, I hope.
f.write("#LWP-Cookies-2.0\n")
f.write(self.as_lwp_str(ignore_discard, ignore_expires))
- finally:
- f.close()
def _really_load(self, f, filename, ignore_discard, ignore_expires):
magic = f.readline()
@@ -1938,8 +1931,7 @@ class LWPCookieJar(FileCookieJar):
if not ignore_expires and c.is_expired(now):
continue
self.set_cookie(c)
-
- except IOError:
+ except OSError:
raise
except Exception:
_warn_unhandled_exception()
@@ -2045,7 +2037,7 @@ class MozillaCookieJar(FileCookieJar):
continue
self.set_cookie(c)
- except IOError:
+ except OSError:
raise
except Exception:
_warn_unhandled_exception()
@@ -2057,8 +2049,7 @@ class MozillaCookieJar(FileCookieJar):
if self.filename is not None: filename = self.filename
else: raise ValueError(MISSING_FILENAME_TEXT)
- f = open(filename, "w")
- try:
+ with open(filename, "w") as f:
f.write(self.header)
now = time.time()
for cookie in self:
@@ -2087,5 +2078,3 @@ class MozillaCookieJar(FileCookieJar):
"\t".join([cookie.domain, initial_dot, cookie.path,
secure, expires, name, value])+
"\n")
- finally:
- f.close()
diff --git a/Lib/http/server.py b/Lib/http/server.py
index 2bfda12df4..7b577b49ac 100644
--- a/Lib/http/server.py
+++ b/Lib/http/server.py
@@ -401,12 +401,17 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
while not self.close_connection:
self.handle_one_request()
- def send_error(self, code, message=None):
+ def send_error(self, code, message=None, explain=None):
"""Send and log an error reply.
- Arguments are the error code, and a detailed message.
- The detailed message defaults to the short entry matching the
- response code.
+ Arguments are
+ * code: an HTTP error code
+ 3 digits
+ * message: a simple optional 1 line reason phrase.
+ *( HTAB / SP / VCHAR / %x80-FF )
+ defaults to short entry matching the response code
+ * explain: a detailed message defaults to the long entry
+ matching the response code.
This sends an error response (so it must be called before any
output has been generated), logs the error, and finally sends
@@ -420,17 +425,20 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
shortmsg, longmsg = '???', '???'
if message is None:
message = shortmsg
- explain = longmsg
+ if explain is None:
+ explain = longmsg
self.log_error("code %d, message %s", code, message)
# using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201)
content = (self.error_message_format %
- {'code': code, 'message': _quote_html(message), 'explain': explain})
+ {'code': code, 'message': _quote_html(message), 'explain': _quote_html(explain)})
+ body = content.encode('UTF-8', 'replace')
self.send_response(code, message)
self.send_header("Content-Type", self.error_content_type)
self.send_header('Connection', 'close')
+ self.send_header('Content-Length', int(len(body)))
self.end_headers()
if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
- self.wfile.write(content.encode('UTF-8', 'replace'))
+ self.wfile.write(body)
def send_response(self, code, message=None):
"""Add the response header to the headers buffer and log the
@@ -709,7 +717,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
ctype = self.guess_type(path)
try:
f = open(path, 'rb')
- except IOError:
+ except OSError:
self.send_error(404, "File not found")
return None
self.send_response(200)
@@ -730,7 +738,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
"""
try:
list = os.listdir(path)
- except os.error:
+ except OSError:
self.send_error(404, "No permission to list directory")
return None
list.sort(key=lambda a: a.lower())
@@ -1124,7 +1132,7 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
try:
try:
os.setuid(nobody)
- except os.error:
+ except OSError:
pass
os.dup2(self.rfile.fileno(), 0)
os.dup2(self.wfile.fileno(), 1)
@@ -1177,15 +1185,15 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
self.log_message("CGI script exited OK")
-def test(HandlerClass = BaseHTTPRequestHandler,
- ServerClass = HTTPServer, protocol="HTTP/1.0", port=8000):
+def test(HandlerClass=BaseHTTPRequestHandler,
+ ServerClass=HTTPServer, protocol="HTTP/1.0", port=8000, bind=""):
"""Test the HTTP request handler class.
This runs an HTTP server on port 8000 (or the first command line
argument).
"""
- server_address = ('', port)
+ server_address = (bind, port)
HandlerClass.protocol_version = protocol
httpd = ServerClass(server_address, HandlerClass)
@@ -1203,12 +1211,16 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true',
help='Run as CGI Server')
+ parser.add_argument('--bind', '-b', default='', metavar='ADDRESS',
+ help='Specify alternate bind address '
+ '[default: all interfaces]')
parser.add_argument('port', action='store',
default=8000, type=int,
nargs='?',
help='Specify alternate port [default: 8000]')
args = parser.parse_args()
if args.cgi:
- test(HandlerClass=CGIHTTPRequestHandler, port=args.port)
+ handler_class = CGIHTTPRequestHandler
else:
- test(HandlerClass=SimpleHTTPRequestHandler, port=args.port)
+ handler_class = SimpleHTTPRequestHandler
+ test(HandlerClass=handler_class, port=args.port, bind=args.bind)
diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py
index c9ad364c2c..eaf68cb754 100644
--- a/Lib/idlelib/EditorWindow.py
+++ b/Lib/idlelib/EditorWindow.py
@@ -548,7 +548,7 @@ class EditorWindow(object):
if sys.platform[:3] == 'win':
try:
os.startfile(self.help_url)
- except WindowsError as why:
+ except OSError as why:
tkMessageBox.showerror(title='Document Start Failure',
message=str(why), parent=self.text)
else:
@@ -871,7 +871,7 @@ class EditorWindow(object):
if sys.platform[:3] == 'win':
try:
os.startfile(helpfile)
- except WindowsError as why:
+ except OSError as why:
tkMessageBox.showerror(title='Document Start Failure',
message=str(why), parent=self.text)
else:
diff --git a/Lib/idlelib/FileList.py b/Lib/idlelib/FileList.py
index 37a337ed9a..a9989a8624 100644
--- a/Lib/idlelib/FileList.py
+++ b/Lib/idlelib/FileList.py
@@ -103,7 +103,7 @@ class FileList:
if not os.path.isabs(filename):
try:
pwd = os.getcwd()
- except os.error:
+ except OSError:
pass
else:
filename = os.path.join(pwd, filename)
diff --git a/Lib/idlelib/GrepDialog.py b/Lib/idlelib/GrepDialog.py
index 2e07312eaf..c0074e24f6 100644
--- a/Lib/idlelib/GrepDialog.py
+++ b/Lib/idlelib/GrepDialog.py
@@ -125,4 +125,3 @@ if __name__ == "__main__":
# Hence Idle must be restarted after editing this file for a live test.
import unittest
unittest.main('idlelib.idle_test.test_grep', verbosity=2, exit=False)
-
diff --git a/Lib/idlelib/IOBinding.py b/Lib/idlelib/IOBinding.py
index cba80483a6..7589ab805a 100644
--- a/Lib/idlelib/IOBinding.py
+++ b/Lib/idlelib/IOBinding.py
@@ -503,7 +503,7 @@ class IOBinding:
else:
try:
pwd = os.getcwd()
- except os.error:
+ except OSError:
pwd = ""
return pwd, ""
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index 54507015a1..478c18337e 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -1,12 +1,10 @@
-What's New in IDLE 3.3.2?
+What's New in IDLE 3.4.0?
=========================
- Issue #17390: Display Python version on Idle title bar.
Initial patch by Edmond Burnett.
-
-What's New in IDLE 3.3.1?
-=========================
+- Issue #5066: Update IDLE docs. Patch by Todd Rovito.
- Issue #17625: Close the replace dialog after it is used.
diff --git a/Lib/idlelib/PathBrowser.py b/Lib/idlelib/PathBrowser.py
index ba40719084..5e5c6be98d 100644
--- a/Lib/idlelib/PathBrowser.py
+++ b/Lib/idlelib/PathBrowser.py
@@ -44,7 +44,7 @@ class DirBrowserTreeItem(TreeItem):
def GetSubList(self):
try:
names = os.listdir(self.dir or os.curdir)
- except os.error:
+ except OSError:
return []
packages = []
for name in names:
diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index b2a1f58c13..36aff92d2c 100644
--- a/Lib/idlelib/PyShell.py
+++ b/Lib/idlelib/PyShell.py
@@ -419,7 +419,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
try:
self.rpcclt = MyRPCClient(addr)
break
- except socket.error as err:
+ except OSError as err:
pass
else:
self.display_port_binding_error()
@@ -1034,7 +1034,10 @@ class PyShell(OutputWindow):
self.close()
return False
else:
- nosub = "==== No Subprocess ===="
+ nosub = ("==== No Subprocess ====\n\n" +
+ "WARNING: Running IDLE without a Subprocess is deprecated\n" +
+ "and will be removed in a later version. See Help/IDLE Help\n" +
+ "for details.\n\n")
sys.displayhook = rpc.displayhook
self.write("Python %s on %s\n%s\n%s" %
@@ -1392,7 +1395,8 @@ USAGE: idle [-deins] [-t title] [file]*
idle [-dns] [-t title] - [arg]*
-h print this help message and exit
- -n run IDLE without a subprocess (see Help/IDLE Help for details)
+ -n run IDLE without a subprocess (DEPRECATED,
+ see Help/IDLE Help for details)
The following options will override the IDLE 'settings' configuration:
@@ -1470,6 +1474,8 @@ def main():
if o == '-i':
enable_shell = True
if o == '-n':
+ print(" Warning: running IDLE without a subprocess is deprecated.",
+ file=sys.stderr)
use_subprocess = False
if o == '-r':
script = a
diff --git a/Lib/idlelib/TreeWidget.py b/Lib/idlelib/TreeWidget.py
index 25bae48047..1f4854ddc4 100644
--- a/Lib/idlelib/TreeWidget.py
+++ b/Lib/idlelib/TreeWidget.py
@@ -381,7 +381,7 @@ class FileTreeItem(TreeItem):
try:
os.rename(self.path, newpath)
self.path = newpath
- except os.error:
+ except OSError:
pass
def GetIconName(self):
@@ -394,7 +394,7 @@ class FileTreeItem(TreeItem):
def GetSubList(self):
try:
names = os.listdir(self.path)
- except os.error:
+ except OSError:
return []
names.sort(key = os.path.normcase)
sublist = []
diff --git a/Lib/idlelib/configHandler.py b/Lib/idlelib/configHandler.py
index a974d54bce..ea2010edec 100644
--- a/Lib/idlelib/configHandler.py
+++ b/Lib/idlelib/configHandler.py
@@ -271,8 +271,10 @@ class IdleConf:
except OSError:
pass
return default
+
def SetOption(self, configType, section, option, value):
"""In user's config file, set section's option to value.
+
"""
self.userCfg[configType].SetOption(section, option, value)
@@ -638,8 +640,10 @@ class IdleConf:
except OSError:
pass
return keyBindings
+
def GetExtraHelpSourceList(self,configSet):
"""Fetch list of extra help sources from a given configSet.
+
Valid configSets are 'user' or 'default'. Return a list of tuples of
the form (menu_item , path_to_help_file , option), or return the empty
list. 'option' is the sequence number of the help resource. 'option'
diff --git a/Lib/idlelib/help.txt b/Lib/idlelib/help.txt
index ff786c53f3..6378a2e722 100644
--- a/Lib/idlelib/help.txt
+++ b/Lib/idlelib/help.txt
@@ -1,142 +1,185 @@
[See the end of this file for ** TIPS ** on using IDLE !!]
-Click on the dotted line at the top of a menu to "tear it off": a
-separate window containing the menu is created.
-
-File Menu:
-
- New File -- Create a new file editing window
- Open... -- Open an existing file
- Recent Files... -- Open a list of recent files
- Open Module... -- Open an existing module (searches sys.path)
- Class Browser -- Show classes and methods in current file
- Path Browser -- Show sys.path directories, modules, classes
+IDLE is the Python IDE built with the tkinter GUI toolkit.
+
+IDLE has the following features:
+-coded in 100% pure Python, using the tkinter GUI toolkit
+-cross-platform: works on Windows, Unix, and OS X
+-multi-window text editor with multiple undo, Python colorizing, smart indent,
+call tips, and many other features
+-Python shell window (a.k.a interactive interpreter)
+-debugger (not complete, but you can set breakpoints, view and step)
+
+Menus:
+
+IDLE has two window types the Shell window and the Editor window. It is
+possible to have multiple editor windows simultaneously. IDLE's
+menus dynamically change based on which window is currently selected. Each menu
+documented below indicates which window type it is associated with. Click on
+the dotted line at the top of a menu to "tear it off": a separate window
+containing the menu is created (for Unix and Windows only).
+
+File Menu (Shell and Editor):
+
+ New File -- Create a new file editing window
+ Open... -- Open an existing file
+ Open Module... -- Open an existing module (searches sys.path)
+ Recent Files... -- Open a list of recent files
+ Class Browser -- Show classes and methods in current file
+ Path Browser -- Show sys.path directories, modules, classes,
and methods
- ---
- Save -- Save current window to the associated file (unsaved
- windows have a * before and after the window title)
-
- Save As... -- Save current window to new file, which becomes
- the associated file
- Save Copy As... -- Save current window to different file
- without changing the associated file
- ---
- Print Window -- Print the current window
- ---
- Close -- Close current window (asks to save if unsaved)
- Exit -- Close all windows, quit (asks to save if unsaved)
-
-Edit Menu:
-
- Undo -- Undo last change to current window
- (A maximum of 1000 changes may be undone)
- Redo -- Redo last undone change to current window
- ---
- Cut -- Copy a selection into system-wide clipboard,
+ ---
+ Save -- Save current window to the associated file (unsaved
+ windows have a * before and after the window title)
+
+ Save As... -- Save current window to new file, which becomes
+ the associated file
+ Save Copy As... -- Save current window to different file
+ without changing the associated file
+ ---
+ Print Window -- Print the current window
+ ---
+ Close -- Close current window (asks to save if unsaved)
+ Exit -- Close all windows, quit (asks to save if unsaved)
+
+Edit Menu (Shell and Editor):
+
+ Undo -- Undo last change to current window
+ (a maximum of 1000 changes may be undone)
+ Redo -- Redo last undone change to current window
+ ---
+ Cut -- Copy a selection into system-wide clipboard,
then delete the selection
- Copy -- Copy selection into system-wide clipboard
- Paste -- Insert system-wide clipboard into window
- Select All -- Select the entire contents of the edit buffer
- ---
- Find... -- Open a search dialog box with many options
- Find Again -- Repeat last search
- Find Selection -- Search for the string in the selection
- Find in Files... -- Open a search dialog box for searching files
- Replace... -- Open a search-and-replace dialog box
- Go to Line -- Ask for a line number and show that line
- Show Calltip -- Open a small window with function param hints
- Show Completions -- Open a scroll window allowing selection keywords
- and attributes. (see '*TIPS*', below)
- Show Parens -- Highlight the surrounding parenthesis
- Expand Word -- Expand the word you have typed to match another
- word in the same buffer; repeat to get a
+ Copy -- Copy selection into system-wide clipboard
+ Paste -- Insert system-wide clipboard into window
+ Select All -- Select the entire contents of the edit buffer
+ ---
+ Find... -- Open a search dialog box with many options
+ Find Again -- Repeat last search
+ Find Selection -- Search for the string in the selection
+ Find in Files... -- Open a search dialog box for searching files
+ Replace... -- Open a search-and-replace dialog box
+ Go to Line -- Ask for a line number and show that line
+ Expand Word -- Expand the word you have typed to match another
+ word in the same buffer; repeat to get a
different expansion
-
-Format Menu (only in Edit window):
-
- Indent Region -- Shift selected lines right 4 spaces
- Dedent Region -- Shift selected lines left 4 spaces
- Comment Out Region -- Insert ## in front of selected lines
- Uncomment Region -- Remove leading # or ## from selected lines
- Tabify Region -- Turns *leading* stretches of spaces into tabs
- (Note: We recommend using 4 space blocks to indent Python code.)
- Untabify Region -- Turn *all* tabs into the right number of spaces
- New Indent Width... -- Open dialog to change indent width
- Format Paragraph -- Reformat the current blank-line-separated
- paragraph
-
-Run Menu (only in Edit window):
-
- Python Shell -- Open or wake up the Python shell window
- ---
- Check Module -- Run a syntax check on the module
- Run Module -- Execute the current file in the __main__ namespace
-
-Shell Menu (only in Shell window):
-
- View Last Restart -- Scroll the shell window to the last restart
- Restart Shell -- Restart the interpreter with a fresh environment
-
-Debug Menu (only in Shell window):
-
- Go to File/Line -- look around the insert point for a filename
- and line number, open the file, and show the line
- Debugger (toggle) -- Run commands in the shell under the debugger
- Stack Viewer -- Show the stack traceback of the last exception
- Auto-open Stack Viewer (toggle) -- Open stack viewer on traceback
-
-Options Menu:
-
- Configure IDLE -- Open a configuration dialog. Fonts, indentation,
+ Show Calltip -- After an unclosed parenthesis for a function, open
+ a small window with function parameter hints
+ Show Parens -- Highlight the surrounding parenthesis
+ Show Completions -- Open a scroll window allowing selection keywords
+ and attributes. (see '*TIPS*', below)
+
+Format Menu (Editor window only):
+
+ Indent Region -- Shift selected lines right by the indent width
+ (default 4 spaces)
+ Dedent Region -- Shift selected lines left by the indent width
+ (default 4 spaces)
+ Comment Out Region -- Insert ## in front of selected lines
+ Uncomment Region -- Remove leading # or ## from selected lines
+ Tabify Region -- Turns *leading* stretches of spaces into tabs.
+ (Note: We recommend using 4 space blocks to indent Python code.)
+ Untabify Region -- Turn *all* tabs into the corrent number of spaces
+ Toggle tabs -- Open a dialog to switch between indenting with
+ spaces and tabs.
+ New Indent Width... -- Open a dialog to change indent width. The
+ accepted default by the Python community is 4
+ spaces.
+ Format Paragraph -- Reformat the current blank-line-separated
+ paragraph. All lines in the paragraph will be
+ formatted to less than 80 columns.
+ ---
+ Strip trailing whitespace -- Removed any space characters after the end
+ of the last non-space character
+
+Run Menu (Editor window only):
+
+ Python Shell -- Open or wake up the Python shell window
+ ---
+ Check Module -- Check the syntax of the module currently open in the
+ Editor window. If the module has not been saved IDLE
+ will prompt the user to save the code.
+ Run Module -- Restart the shell to clean the environment, then
+ execute the currently open module. If the module has
+ not been saved IDLE will prompt the user to save the
+ code.
+
+Shell Menu (Shell window only):
+
+ View Last Restart -- Scroll the shell window to the last Shell restart
+ Restart Shell -- Restart the shell to clean the environment
+
+Debug Menu (Shell window only):
+
+ Go to File/Line -- Look around the insert point for a filename
+ and line number, open the file, and show the line.
+ Useful to view the source lines referenced in an
+ exception traceback. Available in the context
+ menu of the Shell window.
+ Debugger (toggle) -- This feature is not complete and considered
+ experimental. Run commands in the shell under the
+ debugger.
+ Stack Viewer -- Show the stack traceback of the last exception
+ Auto-open Stack Viewer (toggle) -- Toggle automatically opening the
+ stack viewer on unhandled
+ exception
+
+Options Menu (Shell and Editor):
+
+ Configure IDLE -- Open a configuration dialog. Fonts, indentation,
keybindings, and color themes may be altered.
- Startup Preferences may be set, and Additional Help
- Sources can be specified.
-
- On OS X this menu is not present, use
- menu 'IDLE -> Preferences...' instead.
- ---
- Code Context -- Open a pane at the top of the edit window which
- shows the block context of the section of code
- which is scrolling off the top or the window.
- (Not present in Shell window.)
-
-Windows Menu:
-
- Zoom Height -- toggles the window between configured size
- and maximum height.
- ---
- The rest of this menu lists the names of all open windows;
- select one to bring it to the foreground (deiconifying it if
- necessary).
+ Startup Preferences may be set, and additional Help
+ sources can be specified.
+
+ ---
+ Code Context (toggle) -- Open a pane at the top of the edit window
+ which shows the block context of the section
+ of code which is scrolling off the top or the
+ window. This is not present in the Shell
+ window only the Editor window.
+
+Windows Menu (Shell and Editor):
+
+ Zoom Height -- Toggles the window between normal size (40x80 initial
+ setting) and maximum height. The initial size is in the Configure
+ IDLE dialog under the general tab.
+ ---
+ The rest of this menu lists the names of all open windows;
+ select one to bring it to the foreground (deiconifying it if
+ necessary).
Help Menu:
- About IDLE -- Version, copyright, license, credits
- IDLE Readme -- Background discussion and change details
- ---
- IDLE Help -- Display this file
- Python Docs -- Access local Python documentation, if
- installed. Otherwise, access www.python.org.
- ---
- (Additional Help Sources may be added here)
-
-Edit context menu (Right-click / Control-click on OS X in Edit window):
-
- Cut -- Copy a selection into system-wide clipboard,
+ About IDLE -- Version, copyright, license, credits
+ ---
+ IDLE Help -- Display this file which is a help file for IDLE
+ detailing the menu options, basic editing and navigation,
+ and other tips.
+ Python Docs -- Access local Python documentation, if
+ installed. Or will start a web browser and open
+ docs.python.org showing the latest Python documentation.
+ ---
+ Additional help sources may be added here with the Configure IDLE
+ dialog under the General tab.
+
+Editor context menu (Right-click / Control-click on OS X in Edit window):
+
+ Cut -- Copy a selection into system-wide clipboard,
then delete the selection
- Copy -- Copy selection into system-wide clipboard
- Paste -- Insert system-wide clipboard into window
- Set Breakpoint -- Sets a breakpoint (when debugger open)
- Clear Breakpoint -- Clears the breakpoint on that line
+ Copy -- Copy selection into system-wide clipboard
+ Paste -- Insert system-wide clipboard into window
+ Set Breakpoint -- Sets a breakpoint. Breakpoints are only enabled
+ when the debugger is open.
+ Clear Breakpoint -- Clears the breakpoint on that line
Shell context menu (Right-click / Control-click on OS X in Shell window):
- Cut -- Copy a selection into system-wide clipboard,
+ Cut -- Copy a selection into system-wide clipboard,
then delete the selection
- Copy -- Copy selection into system-wide clipboard
- Paste -- Insert system-wide clipboard into window
- ---
- Go to file/line -- Same as in Debug menu
+ Copy -- Copy selection into system-wide clipboard
+ Paste -- Insert system-wide clipboard into window
+ ---
+ Go to file/line -- Same as in Debug menu
** TIPS **
@@ -144,159 +187,182 @@ Shell context menu (Right-click / Control-click on OS X in Shell window):
Additional Help Sources:
- Windows users can Google on zopeshelf.chm to access Zope help files in
- the Windows help format. The Additional Help Sources feature of the
- configuration GUI supports .chm, along with any other filetypes
- supported by your browser. Supply a Menu Item title, and enter the
- location in the Help File Path slot of the New Help Source dialog. Use
- http:// and/or www. to identify external URLs, or download the file and
- browse for its path on your machine using the Browse button.
+ Windows users can Google on zopeshelf.chm to access Zope help files in
+ the Windows help format. The Additional Help Sources feature of the
+ configuration GUI supports .chm, along with any other filetypes
+ supported by your browser. Supply a Menu Item title, and enter the
+ location in the Help File Path slot of the New Help Source dialog. Use
+ http:// and/or www. to identify external URLs, or download the file and
+ browse for its path on your machine using the Browse button.
- All users can access the extensive sources of help, including
- tutorials, available at www.python.org/doc. Selected URLs can be added
- or removed from the Help menu at any time using Configure IDLE.
+ All users can access the extensive sources of help, including
+ tutorials, available at docs.python.org. Selected URLs can be added
+ or removed from the Help menu at any time using Configure IDLE.
Basic editing and navigation:
- Backspace deletes char to the left; DEL deletes char to the right.
- Control-backspace deletes word left, Control-DEL deletes word right.
- Arrow keys and Page Up/Down move around.
- Control-left/right Arrow moves by words in a strange but useful way.
- Home/End go to begin/end of line.
- Control-Home/End go to begin/end of file.
- Some useful Emacs bindings are inherited from Tcl/Tk:
- Control-a beginning of line
- Control-e end of line
- Control-k kill line (but doesn't put it in clipboard)
- Control-l center window around the insertion point
- Standard Windows bindings may work on that platform.
- Keybindings are selected in the Settings Dialog, look there.
+ Backspace deletes char to the left; DEL deletes char to the right.
+ Control-backspace deletes word left, Control-DEL deletes word right.
+ Arrow keys and Page Up/Down move around.
+ Control-left/right Arrow moves by words in a strange but useful way.
+ Home/End go to begin/end of line.
+ Control-Home/End go to begin/end of file.
+ Some useful Emacs bindings are inherited from Tcl/Tk:
+ Control-a beginning of line
+ Control-e end of line
+ Control-k kill line (but doesn't put it in clipboard)
+ Control-l center window around the insertion point
+ Standard keybindings (like Control-c to copy and Control-v to
+ paste) may work. Keybindings are selected in the Configure IDLE
+ dialog.
Automatic indentation:
- After a block-opening statement, the next line is indented by 4 spaces
- (in the Python Shell window by one tab). After certain keywords
- (break, return etc.) the next line is dedented. In leading
- indentation, Backspace deletes up to 4 spaces if they are there. Tab
- inserts spaces (in the Python Shell window one tab), number depends on
- Indent Width. (N.B. Currently tabs are restricted to four spaces due
- to Tcl/Tk issues.)
+ After a block-opening statement, the next line is indented by 4 spaces
+ (in the Python Shell window by one tab). After certain keywords
+ (break, return etc.) the next line is dedented. In leading
+ indentation, Backspace deletes up to 4 spaces if they are there. Tab
+ inserts spaces (in the Python Shell window one tab), number depends on
+ Indent Width. Currently tabs are restricted to four spaces due
+ to Tcl/Tk limitations.
See also the indent/dedent region commands in the edit menu.
Completions:
- Completions are supplied for functions, classes, and attributes of
- classes, both built-in and user-defined. Completions are also provided
- for filenames.
-
- The AutoCompleteWindow (ACW) will open after a predefined delay
- (default is two seconds) after a '.' or (in a string) an os.sep is
- typed. If after one of those characters (plus zero or more other
- characters) you type a Tab the ACW will open immediately if a possible
- continuation is found.
-
- If there is only one possible completion for the characters entered, a
- Tab will supply that completion without opening the ACW.
-
- 'Show Completions' will force open a completions window. In an empty
- string, this will contain the files in the current directory. On a
- blank line, it will contain the built-in and user-defined functions and
- classes in the current name spaces, plus any modules imported. If some
- characters have been entered, the ACW will attempt to be more specific.
-
- If string of characters is typed, the ACW selection will jump to the
- entry most closely matching those characters. Entering a Tab will cause
- the longest non-ambiguous match to be entered in the Edit window or
- Shell. Two Tabs in a row will supply the current ACW selection, as
- will Return or a double click. Cursor keys, Page Up/Down, mouse
- selection, and the scrollwheel all operate on the ACW.
-
- 'Hidden' attributes can be accessed by typing the beginning of hidden
- name after a '.'. e.g. '_'. This allows access to modules with
- '__all__' set, or to class-private attributes.
-
- Completions and the 'Expand Word' facility can save a lot of typing!
-
- Completions are currently limited to those in the namespaces. Names in
- an Edit window which are not via __main__ or sys.modules will not be
- found. Run the module once with your imports to correct this
- situation. Note that IDLE itself places quite a few modules in
- sys.modules, so much can be found by default, e.g. the re module.
-
- If you don't like the ACW popping up unbidden, simply make the delay
- longer or disable the extension. OTOH, you could make the delay zero.
-
- You could also switch off the CallTips extension. (We will be adding
- a delay to the call tip window.)
+ Completions are supplied for functions, classes, and attributes of
+ classes, both built-in and user-defined. Completions are also provided
+ for filenames.
+
+ The AutoCompleteWindow (ACW) will open after a predefined delay
+ (default is two seconds) after a '.' or (in a string) an os.sep is
+ typed. If after one of those characters (plus zero or more other
+ characters) a tab is typed the ACW will open immediately if a possible
+ continuation is found.
+
+ If there is only one possible completion for the characters entered, a
+ tab will supply that completion without opening the ACW.
+
+ 'Show Completions' will force open a completions window, by default the
+ Control-space keys will open a completions window. In an empty
+ string, this will contain the files in the current directory. On a
+ blank line, it will contain the built-in and user-defined functions and
+ classes in the current name spaces, plus any modules imported. If some
+ characters have been entered, the ACW will attempt to be more specific.
+
+ If string of characters is typed, the ACW selection will jump to the
+ entry most closely matching those characters. Entering a tab will cause
+ the longest non-ambiguous match to be entered in the Edit window or
+ Shell. Two tabs in a row will supply the current ACW selection, as
+ will return or a double click. Cursor keys, Page Up/Down, mouse
+ selection, and the scroll wheel all operate on the ACW.
+
+ "Hidden" attributes can be accessed by typing the beginning of hidden
+ name after a '.', e.g. '_'. This allows access to modules with
+ '__all__' set, or to class-private attributes.
+
+ Completions and the 'Expand Word' facility can save a lot of typing!
+
+ Completions are currently limited to those in the namespaces. Names in
+ an Editor window which are not via __main__ or sys.modules will not be
+ found. Run the module once with your imports to correct this
+ situation. Note that IDLE itself places quite a few modules in
+ sys.modules, so much can be found by default, e.g. the re module.
+
+ If you don't like the ACW popping up unbidden, simply make the delay
+ longer or disable the extension. Or another option is the delay could
+ be set to zero. Another alternative to preventing ACW popups is to
+ disable the call tips extension.
Python Shell window:
- Control-c interrupts executing command.
- Control-d sends end-of-file; closes window if typed at >>> prompt.
+ Control-c interrupts executing command.
+ Control-d sends end-of-file; closes window if typed at >>> prompt.
+ Alt-/ expand word is also useful to reduce typing.
Command history:
- Alt-p retrieves previous command matching what you have typed.
- Alt-n retrieves next.
- (These are Control-p, Control-n on OS X)
- Return while cursor is on a previous command retrieves that command.
- Expand word is also useful to reduce typing.
+ Alt-p retrieves previous command matching what you have typed. On OS X
+ use Control-p.
+ Alt-n retrieves next. On OS X use Control-n.
+ Return while cursor is on a previous command retrieves that command.
Syntax colors:
- The coloring is applied in a background "thread", so you may
- occasionally see uncolorized text. To change the color
- scheme, use the Configure IDLE / Highlighting dialog.
+ The coloring is applied in a background "thread", so you may
+ occasionally see uncolorized text. To change the color
+ scheme, use the Configure IDLE / Highlighting dialog.
Python default syntax colors:
- Keywords orange
- Builtins royal purple
- Strings green
- Comments red
- Definitions blue
+ Keywords orange
+ Builtins royal purple
+ Strings green
+ Comments red
+ Definitions blue
Shell default colors:
- Console output brown
- stdout blue
- stderr red
- stdin black
+ Console output brown
+ stdout blue
+ stderr red
+ stdin black
Other preferences:
- The font preferences, keybinding, and startup preferences can
- be changed using the Settings dialog.
+ The font preferences, highlighting, keys, and general preferences can
+ be changed via the Configure IDLE menu option. Be sure to note that
+ keys can be user defined, IDLE ships with four built in key sets. In
+ addition a user can create a custom key set in the Configure IDLE
+ dialog under the keys tab.
Command line usage:
- Enter idle -h at the command prompt to get a usage message.
-
-Running without a subprocess:
-
- If IDLE is started with the -n command line switch it will run in a
- single process and will not create the subprocess which runs the RPC
- Python execution server. This can be useful if Python cannot create
- the subprocess or the RPC socket interface on your platform. However,
- in this mode user code is not isolated from IDLE itself. Also, the
- environment is not restarted when Run/Run Module (F5) is selected. If
- your code has been modified, you must reload() the affected modules and
- re-import any specific items (e.g. from foo import baz) if the changes
- are to take effect. For these reasons, it is preferable to run IDLE
- with the default subprocess if at all possible.
+ Enter idle -h at the command prompt to get a usage message.
+
+ idle.py [-c command] [-d] [-e] [-s] [-t title] [arg] ...
+
+ -c command run this command
+ -d enable debugger
+ -e edit mode; arguments are files to be edited
+ -s run $IDLESTARTUP or $PYTHONSTARTUP first
+ -t title set title of shell window
+
+ If there are arguments:
+ 1. If -e is used, arguments are files opened for editing and sys.argv
+ reflects the arguments passed to IDLE itself.
+ 2. Otherwise, if -c is used, all arguments are placed in
+ sys.argv[1:...], with sys.argv[0] set to -c.
+ 3. Otherwise, if neither -e nor -c is used, the first argument is a
+ script which is executed with the remaining arguments in
+ sys.argv[1:...] and sys.argv[0] set to the script name. If the
+ script name is -, no script is executed but an interactive Python
+ session is started; the arguments are still available in sys.argv.
+
+Running without a subprocess: (DEPRECATED in Python 3.4 see Issue 16123)
+
+ If IDLE is started with the -n command line switch it will run in a
+ single process and will not create the subprocess which runs the RPC
+ Python execution server. This can be useful if Python cannot create
+ the subprocess or the RPC socket interface on your platform. However,
+ in this mode user code is not isolated from IDLE itself. Also, the
+ environment is not restarted when Run/Run Module (F5) is selected. If
+ your code has been modified, you must reload() the affected modules and
+ re-import any specific items (e.g. from foo import baz) if the changes
+ are to take effect. For these reasons, it is preferable to run IDLE
+ with the default subprocess if at all possible.
Extensions:
- IDLE contains an extension facility. See the beginning of
- config-extensions.def in the idlelib directory for further information.
- The default extensions are currently:
-
- FormatParagraph
- AutoExpand
- ZoomHeight
- ScriptBinding
- CallTips
- ParenMatch
- AutoComplete
- CodeContext
+ IDLE contains an extension facility. See the beginning of
+ config-extensions.def in the idlelib directory for further information.
+ The default extensions are currently:
+
+ FormatParagraph
+ AutoExpand
+ ZoomHeight
+ ScriptBinding
+ CallTips
+ ParenMatch
+ AutoComplete
+ CodeContext
diff --git a/Lib/idlelib/idlever.py b/Lib/idlelib/idlever.py
index 60f1d329aa..cd2c8bfc39 100644
--- a/Lib/idlelib/idlever.py
+++ b/Lib/idlelib/idlever.py
@@ -1 +1 @@
-IDLE_VERSION = "3.3.3"
+IDLE_VERSION = "3.4.0b1"
diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py
index ddce6e9389..9c51b8f6b5 100644
--- a/Lib/idlelib/rpc.py
+++ b/Lib/idlelib/rpc.py
@@ -199,7 +199,7 @@ class SocketIO(object):
raise
except KeyboardInterrupt:
raise
- except socket.error:
+ except OSError:
raise
except Exception as ex:
return ("CALLEXC", ex)
@@ -340,7 +340,7 @@ class SocketIO(object):
n = self.sock.send(s[:BUFSIZE])
except (AttributeError, TypeError):
raise OSError("socket no longer exists")
- except socket.error:
+ except OSError:
raise
else:
s = s[n:]
@@ -357,7 +357,7 @@ class SocketIO(object):
return None
try:
s = self.sock.recv(BUFSIZE)
- except socket.error:
+ except OSError:
raise EOFError
if len(s) == 0:
raise EOFError
@@ -537,7 +537,7 @@ class RPCClient(SocketIO):
SocketIO.__init__(self, working_sock)
else:
print("** Invalid host: ", address, file=sys.__stderr__)
- raise socket.error
+ raise OSError
def get_remote_proxy(self, oid):
return RPCProxy(self, oid)
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index c1859b66b2..13cec62976 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -150,8 +150,8 @@ def manage_socket(address):
try:
server = MyRPCServer(address, MyHandler)
break
- except socket.error as err:
- print("IDLE Subprocess: socket error: " + err.args[1] +
+ except OSError as err:
+ print("IDLE Subprocess: OSError: " + err.args[1] +
", retrying....", file=sys.__stderr__)
socket_error = err
else:
diff --git a/Lib/imaplib.py b/Lib/imaplib.py
index 3f6656a762..dabf161b87 100644
--- a/Lib/imaplib.py
+++ b/Lib/imaplib.py
@@ -185,7 +185,7 @@ class IMAP4:
except Exception:
try:
self.shutdown()
- except socket.error:
+ except OSError:
pass
raise
@@ -281,7 +281,7 @@ class IMAP4:
self.file.close()
try:
self.sock.shutdown(socket.SHUT_RDWR)
- except socket.error as e:
+ except OSError as e:
# The server might already have closed the connection
if e.errno != errno.ENOTCONN:
raise
@@ -554,7 +554,7 @@ class IMAP4:
import hmac
pwd = (self.password.encode('ASCII') if isinstance(self.password, str)
else self.password)
- return self.user + " " + hmac.HMAC(pwd, challenge).hexdigest()
+ return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
def logout(self):
@@ -742,9 +742,7 @@ class IMAP4:
raise self.abort('TLS not supported by server')
# Generate a default SSL context if none was passed.
if ssl_context is None:
- ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- # SSLv2 considered harmful.
- ssl_context.options |= ssl.OP_NO_SSLv2
+ ssl_context = ssl._create_stdlib_context()
typ, dat = self._simple_command(name)
if typ == 'OK':
self.sock = ssl_context.wrap_socket(self.sock)
@@ -915,7 +913,7 @@ class IMAP4:
try:
self.send(data + CRLF)
- except (socket.error, OSError) as val:
+ except OSError as val:
raise self.abort('socket error: %s' % val)
if literal is None:
@@ -940,7 +938,7 @@ class IMAP4:
try:
self.send(literal)
self.send(CRLF)
- except (socket.error, OSError) as val:
+ except OSError as val:
raise self.abort('socket error: %s' % val)
if not literator:
@@ -1085,7 +1083,7 @@ class IMAP4:
# Protocol mandates all lines terminated by CRLF
if not line.endswith(b'\r\n'):
- raise self.abort('socket error: unterminated line')
+ raise self.abort('socket error: unterminated line: %r' % line)
line = line[:-2]
if __debug__:
@@ -1210,15 +1208,15 @@ if HAVE_SSL:
self.keyfile = keyfile
self.certfile = certfile
+ if ssl_context is None:
+ ssl_context = ssl._create_stdlib_context(certfile=certfile,
+ keyfile=keyfile)
self.ssl_context = ssl_context
IMAP4.__init__(self, host, port)
def _create_socket(self):
sock = IMAP4._create_socket(self)
- if self.ssl_context:
- return self.ssl_context.wrap_socket(sock)
- else:
- return ssl.wrap_socket(sock, self.keyfile, self.certfile)
+ return self.ssl_context.wrap_socket(sock)
def open(self, host='', port=IMAP4_SSL_PORT):
"""Setup connection to remote server on "host:port".
diff --git a/Lib/imghdr.py b/Lib/imghdr.py
index 6ee45daab8..0cba063a9c 100644
--- a/Lib/imghdr.py
+++ b/Lib/imghdr.py
@@ -149,7 +149,7 @@ def testall(list, recursive, toplevel):
sys.stdout.flush()
try:
print(what(filename))
- except IOError:
+ except OSError:
print('*** not found ***')
if __name__ == '__main__':
diff --git a/Lib/imp.py b/Lib/imp.py
index 408838387b..c8449c6155 100644
--- a/Lib/imp.py
+++ b/Lib/imp.py
@@ -16,18 +16,20 @@ except ImportError:
# Platform doesn't support dynamic loading.
load_dynamic = None
-# Directly exposed by this module
-from importlib._bootstrap import new_module
-from importlib._bootstrap import cache_from_source, source_from_cache
+from importlib._bootstrap import SourcelessFileLoader, _ERR_MSG, _SpecMethods
-
-from importlib import _bootstrap
from importlib import machinery
+from importlib import util
+import importlib
import os
import sys
import tokenize
+import types
import warnings
+warnings.warn("the imp module is deprecated in favour of importlib; "
+ "see the module's documentation for alternative uses",
+ PendingDeprecationWarning)
# DEPRECATED
SEARCH_ERROR = 0
@@ -42,9 +44,23 @@ PY_CODERESOURCE = 8
IMP_HOOK = 9
+def new_module(name):
+ """**DEPRECATED**
+
+ Create a new module.
+
+ The module is not entered into sys.modules.
+
+ """
+ return types.ModuleType(name)
+
+
def get_magic():
- """Return the magic number for .pyc or .pyo files."""
- return _bootstrap._MAGIC_BYTES
+ """**DEPRECATED**
+
+ Return the magic number for .pyc or .pyo files.
+ """
+ return util.MAGIC_NUMBER
def get_tag():
@@ -52,12 +68,42 @@ def get_tag():
return sys.implementation.cache_tag
+def cache_from_source(path, debug_override=None):
+ """**DEPRECATED**
+
+ Given the path to a .py file, return the path to its .pyc/.pyo file.
+
+ The .py file does not need to exist; this simply returns the path to the
+ .pyc/.pyo file calculated as if the .py file were imported. The extension
+ will be .pyc unless sys.flags.optimize is non-zero, then it will be .pyo.
+
+ If debug_override is not None, then it must be a boolean and is used in
+ place of sys.flags.optimize.
+
+ If sys.implementation.cache_tag is None then NotImplementedError is raised.
+
+ """
+ return util.cache_from_source(path, debug_override)
+
+
+def source_from_cache(path):
+ """**DEPRECATED**
+
+ Given the path to a .pyc./.pyo file, return the path to its .py file.
+
+ The .pyc/.pyo file does not need to exist; this simply returns the path to
+ the .py file calculated to correspond to the .pyc/.pyo file. If path does
+ not conform to PEP 3147 format, ValueError will be raised. If
+ sys.implementation.cache_tag is None then NotImplementedError is raised.
+
+ """
+ return util.source_from_cache(path)
+
+
def get_suffixes():
- warnings.warn('imp.get_suffixes() is deprecated; use the constants '
- 'defined on importlib.machinery instead',
- DeprecationWarning, 2)
+ """**DEPRECATED**"""
extensions = [(s, 'rb', C_EXTENSION) for s in machinery.EXTENSION_SUFFIXES]
- source = [(s, 'U', PY_SOURCE) for s in machinery.SOURCE_SUFFIXES]
+ source = [(s, 'r', PY_SOURCE) for s in machinery.SOURCE_SUFFIXES]
bytecode = [(s, 'rb', PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES]
return extensions + source + bytecode
@@ -65,7 +111,11 @@ def get_suffixes():
class NullImporter:
- """Null import object."""
+ """**DEPRECATED**
+
+ Null import object.
+
+ """
def __init__(self, path):
if path == '':
@@ -106,48 +156,49 @@ class _HackedGetData:
return super().get_data(path)
-class _LoadSourceCompatibility(_HackedGetData, _bootstrap.SourceFileLoader):
+class _LoadSourceCompatibility(_HackedGetData, machinery.SourceFileLoader):
"""Compatibility support for implementing load_source()."""
def load_source(name, pathname, file=None):
- msg = ('imp.load_source() is deprecated; use '
- 'importlib.machinery.SourceFileLoader(name, pathname).load_module()'
- ' instead')
- warnings.warn(msg, DeprecationWarning, 2)
- _LoadSourceCompatibility(name, pathname, file).load_module(name)
- module = sys.modules[name]
+ loader = _LoadSourceCompatibility(name, pathname, file)
+ spec = util.spec_from_file_location(name, pathname, loader=loader)
+ methods = _SpecMethods(spec)
+ if name in sys.modules:
+ module = methods.exec(sys.modules[name])
+ else:
+ module = methods.load()
# To allow reloading to potentially work, use a non-hacked loader which
# won't rely on a now-closed file object.
- module.__loader__ = _bootstrap.SourceFileLoader(name, pathname)
+ module.__loader__ = machinery.SourceFileLoader(name, pathname)
+ module.__spec__.loader = module.__loader__
return module
-class _LoadCompiledCompatibility(_HackedGetData,
- _bootstrap.SourcelessFileLoader):
+class _LoadCompiledCompatibility(_HackedGetData, SourcelessFileLoader):
"""Compatibility support for implementing load_compiled()."""
def load_compiled(name, pathname, file=None):
- msg = ('imp.load_compiled() is deprecated; use '
- 'importlib.machinery.SourcelessFileLoader(name, pathname).'
- 'load_module() instead ')
- warnings.warn(msg, DeprecationWarning, 2)
- _LoadCompiledCompatibility(name, pathname, file).load_module(name)
- module = sys.modules[name]
+ """**DEPRECATED**"""
+ loader = _LoadCompiledCompatibility(name, pathname, file)
+ spec = util.spec_from_file_location(name, pathname, loader=loader)
+ methods = _SpecMethods(spec)
+ if name in sys.modules:
+ module = methods.exec(sys.modules[name])
+ else:
+ module = methods.load()
# To allow reloading to potentially work, use a non-hacked loader which
# won't rely on a now-closed file object.
- module.__loader__ = _bootstrap.SourcelessFileLoader(name, pathname)
+ module.__loader__ = SourcelessFileLoader(name, pathname)
+ module.__spec__.loader = module.__loader__
return module
def load_package(name, path):
- msg = ('imp.load_package() is deprecated; use either '
- 'importlib.machinery.SourceFileLoader() or '
- 'importlib.machinery.SourcelessFileLoader() instead')
- warnings.warn(msg, DeprecationWarning, 2)
+ """**DEPRECATED**"""
if os.path.isdir(path):
extensions = (machinery.SOURCE_SUFFIXES[:] +
machinery.BYTECODE_SUFFIXES[:])
@@ -157,7 +208,13 @@ def load_package(name, path):
break
else:
raise ValueError('{!r} is not a package'.format(path))
- return _bootstrap.SourceFileLoader(name, path).load_module(name)
+ spec = util.spec_from_file_location(name, path,
+ submodule_search_locations=[])
+ methods = _SpecMethods(spec)
+ if name in sys.modules:
+ return methods.exec(sys.modules[name])
+ else:
+ return methods.load()
def load_module(name, file, filename, details):
@@ -169,32 +226,30 @@ def load_module(name, file, filename, details):
"""
suffix, mode, type_ = details
- with warnings.catch_warnings():
- warnings.simplefilter('ignore')
- if mode and (not mode.startswith(('r', 'U')) or '+' in mode):
- raise ValueError('invalid file open mode {!r}'.format(mode))
- elif file is None and type_ in {PY_SOURCE, PY_COMPILED}:
- msg = 'file object required for import (type code {})'.format(type_)
- raise ValueError(msg)
- elif type_ == PY_SOURCE:
- return load_source(name, filename, file)
- elif type_ == PY_COMPILED:
- return load_compiled(name, filename, file)
- elif type_ == C_EXTENSION and load_dynamic is not None:
- if file is None:
- with open(filename, 'rb') as opened_file:
- return load_dynamic(name, filename, opened_file)
- else:
- return load_dynamic(name, filename, file)
- elif type_ == PKG_DIRECTORY:
- return load_package(name, filename)
- elif type_ == C_BUILTIN:
- return init_builtin(name)
- elif type_ == PY_FROZEN:
- return init_frozen(name)
+ if mode and (not mode.startswith(('r', 'U')) or '+' in mode):
+ raise ValueError('invalid file open mode {!r}'.format(mode))
+ elif file is None and type_ in {PY_SOURCE, PY_COMPILED}:
+ msg = 'file object required for import (type code {})'.format(type_)
+ raise ValueError(msg)
+ elif type_ == PY_SOURCE:
+ return load_source(name, filename, file)
+ elif type_ == PY_COMPILED:
+ return load_compiled(name, filename, file)
+ elif type_ == C_EXTENSION and load_dynamic is not None:
+ if file is None:
+ with open(filename, 'rb') as opened_file:
+ return load_dynamic(name, filename, opened_file)
else:
- msg = "Don't know how to import {} (type code {})".format(name, type_)
- raise ImportError(msg, name=name)
+ return load_dynamic(name, filename, file)
+ elif type_ == PKG_DIRECTORY:
+ return load_package(name, filename)
+ elif type_ == C_BUILTIN:
+ return init_builtin(name)
+ elif type_ == PY_FROZEN:
+ return init_frozen(name)
+ else:
+ msg = "Don't know how to import {} (type code {})".format(name, type_)
+ raise ImportError(msg, name=name)
def find_module(name, path=None):
@@ -230,54 +285,31 @@ def find_module(name, path=None):
file_path = os.path.join(package_directory, package_file_name)
if os.path.isfile(file_path):
return None, package_directory, ('', '', PKG_DIRECTORY)
- with warnings.catch_warnings():
- warnings.simplefilter('ignore')
- for suffix, mode, type_ in get_suffixes():
- file_name = name + suffix
- file_path = os.path.join(entry, file_name)
- if os.path.isfile(file_path):
- break
- else:
- continue
- break # Break out of outer loop when breaking out of inner loop.
+ for suffix, mode, type_ in get_suffixes():
+ file_name = name + suffix
+ file_path = os.path.join(entry, file_name)
+ if os.path.isfile(file_path):
+ break
+ else:
+ continue
+ break # Break out of outer loop when breaking out of inner loop.
else:
- raise ImportError(_bootstrap._ERR_MSG.format(name), name=name)
+ raise ImportError(_ERR_MSG.format(name), name=name)
encoding = None
- if mode == 'U':
+ if 'b' not in mode:
with open(file_path, 'rb') as file:
encoding = tokenize.detect_encoding(file.readline)[0]
file = open(file_path, mode, encoding=encoding)
return file, file_path, (suffix, mode, type_)
-_RELOADING = {}
-
def reload(module):
- """Reload the module and return it.
+ """**DEPRECATED**
+
+ Reload the module and return it.
The module must have been successfully imported before.
"""
- if not module or type(module) != type(sys):
- raise TypeError("reload() argument must be module")
- name = module.__name__
- if name not in sys.modules:
- msg = "module {} not in sys.modules"
- raise ImportError(msg.format(name), name=name)
- if name in _RELOADING:
- return _RELOADING[name]
- _RELOADING[name] = module
- try:
- parent_name = name.rpartition('.')[0]
- if parent_name and parent_name not in sys.modules:
- msg = "parent {!r} not in sys.modules"
- raise ImportError(msg.format(parent_name), name=parent_name)
- module.__loader__.load_module(name)
- # The module may have replaced itself in sys.modules!
- return sys.modules[module.__name__]
- finally:
- try:
- del _RELOADING[name]
- except KeyError:
- pass
+ return importlib.reload(module)
diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py
index 22c90f24a7..3e969bbead 100644
--- a/Lib/importlib/__init__.py
+++ b/Lib/importlib/__init__.py
@@ -1,5 +1,5 @@
"""A pure Python implementation of import."""
-__all__ = ['__import__', 'import_module', 'invalidate_caches']
+__all__ = ['__import__', 'import_module', 'invalidate_caches', 'reload']
# Bootstrap help #####################################################
@@ -11,6 +11,7 @@ __all__ = ['__import__', 'import_module', 'invalidate_caches']
# initialised below if the frozen one is not available).
import _imp # Just the builtin component, NOT the full Python module
import sys
+import types
try:
import _frozen_importlib as _bootstrap
@@ -45,19 +46,42 @@ def invalidate_caches():
finder.invalidate_caches()
-def find_loader(name, path=None):
- """Find the loader for the specified module.
+def find_spec(name, path=None):
+ """Return the spec for the specified module.
First, sys.modules is checked to see if the module was already imported. If
- so, then sys.modules[name].__loader__ is returned. If that happens to be
+ so, then sys.modules[name].__spec__ is returned. If that happens to be
set to None, then ValueError is raised. If the module is not in
- sys.modules, then sys.meta_path is searched for a suitable loader with the
- value of 'path' given to the finders. None is returned if no loader could
+ sys.modules, then sys.meta_path is searched for a suitable spec with the
+ value of 'path' given to the finders. None is returned if no spec could
be found.
Dotted names do not have their parent packages implicitly imported. You will
most likely need to explicitly import all parent packages in the proper
- order for a submodule to get the correct loader.
+ order for a submodule to get the correct spec.
+
+ """
+ if name not in sys.modules:
+ return _bootstrap._find_spec(name, path)
+ else:
+ module = sys.modules[name]
+ if module is None:
+ return None
+ try:
+ spec = module.__spec__
+ except AttributeError:
+ raise ValueError('{}.__spec__ is not set'.format(name))
+ else:
+ if spec is None:
+ raise ValueError('{}.__spec__ is None'.format(name))
+ return spec
+
+
+# XXX Deprecate...
+def find_loader(name, path=None):
+ """Return the loader for the specified module.
+
+ This is a backward-compatible wrapper around find_spec().
"""
try:
@@ -68,7 +92,20 @@ def find_loader(name, path=None):
return loader
except KeyError:
pass
- return _bootstrap._find_module(name, path)
+ except AttributeError:
+ raise ValueError('{}.__loader__ is not set'.format(name))
+
+ spec = _bootstrap._find_spec(name, path)
+ # We won't worry about malformed specs (missing attributes).
+ if spec is None:
+ return None
+ if spec.loader is None:
+ if spec.submodule_search_locations is None:
+ raise ImportError('spec for {} missing loader'.format(name),
+ name=name)
+ raise ImportError('namespace packages do not have loaders',
+ name=name)
+ return spec.loader
def import_module(name, package=None):
@@ -82,9 +119,50 @@ def import_module(name, package=None):
level = 0
if name.startswith('.'):
if not package:
- raise TypeError("relative imports require the 'package' argument")
+ msg = ("the 'package' argument is required to perform a relative "
+ "import for {!r}")
+ raise TypeError(msg.format(name))
for character in name:
if character != '.':
break
level += 1
return _bootstrap._gcd_import(name[level:], package, level)
+
+
+_RELOADING = {}
+
+
+def reload(module):
+ """Reload the module and return it.
+
+ The module must have been successfully imported before.
+
+ """
+ if not module or not isinstance(module, types.ModuleType):
+ raise TypeError("reload() argument must be module")
+ try:
+ name = module.__spec__.name
+ except AttributeError:
+ name = module.__name__
+
+ if sys.modules.get(name) is not module:
+ msg = "module {} not in sys.modules"
+ raise ImportError(msg.format(name), name=name)
+ if name in _RELOADING:
+ return _RELOADING[name]
+ _RELOADING[name] = module
+ try:
+ parent_name = name.rpartition('.')[0]
+ if parent_name and parent_name not in sys.modules:
+ msg = "parent {!r} not in sys.modules"
+ raise ImportError(msg.format(parent_name), name=parent_name)
+ spec = module.__spec__ = _bootstrap._find_spec(name, None, module)
+ methods = _bootstrap._SpecMethods(spec)
+ methods.exec(module)
+ # The module may have replaced itself in sys.modules!
+ return sys.modules[name]
+ finally:
+ try:
+ del _RELOADING[name]
+ except KeyError:
+ pass
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index e40ec925ce..6b8f979b8b 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -9,7 +9,7 @@ work. One should use importlib as the public-facing version of this module.
#
# IMPORTANT: Whenever making changes to this module, be sure to run
# a top-level make in order to get the frozen version of the module
-# update. Not doing so, will result in the Makefile to fail for
+# update. Not doing so will result in the Makefile to fail for
# all others who don't have a ./python around to freeze the module
# in the early stages of compilation.
#
@@ -20,10 +20,6 @@ work. One should use importlib as the public-facing version of this module.
# reference any injected objects! This includes not only global code but also
# anything specified at the class level.
-# XXX Make sure all public names have no single leading underscore and all
-# others do.
-
-
# Bootstrap-related code ######################################################
_CASE_INSENSITIVE_PLATFORMS = 'win', 'cygwin', 'darwin'
@@ -41,76 +37,58 @@ def _make_relax_case():
return _relax_case
-# TODO: Expose from marshal
def _w_long(x):
- """Convert a 32-bit integer to little-endian.
+ """Convert a 32-bit integer to little-endian."""
+ return (int(x) & 0xFFFFFFFF).to_bytes(4, 'little')
- XXX Temporary until marshal's long functions are exposed.
-
- """
- x = int(x)
- int_bytes = []
- int_bytes.append(x & 0xFF)
- int_bytes.append((x >> 8) & 0xFF)
- int_bytes.append((x >> 16) & 0xFF)
- int_bytes.append((x >> 24) & 0xFF)
- return bytearray(int_bytes)
-
-# TODO: Expose from marshal
def _r_long(int_bytes):
- """Convert 4 bytes in little-endian to an integer.
-
- XXX Temporary until marshal's long function are exposed.
-
- """
- x = int_bytes[0]
- x |= int_bytes[1] << 8
- x |= int_bytes[2] << 16
- x |= int_bytes[3] << 24
- return x
+ """Convert 4 bytes in little-endian to an integer."""
+ return int.from_bytes(int_bytes, 'little')
def _path_join(*path_parts):
"""Replacement for os.path.join()."""
- new_parts = []
- for part in path_parts:
- if not part:
- continue
- new_parts.append(part)
- if part[-1] not in path_separators:
- new_parts.append(path_sep)
- return ''.join(new_parts[:-1]) # Drop superfluous path separator.
+ return path_sep.join([part.rstrip(path_separators)
+ for part in path_parts if part])
def _path_split(path):
"""Replacement for os.path.split()."""
+ if len(path_separators) == 1:
+ front, _, tail = path.rpartition(path_sep)
+ return front, tail
for x in reversed(path):
if x in path_separators:
- sep = x
- break
- else:
- sep = path_sep
- front, _, tail = path.rpartition(sep)
- return front, tail
+ front, tail = path.rsplit(x, maxsplit=1)
+ return front, tail
+ return '', path
+
+
+def _path_stat(path):
+ """Stat the path.
+
+ Made a separate function to make it easier to override in experiments
+ (e.g. cache stat results).
+
+ """
+ return _os.stat(path)
def _path_is_mode_type(path, mode):
"""Test whether the path is the specified mode type."""
try:
- stat_info = _os.stat(path)
+ stat_info = _path_stat(path)
except OSError:
return False
return (stat_info.st_mode & 0o170000) == mode
-# XXX Could also expose Modules/getpath.c:isfile()
def _path_isfile(path):
"""Replacement for os.path.isfile."""
return _path_is_mode_type(path, 0o100000)
-# XXX Could also expose Modules/getpath.c:isdir()
def _path_isdir(path):
"""Replacement for os.path.isdir."""
if not path:
@@ -148,17 +126,30 @@ def _wrap(new, old):
new.__dict__.update(old.__dict__)
+def _new_module(name):
+ return type(sys)(name)
+
+
_code_type = type(_wrap.__code__)
-def new_module(name):
- """Create a new module.
- The module is not entered into sys.modules.
+class _ManageReload:
- """
- return type(_io)(name)
+ """Manages the possible clean-up of sys.modules for load_module()."""
+ def __init__(self, name):
+ self._name = name
+
+ def __enter__(self):
+ self._is_reload = self._name in sys.modules
+
+ def __exit__(self, *args):
+ if any(arg is not None for arg in args) and not self._is_reload:
+ try:
+ del sys.modules[self._name]
+ except KeyError:
+ pass
# Module-level locking ########################################################
@@ -214,7 +205,7 @@ class _ModuleLock:
self.count += 1
return True
if self.has_deadlock():
- raise _DeadlockError("deadlock detected by %r" % self)
+ raise _DeadlockError('deadlock detected by %r' % self)
if self.wakeup.acquire(False):
self.waiters += 1
# Wait for a release() call
@@ -227,7 +218,7 @@ class _ModuleLock:
tid = _thread.get_ident()
with self.lock:
if self.owner != tid:
- raise RuntimeError("cannot release un-acquired lock")
+ raise RuntimeError('cannot release un-acquired lock')
assert self.count > 0
self.count -= 1
if self.count == 0:
@@ -237,7 +228,7 @@ class _ModuleLock:
self.wakeup.release()
def __repr__(self):
- return "_ModuleLock(%r) at %d" % (self.name, id(self))
+ return '_ModuleLock({!r}) at {}'.format(self.name, id(self))
class _DummyModuleLock:
@@ -254,11 +245,28 @@ class _DummyModuleLock:
def release(self):
if self.count == 0:
- raise RuntimeError("cannot release un-acquired lock")
+ raise RuntimeError('cannot release un-acquired lock')
self.count -= 1
def __repr__(self):
- return "_DummyModuleLock(%r) at %d" % (self.name, id(self))
+ return '_DummyModuleLock({!r}) at {}'.format(self.name, id(self))
+
+
+class _ModuleLockManager:
+
+ def __init__(self, name):
+ self._name = name
+ self._lock = None
+
+ def __enter__(self):
+ try:
+ self._lock = _get_module_lock(self._name)
+ finally:
+ _imp.release_lock()
+ self._lock.acquire()
+
+ def __exit__(self, *args, **kwargs):
+ self._lock.release()
# The following two functions are for consumption by Python/import.c.
@@ -315,95 +323,108 @@ def _call_with_frames_removed(f, *args, **kwds):
# Finder/loader utility code ###############################################
-"""Magic word to reject .pyc files generated by other Python versions.
-It should change for each incompatible change to the bytecode.
-
-The value of CR and LF is incorporated so if you ever read or write
-a .pyc file in text mode the magic number will be wrong; also, the
-Apple MPW compiler swaps their values, botching string constants.
-
-The magic numbers must be spaced apart at least 2 values, as the
--U interpeter flag will cause MAGIC+1 being used. They have been
-odd numbers for some time now.
-
-There were a variety of old schemes for setting the magic number.
-The current working scheme is to increment the previous value by
-10.
-
-Starting with the adoption of PEP 3147 in Python 3.2, every bump in magic
-number also includes a new "magic tag", i.e. a human readable string used
-to represent the magic number in __pycache__ directories. When you change
-the magic number, you must also set a new unique magic tag. Generally this
-can be named after the Python major version of the magic number bump, but
-it can really be anything, as long as it's different than anything else
-that's come before. The tags are included in the following table, starting
-with Python 3.2a0.
-
-Known values:
- Python 1.5: 20121
- Python 1.5.1: 20121
- Python 1.5.2: 20121
- Python 1.6: 50428
- Python 2.0: 50823
- Python 2.0.1: 50823
- Python 2.1: 60202
- Python 2.1.1: 60202
- Python 2.1.2: 60202
- Python 2.2: 60717
- Python 2.3a0: 62011
- Python 2.3a0: 62021
- Python 2.3a0: 62011 (!)
- Python 2.4a0: 62041
- Python 2.4a3: 62051
- Python 2.4b1: 62061
- Python 2.5a0: 62071
- Python 2.5a0: 62081 (ast-branch)
- Python 2.5a0: 62091 (with)
- Python 2.5a0: 62092 (changed WITH_CLEANUP opcode)
- Python 2.5b3: 62101 (fix wrong code: for x, in ...)
- Python 2.5b3: 62111 (fix wrong code: x += yield)
- Python 2.5c1: 62121 (fix wrong lnotab with for loops and
- storing constants that should have been removed)
- Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp)
- Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode)
- Python 2.6a1: 62161 (WITH_CLEANUP optimization)
- Python 3000: 3000
- 3010 (removed UNARY_CONVERT)
- 3020 (added BUILD_SET)
- 3030 (added keyword-only parameters)
- 3040 (added signature annotations)
- 3050 (print becomes a function)
- 3060 (PEP 3115 metaclass syntax)
- 3061 (string literals become unicode)
- 3071 (PEP 3109 raise changes)
- 3081 (PEP 3137 make __file__ and __name__ unicode)
- 3091 (kill str8 interning)
- 3101 (merge from 2.6a0, see 62151)
- 3103 (__file__ points to source file)
- Python 3.0a4: 3111 (WITH_CLEANUP optimization).
- Python 3.0a5: 3131 (lexical exception stacking, including POP_EXCEPT)
- Python 3.1a0: 3141 (optimize list, set and dict comprehensions:
- change LIST_APPEND and SET_ADD, add MAP_ADD)
- Python 3.1a0: 3151 (optimize conditional branches:
- introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE)
- Python 3.2a0: 3160 (add SETUP_WITH)
- tag: cpython-32
- Python 3.2a1: 3170 (add DUP_TOP_TWO, remove DUP_TOPX and ROT_FOUR)
- tag: cpython-32
- Python 3.2a2 3180 (add DELETE_DEREF)
- Python 3.3a0 3190 __class__ super closure changed
- Python 3.3a0 3200 (__qualname__ added)
- 3210 (added size modulo 2**32 to the pyc header)
- Python 3.3a1 3220 (changed PEP 380 implementation)
- Python 3.3a4 3230 (revert changes to implicit __class__ closure)
-
-MAGIC must change whenever the bytecode emitted by the compiler may no
-longer be understood by older implementations of the eval loop (usually
-due to the addition of new opcodes).
+# Magic word to reject .pyc files generated by other Python versions.
+# It should change for each incompatible change to the bytecode.
+#
+# The value of CR and LF is incorporated so if you ever read or write
+# a .pyc file in text mode the magic number will be wrong; also, the
+# Apple MPW compiler swaps their values, botching string constants.
+#
+# The magic numbers must be spaced apart at least 2 values, as the
+# -U interpeter flag will cause MAGIC+1 being used. They have been
+# odd numbers for some time now.
+#
+# There were a variety of old schemes for setting the magic number.
+# The current working scheme is to increment the previous value by
+# 10.
+#
+# Starting with the adoption of PEP 3147 in Python 3.2, every bump in magic
+# number also includes a new "magic tag", i.e. a human readable string used
+# to represent the magic number in __pycache__ directories. When you change
+# the magic number, you must also set a new unique magic tag. Generally this
+# can be named after the Python major version of the magic number bump, but
+# it can really be anything, as long as it's different than anything else
+# that's come before. The tags are included in the following table, starting
+# with Python 3.2a0.
+#
+# Known values:
+# Python 1.5: 20121
+# Python 1.5.1: 20121
+# Python 1.5.2: 20121
+# Python 1.6: 50428
+# Python 2.0: 50823
+# Python 2.0.1: 50823
+# Python 2.1: 60202
+# Python 2.1.1: 60202
+# Python 2.1.2: 60202
+# Python 2.2: 60717
+# Python 2.3a0: 62011
+# Python 2.3a0: 62021
+# Python 2.3a0: 62011 (!)
+# Python 2.4a0: 62041
+# Python 2.4a3: 62051
+# Python 2.4b1: 62061
+# Python 2.5a0: 62071
+# Python 2.5a0: 62081 (ast-branch)
+# Python 2.5a0: 62091 (with)
+# Python 2.5a0: 62092 (changed WITH_CLEANUP opcode)
+# Python 2.5b3: 62101 (fix wrong code: for x, in ...)
+# Python 2.5b3: 62111 (fix wrong code: x += yield)
+# Python 2.5c1: 62121 (fix wrong lnotab with for loops and
+# storing constants that should have been removed)
+# Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp)
+# Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode)
+# Python 2.6a1: 62161 (WITH_CLEANUP optimization)
+# Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND)
+# Python 2.7a0: 62181 (optimize conditional branches:
+# introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE)
+# Python 2.7a0 62191 (introduce SETUP_WITH)
+# Python 2.7a0 62201 (introduce BUILD_SET)
+# Python 2.7a0 62211 (introduce MAP_ADD and SET_ADD)
+# Python 3000: 3000
+# 3010 (removed UNARY_CONVERT)
+# 3020 (added BUILD_SET)
+# 3030 (added keyword-only parameters)
+# 3040 (added signature annotations)
+# 3050 (print becomes a function)
+# 3060 (PEP 3115 metaclass syntax)
+# 3061 (string literals become unicode)
+# 3071 (PEP 3109 raise changes)
+# 3081 (PEP 3137 make __file__ and __name__ unicode)
+# 3091 (kill str8 interning)
+# 3101 (merge from 2.6a0, see 62151)
+# 3103 (__file__ points to source file)
+# Python 3.0a4: 3111 (WITH_CLEANUP optimization).
+# Python 3.0a5: 3131 (lexical exception stacking, including POP_EXCEPT)
+# Python 3.1a0: 3141 (optimize list, set and dict comprehensions:
+# change LIST_APPEND and SET_ADD, add MAP_ADD)
+# Python 3.1a0: 3151 (optimize conditional branches:
+# introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE)
+# Python 3.2a0: 3160 (add SETUP_WITH)
+# tag: cpython-32
+# Python 3.2a1: 3170 (add DUP_TOP_TWO, remove DUP_TOPX and ROT_FOUR)
+# tag: cpython-32
+# Python 3.2a2 3180 (add DELETE_DEREF)
+# Python 3.3a0 3190 __class__ super closure changed
+# Python 3.3a0 3200 (__qualname__ added)
+# 3210 (added size modulo 2**32 to the pyc header)
+# Python 3.3a1 3220 (changed PEP 380 implementation)
+# Python 3.3a4 3230 (revert changes to implicit __class__ closure)
+# Python 3.4a1 3250 (evaluate positional default arguments before
+# keyword-only defaults)
+# Python 3.4a1 3260 (add LOAD_CLASSDEREF; allow locals of class to override
+# free vars)
+# Python 3.4a1 3270 (various tweaks to the __class__ closure)
+# Python 3.4a1 3280 (remove implicit class argument)
+# Python 3.4a4 3290 (changes to __qualname__ computation)
+# Python 3.4a4 3300 (more changes to __qualname__ computation)
+#
+# MAGIC must change whenever the bytecode emitted by the compiler may no
+# longer be understood by older implementations of the eval loop (usually
+# due to the addition of new opcodes).
-"""
-_RAW_MAGIC_NUMBER = 3230 | ord('\r') << 16 | ord('\n') << 24
-_MAGIC_BYTES = bytes(_RAW_MAGIC_NUMBER >> n & 0xff for n in range(0, 25, 8))
+MAGIC_NUMBER = (3300).to_bytes(2, 'little') + b'\r\n'
+_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
_PYCACHE = '__pycache__'
@@ -481,6 +502,18 @@ def _get_sourcefile(bytecode_path):
return source_path if _path_isfile(source_path) else bytecode_path
+def _calc_mode(path):
+ """Calculate the mode permissions for a bytecode file."""
+ try:
+ mode = _path_stat(path).st_mode
+ except OSError:
+ mode = 0o666
+ # We always ensure write access so we can update cached files
+ # later even when the source files are read-only on Windows (#6074)
+ mode |= 0o200
+ return mode
+
+
def _verbose_message(message, *args, verbosity=1):
"""Print the message to stderr if -v/PYTHONVERBOSE is turned on."""
if sys.flags.verbose >= verbosity:
@@ -489,85 +522,6 @@ def _verbose_message(message, *args, verbosity=1):
print(message.format(*args), file=sys.stderr)
-def set_package(fxn):
- """Set __package__ on the returned module."""
- def set_package_wrapper(*args, **kwargs):
- module = fxn(*args, **kwargs)
- if getattr(module, '__package__', None) is None:
- module.__package__ = module.__name__
- if not hasattr(module, '__path__'):
- module.__package__ = module.__package__.rpartition('.')[0]
- return module
- _wrap(set_package_wrapper, fxn)
- return set_package_wrapper
-
-
-def set_loader(fxn):
- """Set __loader__ on the returned module."""
- def set_loader_wrapper(self, *args, **kwargs):
- module = fxn(self, *args, **kwargs)
- if not hasattr(module, '__loader__'):
- module.__loader__ = self
- return module
- _wrap(set_loader_wrapper, fxn)
- return set_loader_wrapper
-
-
-def module_for_loader(fxn):
- """Decorator to handle selecting the proper module for loaders.
-
- The decorated function is passed the module to use instead of the module
- name. The module passed in to the function is either from sys.modules if
- it already exists or is a new module. If the module is new, then __name__
- is set the first argument to the method, __loader__ is set to self, and
- __package__ is set accordingly (if self.is_package() is defined) will be set
- before it is passed to the decorated function (if self.is_package() does
- not work for the module it will be set post-load).
-
- If an exception is raised and the decorator created the module it is
- subsequently removed from sys.modules.
-
- The decorator assumes that the decorated function takes the module name as
- the second argument.
-
- """
- def module_for_loader_wrapper(self, fullname, *args, **kwargs):
- module = sys.modules.get(fullname)
- is_reload = module is not None
- if not is_reload:
- # This must be done before open() is called as the 'io' module
- # implicitly imports 'locale' and would otherwise trigger an
- # infinite loop.
- module = new_module(fullname)
- # This must be done before putting the module in sys.modules
- # (otherwise an optimization shortcut in import.c becomes wrong)
- module.__initializing__ = True
- sys.modules[fullname] = module
- module.__loader__ = self
- try:
- is_package = self.is_package(fullname)
- except (ImportError, AttributeError):
- pass
- else:
- if is_package:
- module.__package__ = fullname
- else:
- module.__package__ = fullname.rpartition('.')[0]
- else:
- module.__initializing__ = True
- try:
- # If __package__ was not set above, __import__() will do it later.
- return fxn(self, module, *args, **kwargs)
- except:
- if not is_reload:
- del sys.modules[fullname]
- raise
- finally:
- module.__initializing__ = False
- _wrap(module_for_loader_wrapper, fxn)
- return module_for_loader_wrapper
-
-
def _check_name(method):
"""Decorator to verify that the module being requested matches the one the
loader can handle.
@@ -580,7 +534,7 @@ def _check_name(method):
if name is None:
name = self.name
elif self.name != name:
- raise ImportError("loader cannot handle %s" % name, name=name)
+ raise ImportError('loader cannot handle %s' % name, name=name)
return method(self, name, *args, **kwargs)
_wrap(_check_name_wrapper, method)
return _check_name_wrapper
@@ -590,7 +544,7 @@ def _requires_builtin(fxn):
"""Decorator to verify the named module is built-in."""
def _requires_builtin_wrapper(self, fullname):
if fullname not in sys.builtin_module_names:
- raise ImportError("{} is not a built-in module".format(fullname),
+ raise ImportError('{!r} is not a built-in module'.format(fullname),
name=fullname)
return fxn(self, fullname)
_wrap(_requires_builtin_wrapper, fxn)
@@ -601,7 +555,7 @@ def _requires_frozen(fxn):
"""Decorator to verify the named module is frozen."""
def _requires_frozen_wrapper(self, fullname):
if not _imp.is_frozen(fullname):
- raise ImportError("{} is not a frozen module".format(fullname),
+ raise ImportError('{!r} is not a frozen module'.format(fullname),
name=fullname)
return fxn(self, fullname)
_wrap(_requires_frozen_wrapper, fxn)
@@ -616,11 +570,659 @@ def _find_module_shim(self, fullname):
# return None.
loader, portions = self.find_loader(fullname)
if loader is None and len(portions):
- msg = "Not importing directory {}: missing __init__"
+ msg = 'Not importing directory {}: missing __init__'
_warnings.warn(msg.format(portions[0]), ImportWarning)
return loader
+def _load_module_shim(self, fullname):
+ """Load the specified module into sys.modules and return it."""
+ # XXX Deprecation Warning here...
+ spec = spec_from_loader(fullname, self)
+ methods = _SpecMethods(spec)
+ if fullname in sys.modules:
+ module = sys.modules[fullname]
+ methods.exec(module)
+ return sys.modules[fullname]
+ else:
+ return methods.load()
+
+
+def _validate_bytecode_header(data, source_stats=None, name=None, path=None):
+ """Validate the header of the passed-in bytecode against source_stats (if
+ given) and returning the bytecode that can be compiled by compile().
+
+ All other arguments are used to enhance error reporting.
+
+ ImportError is raised when the magic number is incorrect or the bytecode is
+ found to be stale. EOFError is raised when the data is found to be
+ truncated.
+
+ """
+ exc_details = {}
+ if name is not None:
+ exc_details['name'] = name
+ else:
+ # To prevent having to make all messages have a conditional name.
+ name = '<bytecode>'
+ if path is not None:
+ exc_details['path'] = path
+ magic = data[:4]
+ raw_timestamp = data[4:8]
+ raw_size = data[8:12]
+ if magic != MAGIC_NUMBER:
+ message = 'bad magic number in {!r}: {!r}'.format(name, magic)
+ _verbose_message(message)
+ raise ImportError(message, **exc_details)
+ elif len(raw_timestamp) != 4:
+ message = 'reached EOF while reading timestamp in {!r}'.format(name)
+ _verbose_message(message)
+ raise EOFError(message)
+ elif len(raw_size) != 4:
+ message = 'reached EOF while reading size of source in {!r}'.format(name)
+ _verbose_message(message)
+ raise EOFError(message)
+ if source_stats is not None:
+ try:
+ source_mtime = int(source_stats['mtime'])
+ except KeyError:
+ pass
+ else:
+ if _r_long(raw_timestamp) != source_mtime:
+ message = 'bytecode is stale for {!r}'.format(name)
+ _verbose_message(message)
+ raise ImportError(message, **exc_details)
+ try:
+ source_size = source_stats['size'] & 0xFFFFFFFF
+ except KeyError:
+ pass
+ else:
+ if _r_long(raw_size) != source_size:
+ raise ImportError('bytecode is stale for {!r}'.format(name),
+ **exc_details)
+ return data[12:]
+
+
+def _compile_bytecode(data, name=None, bytecode_path=None, source_path=None):
+ """Compile bytecode as returned by _validate_bytecode_header()."""
+ code = marshal.loads(data)
+ if isinstance(code, _code_type):
+ _verbose_message('code object from {!r}', bytecode_path)
+ if source_path is not None:
+ _imp._fix_co_filename(code, source_path)
+ return code
+ else:
+ raise ImportError('Non-code object in {!r}'.format(bytecode_path),
+ name=name, path=bytecode_path)
+
+def _code_to_bytecode(code, mtime=0, source_size=0):
+ """Compile a code object into bytecode for writing out to a byte-compiled
+ file."""
+ data = bytearray(MAGIC_NUMBER)
+ data.extend(_w_long(mtime))
+ data.extend(_w_long(source_size))
+ data.extend(marshal.dumps(code))
+ return data
+
+
+def decode_source(source_bytes):
+ """Decode bytes representing source code and return the string.
+
+ Universal newline support is used in the decoding.
+ """
+ import tokenize # To avoid bootstrap issues.
+ source_bytes_readline = _io.BytesIO(source_bytes).readline
+ encoding = tokenize.detect_encoding(source_bytes_readline)
+ newline_decoder = _io.IncrementalNewlineDecoder(None, True)
+ return newline_decoder.decode(source_bytes.decode(encoding[0]))
+
+
+# Module specifications #######################################################
+
+def _module_repr(module):
+ # The implementation of ModuleType__repr__().
+ loader = getattr(module, '__loader__', None)
+ if hasattr(loader, 'module_repr'):
+ # XXX Deprecation Warning here...
+ try:
+ return loader.module_repr(module)
+ except Exception:
+ pass
+ try:
+ spec = module.__spec__
+ except AttributeError:
+ pass
+ else:
+ if spec is not None:
+ return _SpecMethods(spec).module_repr()
+
+ # We could use module.__class__.__name__ instead of 'module' in the
+ # various repr permutations.
+ try:
+ name = module.__name__
+ except AttributeError:
+ name = '?'
+ try:
+ filename = module.__file__
+ except AttributeError:
+ if loader is None:
+ return '<module {!r}>'.format(name)
+ else:
+ return '<module {!r} ({!r})>'.format(name, loader)
+ else:
+ return '<module {!r} from {!r}>'.format(name, filename)
+
+
+class _installed_safely:
+
+ def __init__(self, module):
+ self._module = module
+ self._spec = module.__spec__
+
+ def __enter__(self):
+ # This must be done before putting the module in sys.modules
+ # (otherwise an optimization shortcut in import.c becomes
+ # wrong)
+ self._spec._initializing = True
+ sys.modules[self._spec.name] = self._module
+
+ def __exit__(self, *args):
+ try:
+ spec = self._spec
+ if any(arg is not None for arg in args):
+ try:
+ del sys.modules[spec.name]
+ except KeyError:
+ pass
+ else:
+ _verbose_message('import {!r} # {!r}', spec.name, spec.loader)
+ finally:
+ self._spec._initializing = False
+
+
+class ModuleSpec:
+ """The specification for a module, used for loading.
+
+ A module's spec is the source for information about the module. For
+ data associated with the module, including source, use the spec's
+ loader.
+
+ `name` is the absolute name of the module. `loader` is the loader
+ to use when loading the module. `parent` is the name of the
+ package the module is in. The parent is derived from the name.
+
+ `is_package` determines if the module is considered a package or
+ not. On modules this is reflected by the `__path__` attribute.
+
+ `origin` is the specific location used by the loader from which to
+ load the module, if that information is available. When filename is
+ set, origin will match.
+
+ `has_location` indicates that a spec's "origin" reflects a location.
+ When this is True, `__file__` attribute of the module is set.
+
+ `cached` is the location of the cached bytecode file, if any. It
+ corresponds to the `__cached__` attribute.
+
+ `submodule_search_locations` is the sequence of path entries to
+ search when importing submodules. If set, is_package should be
+ True--and False otherwise.
+
+ Packages are simply modules that (may) have submodules. If a spec
+ has a non-None value in `submodule_search_locations`, the import
+ system will consider modules loaded from the spec as packages.
+
+ Only finders (see importlib.abc.MetaPathFinder and
+ importlib.abc.PathEntryFinder) should modify ModuleSpec instances.
+
+ """
+
+ def __init__(self, name, loader, *, origin=None, loader_state=None,
+ is_package=None):
+ self.name = name
+ self.loader = loader
+ self.origin = origin
+ self.loader_state = loader_state
+ self.submodule_search_locations = [] if is_package else None
+
+ # file-location attributes
+ self._set_fileattr = False
+ self._cached = None
+
+ def __repr__(self):
+ args = ['name={!r}'.format(self.name),
+ 'loader={!r}'.format(self.loader)]
+ if self.origin is not None:
+ args.append('origin={!r}'.format(self.origin))
+ if self.submodule_search_locations is not None:
+ args.append('submodule_search_locations={}'
+ .format(self.submodule_search_locations))
+ return '{}({})'.format(self.__class__.__name__, ', '.join(args))
+
+ def __eq__(self, other):
+ smsl = self.submodule_search_locations
+ try:
+ return (self.name == other.name and
+ self.loader == other.loader and
+ self.origin == other.origin and
+ smsl == other.submodule_search_locations and
+ self.cached == other.cached and
+ self.has_location == other.has_location)
+ except AttributeError:
+ return False
+
+ @property
+ def cached(self):
+ if self._cached is None:
+ if self.origin is not None and self._set_fileattr:
+ filename = self.origin
+ if filename.endswith(tuple(SOURCE_SUFFIXES)):
+ try:
+ self._cached = cache_from_source(filename)
+ except NotImplementedError:
+ pass
+ elif filename.endswith(tuple(BYTECODE_SUFFIXES)):
+ self._cached = filename
+ return self._cached
+
+ @cached.setter
+ def cached(self, cached):
+ self._cached = cached
+
+ @property
+ def parent(self):
+ """The name of the module's parent."""
+ if self.submodule_search_locations is None:
+ return self.name.rpartition('.')[0]
+ else:
+ return self.name
+
+ @property
+ def has_location(self):
+ return self._set_fileattr
+
+
+def spec_from_loader(name, loader, *, origin=None, is_package=None):
+ """Return a module spec based on various loader methods."""
+ if hasattr(loader, 'get_filename'):
+ if is_package is None:
+ return spec_from_file_location(name, loader=loader)
+ search = [] if is_package else None
+ return spec_from_file_location(name, loader=loader,
+ submodule_search_locations=search)
+
+ if is_package is None:
+ if hasattr(loader, 'is_package'):
+ try:
+ is_package = loader.is_package(name)
+ except ImportError:
+ is_package = None # aka, undefined
+ else:
+ # the default
+ is_package = False
+
+ return ModuleSpec(name, loader, origin=origin, is_package=is_package)
+
+
+_POPULATE = object()
+
+
+def spec_from_file_location(name, location=None, *, loader=None,
+ submodule_search_locations=_POPULATE):
+ """Return a module spec based on a file location.
+
+ To indicate that the module is a package, set
+ submodule_search_locations to a list of directory paths. An
+ empty list is sufficient, though its not otherwise useful to the
+ import system.
+
+ The loader must take a spec as its only __init__() arg.
+
+ """
+ if location is None:
+ # The caller may simply want a partially populated location-
+ # oriented spec. So we set the location to a bogus value and
+ # fill in as much as we can.
+ location = '<unknown>'
+ if hasattr(loader, 'get_filename'):
+ # ExecutionLoader
+ try:
+ location = loader.get_filename(name)
+ except ImportError:
+ pass
+
+ # If the location is on the filesystem, but doesn't actually exist,
+ # we could return None here, indicating that the location is not
+ # valid. However, we don't have a good way of testing since an
+ # indirect location (e.g. a zip file or URL) will look like a
+ # non-existent file relative to the filesystem.
+
+ spec = ModuleSpec(name, loader, origin=location)
+ spec._set_fileattr = True
+
+ # Pick a loader if one wasn't provided.
+ if loader is None:
+ for loader_class, suffixes in _get_supported_file_loaders():
+ if location.endswith(tuple(suffixes)):
+ loader = loader_class(name, location)
+ spec.loader = loader
+ break
+ else:
+ return None
+
+ # Set submodule_search_paths appropriately.
+ if submodule_search_locations is _POPULATE:
+ # Check the loader.
+ if hasattr(loader, 'is_package'):
+ try:
+ is_package = loader.is_package(name)
+ except ImportError:
+ pass
+ else:
+ if is_package:
+ spec.submodule_search_locations = []
+ else:
+ spec.submodule_search_locations = submodule_search_locations
+ if spec.submodule_search_locations == []:
+ if location:
+ dirname = _path_split(location)[0]
+ spec.submodule_search_locations.append(dirname)
+
+ return spec
+
+
+def _spec_from_module(module, loader=None, origin=None):
+ # This function is meant for use in _setup().
+ try:
+ spec = module.__spec__
+ except AttributeError:
+ pass
+ else:
+ if spec is not None:
+ return spec
+
+ name = module.__name__
+ if loader is None:
+ try:
+ loader = module.__loader__
+ except AttributeError:
+ # loader will stay None.
+ pass
+ try:
+ location = module.__file__
+ except AttributeError:
+ location = None
+ if origin is None:
+ if location is None:
+ try:
+ origin = loader._ORIGIN
+ except AttributeError:
+ origin = None
+ else:
+ origin = location
+ try:
+ cached = module.__cached__
+ except AttributeError:
+ cached = None
+ try:
+ submodule_search_locations = list(module.__path__)
+ except AttributeError:
+ submodule_search_locations = None
+
+ spec = ModuleSpec(name, loader, origin=origin)
+ spec._set_fileattr = False if location is None else True
+ spec.cached = cached
+ spec.submodule_search_locations = submodule_search_locations
+ return spec
+
+
+class _SpecMethods:
+
+ """Convenience wrapper around spec objects to provide spec-specific
+ methods."""
+
+ def __init__(self, spec):
+ self.spec = spec
+
+ @classmethod
+ def from_module(cls, module):
+ """Create a spec from a module's attributes."""
+ try:
+ spec = module.__spec__
+ except AttributeError:
+ try:
+ loader = spec.__loader__
+ except AttributeError:
+ spec = _find_spec(module.__name__)
+ if spec is None:
+ spec = spec_from_loader(module.__name__, loader)
+ else:
+ spec = spec_from_loader(module.__name__, loader)
+ return cls(spec)
+
+ def module_repr(self):
+ """Return the repr to use for the module."""
+ # We mostly replicate _module_repr() using the spec attributes.
+ spec = self.spec
+ name = '?' if spec.name is None else spec.name
+ if spec.origin is None:
+ if spec.loader is None:
+ return '<module {!r}>'.format(name)
+ else:
+ return '<module {!r} ({!r})>'.format(name, spec.loader)
+ else:
+ if spec.has_location:
+ return '<module {!r} from {!r}>'.format(name, spec.origin)
+ else:
+ return '<module {!r} ({})>'.format(spec.name, spec.origin)
+
+ def init_module_attrs(self, module, *, _override=False, _force_name=True):
+ """Set the module's attributes.
+
+ All missing import-related module attributes will be set. Here
+ is how the spec attributes map onto the module:
+
+ spec.name -> module.__name__
+ spec.loader -> module.__loader__
+ spec.parent -> module.__package__
+ spec -> module.__spec__
+
+ Optional:
+ spec.origin -> module.__file__ (if spec.set_fileattr is true)
+ spec.cached -> module.__cached__ (if __file__ also set)
+ spec.submodule_search_locations -> module.__path__ (if set)
+
+ """
+ spec = self.spec
+
+ # The passed in module may be not support attribute assignment,
+ # in which case we simply don't set the attributes.
+
+ # __name__
+ if (_override or _force_name or
+ getattr(module, '__name__', None) is None):
+ try:
+ module.__name__ = spec.name
+ except AttributeError:
+ pass
+
+ # __loader__
+ if _override or getattr(module, '__loader__', None) is None:
+ loader = spec.loader
+ if loader is None:
+ # A backward compatibility hack.
+ if spec.submodule_search_locations is not None:
+ loader = _NamespaceLoader.__new__(_NamespaceLoader)
+ loader._path = spec.submodule_search_locations
+ try:
+ module.__loader__ = loader
+ except AttributeError:
+ pass
+
+ # __package__
+ if _override or getattr(module, '__package__', None) is None:
+ try:
+ module.__package__ = spec.parent
+ except AttributeError:
+ pass
+
+ # __spec__
+ try:
+ module.__spec__ = spec
+ except AttributeError:
+ pass
+
+ # __path__
+ if _override or getattr(module, '__path__', None) is None:
+ if spec.submodule_search_locations is not None:
+ try:
+ module.__path__ = spec.submodule_search_locations
+ except AttributeError:
+ pass
+
+ if spec.has_location:
+ # __file__
+ if _override or getattr(module, '__file__', None) is None:
+ try:
+ module.__file__ = spec.origin
+ except AttributeError:
+ pass
+
+ # __cached__
+ if _override or getattr(module, '__cached__', None) is None:
+ if spec.cached is not None:
+ try:
+ module.__cached__ = spec.cached
+ except AttributeError:
+ pass
+
+ def create(self):
+ """Return a new module to be loaded.
+
+ The import-related module attributes are also set with the
+ appropriate values from the spec.
+
+ """
+ spec = self.spec
+ # Typically loaders will not implement create_module().
+ if hasattr(spec.loader, 'create_module'):
+ # If create_module() returns `None` it means the default
+ # module creation should be used.
+ module = spec.loader.create_module(spec)
+ else:
+ module = None
+ if module is None:
+ # This must be done before open() is ever called as the 'io'
+ # module implicitly imports 'locale' and would otherwise
+ # trigger an infinite loop.
+ module = _new_module(spec.name)
+ self.init_module_attrs(module)
+ return module
+
+ def _exec(self, module):
+ """Do everything necessary to execute the module.
+
+ The namespace of `module` is used as the target of execution.
+ This method uses the loader's `exec_module()` method.
+
+ """
+ self.spec.loader.exec_module(module)
+
+ # Used by importlib.reload() and _load_module_shim().
+ def exec(self, module):
+ """Execute the spec in an existing module's namespace."""
+ name = self.spec.name
+ _imp.acquire_lock()
+ with _ModuleLockManager(name):
+ if sys.modules.get(name) is not module:
+ msg = 'module {!r} not in sys.modules'.format(name)
+ raise ImportError(msg, name=name)
+ if self.spec.loader is None:
+ if self.spec.submodule_search_locations is None:
+ raise ImportError('missing loader', name=self.spec.name)
+ # namespace package
+ self.init_module_attrs(module, _override=True)
+ return module
+ self.init_module_attrs(module, _override=True)
+ if not hasattr(self.spec.loader, 'exec_module'):
+ # XXX DeprecationWarning goes here...
+ self.spec.loader.load_module(name)
+ else:
+ self._exec(module)
+ return sys.modules[name]
+
+ def _load_backward_compatible(self):
+ # XXX DeprecationWarning goes here...
+ spec = self.spec
+ # The module must be in sys.modules!
+ spec.loader.load_module(spec.name)
+ module = sys.modules[spec.name]
+ if getattr(module, '__loader__', None) is None:
+ try:
+ module.__loader__ = spec.loader
+ except AttributeError:
+ pass
+ if getattr(module, '__package__', None) is None:
+ try:
+ # Since module.__path__ may not line up with
+ # spec.submodule_search_paths, we can't necessarily rely
+ # on spec.parent here.
+ module.__package__ = module.__name__
+ if not hasattr(module, '__path__'):
+ module.__package__ = spec.name.rpartition('.')[0]
+ except AttributeError:
+ pass
+ if getattr(module, '__spec__', None) is None:
+ try:
+ module.__spec__ = spec
+ except AttributeError:
+ pass
+ return module
+
+ # XXX If we don't end up using this for pythonrun.c/runpy, we should
+ # get rid of it.
+ def _load_existing(self, module):
+ """Exec the spec'ed module into an existing module's namespace."""
+ # For use by runpy.
+ with _installed_safely(module):
+ loaded = self.exec(module)
+ return loaded
+
+ def _load_unlocked(self):
+ # A helper for direct use by the import system.
+ if self.spec.loader is not None:
+ # not a namespace package
+ if not hasattr(self.spec.loader, 'exec_module'):
+ return self._load_backward_compatible()
+
+ module = self.create()
+ with _installed_safely(module):
+ if self.spec.loader is None:
+ if self.spec.submodule_search_locations is None:
+ raise ImportError('missing loader', name=self.spec.name)
+ # A namespace package so do nothing.
+ else:
+ self._exec(module)
+
+ # We don't ensure that the import-related module attributes get
+ # set in the sys.modules replacement case. Such modules are on
+ # their own.
+ return sys.modules[self.spec.name]
+
+ # A method used during testing of _load_unlocked() and by
+ # _load_module_shim().
+ def load(self):
+ """Return a new module object, loaded by the spec's loader.
+
+ The module is not added to its parent.
+
+ If a module is already in sys.modules, that existing module gets
+ clobbered.
+
+ """
+ _imp.acquire_lock()
+ with _ModuleLockManager(self.spec.name):
+ return self._load_unlocked()
# Loaders #####################################################################
@@ -634,9 +1236,19 @@ class BuiltinImporter:
"""
+ @staticmethod
+ def module_repr(module):
+ # XXX deprecate
+ return '<module {!r} (built-in)>'.format(module.__name__)
+
@classmethod
- def module_repr(cls, module):
- return "<module '{}' (built-in)>".format(module.__name__)
+ def find_spec(cls, fullname, path=None, target=None):
+ if path is not None:
+ return None
+ if _imp.is_builtin(fullname):
+ return spec_from_loader(fullname, cls, origin='built-in')
+ else:
+ return None
@classmethod
def find_module(cls, fullname, path=None):
@@ -645,23 +1257,18 @@ class BuiltinImporter:
If 'path' is ever specified then the search is considered a failure.
"""
- if path is not None:
- return None
- return cls if _imp.is_builtin(fullname) else None
+ spec = cls.find_spec(fullname, path)
+ return spec.loader if spec is not None else None
@classmethod
- @set_package
- @set_loader
@_requires_builtin
def load_module(cls, fullname):
"""Load a built-in module."""
- is_reload = fullname in sys.modules
- try:
- return _call_with_frames_removed(_imp.init_builtin, fullname)
- except:
- if not is_reload and fullname in sys.modules:
- del sys.modules[fullname]
- raise
+ with _ManageReload(fullname):
+ module = _call_with_frames_removed(_imp.init_builtin, fullname)
+ module.__loader__ = cls
+ module.__package__ = ''
+ return module
@classmethod
@_requires_builtin
@@ -679,6 +1286,7 @@ class BuiltinImporter:
@_requires_builtin
def is_package(cls, fullname):
"""Return False as built-in modules are never packages."""
+ # XXX DeprecationWarning here...
return False
@@ -691,31 +1299,36 @@ class FrozenImporter:
"""
+ @staticmethod
+ def module_repr(m):
+ # XXX deprecate
+ return '<module {!r} (frozen)>'.format(m.__name__)
+
@classmethod
- def module_repr(cls, m):
- return "<module '{}' (frozen)>".format(m.__name__)
+ def find_spec(cls, fullname, path=None, target=None):
+ if _imp.is_frozen(fullname):
+ return spec_from_loader(fullname, cls, origin='frozen')
+ else:
+ return None
@classmethod
def find_module(cls, fullname, path=None):
"""Find a frozen module."""
return cls if _imp.is_frozen(fullname) else None
+ @staticmethod
+ def exec_module(module):
+ name = module.__spec__.name
+ if not _imp.is_frozen(name):
+ raise ImportError('{!r} is not a frozen module'.format(name),
+ name=name)
+ code = _call_with_frames_removed(_imp.get_frozen_object, name)
+ exec(code, module.__dict__)
+
@classmethod
- @set_package
- @set_loader
- @_requires_frozen
def load_module(cls, fullname):
"""Load a frozen module."""
- is_reload = fullname in sys.modules
- try:
- m = _call_with_frames_removed(_imp.init_frozen, fullname)
- # Let our own module_repr() method produce a suitable repr.
- del m.__file__
- return m
- except:
- if not is_reload and fullname in sys.modules:
- del sys.modules[fullname]
- raise
+ return _load_module_shim(cls, fullname)
@classmethod
@_requires_frozen
@@ -738,22 +1351,21 @@ class FrozenImporter:
class WindowsRegistryFinder:
- """Meta path finder for modules declared in the Windows registry.
- """
+ """Meta path finder for modules declared in the Windows registry."""
REGISTRY_KEY = (
- "Software\\Python\\PythonCore\\{sys_version}"
- "\\Modules\\{fullname}")
+ 'Software\\Python\\PythonCore\\{sys_version}'
+ '\\Modules\\{fullname}')
REGISTRY_KEY_DEBUG = (
- "Software\\Python\\PythonCore\\{sys_version}"
- "\\Modules\\{fullname}\\Debug")
+ 'Software\\Python\\PythonCore\\{sys_version}'
+ '\\Modules\\{fullname}\\Debug')
DEBUG_BUILD = False # Changed in _setup()
@classmethod
def _open_registry(cls, key):
try:
return _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, key)
- except WindowsError:
+ except OSError:
return _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, key)
@classmethod
@@ -766,24 +1378,35 @@ class WindowsRegistryFinder:
sys_version=sys.version[:3])
try:
with cls._open_registry(key) as hkey:
- filepath = _winreg.QueryValue(hkey, "")
- except WindowsError:
+ filepath = _winreg.QueryValue(hkey, '')
+ except OSError:
return None
return filepath
@classmethod
- def find_module(cls, fullname, path=None):
- """Find module named in the registry."""
+ def find_spec(cls, fullname, path=None, target=None):
+ # XXX untested! Need a Windows person to write tests (otherwise mock out appropriately)
filepath = cls._search_registry(fullname)
if filepath is None:
return None
try:
- _os.stat(filepath)
+ _path_stat(filepath)
except OSError:
return None
for loader, suffixes in _get_supported_file_loaders():
if filepath.endswith(tuple(suffixes)):
- return loader(fullname, filepath)
+ spec = spec_from_loader(fullname, loader(fullname, filepath),
+ origin=filepath)
+ return spec
+
+ @classmethod
+ def find_module(cls, fullname, path=None):
+ """Find module named in the registry."""
+ spec = self.find_spec(fullname, path)
+ if spec is not None:
+ return spec.loader
+ else:
+ return None
class _LoaderBasics:
@@ -791,6 +1414,7 @@ class _LoaderBasics:
"""Base class of common code needed by both SourceLoader and
SourcelessFileLoader."""
+ # XXX deprecate?
def is_package(self, fullname):
"""Concrete implementation of InspectLoader.is_package by checking if
the path returned by get_filename has a filename of '__init__.py'."""
@@ -799,74 +1423,15 @@ class _LoaderBasics:
tail_name = fullname.rpartition('.')[2]
return filename_base == '__init__' and tail_name != '__init__'
- def _bytes_from_bytecode(self, fullname, data, bytecode_path, source_stats):
- """Return the marshalled bytes from bytecode, verifying the magic
- number, timestamp and source size along the way.
+ def exec_module(self, module):
+ """Execute the module."""
+ code = self.get_code(module.__name__)
+ if code is None:
+ raise ImportError('cannot load module {!r} when get_code() '
+ 'returns None'.format(module.__name__))
+ _call_with_frames_removed(exec, code, module.__dict__)
- If source_stats is None then skip the timestamp check.
-
- """
- magic = data[:4]
- raw_timestamp = data[4:8]
- raw_size = data[8:12]
- if magic != _MAGIC_BYTES:
- msg = 'bad magic number in {!r}: {!r}'.format(fullname, magic)
- _verbose_message(msg)
- raise ImportError(msg, name=fullname, path=bytecode_path)
- elif len(raw_timestamp) != 4:
- message = 'bad timestamp in {}'.format(fullname)
- _verbose_message(message)
- raise EOFError(message)
- elif len(raw_size) != 4:
- message = 'bad size in {}'.format(fullname)
- _verbose_message(message)
- raise EOFError(message)
- if source_stats is not None:
- try:
- source_mtime = int(source_stats['mtime'])
- except KeyError:
- pass
- else:
- if _r_long(raw_timestamp) != source_mtime:
- message = 'bytecode is stale for {}'.format(fullname)
- _verbose_message(message)
- raise ImportError(message, name=fullname,
- path=bytecode_path)
- try:
- source_size = source_stats['size'] & 0xFFFFFFFF
- except KeyError:
- pass
- else:
- if _r_long(raw_size) != source_size:
- raise ImportError(
- "bytecode is stale for {}".format(fullname),
- name=fullname, path=bytecode_path)
- # Can't return the code object as errors from marshal loading need to
- # propagate even when source is available.
- return data[12:]
-
- @module_for_loader
- def _load_module(self, module, *, sourceless=False):
- """Helper for load_module able to handle either source or sourceless
- loading."""
- name = module.__name__
- code_object = self.get_code(name)
- module.__file__ = self.get_filename(name)
- if not sourceless:
- try:
- module.__cached__ = cache_from_source(module.__file__)
- except NotImplementedError:
- module.__cached__ = module.__file__
- else:
- module.__cached__ = module.__file__
- module.__package__ = name
- if self.is_package(name):
- module.__path__ = [_path_split(module.__file__)[0]]
- else:
- module.__package__ = module.__package__.rpartition('.')[0]
- module.__loader__ = self
- _call_with_frames_removed(exec, code_object, module.__dict__)
- return module
+ load_module = _load_module_shim
class SourceLoader(_LoaderBasics):
@@ -874,8 +1439,10 @@ class SourceLoader(_LoaderBasics):
def path_mtime(self, path):
"""Optional method that returns the modification time (an int) for the
specified path, where path is a str.
+
+ Raises IOError when the path cannot be handled.
"""
- raise NotImplementedError
+ raise IOError
def path_stats(self, path):
"""Optional method returning a metadata dict for the specified path
@@ -886,6 +1453,7 @@ class SourceLoader(_LoaderBasics):
- 'size' (optional) is the size in bytes of the source code.
Implementing this method allows the loader to read bytecode files.
+ Raises IOError when the path cannot be handled.
"""
return {'mtime': self.path_mtime(path)}
@@ -903,32 +1471,26 @@ class SourceLoader(_LoaderBasics):
"""Optional method which writes data (bytes) to a file path (a str).
Implementing this method allows for the writing of bytecode files.
-
"""
- raise NotImplementedError
def get_source(self, fullname):
"""Concrete implementation of InspectLoader.get_source."""
- import tokenize
path = self.get_filename(fullname)
try:
source_bytes = self.get_data(path)
- except IOError as exc:
- raise ImportError("source not available through get_data()",
- name=fullname) from exc
- readsource = _io.BytesIO(source_bytes).readline
- try:
- encoding = tokenize.detect_encoding(readsource)
- except SyntaxError as exc:
- raise ImportError("Failed to detect encoding",
- name=fullname) from exc
- newline_decoder = _io.IncrementalNewlineDecoder(None, True)
- try:
- return newline_decoder.decode(source_bytes.decode(encoding[0]))
- except UnicodeDecodeError as exc:
- raise ImportError("Failed to decode source file",
+ except OSError as exc:
+ raise ImportError('source not available through get_data()',
name=fullname) from exc
+ return decode_source(source_bytes)
+
+ def source_to_code(self, data, path, *, _optimize=-1):
+ """Return the code object compiled from source.
+
+ The 'data' argument can be any object type that compile() supports.
+ """
+ return _call_with_frames_removed(compile, data, path, 'exec',
+ dont_inherit=True, optimize=_optimize)
def get_code(self, fullname):
"""Concrete implementation of InspectLoader.get_code.
@@ -946,45 +1508,34 @@ class SourceLoader(_LoaderBasics):
else:
try:
st = self.path_stats(source_path)
- except NotImplementedError:
+ except IOError:
pass
else:
source_mtime = int(st['mtime'])
try:
data = self.get_data(bytecode_path)
- except IOError:
+ except OSError:
pass
else:
try:
- bytes_data = self._bytes_from_bytecode(fullname, data,
- bytecode_path,
- st)
+ bytes_data = _validate_bytecode_header(data,
+ source_stats=st, name=fullname,
+ path=bytecode_path)
except (ImportError, EOFError):
pass
else:
_verbose_message('{} matches {}', bytecode_path,
source_path)
- found = marshal.loads(bytes_data)
- if isinstance(found, _code_type):
- _imp._fix_co_filename(found, source_path)
- _verbose_message('code object from {}',
- bytecode_path)
- return found
- else:
- msg = "Non-code object in {}"
- raise ImportError(msg.format(bytecode_path),
- name=fullname, path=bytecode_path)
+ return _compile_bytecode(bytes_data, name=fullname,
+ bytecode_path=bytecode_path,
+ source_path=source_path)
source_bytes = self.get_data(source_path)
- code_object = _call_with_frames_removed(compile,
- source_bytes, source_path, 'exec',
- dont_inherit=True)
+ code_object = self.source_to_code(source_bytes, source_path)
_verbose_message('code object from {}', source_path)
if (not sys.dont_write_bytecode and bytecode_path is not None and
- source_mtime is not None):
- data = bytearray(_MAGIC_BYTES)
- data.extend(_w_long(source_mtime))
- data.extend(_w_long(len(source_bytes)))
- data.extend(marshal.dumps(code_object))
+ source_mtime is not None):
+ data = _code_to_bytecode(code_object, source_mtime,
+ len(source_bytes))
try:
self._cache_bytecode(source_path, bytecode_path, data)
_verbose_message('wrote {!r}', bytecode_path)
@@ -992,16 +1543,6 @@ class SourceLoader(_LoaderBasics):
pass
return code_object
- def load_module(self, fullname):
- """Concrete implementation of Loader.load_module.
-
- Requires ExecutionLoader.get_filename and ResourceLoader.get_data to be
- implemented to load source code. Use of bytecode is dictated by whether
- get_code uses/writes bytecode.
-
- """
- return self._load_module(fullname)
-
class FileLoader:
@@ -1017,7 +1558,9 @@ class FileLoader:
@_check_name
def load_module(self, fullname):
"""Load a module from a file."""
- # Issue #14857: Avoid the zero-argument form so the implementation
+ # The only reason for this method is for the name check.
+
+ # Issue #14857: Avoid the zero-argument form of super so the implementation
# of that form can be updated without breaking the frozen module
return super(FileLoader, self).load_module(fullname)
@@ -1038,18 +1581,12 @@ class SourceFileLoader(FileLoader, SourceLoader):
def path_stats(self, path):
"""Return the metadata for the path."""
- st = _os.stat(path)
+ st = _path_stat(path)
return {'mtime': st.st_mtime, 'size': st.st_size}
def _cache_bytecode(self, source_path, bytecode_path, data):
# Adapt between the two APIs
- try:
- mode = _os.stat(source_path).st_mode
- except OSError:
- mode = 0o666
- # We always ensure write access so we can update cached files
- # later even when the source files are read-only on Windows (#6074)
- mode |= 0o200
+ mode = _calc_mode(source_path)
return self.set_data(bytecode_path, data, _mode=mode)
def set_data(self, path, data, *, _mode=0o666):
@@ -1085,20 +1622,11 @@ class SourcelessFileLoader(FileLoader, _LoaderBasics):
"""Loader which handles sourceless file imports."""
- def load_module(self, fullname):
- return self._load_module(fullname, sourceless=True)
-
def get_code(self, fullname):
path = self.get_filename(fullname)
data = self.get_data(path)
- bytes_data = self._bytes_from_bytecode(fullname, data, path, None)
- found = marshal.loads(bytes_data)
- if isinstance(found, _code_type):
- _verbose_message('code object from {!r}', path)
- return found
- else:
- raise ImportError("Non-code object in {}".format(path),
- name=fullname, path=path)
+ bytes_data = _validate_bytecode_header(data, name=fullname, path=path)
+ return _compile_bytecode(bytes_data, name=fullname, bytecode_path=path)
def get_source(self, fullname):
"""Return None as there is no source code."""
@@ -1122,22 +1650,20 @@ class ExtensionFileLoader:
self.path = path
@_check_name
- @set_package
- @set_loader
def load_module(self, fullname):
"""Load an extension module."""
- is_reload = fullname in sys.modules
- try:
+ with _ManageReload(fullname):
module = _call_with_frames_removed(_imp.load_dynamic,
fullname, self.path)
- _verbose_message('extension module loaded from {!r}', self.path)
- if self.is_package(fullname) and not hasattr(module, '__path__'):
- module.__path__ = [_path_split(self.path)[0]]
- return module
- except:
- if not is_reload and fullname in sys.modules:
- del sys.modules[fullname]
- raise
+ _verbose_message('extension module loaded from {!r}', self.path)
+ is_package = self.is_package(fullname)
+ if is_package and not hasattr(module, '__path__'):
+ module.__path__ = [_path_split(self.path)[0]]
+ module.__loader__ = self
+ module.__package__ = module.__name__
+ if not is_package:
+ module.__package__ = module.__package__.rpartition('.')[0]
+ return module
def is_package(self, fullname):
"""Return True if the extension module is a package."""
@@ -1153,6 +1679,11 @@ class ExtensionFileLoader:
"""Return None as extension modules have no source code."""
return None
+ @_check_name
+ def get_filename(self, fullname):
+ """Return the path to the source file as found by the finder."""
+ return self.path
+
class _NamespacePath:
"""Represents a namespace package's path. It uses the module name
@@ -1185,11 +1716,12 @@ class _NamespacePath:
# If the parent's path has changed, recalculate _path
parent_path = tuple(self._get_parent_path()) # Make a copy
if parent_path != self._last_parent_path:
- loader, new_path = self._path_finder(self._name, parent_path)
+ spec = self._path_finder(self._name, parent_path)
# Note that no changes are made if a loader is returned, but we
# do remember the new parent path
- if loader is None:
- self._path = new_path
+ if spec is not None and spec.loader is None:
+ if spec.submodule_search_locations:
+ self._path = spec.submodule_search_locations
self._last_parent_path = parent_path # Save the copy
return self._path
@@ -1200,7 +1732,7 @@ class _NamespacePath:
return len(self._recalculate())
def __repr__(self):
- return "_NamespacePath({!r})".format(self._path)
+ return '_NamespacePath({!r})'.format(self._path)
def __contains__(self, item):
return item in self._recalculate()
@@ -1209,20 +1741,30 @@ class _NamespacePath:
self._path.append(item)
-class NamespaceLoader:
+# We use this exclusively in init_module_attrs() for backward-compatibility.
+class _NamespaceLoader:
def __init__(self, name, path, path_finder):
self._path = _NamespacePath(name, path, path_finder)
+ # XXX Deprecate
@classmethod
def module_repr(cls, module):
- return "<module '{}' (namespace)>".format(module.__name__)
+ return '<module {!r} (namespace)>'.format(module.__name__)
+
+ def is_package(self, fullname):
+ return True
- @module_for_loader
- def load_module(self, module):
+ def get_source(self, fullname):
+ return ''
+
+ def get_code(self, fullname):
+ return compile('', '<string>', 'exec', dont_inherit=True)
+
+ # XXX Deprecate
+ def load_module(self, fullname):
"""Load a namespace module."""
_verbose_message('namespace module loaded with path {!r}', self._path)
- module.__path__ = self._path
- return module
+ return _load_module_shim(self, fullname)
# Finders #####################################################################
@@ -1265,7 +1807,7 @@ class PathFinder:
"""
if path == '':
- path = '.'
+ path = _os.getcwd()
try:
finder = sys.path_importer_cache[path]
except KeyError:
@@ -1274,7 +1816,20 @@ class PathFinder:
return finder
@classmethod
- def _get_loader(cls, fullname, path):
+ def _legacy_get_spec(cls, fullname, finder):
+ if hasattr(finder, 'find_loader'):
+ loader, portions = finder.find_loader(fullname)
+ else:
+ loader = finder.find_module(fullname)
+ portions = None
+ if loader is not None:
+ return spec_from_loader(fullname, loader)
+ spec = ModuleSpec(fullname, None)
+ spec.submodule_search_locations = portions
+ return spec
+
+ @classmethod
+ def _get_spec(cls, fullname, path, target=None):
"""Find the loader or namespace_path for this module/package name."""
# If this ends up being a namespace package, namespace_path is
# the list of paths that will become its __path__
@@ -1284,38 +1839,58 @@ class PathFinder:
continue
finder = cls._path_importer_cache(entry)
if finder is not None:
- if hasattr(finder, 'find_loader'):
- loader, portions = finder.find_loader(fullname)
+ if hasattr(finder, 'find_spec'):
+ spec = finder.find_spec(fullname, target)
else:
- loader = finder.find_module(fullname)
- portions = []
- if loader is not None:
- # We found a loader: return it immediately.
- return loader, namespace_path
+ spec = cls._legacy_get_spec(fullname, finder)
+ if spec is None:
+ continue
+ if spec.loader is not None:
+ return spec
+ portions = spec.submodule_search_locations
+ if portions is None:
+ raise ImportError('spec missing loader')
# This is possibly part of a namespace package.
# Remember these path entries (if any) for when we
# create a namespace package, and continue iterating
# on path.
namespace_path.extend(portions)
else:
- return None, namespace_path
+ spec = ModuleSpec(fullname, None)
+ spec.submodule_search_locations = namespace_path
+ return spec
@classmethod
- def find_module(cls, fullname, path=None):
- """Find the module on sys.path or 'path' based on sys.path_hooks and
+ def find_spec(cls, fullname, path=None, target=None):
+ """find the module on sys.path or 'path' based on sys.path_hooks and
sys.path_importer_cache."""
if path is None:
path = sys.path
- loader, namespace_path = cls._get_loader(fullname, path)
- if loader is not None:
- return loader
- else:
+ spec = cls._get_spec(fullname, path, target)
+ if spec is None:
+ return None
+ elif spec.loader is None:
+ namespace_path = spec.submodule_search_locations
if namespace_path:
# We found at least one namespace path. Return a
- # loader which can create the namespace package.
- return NamespaceLoader(fullname, namespace_path, cls._get_loader)
+ # spec which can create the namespace package.
+ spec.origin = 'namespace'
+ spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec)
+ return spec
else:
return None
+ else:
+ return spec
+
+ @classmethod
+ def find_module(cls, fullname, path=None):
+ """find the module on sys.path or 'path' based on sys.path_hooks and
+ sys.path_importer_cache."""
+ # XXX Deprecation warning here.
+ spec = cls.find_spec(fullname, path)
+ if spec is None:
+ return None
+ return spec.loader
class FileFinder:
@@ -1350,10 +1925,28 @@ class FileFinder:
def find_loader(self, fullname):
"""Try to find a loader for the specified module, or the namespace
package portions. Returns (loader, list-of-portions)."""
+ spec = self.find_spec(fullname)
+ if spec is None:
+ return None, []
+ return spec.loader, spec.submodule_search_locations or []
+
+ def _get_spec(self, loader_class, fullname, path, submodule_search_locations, target):
+ loader = loader_class(fullname, path)
+ try:
+ get_spec = loader._get_spec
+ except AttributeError:
+ return spec_from_file_location(fullname, path, loader=loader,
+ submodule_search_locations=submodule_search_locations)
+ else:
+ return get_spec(fullname, path, submodule_search_locations, target)
+
+ def find_spec(self, fullname, target=None):
+ """Try to find a loader for the specified module, or the namespace
+ package portions. Returns (loader, list-of-portions)."""
is_namespace = False
tail_module = fullname.rpartition('.')[2]
try:
- mtime = _os.stat(self.path).st_mtime
+ mtime = _path_stat(self.path or _os.getcwd()).st_mtime
except OSError:
mtime = -1
if mtime != self._path_mtime:
@@ -1369,33 +1962,34 @@ class FileFinder:
# Check if the module is the name of a directory (and thus a package).
if cache_module in cache:
base_path = _path_join(self.path, tail_module)
- if _path_isdir(base_path):
- for suffix, loader in self._loaders:
- init_filename = '__init__' + suffix
- full_path = _path_join(base_path, init_filename)
- if _path_isfile(full_path):
- return (loader(fullname, full_path), [base_path])
- else:
- # A namespace package, return the path if we don't also
- # find a module in the next section.
- is_namespace = True
+ for suffix, loader_class in self._loaders:
+ init_filename = '__init__' + suffix
+ full_path = _path_join(base_path, init_filename)
+ if _path_isfile(full_path):
+ return self._get_spec(loader_class, fullname, full_path, [base_path], target)
+ else:
+ # If a namespace package, return the path if we don't
+ # find a module in the next section.
+ is_namespace = _path_isdir(base_path)
# Check for a file w/ a proper suffix exists.
- for suffix, loader in self._loaders:
+ for suffix, loader_class in self._loaders:
full_path = _path_join(self.path, tail_module + suffix)
_verbose_message('trying {}'.format(full_path), verbosity=2)
if cache_module + suffix in cache:
if _path_isfile(full_path):
- return (loader(fullname, full_path), [])
+ return self._get_spec(loader_class, fullname, full_path, None, target)
if is_namespace:
_verbose_message('possible namespace for {}'.format(base_path))
- return (None, [base_path])
- return (None, [])
+ spec = ModuleSpec(fullname, None)
+ spec.submodule_search_locations = [base_path]
+ return spec
+ return None
def _fill_cache(self):
"""Fill the cache of potential modules and packages for this directory."""
path = self.path
try:
- contents = _os.listdir(path)
+ contents = _os.listdir(path or _os.getcwd())
except (FileNotFoundError, PermissionError, NotADirectoryError):
# Directory has either been removed, turned into a file, or made
# unreadable.
@@ -1420,7 +2014,7 @@ class FileFinder:
lower_suffix_contents.add(new_name)
self._path_cache = lower_suffix_contents
if sys.platform.startswith(_CASE_INSENSITIVE_PLATFORMS):
- self._relaxed_path_cache = set(fn.lower() for fn in contents)
+ self._relaxed_path_cache = {fn.lower() for fn in contents}
@classmethod
def path_hook(cls, *loader_details):
@@ -1435,13 +2029,13 @@ class FileFinder:
def path_hook_for_FileFinder(path):
"""Path hook for importlib.machinery.FileFinder."""
if not _path_isdir(path):
- raise ImportError("only directories are supported", path=path)
+ raise ImportError('only directories are supported', path=path)
return cls(path, *loader_details)
return path_hook_for_FileFinder
def __repr__(self):
- return "FileFinder(%r)" % (self.path,)
+ return 'FileFinder({!r})'.format(self.path)
# Import itself ###############################################################
@@ -1468,19 +2062,43 @@ def _resolve_name(name, package, level):
return '{}.{}'.format(base, name) if name else base
-def _find_module(name, path):
+def _find_spec(name, path, target=None):
"""Find a module's loader."""
if not sys.meta_path:
_warnings.warn('sys.meta_path is empty', ImportWarning)
+ # We check sys.modules here for the reload case. While a passed-in
+ # target will usually indicate a reload there is no guarantee, whereas
+ # sys.modules provides one.
+ is_reload = name in sys.modules
for finder in sys.meta_path:
with _ImportLockContext():
- loader = finder.find_module(name, path)
- if loader is not None:
+ try:
+ find_spec = finder.find_spec
+ except AttributeError:
+ loader = finder.find_module(name, path)
+ if loader is None:
+ continue
+ spec = spec_from_loader(name, loader)
+ else:
+ spec = find_spec(name, path, target)
+ if spec is not None:
# The parent import may have already imported this module.
- if name not in sys.modules:
- return loader
+ if not is_reload and name in sys.modules:
+ module = sys.modules[name]
+ try:
+ __spec__ = module.__spec__
+ except AttributeError:
+ # We use the found spec since that is the one that
+ # we would have used if the parent module hadn't
+ # beaten us to the punch.
+ return spec
+ else:
+ if __spec__ is None:
+ return spec
+ else:
+ return __spec__
else:
- return sys.modules[name].__loader__
+ return spec
else:
return None
@@ -1488,21 +2106,22 @@ def _find_module(name, path):
def _sanity_check(name, package, level):
"""Verify arguments are "sane"."""
if not isinstance(name, str):
- raise TypeError("module name must be str, not {}".format(type(name)))
+ raise TypeError('module name must be str, not {}'.format(type(name)))
if level < 0:
raise ValueError('level must be >= 0')
if package:
if not isinstance(package, str):
- raise TypeError("__package__ not set to a string")
+ raise TypeError('__package__ not set to a string')
elif package not in sys.modules:
- msg = ("Parent module {!r} not loaded, cannot perform relative "
- "import")
+ msg = ('Parent module {!r} not loaded, cannot perform relative '
+ 'import')
raise SystemError(msg.format(package))
if not name and level == 0:
- raise ValueError("Empty module name")
+ raise ValueError('Empty module name')
-_ERR_MSG = 'No module named {!r}'
+_ERR_MSG_PREFIX = 'No module named '
+_ERR_MSG = _ERR_MSG_PREFIX + '{!r}'
def _find_and_load_unlocked(name, import_):
path = None
@@ -1513,58 +2132,28 @@ def _find_and_load_unlocked(name, import_):
# Crazy side-effects!
if name in sys.modules:
return sys.modules[name]
- # Backwards-compatibility; be nicer to skip the dict lookup.
parent_module = sys.modules[parent]
try:
path = parent_module.__path__
except AttributeError:
- msg = (_ERR_MSG + '; {} is not a package').format(name, parent)
+ msg = (_ERR_MSG + '; {!r} is not a package').format(name, parent)
raise ImportError(msg, name=name)
- loader = _find_module(name, path)
- if loader is None:
- exc = ImportError(_ERR_MSG.format(name), name=name)
- # TODO(brett): switch to a proper ModuleNotFound exception in Python
- # 3.4.
- exc._not_found = True
- raise exc
- elif name not in sys.modules:
- # The parent import may have already imported this module.
- loader.load_module(name)
- _verbose_message('import {!r} # {!r}', name, loader)
- # Backwards-compatibility; be nicer to skip the dict lookup.
- module = sys.modules[name]
+ spec = _find_spec(name, path)
+ if spec is None:
+ raise ImportError(_ERR_MSG.format(name), name=name)
+ else:
+ module = _SpecMethods(spec)._load_unlocked()
if parent:
# Set the module as an attribute on its parent.
parent_module = sys.modules[parent]
setattr(parent_module, name.rpartition('.')[2], module)
- # Set __package__ if the loader did not.
- if getattr(module, '__package__', None) is None:
- try:
- module.__package__ = module.__name__
- if not hasattr(module, '__path__'):
- module.__package__ = module.__package__.rpartition('.')[0]
- except AttributeError:
- pass
- # Set loader if need be.
- if not hasattr(module, '__loader__'):
- try:
- module.__loader__ = loader
- except AttributeError:
- pass
return module
def _find_and_load(name, import_):
"""Find and load the module, and release the import lock."""
- try:
- lock = _get_module_lock(name)
- finally:
- _imp.release_lock()
- lock.acquire()
- try:
+ with _ModuleLockManager(name):
return _find_and_load_unlocked(name, import_)
- finally:
- lock.release()
def _gcd_import(name, package=None, level=0):
@@ -1585,8 +2174,8 @@ def _gcd_import(name, package=None, level=0):
module = sys.modules[name]
if module is None:
_imp.release_lock()
- message = ("import of {} halted; "
- "None in sys.modules".format(name))
+ message = ('import of {} halted; '
+ 'None in sys.modules'.format(name))
raise ImportError(message, name=name)
_lock_unlock_module(name)
return module
@@ -1616,9 +2205,7 @@ def _handle_fromlist(module, fromlist, import_):
# Backwards-compatibility dictates we ignore failed
# imports triggered by fromlist for modules that don't
# exist.
- # TODO(brett): In Python 3.4, have import raise
- # ModuleNotFound and catch that.
- if getattr(exc, '_not_found', False):
+ if str(exc).startswith(_ERR_MSG_PREFIX):
if exc.name == from_name:
continue
raise
@@ -1686,6 +2273,13 @@ def __import__(name, globals=None, locals=None, fromlist=(), level=0):
return _handle_fromlist(module, fromlist, _gcd_import)
+def _builtin_from_name(name):
+ spec = BuiltinImporter.find_spec(name)
+ if spec is None:
+ raise ImportError('no built-in module named ' + name)
+ methods = _SpecMethods(spec)
+ return methods._load_unlocked()
+
def _setup(sys_module, _imp_module):
"""Setup importlib by importing needed built-in modules and injecting them
@@ -1704,24 +2298,31 @@ def _setup(sys_module, _imp_module):
else:
BYTECODE_SUFFIXES = DEBUG_BYTECODE_SUFFIXES
+ # Set up the spec for existing builtin/frozen modules.
module_type = type(sys)
for name, module in sys.modules.items():
if isinstance(module, module_type):
- if not hasattr(module, '__loader__'):
- if name in sys.builtin_module_names:
- module.__loader__ = BuiltinImporter
- elif _imp.is_frozen(name):
- module.__loader__ = FrozenImporter
+ if name in sys.builtin_module_names:
+ loader = BuiltinImporter
+ elif _imp.is_frozen(name):
+ loader = FrozenImporter
+ else:
+ continue
+ spec = _spec_from_module(module, loader)
+ methods = _SpecMethods(spec)
+ methods.init_module_attrs(module)
+ # Directly load built-in modules needed during bootstrap.
self_module = sys.modules[__name__]
for builtin_name in ('_io', '_warnings', 'builtins', 'marshal'):
if builtin_name not in sys.modules:
- builtin_module = BuiltinImporter.load_module(builtin_name)
+ builtin_module = _builtin_from_name(builtin_name)
else:
builtin_module = sys.modules[builtin_name]
setattr(self_module, builtin_name, builtin_module)
- os_details = ('posix', ['/']), ('nt', ['\\', '/']), ('os2', ['\\', '/'])
+ # Directly load the os module (needed during bootstrap).
+ os_details = ('posix', ['/']), ('nt', ['\\', '/'])
for builtin_os, path_separators in os_details:
# Assumption made in _path_join()
assert all(len(sep) == 1 for sep in path_separators)
@@ -1731,32 +2332,33 @@ def _setup(sys_module, _imp_module):
break
else:
try:
- os_module = BuiltinImporter.load_module(builtin_os)
- # TODO: rip out os2 code after 3.3 is released as per PEP 11
- if builtin_os == 'os2' and 'EMX GCC' in sys.version:
- path_sep = path_separators[1]
+ os_module = _builtin_from_name(builtin_os)
break
except ImportError:
continue
else:
raise ImportError('importlib requires posix or nt')
+ setattr(self_module, '_os', os_module)
+ setattr(self_module, 'path_sep', path_sep)
+ setattr(self_module, 'path_separators', ''.join(path_separators))
+ # Directly load the _thread module (needed during bootstrap).
try:
- thread_module = BuiltinImporter.load_module('_thread')
+ thread_module = _builtin_from_name('_thread')
except ImportError:
# Python was built without threads
thread_module = None
- weakref_module = BuiltinImporter.load_module('_weakref')
+ setattr(self_module, '_thread', thread_module)
+ # Directly load the _weakref module (needed during bootstrap).
+ weakref_module = _builtin_from_name('_weakref')
+ setattr(self_module, '_weakref', weakref_module)
+
+ # Directly load the winreg module (needed during bootstrap).
if builtin_os == 'nt':
- winreg_module = BuiltinImporter.load_module('winreg')
+ winreg_module = _builtin_from_name('winreg')
setattr(self_module, '_winreg', winreg_module)
- setattr(self_module, '_os', os_module)
- setattr(self_module, '_thread', thread_module)
- setattr(self_module, '_weakref', weakref_module)
- setattr(self_module, 'path_sep', path_sep)
- setattr(self_module, 'path_separators', set(path_separators))
# Constants
setattr(self_module, '_relax_case', _make_relax_case())
EXTENSION_SUFFIXES.extend(_imp.extension_suffixes())
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
index 387567a450..3995ff2a68 100644
--- a/Lib/importlib/abc.py
+++ b/Lib/importlib/abc.py
@@ -8,11 +8,6 @@ except ImportError as exc:
raise
_frozen_importlib = None
import abc
-import imp
-import marshal
-import sys
-import tokenize
-import warnings
def _register(abstract_cls, *classes):
@@ -37,28 +32,31 @@ class Finder(metaclass=abc.ABCMeta):
def find_module(self, fullname, path=None):
"""An abstract method that should find a module.
The fullname is a str and the optional path is a str or None.
- Returns a Loader object.
+ Returns a Loader object or None.
"""
- raise NotImplementedError
class MetaPathFinder(Finder):
"""Abstract base class for import finders on sys.meta_path."""
- @abc.abstractmethod
+ # We don't define find_spec() here since that would break
+ # hasattr checks we do to support backward compatibility.
+
+ # XXX Deprecate
def find_module(self, fullname, path):
- """Abstract method which, when implemented, should find a module.
- The fullname is a str and the path is a str or None.
- Returns a Loader object.
+ """Return a loader for the module.
+
+ If no module is found, return None. The fullname is a str and
+ the path is a list of strings or None.
+
"""
- raise NotImplementedError
+ return None
def invalidate_caches(self):
"""An optional method for clearing the finder's cache, if any.
This method is used by importlib.invalidate_caches().
"""
- return NotImplemented
_register(MetaPathFinder, machinery.BuiltinImporter, machinery.FrozenImporter,
machinery.PathFinder, machinery.WindowsRegistryFinder)
@@ -68,23 +66,31 @@ class PathEntryFinder(Finder):
"""Abstract base class for path entry finders used by PathFinder."""
- @abc.abstractmethod
+ # We don't define find_spec() here since that would break
+ # hasattr checks we do to support backward compatibility.
+
+ # XXX Deprecate.
def find_loader(self, fullname):
- """Abstract method which, when implemented, returns a module loader.
- The fullname is a str. Returns a 2-tuple of (Loader, portion) where
- portion is a sequence of file system locations contributing to part of
- a namespace package. The sequence may be empty and the loader may be
- None.
+ """Return (loader, namespace portion) for the path entry.
+
+ The fullname is a str. The namespace portion is a sequence of
+ path entries contributing to part of a namespace package. The
+ sequence may be empty. If loader is not None, the portion will
+ be ignored.
+
+ The portion will be discarded if another path entry finder
+ locates the module as a normal module or package.
+
"""
- raise NotImplementedError
+ return None, []
+ # XXX Deprecate.
find_module = _bootstrap._find_module_shim
def invalidate_caches(self):
"""An optional method for clearing the finder's cache, if any.
This method is used by PathFinder.invalidate_caches().
"""
- return NotImplemented
_register(PathEntryFinder, machinery.FileFinder)
@@ -93,16 +99,43 @@ class Loader(metaclass=abc.ABCMeta):
"""Abstract base class for import loaders."""
- @abc.abstractmethod
+ def create_module(self, spec):
+ """Return a module to initialize and into which to load.
+
+ This method should raise ImportError if anything prevents it
+ from creating a new module. It may return None to indicate
+ that the spec should create the new module.
+
+ create_module() is optional.
+
+ """
+ # By default, defer to _SpecMethods.create() for the new module.
+ return None
+
+ # We don't define exec_module() here since that would break
+ # hasattr checks we do to support backward compatibility.
+
+ # XXX Deprecate.
def load_module(self, fullname):
- """Abstract method which when implemented should load a module.
- The fullname is a str."""
- raise NotImplementedError
+ """Return the loaded module.
- @abc.abstractmethod
+ The module must be added to sys.modules and have import-related
+ attributes set properly. The fullname is a str.
+
+ ImportError is raised on failure.
+
+ """
+ raise ImportError
+
+ # XXX Deprecate.
def module_repr(self, module):
- """Abstract method which when implemented calculates and returns the
- given module's repr."""
+ """Return a module's repr.
+
+ Used by the module type when the method does not raise
+ NotImplementedError.
+
+ """
+ # The exception will cause ModuleType.__repr__ to ignore this method.
raise NotImplementedError
@@ -119,7 +152,7 @@ class ResourceLoader(Loader):
def get_data(self, path):
"""Abstract method which when implemented should return the bytes for
the specified path. The path must be a str."""
- raise NotImplementedError
+ raise IOError
class InspectLoader(Loader):
@@ -131,26 +164,47 @@ class InspectLoader(Loader):
"""
- @abc.abstractmethod
def is_package(self, fullname):
- """Abstract method which when implemented should return whether the
- module is a package. The fullname is a str. Returns a bool."""
- raise NotImplementedError
+ """Optional method which when implemented should return whether the
+ module is a package. The fullname is a str. Returns a bool.
+
+ Raises ImportError if the module cannot be found.
+ """
+ raise ImportError
- @abc.abstractmethod
def get_code(self, fullname):
- """Abstract method which when implemented should return the code object
- for the module. The fullname is a str. Returns a types.CodeType."""
- raise NotImplementedError
+ """Method which returns the code object for the module.
+
+ The fullname is a str. Returns a types.CodeType if possible, else
+ returns None if a code object does not make sense
+ (e.g. built-in module). Raises ImportError if the module cannot be
+ found.
+ """
+ source = self.get_source(fullname)
+ if source is None:
+ return None
+ return self.source_to_code(source)
@abc.abstractmethod
def get_source(self, fullname):
"""Abstract method which should return the source code for the
- module. The fullname is a str. Returns a str."""
- raise NotImplementedError
+ module. The fullname is a str. Returns a str.
-_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter,
- machinery.ExtensionFileLoader)
+ Raises ImportError if the module cannot be found.
+ """
+ raise ImportError
+
+ def source_to_code(self, data, path='<string>'):
+ """Compile 'data' into a code object.
+
+ The 'data' argument can be anything that compile() can handle. The'path'
+ argument should be where the data was retrieved (when applicable)."""
+ return compile(data, path, 'exec', dont_inherit=True)
+
+ exec_module = _bootstrap._LoaderBasics.exec_module
+ load_module = _bootstrap._LoaderBasics.load_module
+
+_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter)
class ExecutionLoader(InspectLoader):
@@ -165,8 +219,29 @@ class ExecutionLoader(InspectLoader):
@abc.abstractmethod
def get_filename(self, fullname):
"""Abstract method which should return the value that __file__ is to be
- set to."""
- raise NotImplementedError
+ set to.
+
+ Raises ImportError if the module cannot be found.
+ """
+ raise ImportError
+
+ def get_code(self, fullname):
+ """Method to return the code object for fullname.
+
+ Should return None if not applicable (e.g. built-in module).
+ Raise ImportError if the module cannot be found.
+ """
+ source = self.get_source(fullname)
+ if source is None:
+ return None
+ try:
+ path = self.get_filename(fullname)
+ except ImportError:
+ return self.source_to_code(source)
+ else:
+ return self.source_to_code(source, path)
+
+_register(ExecutionLoader, machinery.ExtensionFileLoader)
class FileLoader(_bootstrap.FileLoader, ResourceLoader, ExecutionLoader):
@@ -198,7 +273,7 @@ class SourceLoader(_bootstrap.SourceLoader, ResourceLoader, ExecutionLoader):
def path_mtime(self, path):
"""Return the (int) modification time for the path (str)."""
if self.path_stats.__func__ is SourceLoader.path_stats:
- raise NotImplementedError
+ raise IOError
return int(self.path_stats(path)['mtime'])
def path_stats(self, path):
@@ -209,7 +284,7 @@ class SourceLoader(_bootstrap.SourceLoader, ResourceLoader, ExecutionLoader):
- 'size' (optional) is the size in bytes of the source code.
"""
if self.path_mtime.__func__ is SourceLoader.path_mtime:
- raise NotImplementedError
+ raise IOError
return {'mtime': self.path_mtime(path)}
def set_data(self, path, data):
@@ -220,185 +295,6 @@ class SourceLoader(_bootstrap.SourceLoader, ResourceLoader, ExecutionLoader):
Any needed intermediary directories are to be created. If for some
reason the file cannot be written because of permissions, fail
silently.
-
"""
- raise NotImplementedError
_register(SourceLoader, machinery.SourceFileLoader)
-
-class PyLoader(SourceLoader):
-
- """Implement the deprecated PyLoader ABC in terms of SourceLoader.
-
- This class has been deprecated! It is slated for removal in Python 3.4.
- If compatibility with Python 3.1 is not needed then implement the
- SourceLoader ABC instead of this class. If Python 3.1 compatibility is
- needed, then use the following idiom to have a single class that is
- compatible with Python 3.1 onwards::
-
- try:
- from importlib.abc import SourceLoader
- except ImportError:
- from importlib.abc import PyLoader as SourceLoader
-
-
- class CustomLoader(SourceLoader):
- def get_filename(self, fullname):
- # Implement ...
-
- def source_path(self, fullname):
- '''Implement source_path in terms of get_filename.'''
- try:
- return self.get_filename(fullname)
- except ImportError:
- return None
-
- def is_package(self, fullname):
- filename = os.path.basename(self.get_filename(fullname))
- return os.path.splitext(filename)[0] == '__init__'
-
- """
-
- @abc.abstractmethod
- def is_package(self, fullname):
- raise NotImplementedError
-
- @abc.abstractmethod
- def source_path(self, fullname):
- """Abstract method. Accepts a str module name and returns the path to
- the source code for the module."""
- raise NotImplementedError
-
- def get_filename(self, fullname):
- """Implement get_filename in terms of source_path.
-
- As get_filename should only return a source file path there is no
- chance of the path not existing but loading still being possible, so
- ImportError should propagate instead of being turned into returning
- None.
-
- """
- warnings.warn("importlib.abc.PyLoader is deprecated and is "
- "slated for removal in Python 3.4; "
- "use SourceLoader instead. "
- "See the importlib documentation on how to be "
- "compatible with Python 3.1 onwards.",
- DeprecationWarning)
- path = self.source_path(fullname)
- if path is None:
- raise ImportError(name=fullname)
- else:
- return path
-
-
-class PyPycLoader(PyLoader):
-
- """Abstract base class to assist in loading source and bytecode by
- requiring only back-end storage methods to be implemented.
-
- This class has been deprecated! Removal is slated for Python 3.4. Implement
- the SourceLoader ABC instead. If Python 3.1 compatibility is needed, see
- PyLoader.
-
- The methods get_code, get_source, and load_module are implemented for the
- user.
-
- """
-
- def get_filename(self, fullname):
- """Return the source or bytecode file path."""
- path = self.source_path(fullname)
- if path is not None:
- return path
- path = self.bytecode_path(fullname)
- if path is not None:
- return path
- raise ImportError("no source or bytecode path available for "
- "{0!r}".format(fullname), name=fullname)
-
- def get_code(self, fullname):
- """Get a code object from source or bytecode."""
- warnings.warn("importlib.abc.PyPycLoader is deprecated and slated for "
- "removal in Python 3.4; use SourceLoader instead. "
- "If Python 3.1 compatibility is required, see the "
- "latest documentation for PyLoader.",
- DeprecationWarning)
- source_timestamp = self.source_mtime(fullname)
- # Try to use bytecode if it is available.
- bytecode_path = self.bytecode_path(fullname)
- if bytecode_path:
- data = self.get_data(bytecode_path)
- try:
- magic = data[:4]
- if len(magic) < 4:
- raise ImportError(
- "bad magic number in {}".format(fullname),
- name=fullname, path=bytecode_path)
- raw_timestamp = data[4:8]
- if len(raw_timestamp) < 4:
- raise EOFError("bad timestamp in {}".format(fullname))
- pyc_timestamp = _bootstrap._r_long(raw_timestamp)
- raw_source_size = data[8:12]
- if len(raw_source_size) != 4:
- raise EOFError("bad file size in {}".format(fullname))
- # Source size is unused as the ABC does not provide a way to
- # get the size of the source ahead of reading it.
- bytecode = data[12:]
- # Verify that the magic number is valid.
- if imp.get_magic() != magic:
- raise ImportError(
- "bad magic number in {}".format(fullname),
- name=fullname, path=bytecode_path)
- # Verify that the bytecode is not stale (only matters when
- # there is source to fall back on.
- if source_timestamp:
- if pyc_timestamp < source_timestamp:
- raise ImportError("bytecode is stale", name=fullname,
- path=bytecode_path)
- except (ImportError, EOFError):
- # If source is available give it a shot.
- if source_timestamp is not None:
- pass
- else:
- raise
- else:
- # Bytecode seems fine, so try to use it.
- return marshal.loads(bytecode)
- elif source_timestamp is None:
- raise ImportError("no source or bytecode available to create code "
- "object for {0!r}".format(fullname),
- name=fullname)
- # Use the source.
- source_path = self.source_path(fullname)
- if source_path is None:
- message = "a source path must exist to load {0}".format(fullname)
- raise ImportError(message, name=fullname)
- source = self.get_data(source_path)
- code_object = compile(source, source_path, 'exec', dont_inherit=True)
- # Generate bytecode and write it out.
- if not sys.dont_write_bytecode:
- data = bytearray(imp.get_magic())
- data.extend(_bootstrap._w_long(source_timestamp))
- data.extend(_bootstrap._w_long(len(source) & 0xFFFFFFFF))
- data.extend(marshal.dumps(code_object))
- self.write_bytecode(fullname, data)
- return code_object
-
- @abc.abstractmethod
- def source_mtime(self, fullname):
- """Abstract method. Accepts a str filename and returns an int
- modification time for the source of the module."""
- raise NotImplementedError
-
- @abc.abstractmethod
- def bytecode_path(self, fullname):
- """Abstract method. Accepts a str filename and returns the str pathname
- to the bytecode for the module."""
- raise NotImplementedError
-
- @abc.abstractmethod
- def write_bytecode(self, fullname, bytecode):
- """Abstract method. Accepts a str filename and bytes object
- representing the bytecode for the module. Returns a boolean
- representing whether the bytecode was written or not."""
- raise NotImplementedError
diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py
index ff826e4fae..2e1b2d73e7 100644
--- a/Lib/importlib/machinery.py
+++ b/Lib/importlib/machinery.py
@@ -5,6 +5,7 @@ import _imp
from ._bootstrap import (SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES,
OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES,
EXTENSION_SUFFIXES)
+from ._bootstrap import ModuleSpec
from ._bootstrap import BuiltinImporter
from ._bootstrap import FrozenImporter
from ._bootstrap import WindowsRegistryFinder
diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py
index 1316437102..04b19515fd 100644
--- a/Lib/importlib/util.py
+++ b/Lib/importlib/util.py
@@ -1,10 +1,18 @@
"""Utility code for constructing importers, etc."""
-from ._bootstrap import module_for_loader
-from ._bootstrap import set_loader
-from ._bootstrap import set_package
+from ._bootstrap import MAGIC_NUMBER
+from ._bootstrap import cache_from_source
+from ._bootstrap import decode_source
+from ._bootstrap import source_from_cache
+from ._bootstrap import spec_from_loader
+from ._bootstrap import spec_from_file_location
from ._bootstrap import _resolve_name
+from contextlib import contextmanager
+import functools
+import sys
+import warnings
+
def resolve_name(name, package):
"""Resolve a relative module name to an absolute one."""
@@ -19,3 +27,94 @@ def resolve_name(name, package):
break
level += 1
return _resolve_name(name[level:], package, level)
+
+
+@contextmanager
+def _module_to_load(name):
+ is_reload = name in sys.modules
+
+ module = sys.modules.get(name)
+ if not is_reload:
+ # This must be done before open() is called as the 'io' module
+ # implicitly imports 'locale' and would otherwise trigger an
+ # infinite loop.
+ module = type(sys)(name)
+ # This must be done before putting the module in sys.modules
+ # (otherwise an optimization shortcut in import.c becomes wrong)
+ module.__initializing__ = True
+ sys.modules[name] = module
+ try:
+ yield module
+ except Exception:
+ if not is_reload:
+ try:
+ del sys.modules[name]
+ except KeyError:
+ pass
+ finally:
+ module.__initializing__ = False
+
+
+# XXX deprecate
+def set_package(fxn):
+ """Set __package__ on the returned module."""
+ @functools.wraps(fxn)
+ def set_package_wrapper(*args, **kwargs):
+ module = fxn(*args, **kwargs)
+ if getattr(module, '__package__', None) is None:
+ module.__package__ = module.__name__
+ if not hasattr(module, '__path__'):
+ module.__package__ = module.__package__.rpartition('.')[0]
+ return module
+ return set_package_wrapper
+
+
+# XXX deprecate
+def set_loader(fxn):
+ """Set __loader__ on the returned module."""
+ @functools.wraps(fxn)
+ def set_loader_wrapper(self, *args, **kwargs):
+ module = fxn(self, *args, **kwargs)
+ if getattr(module, '__loader__', None) is None:
+ module.__loader__ = self
+ return module
+ return set_loader_wrapper
+
+
+def module_for_loader(fxn):
+ """Decorator to handle selecting the proper module for loaders.
+
+ The decorated function is passed the module to use instead of the module
+ name. The module passed in to the function is either from sys.modules if
+ it already exists or is a new module. If the module is new, then __name__
+ is set the first argument to the method, __loader__ is set to self, and
+ __package__ is set accordingly (if self.is_package() is defined) will be set
+ before it is passed to the decorated function (if self.is_package() does
+ not work for the module it will be set post-load).
+
+ If an exception is raised and the decorator created the module it is
+ subsequently removed from sys.modules.
+
+ The decorator assumes that the decorated function takes the module name as
+ the second argument.
+
+ """
+ warnings.warn('The import system now takes care of this automatically.',
+ PendingDeprecationWarning, stacklevel=2)
+ @functools.wraps(fxn)
+ def module_for_loader_wrapper(self, fullname, *args, **kwargs):
+ with _module_to_load(fullname) as module:
+ module.__loader__ = self
+ try:
+ is_package = self.is_package(fullname)
+ except (ImportError, AttributeError):
+ pass
+ else:
+ if is_package:
+ module.__package__ = fullname
+ else:
+ module.__package__ = fullname.rpartition('.')[0]
+ # If __package__ was not set above, __import__() will do it later.
+ return fxn(self, module, *args, **kwargs)
+
+ return module_for_loader_wrapper
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 9337bd590b..dc94e44b29 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -31,7 +31,7 @@ Here are some of the useful functions provided by this module:
__author__ = ('Ka-Ping Yee <ping@lfw.org>',
'Yury Selivanov <yselivanov@sprymix.com>')
-import imp
+import ast
import importlib.machinery
import itertools
import linecache
@@ -268,21 +268,40 @@ def getmembers(object, predicate=None):
else:
mro = ()
results = []
- for key in dir(object):
- # First try to get the value via __dict__. Some descriptors don't
- # like calling their __get__ (see bug #1785).
- for base in mro:
- if key in base.__dict__:
- value = base.__dict__[key]
- break
- else:
- try:
- value = getattr(object, key)
- except AttributeError:
+ processed = set()
+ names = dir(object)
+ # :dd any DynamicClassAttributes to the list of names if object is a class;
+ # this may result in duplicate entries if, for example, a virtual
+ # attribute with the same name as a DynamicClassAttribute exists
+ try:
+ for base in object.__bases__:
+ for k, v in base.__dict__.items():
+ if isinstance(v, types.DynamicClassAttribute):
+ names.append(k)
+ except AttributeError:
+ pass
+ for key in names:
+ # First try to get the value via getattr. Some descriptors don't
+ # like calling their __get__ (see bug #1785), so fall back to
+ # looking in the __dict__.
+ try:
+ value = getattr(object, key)
+ # handle the duplicate key
+ if key in processed:
+ raise AttributeError
+ except AttributeError:
+ for base in mro:
+ if key in base.__dict__:
+ value = base.__dict__[key]
+ break
+ else:
+ # could be a (currently) missing slot member, or a buggy
+ # __dir__; discard and move on
continue
if not predicate or predicate(value):
results.append((key, value))
- results.sort()
+ processed.add(key)
+ results.sort(key=lambda pair: pair[0])
return results
Attribute = namedtuple('Attribute', 'name kind defining_class object')
@@ -299,60 +318,106 @@ def classify_class_attrs(cls):
'class method' created via classmethod()
'static method' created via staticmethod()
'property' created via property()
- 'method' any other flavor of method
+ 'method' any other flavor of method or descriptor
'data' not a method
2. The class which defined this attribute (a class).
- 3. The object as obtained directly from the defining class's
- __dict__, not via getattr. This is especially important for
- data attributes: C.data is just a data object, but
- C.__dict__['data'] may be a data descriptor with additional
- info, like a __doc__ string.
+ 3. The object as obtained by calling getattr; if this fails, or if the
+ resulting object does not live anywhere in the class' mro (including
+ metaclasses) then the object is looked up in the defining class's
+ dict (found by walking the mro).
+
+ If one of the items in dir(cls) is stored in the metaclass it will now
+ be discovered and not have None be listed as the class in which it was
+ defined. Any items whose home class cannot be discovered are skipped.
"""
mro = getmro(cls)
+ metamro = getmro(type(cls)) # for attributes stored in the metaclass
+ metamro = tuple([cls for cls in metamro if cls not in (type, object)])
+ class_bases = (cls,) + mro
+ all_bases = class_bases + metamro
names = dir(cls)
+ # :dd any DynamicClassAttributes to the list of names;
+ # this may result in duplicate entries if, for example, a virtual
+ # attribute with the same name as a DynamicClassAttribute exists.
+ for base in mro:
+ for k, v in base.__dict__.items():
+ if isinstance(v, types.DynamicClassAttribute):
+ names.append(k)
result = []
+ processed = set()
+
for name in names:
# Get the object associated with the name, and where it was defined.
+ # Normal objects will be looked up with both getattr and directly in
+ # its class' dict (in case getattr fails [bug #1785], and also to look
+ # for a docstring).
+ # For DynamicClassAttributes on the second pass we only look in the
+ # class's dict.
+ #
# Getting an obj from the __dict__ sometimes reveals more than
# using getattr. Static and class methods are dramatic examples.
- # Furthermore, some objects may raise an Exception when fetched with
- # getattr(). This is the case with some descriptors (bug #1785).
- # Thus, we only use getattr() as a last resort.
homecls = None
- for base in (cls,) + mro:
+ get_obj = None
+ dict_obj = None
+ if name not in processed:
+ try:
+ if name == '__dict__':
+ raise Exception("__dict__ is special, don't want the proxy")
+ get_obj = getattr(cls, name)
+ except Exception as exc:
+ pass
+ else:
+ homecls = getattr(get_obj, "__objclass__", homecls)
+ if homecls not in class_bases:
+ # if the resulting object does not live somewhere in the
+ # mro, drop it and search the mro manually
+ homecls = None
+ last_cls = None
+ # first look in the classes
+ for srch_cls in class_bases:
+ srch_obj = getattr(srch_cls, name, None)
+ if srch_obj == get_obj:
+ last_cls = srch_cls
+ # then check the metaclasses
+ for srch_cls in metamro:
+ try:
+ srch_obj = srch_cls.__getattr__(cls, name)
+ except AttributeError:
+ continue
+ if srch_obj == get_obj:
+ last_cls = srch_cls
+ if last_cls is not None:
+ homecls = last_cls
+ for base in all_bases:
if name in base.__dict__:
- obj = base.__dict__[name]
- homecls = base
+ dict_obj = base.__dict__[name]
+ if homecls not in metamro:
+ homecls = base
break
- else:
- obj = getattr(cls, name)
- homecls = getattr(obj, "__objclass__", homecls)
-
- # Classify the object.
- if isinstance(obj, staticmethod):
+ if homecls is None:
+ # unable to locate the attribute anywhere, most likely due to
+ # buggy custom __dir__; discard and move on
+ continue
+ obj = get_obj or dict_obj
+ # Classify the object or its descriptor.
+ if isinstance(dict_obj, staticmethod):
kind = "static method"
- elif isinstance(obj, classmethod):
+ obj = dict_obj
+ elif isinstance(dict_obj, classmethod):
kind = "class method"
- elif isinstance(obj, property):
+ obj = dict_obj
+ elif isinstance(dict_obj, property):
kind = "property"
- elif ismethoddescriptor(obj):
+ obj = dict_obj
+ elif isfunction(obj) or ismethoddescriptor(obj):
kind = "method"
- elif isdatadescriptor(obj):
- kind = "data"
else:
- obj_via_getattr = getattr(cls, name)
- if (isfunction(obj_via_getattr) or
- ismethoddescriptor(obj_via_getattr)):
- kind = "method"
- else:
- kind = "data"
- obj = obj_via_getattr
-
+ kind = "data"
result.append(Attribute(name, kind, homecls, obj))
-
+ processed.add(name)
return result
# ----------------------------------------------------------- class helpers
@@ -361,6 +426,40 @@ def getmro(cls):
"Return tuple of base classes (including cls) in method resolution order."
return cls.__mro__
+# -------------------------------------------------------- function helpers
+
+def unwrap(func, *, stop=None):
+ """Get the object wrapped by *func*.
+
+ Follows the chain of :attr:`__wrapped__` attributes returning the last
+ object in the chain.
+
+ *stop* is an optional callback accepting an object in the wrapper chain
+ as its sole argument that allows the unwrapping to be terminated early if
+ the callback returns a true value. If the callback never returns a true
+ value, the last object in the chain is returned as usual. For example,
+ :func:`signature` uses this to stop unwrapping if any object in the
+ chain has a ``__signature__`` attribute defined.
+
+ :exc:`ValueError` is raised if a cycle is encountered.
+
+ """
+ if stop is None:
+ def _is_wrapper(f):
+ return hasattr(f, '__wrapped__')
+ else:
+ def _is_wrapper(f):
+ return hasattr(f, '__wrapped__') and not stop(f)
+ f = func # remember the original func for error reporting
+ memo = {id(f)} # Memoise by id to tolerate non-hashable objects
+ while _is_wrapper(func):
+ func = func.__wrapped__
+ id_func = id(func)
+ if id_func in memo:
+ raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
+ memo.add(id_func)
+ return func
+
# -------------------------------------------------- source code extraction
def indentsize(line):
"""Return the indent size, in spaces, at the start of a line of text."""
@@ -440,6 +539,9 @@ def getmoduleinfo(path):
"""Get the module name, suffix, mode, and module type for a given file."""
warnings.warn('inspect.getmoduleinfo() is deprecated', DeprecationWarning,
2)
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', PendingDeprecationWarning)
+ import imp
filename = os.path.basename(path)
suffixes = [(-len(suffix), suffix, mode, mtype)
for suffix, mode, mtype in imp.get_suffixes()]
@@ -476,7 +578,7 @@ def getsourcefile(object):
if os.path.exists(filename):
return filename
# only return a non-existent filename if the module has a PEP 302 loader
- if hasattr(getmodule(object, filename), '__loader__'):
+ if getattr(getmodule(object, filename), '__loader__', None) is not None:
return filename
# or it is in the linecache
if filename in linecache.cache:
@@ -545,13 +647,13 @@ def findsource(object):
The argument may be a module, class, method, function, traceback, frame,
or code object. The source code is returned as a list of all the lines
- in the file and the line number indexes a line in that list. An IOError
+ in the file and the line number indexes a line in that list. An OSError
is raised if the source code cannot be retrieved."""
file = getfile(object)
sourcefile = getsourcefile(object)
if not sourcefile and file[:1] + file[-1:] != '<>':
- raise IOError('source code not available')
+ raise OSError('source code not available')
file = sourcefile if sourcefile else file
module = getmodule(object, file)
@@ -560,7 +662,7 @@ def findsource(object):
else:
lines = linecache.getlines(file)
if not lines:
- raise IOError('could not get source code')
+ raise OSError('could not get source code')
if ismodule(object):
return lines, 0
@@ -586,7 +688,7 @@ def findsource(object):
candidates.sort()
return lines, candidates[0][1]
else:
- raise IOError('could not find class definition')
+ raise OSError('could not find class definition')
if ismethod(object):
object = object.__func__
@@ -598,14 +700,14 @@ def findsource(object):
object = object.f_code
if iscode(object):
if not hasattr(object, 'co_firstlineno'):
- raise IOError('could not find function definition')
+ raise OSError('could not find function definition')
lnum = object.co_firstlineno - 1
pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
while lnum > 0:
if pat.match(lines[lnum]): break
lnum = lnum - 1
return lines, lnum
- raise IOError('could not find code object')
+ raise OSError('could not find code object')
def getcomments(object):
"""Get lines of comments immediately preceding an object's source code.
@@ -614,7 +716,7 @@ def getcomments(object):
"""
try:
lines, lnum = findsource(object)
- except (IOError, TypeError):
+ except (OSError, TypeError):
return None
if ismodule(object):
@@ -710,7 +812,7 @@ def getsourcelines(object):
The argument may be a module, class, method, function, traceback, frame,
or code object. The source code is returned as a list of the lines
corresponding to the object and the line number indicates where in the
- original source file the first line of code was found. An IOError is
+ original source file the first line of code was found. An OSError is
raised if the source code cannot be retrieved."""
lines, lnum = findsource(object)
@@ -722,7 +824,7 @@ def getsource(object):
The argument may be a module, class, method, function, traceback, frame,
or code object. The source code is returned as a single string. An
- IOError is raised if the source code cannot be retrieved."""
+ OSError is raised if the source code cannot be retrieved."""
lines, lnum = getsourcelines(object)
return ''.join(lines)
@@ -1123,7 +1225,7 @@ def getframeinfo(frame, context=1):
start = lineno - 1 - context//2
try:
lines, lnum = findsource(frame)
- except IOError:
+ except OSError:
lines = index = None
else:
start = max(start, 1)
@@ -1345,6 +1447,9 @@ def signature(obj):
sig = signature(obj.__func__)
return sig.replace(parameters=tuple(sig.parameters.values())[1:])
+ # Was this function wrapped by a decorator?
+ obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__")))
+
try:
sig = obj.__signature__
except AttributeError:
@@ -1353,17 +1458,13 @@ def signature(obj):
if sig is not None:
return sig
- try:
- # Was this function wrapped by a decorator?
- wrapped = obj.__wrapped__
- except AttributeError:
- pass
- else:
- return signature(wrapped)
if isinstance(obj, types.FunctionType):
return Signature.from_function(obj)
+ if isinstance(obj, types.BuiltinFunctionType):
+ return Signature.from_builtin(obj)
+
if isinstance(obj, functools.partial):
sig = signature(obj.func)
@@ -1845,6 +1946,64 @@ class Signature:
return_annotation=annotations.get('return', _empty),
__validate_parameters__=False)
+ @classmethod
+ def from_builtin(cls, func):
+ s = getattr(func, "__text_signature__", None)
+ if not s:
+ return None
+
+ if s.endswith("/)"):
+ kind = Parameter.POSITIONAL_ONLY
+ s = s[:-2] + ')'
+ else:
+ kind = Parameter.POSITIONAL_OR_KEYWORD
+
+ s = "def foo" + s + ": pass"
+
+ try:
+ module = ast.parse(s)
+ except SyntaxError:
+ return None
+ if not isinstance(module, ast.Module):
+ return None
+
+ # ast.FunctionDef
+ f = module.body[0]
+
+ parameters = []
+ empty = Parameter.empty
+
+ def p(name_node, default_node, default=empty):
+ name = name_node.arg
+
+ if isinstance(default_node, ast.Num):
+ default = default.n
+ elif isinstance(default_node, ast.NameConstant):
+ default = default_node.value
+ parameters.append(Parameter(name, kind, default=default, annotation=empty))
+
+ # non-keyword-only parameters
+ for name, default in reversed(list(itertools.zip_longest(reversed(f.args.args), reversed(f.args.defaults), fillvalue=None))):
+ p(name, default)
+
+ # *args
+ if f.args.vararg:
+ kind = Parameter.VAR_POSITIONAL
+ p(f.args.vararg, empty)
+
+ # keyword-only arguments
+ kind = Parameter.KEYWORD_ONLY
+ for name, default in zip(f.args.kwonlyargs, f.args.kw_defaults):
+ p(name, default)
+
+ # **kwargs
+ if f.args.kwarg:
+ kind = Parameter.VAR_KEYWORD
+ p(f.args.kwarg, empty)
+
+ return cls(parameters, return_annotation=cls.empty)
+
+
@property
def parameters(self):
return self._parameters
@@ -2072,3 +2231,64 @@ class Signature:
rendered += ' -> {}'.format(anno)
return rendered
+
+def _main():
+ """ Logic for inspecting an object given at command line """
+ import argparse
+ import importlib
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ 'object',
+ help="The object to be analysed. "
+ "It supports the 'module:qualname' syntax")
+ parser.add_argument(
+ '-d', '--details', action='store_true',
+ help='Display info about the module rather than its source code')
+
+ args = parser.parse_args()
+
+ target = args.object
+ mod_name, has_attrs, attrs = target.partition(":")
+ try:
+ obj = module = importlib.import_module(mod_name)
+ except Exception as exc:
+ msg = "Failed to import {} ({}: {})".format(mod_name,
+ type(exc).__name__,
+ exc)
+ print(msg, file=sys.stderr)
+ exit(2)
+
+ if has_attrs:
+ parts = attrs.split(".")
+ obj = module
+ for part in parts:
+ obj = getattr(obj, part)
+
+ if module.__name__ in sys.builtin_module_names:
+ print("Can't get info for builtin modules.", file=sys.stderr)
+ exit(1)
+
+ if args.details:
+ print('Target: {}'.format(target))
+ print('Origin: {}'.format(getsourcefile(module)))
+ print('Cached: {}'.format(module.__cached__))
+ if obj is module:
+ print('Loader: {}'.format(repr(module.__loader__)))
+ if hasattr(module, '__path__'):
+ print('Submodule search path: {}'.format(module.__path__))
+ else:
+ try:
+ __, lineno = findsource(obj)
+ except Exception:
+ pass
+ else:
+ print('Line: {}'.format(lineno))
+
+ print('\n')
+ else:
+ print(getsource(obj))
+
+
+if __name__ == "__main__":
+ _main()
diff --git a/Lib/io.py b/Lib/io.py
index bda4def3da..39878b8337 100644
--- a/Lib/io.py
+++ b/Lib/io.py
@@ -4,7 +4,7 @@ builtin open function is defined in this module.
At the top of the I/O hierarchy is the abstract base class IOBase. It
defines the basic interface to a stream. Note, however, that there is no
separation between reading and writing to streams; implementations are
-allowed to raise an IOError if they do not support a given operation.
+allowed to raise an OSError if they do not support a given operation.
Extending IOBase is RawIOBase which deals simply with the reading and
writing of raw bytes to a stream. FileIO subclasses RawIOBase to provide
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index eabfd6f0a1..97ff13dfd0 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -975,13 +975,25 @@ class _BaseNetwork(_IPAddressBase):
"""Test if this address is allocated for private networks.
Returns:
- A boolean, True if the address is reserved per RFC 4193.
+ A boolean, True if the address is reserved per
+ iana-ipv4-special-registry or iana-ipv6-special-registry.
"""
return (self.network_address.is_private and
self.broadcast_address.is_private)
@property
+ def is_global(self):
+ """Test if this address is allocated for public networks.
+
+ Returns:
+ A boolean, True if the address is not reserved per
+ iana-ipv4-special-registry or iana-ipv6-special-registry.
+
+ """
+ return not self.is_private
+
+ @property
def is_unspecified(self):
"""Test if the address is unspecified.
@@ -1221,19 +1233,30 @@ class IPv4Address(_BaseV4, _BaseAddress):
return self in reserved_network
@property
+ @functools.lru_cache()
def is_private(self):
"""Test if this address is allocated for private networks.
Returns:
- A boolean, True if the address is reserved per RFC 1918.
+ A boolean, True if the address is reserved per
+ iana-ipv4-special-registry.
"""
- private_10 = IPv4Network('10.0.0.0/8')
- private_172 = IPv4Network('172.16.0.0/12')
- private_192 = IPv4Network('192.168.0.0/16')
- return (self in private_10 or
- self in private_172 or
- self in private_192)
+ return (self in IPv4Network('0.0.0.0/8') or
+ self in IPv4Network('10.0.0.0/8') or
+ self in IPv4Network('127.0.0.0/8') or
+ self in IPv4Network('169.254.0.0/16') or
+ self in IPv4Network('172.16.0.0/12') or
+ self in IPv4Network('192.0.0.0/29') or
+ self in IPv4Network('192.0.0.170/31') or
+ self in IPv4Network('192.0.2.0/24') or
+ self in IPv4Network('192.168.0.0/16') or
+ self in IPv4Network('198.18.0.0/15') or
+ self in IPv4Network('198.51.100.0/24') or
+ self in IPv4Network('203.0.113.0/24') or
+ self in IPv4Network('240.0.0.0/4') or
+ self in IPv4Network('255.255.255.255/32'))
+
@property
def is_multicast(self):
@@ -1466,6 +1489,21 @@ class IPv4Network(_BaseV4, _BaseNetwork):
if self._prefixlen == (self._max_prefixlen - 1):
self.hosts = self.__iter__
+ @property
+ @functools.lru_cache()
+ def is_global(self):
+ """Test if this address is allocated for public networks.
+
+ Returns:
+ A boolean, True if the address is not reserved per
+ iana-ipv4-special-registry.
+
+ """
+ return (not (self.network_address in IPv4Network('100.64.0.0/10') and
+ self.broadcast_address in IPv4Network('100.64.0.0/10')) and
+ not self.is_private)
+
+
class _BaseV6:
@@ -1822,15 +1860,36 @@ class IPv6Address(_BaseV6, _BaseAddress):
return self in sitelocal_network
@property
+ @functools.lru_cache()
def is_private(self):
"""Test if this address is allocated for private networks.
Returns:
- A boolean, True if the address is reserved per RFC 4193.
+ A boolean, True if the address is reserved per
+ iana-ipv6-special-registry.
+
+ """
+ return (self in IPv6Network('::1/128') or
+ self in IPv6Network('::/128') or
+ self in IPv6Network('::ffff:0:0/96') or
+ self in IPv6Network('100::/64') or
+ self in IPv6Network('2001::/23') or
+ self in IPv6Network('2001:2::/48') or
+ self in IPv6Network('2001:db8::/32') or
+ self in IPv6Network('2001:10::/28') or
+ self in IPv6Network('fc00::/7') or
+ self in IPv6Network('fe80::/10'))
+
+ @property
+ def is_global(self):
+ """Test if this address is allocated for public networks.
+
+ Returns:
+ A boolean, true if the address is not reserved per
+ iana-ipv6-special-registry.
"""
- private_network = IPv6Network('fc00::/7')
- return self in private_network
+ return not self.is_private
@property
def is_unspecified(self):
diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py
index 48a4f8f863..a459f77a7b 100644
--- a/Lib/json/__init__.py
+++ b/Lib/json/__init__.py
@@ -39,8 +39,7 @@ Compact encoding::
Pretty printing::
>>> import json
- >>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True,
- ... indent=4, separators=(',', ': ')))
+ >>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4))
{
"4": 5,
"6": 7
@@ -146,13 +145,12 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
If ``indent`` is a non-negative integer, then JSON array elements and
object members will be pretty-printed with that indent level. An indent
level of 0 will only insert newlines. ``None`` is the most compact
- representation. Since the default item separator is ``', '``, the
- output might include trailing whitespace when ``indent`` is specified.
- You can use ``separators=(',', ': ')`` to avoid this.
+ representation.
- If ``separators`` is an ``(item_separator, dict_separator)`` tuple
- then it will be used instead of the default ``(', ', ': ')`` separators.
- ``(',', ':')`` is the most compact JSON representation.
+ If specified, ``separators`` should be an ``(item_separator, key_separator)``
+ tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and
+ ``(',', ': ')`` otherwise. To get the most compact JSON representation,
+ you should specify ``(',', ':')`` to eliminate whitespace.
``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
@@ -209,13 +207,12 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
If ``indent`` is a non-negative integer, then JSON array elements and
object members will be pretty-printed with that indent level. An indent
level of 0 will only insert newlines. ``None`` is the most compact
- representation. Since the default item separator is ``', '``, the
- output might include trailing whitespace when ``indent`` is specified.
- You can use ``separators=(',', ': ')`` to avoid this.
+ representation.
- If ``separators`` is an ``(item_separator, dict_separator)`` tuple
- then it will be used instead of the default ``(', ', ': ')`` separators.
- ``(',', ':')`` is the most compact JSON representation.
+ If specified, ``separators`` should be an ``(item_separator, key_separator)``
+ tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and
+ ``(',', ': ')`` otherwise. To get the most compact JSON representation,
+ you should specify ``(',', ':')`` to eliminate whitespace.
``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
@@ -313,6 +310,11 @@ def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
The ``encoding`` argument is ignored and deprecated.
"""
+ if not isinstance(s, str):
+ raise TypeError('the JSON object must be str, not {!r}'.format(
+ s.__class__.__name__))
+ if s.startswith(u'\ufeff'):
+ raise ValueError("Unexpected UTF-8 BOM (decode using utf-8-sig)")
if (cls is None and object_hook is None and
parse_int is None and parse_float is None and
parse_constant is None and object_pairs_hook is None and not kw):
diff --git a/Lib/json/decoder.py b/Lib/json/decoder.py
index 80d3420a95..59e5f41f4d 100644
--- a/Lib/json/decoder.py
+++ b/Lib/json/decoder.py
@@ -1,9 +1,6 @@
"""Implementation of JSONDecoder
"""
-import binascii
import re
-import sys
-import struct
from json import scanner
try:
@@ -15,14 +12,9 @@ __all__ = ['JSONDecoder']
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
-def _floatconstants():
- _BYTES = binascii.unhexlify(b'7FF80000000000007FF0000000000000')
- if sys.byteorder != 'big':
- _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
- nan, inf = struct.unpack('dd', _BYTES)
- return nan, inf, -inf
-
-NaN, PosInf, NegInf = _floatconstants()
+NaN = float('nan')
+PosInf = float('inf')
+NegInf = float('-inf')
def linecol(doc, pos):
@@ -195,8 +187,8 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
try:
value, end = scan_once(s, end)
- except StopIteration:
- raise ValueError(errmsg("Expecting object", s, end))
+ except StopIteration as err:
+ raise ValueError(errmsg("Expecting value", s, err.value)) from None
pairs_append((key, value))
try:
nextchar = s[end]
@@ -239,8 +231,8 @@ def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
while True:
try:
value, end = scan_once(s, end)
- except StopIteration:
- raise ValueError(errmsg("Expecting object", s, end))
+ except StopIteration as err:
+ raise ValueError(errmsg("Expecting value", s, err.value)) from None
_append(value)
nextchar = s[end:end + 1]
if nextchar in _ws:
@@ -250,7 +242,7 @@ def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
if nextchar == ']':
break
elif nextchar != ',':
- raise ValueError(errmsg("Expecting ',' delimiter", s, end))
+ raise ValueError(errmsg("Expecting ',' delimiter", s, end - 1))
try:
if s[end] in _ws:
end += 1
@@ -365,6 +357,6 @@ class JSONDecoder(object):
"""
try:
obj, end = self.scan_once(s, idx)
- except StopIteration:
- raise ValueError("No JSON object could be decoded")
+ except StopIteration as err:
+ raise ValueError(errmsg("Expecting value", s, err.value)) from None
return obj, end
diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py
index 1d8b20c095..05138383bc 100644
--- a/Lib/json/encoder.py
+++ b/Lib/json/encoder.py
@@ -125,14 +125,12 @@ class JSONEncoder(object):
If indent is a non-negative integer, then JSON array
elements and object members will be pretty-printed with that
indent level. An indent level of 0 will only insert newlines.
- None is the most compact representation. Since the default
- item separator is ', ', the output might include trailing
- whitespace when indent is specified. You can use
- separators=(',', ': ') to avoid this.
+ None is the most compact representation.
- If specified, separators should be a (item_separator, key_separator)
- tuple. The default is (', ', ': '). To get the most compact JSON
- representation you should specify (',', ':') to eliminate whitespace.
+ If specified, separators should be an (item_separator, key_separator)
+ tuple. The default is (', ', ': ') if *indent* is ``None`` and
+ (',', ': ') otherwise. To get the most compact JSON representation,
+ you should specify (',', ':') to eliminate whitespace.
If specified, default is a function that gets called for objects
that can't otherwise be serialized. It should return a JSON encodable
@@ -148,6 +146,8 @@ class JSONEncoder(object):
self.indent = indent
if separators is not None:
self.item_separator, self.key_separator = separators
+ elif indent is not None:
+ self.item_separator = ','
if default is not None:
self.default = default
@@ -175,6 +175,7 @@ class JSONEncoder(object):
def encode(self, o):
"""Return a JSON string representation of a Python data structure.
+ >>> from json.encoder import JSONEncoder
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
'{"foo": ["bar", "baz"]}'
@@ -298,9 +299,13 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
elif value is False:
yield buf + 'false'
elif isinstance(value, int):
- yield buf + str(value)
+ # Subclasses of int/float may override __str__, but we still
+ # want to encode them as integers/floats in JSON. One example
+ # within the standard library is IntEnum.
+ yield buf + str(int(value))
elif isinstance(value, float):
- yield buf + _floatstr(value)
+ # see comment above for int
+ yield buf + _floatstr(float(value))
else:
yield buf
if isinstance(value, (list, tuple)):
@@ -309,8 +314,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
- for chunk in chunks:
- yield chunk
+ yield from chunks
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + _indent * _current_indent_level
@@ -347,7 +351,8 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
# JavaScript is weakly typed for these, so it makes sense to
# also allow them. Many encoders seem to do something like this.
elif isinstance(key, float):
- key = _floatstr(key)
+ # see comment for int/float in _make_iterencode
+ key = _floatstr(float(key))
elif key is True:
key = 'true'
elif key is False:
@@ -355,7 +360,8 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
elif key is None:
key = 'null'
elif isinstance(key, int):
- key = str(key)
+ # see comment for int/float in _make_iterencode
+ key = str(int(key))
elif _skipkeys:
continue
else:
@@ -375,9 +381,11 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
elif value is False:
yield 'false'
elif isinstance(value, int):
- yield str(value)
+ # see comment for int/float in _make_iterencode
+ yield str(int(value))
elif isinstance(value, float):
- yield _floatstr(value)
+ # see comment for int/float in _make_iterencode
+ yield _floatstr(float(value))
else:
if isinstance(value, (list, tuple)):
chunks = _iterencode_list(value, _current_indent_level)
@@ -385,8 +393,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
- for chunk in chunks:
- yield chunk
+ yield from chunks
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + _indent * _current_indent_level
@@ -404,15 +411,15 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
elif o is False:
yield 'false'
elif isinstance(o, int):
- yield str(o)
+ # see comment for int/float in _make_iterencode
+ yield str(int(o))
elif isinstance(o, float):
- yield _floatstr(o)
+ # see comment for int/float in _make_iterencode
+ yield _floatstr(float(o))
elif isinstance(o, (list, tuple)):
- for chunk in _iterencode_list(o, _current_indent_level):
- yield chunk
+ yield from _iterencode_list(o, _current_indent_level)
elif isinstance(o, dict):
- for chunk in _iterencode_dict(o, _current_indent_level):
- yield chunk
+ yield from _iterencode_dict(o, _current_indent_level)
else:
if markers is not None:
markerid = id(o)
@@ -420,8 +427,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
raise ValueError("Circular reference detected")
markers[markerid] = o
o = _default(o)
- for chunk in _iterencode(o, _current_indent_level):
- yield chunk
+ yield from _iterencode(o, _current_indent_level)
if markers is not None:
del markers[markerid]
return _iterencode
diff --git a/Lib/json/scanner.py b/Lib/json/scanner.py
index 23eef61b94..86426cde1a 100644
--- a/Lib/json/scanner.py
+++ b/Lib/json/scanner.py
@@ -29,7 +29,7 @@ def py_make_scanner(context):
try:
nextchar = string[idx]
except IndexError:
- raise StopIteration
+ raise StopIteration(idx)
if nextchar == '"':
return parse_string(string, idx + 1, strict)
@@ -60,7 +60,7 @@ def py_make_scanner(context):
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
return parse_constant('-Infinity'), idx + 9
else:
- raise StopIteration
+ raise StopIteration(idx)
def scan_once(string, idx):
try:
diff --git a/Lib/json/tool.py b/Lib/json/tool.py
index ecf9c47885..7db4528571 100644
--- a/Lib/json/tool.py
+++ b/Lib/json/tool.py
@@ -31,8 +31,7 @@ def main():
except ValueError as e:
raise SystemExit(e)
with outfile:
- json.dump(obj, outfile, sort_keys=True,
- indent=4, separators=(',', ': '))
+ json.dump(obj, outfile, sort_keys=True, indent=4)
outfile.write('\n')
diff --git a/Lib/keyword.py b/Lib/keyword.py
index dad39cc377..6e1e882a91 100755
--- a/Lib/keyword.py
+++ b/Lib/keyword.py
@@ -60,6 +60,12 @@ def main():
if len(args) > 1: optfile = args[1]
else: optfile = "Lib/keyword.py"
+ # load the output skeleton from the target, taking care to preserve its
+ # newline convention.
+ with open(optfile, newline='') as fp:
+ format = fp.readlines()
+ nl = format[0][len(format[0].strip()):] if format else '\n'
+
# scan the source file for keywords
with open(iptfile) as fp:
strprog = re.compile('"([^"]+)"')
@@ -68,26 +74,21 @@ def main():
if '{1, "' in line:
match = strprog.search(line)
if match:
- lines.append(" '" + match.group(1) + "',\n")
+ lines.append(" '" + match.group(1) + "'," + nl)
lines.sort()
- # load the output skeleton from the target
- with open(optfile) as fp:
- format = fp.readlines()
-
- # insert the lines of keywords
+ # insert the lines of keywords into the skeleton
try:
- start = format.index("#--start keywords--\n") + 1
- end = format.index("#--end keywords--\n")
+ start = format.index("#--start keywords--" + nl) + 1
+ end = format.index("#--end keywords--" + nl)
format[start:end] = lines
except ValueError:
sys.stderr.write("target does not contain format markers\n")
sys.exit(1)
# write the output file
- fp = open(optfile, 'w')
- fp.write(''.join(format))
- fp.close()
+ with open(optfile, 'w', newline='') as fp:
+ fp.writelines(format)
if __name__ == "__main__":
main()
diff --git a/Lib/lib2to3/btm_utils.py b/Lib/lib2to3/btm_utils.py
index 2276dc9e96..339750edba 100644
--- a/Lib/lib2to3/btm_utils.py
+++ b/Lib/lib2to3/btm_utils.py
@@ -96,8 +96,7 @@ class MinNode(object):
def leaves(self):
"Generator that returns the leaves of the tree"
for child in self.children:
- for x in child.leaves():
- yield x
+ yield from child.leaves()
if not self.children:
yield self
@@ -277,7 +276,6 @@ def rec_test(sequence, test_func):
sub-iterables"""
for x in sequence:
if isinstance(x, (list, tuple)):
- for y in rec_test(x, test_func):
- yield y
+ yield from rec_test(x, test_func)
else:
yield test_func(x)
diff --git a/Lib/lib2to3/fixer_util.py b/Lib/lib2to3/fixer_util.py
index 60d219f577..6e259c54ac 100644
--- a/Lib/lib2to3/fixer_util.py
+++ b/Lib/lib2to3/fixer_util.py
@@ -129,6 +129,29 @@ def FromImport(package_name, name_leafs):
imp = Node(syms.import_from, children)
return imp
+def ImportAndCall(node, results, names):
+ """Returns an import statement and calls a method
+ of the module:
+
+ import module
+ module.name()"""
+ obj = results["obj"].clone()
+ if obj.type == syms.arglist:
+ newarglist = obj.clone()
+ else:
+ newarglist = Node(syms.arglist, [obj.clone()])
+ after = results["after"]
+ if after:
+ after = [n.clone() for n in after]
+ new = Node(syms.power,
+ Attr(Name(names[0]), Name(names[1])) +
+ [Node(syms.trailer,
+ [results["lpar"].clone(),
+ newarglist,
+ results["rpar"].clone()])] + after)
+ new.prefix = node.prefix
+ return new
+
###########################################################
### Determine whether a node represents a given literal
diff --git a/Lib/lib2to3/fixes/fix_asserts.py b/Lib/lib2to3/fixes/fix_asserts.py
new file mode 100644
index 0000000000..5bcec885f5
--- /dev/null
+++ b/Lib/lib2to3/fixes/fix_asserts.py
@@ -0,0 +1,34 @@
+"""Fixer that replaces deprecated unittest method names."""
+
+# Author: Ezio Melotti
+
+from ..fixer_base import BaseFix
+from ..fixer_util import Name
+
+NAMES = dict(
+ assert_="assertTrue",
+ assertEquals="assertEqual",
+ assertNotEquals="assertNotEqual",
+ assertAlmostEquals="assertAlmostEqual",
+ assertNotAlmostEquals="assertNotAlmostEqual",
+ assertRegexpMatches="assertRegex",
+ assertRaisesRegexp="assertRaisesRegex",
+ failUnlessEqual="assertEqual",
+ failIfEqual="assertNotEqual",
+ failUnlessAlmostEqual="assertAlmostEqual",
+ failIfAlmostEqual="assertNotAlmostEqual",
+ failUnless="assertTrue",
+ failUnlessRaises="assertRaises",
+ failIf="assertFalse",
+)
+
+
+class FixAsserts(BaseFix):
+
+ PATTERN = """
+ power< any+ trailer< '.' meth=(%s)> any* >
+ """ % '|'.join(map(repr, NAMES))
+
+ def transform(self, node, results):
+ name = results["meth"][0]
+ name.replace(Name(NAMES[str(name)], prefix=name.prefix))
diff --git a/Lib/lib2to3/fixes/fix_intern.py b/Lib/lib2to3/fixes/fix_intern.py
index 6be11cd39d..fb2973c242 100644
--- a/Lib/lib2to3/fixes/fix_intern.py
+++ b/Lib/lib2to3/fixes/fix_intern.py
@@ -6,9 +6,8 @@
intern(s) -> sys.intern(s)"""
# Local imports
-from .. import pytree
from .. import fixer_base
-from ..fixer_util import Name, Attr, touch_import
+from ..fixer_util import ImportAndCall, touch_import
class FixIntern(fixer_base.BaseFix):
@@ -26,21 +25,7 @@ class FixIntern(fixer_base.BaseFix):
"""
def transform(self, node, results):
- syms = self.syms
- obj = results["obj"].clone()
- if obj.type == syms.arglist:
- newarglist = obj.clone()
- else:
- newarglist = pytree.Node(syms.arglist, [obj.clone()])
- after = results["after"]
- if after:
- after = [n.clone() for n in after]
- new = pytree.Node(syms.power,
- Attr(Name("sys"), Name("intern")) +
- [pytree.Node(syms.trailer,
- [results["lpar"].clone(),
- newarglist,
- results["rpar"].clone()])] + after)
- new.prefix = node.prefix
+ names = ('sys', 'intern')
+ new = ImportAndCall(node, results, names)
touch_import(None, 'sys', node)
return new
diff --git a/Lib/lib2to3/fixes/fix_reload.py b/Lib/lib2to3/fixes/fix_reload.py
new file mode 100644
index 0000000000..1855357588
--- /dev/null
+++ b/Lib/lib2to3/fixes/fix_reload.py
@@ -0,0 +1,28 @@
+"""Fixer for reload().
+
+reload(s) -> imp.reload(s)"""
+
+# Local imports
+from .. import fixer_base
+from ..fixer_util import ImportAndCall, touch_import
+
+
+class FixReload(fixer_base.BaseFix):
+ BM_compatible = True
+ order = "pre"
+
+ PATTERN = """
+ power< 'reload'
+ trailer< lpar='('
+ ( not(arglist | argument<any '=' any>) obj=any
+ | obj=arglist<(not argument<any '=' any>) any ','> )
+ rpar=')' >
+ after=any*
+ >
+ """
+
+ def transform(self, node, results):
+ names = ('imp', 'reload')
+ new = ImportAndCall(node, results, names)
+ touch_import(None, 'imp', node)
+ return new
diff --git a/Lib/lib2to3/main.py b/Lib/lib2to3/main.py
index f9cc18b5d3..93bae9021a 100644
--- a/Lib/lib2to3/main.py
+++ b/Lib/lib2to3/main.py
@@ -90,11 +90,11 @@ class StdoutRefactoringTool(refactor.MultiprocessRefactoringTool):
if os.path.lexists(backup):
try:
os.remove(backup)
- except os.error as err:
+ except OSError as err:
self.log_message("Can't remove backup %s", backup)
try:
os.rename(filename, backup)
- except os.error as err:
+ except OSError as err:
self.log_message("Can't rename %s to %s", filename, backup)
# Actually write the new file
write = super(StdoutRefactoringTool, self).write_file
diff --git a/Lib/lib2to3/pgen2/conv.py b/Lib/lib2to3/pgen2/conv.py
index bf49762ae4..ed0cac532e 100644
--- a/Lib/lib2to3/pgen2/conv.py
+++ b/Lib/lib2to3/pgen2/conv.py
@@ -60,7 +60,7 @@ class Converter(grammar.Grammar):
"""
try:
f = open(filename)
- except IOError as err:
+ except OSError as err:
print("Can't open %s: %s" % (filename, err))
return False
self.symbol2number = {}
@@ -111,7 +111,7 @@ class Converter(grammar.Grammar):
"""
try:
f = open(filename)
- except IOError as err:
+ except OSError as err:
print("Can't open %s: %s" % (filename, err))
return False
# The code below essentially uses f's iterator-ness!
diff --git a/Lib/lib2to3/pgen2/driver.py b/Lib/lib2to3/pgen2/driver.py
index 4c611c6640..3ccc69dcf6 100644
--- a/Lib/lib2to3/pgen2/driver.py
+++ b/Lib/lib2to3/pgen2/driver.py
@@ -123,7 +123,7 @@ def load_grammar(gt="Grammar.txt", gp=None,
logger.info("Writing grammar tables to %s", gp)
try:
g.dump(gp)
- except IOError as e:
+ except OSError as e:
logger.info("Writing failed:"+str(e))
else:
g = grammar.Grammar()
diff --git a/Lib/lib2to3/pgen2/grammar.py b/Lib/lib2to3/pgen2/grammar.py
index 14c5f70f1d..7f1c5648e2 100644
--- a/Lib/lib2to3/pgen2/grammar.py
+++ b/Lib/lib2to3/pgen2/grammar.py
@@ -86,15 +86,13 @@ class Grammar(object):
def dump(self, filename):
"""Dump the grammar tables to a pickle file."""
- f = open(filename, "wb")
- pickle.dump(self.__dict__, f, 2)
- f.close()
+ with open(filename, "wb") as f:
+ pickle.dump(self.__dict__, f, 2)
def load(self, filename):
"""Load the grammar tables from a pickle file."""
- f = open(filename, "rb")
- d = pickle.load(f)
- f.close()
+ with open(filename, "rb") as f:
+ d = pickle.load(f)
self.__dict__.update(d)
def copy(self):
diff --git a/Lib/lib2to3/pytree.py b/Lib/lib2to3/pytree.py
index 17cbf0a2f9..c4a1be3500 100644
--- a/Lib/lib2to3/pytree.py
+++ b/Lib/lib2to3/pytree.py
@@ -194,8 +194,7 @@ class Base(object):
def leaves(self):
for child in self.children:
- for x in child.leaves():
- yield x
+ yield from child.leaves()
def depth(self):
if self.parent is None:
@@ -274,16 +273,14 @@ class Node(Base):
def post_order(self):
"""Return a post-order iterator for the tree."""
for child in self.children:
- for node in child.post_order():
- yield node
+ yield from child.post_order()
yield self
def pre_order(self):
"""Return a pre-order iterator for the tree."""
yield self
for child in self.children:
- for node in child.pre_order():
- yield node
+ yield from child.pre_order()
def _prefix_getter(self):
"""
diff --git a/Lib/lib2to3/refactor.py b/Lib/lib2to3/refactor.py
index 201e193fe2..8100317871 100644
--- a/Lib/lib2to3/refactor.py
+++ b/Lib/lib2to3/refactor.py
@@ -326,7 +326,7 @@ class RefactoringTool(object):
"""
try:
f = open(filename, "rb")
- except IOError as err:
+ except OSError as err:
self.log_error("Can't open %s: %s", filename, err)
return None, None
try:
@@ -534,12 +534,12 @@ class RefactoringTool(object):
"""
try:
f = _open_with_encoding(filename, "w", encoding=encoding)
- except os.error as err:
+ except OSError as err:
self.log_error("Can't create %s: %s", filename, err)
return
try:
f.write(_to_system_newlines(new_text))
- except os.error as err:
+ except OSError as err:
self.log_error("Can't write %s: %s", filename, err)
finally:
f.close()
diff --git a/Lib/lib2to3/tests/pytree_idempotency.py b/Lib/lib2to3/tests/pytree_idempotency.py
index a02bbfe201..731c403120 100755
--- a/Lib/lib2to3/tests/pytree_idempotency.py
+++ b/Lib/lib2to3/tests/pytree_idempotency.py
@@ -53,7 +53,7 @@ def main():
for dir in sys.path:
try:
names = os.listdir(dir)
- except os.error:
+ except OSError:
continue
print("Scanning", dir, "...", file=sys.stderr)
for name in names:
diff --git a/Lib/lib2to3/tests/test_fixers.py b/Lib/lib2to3/tests/test_fixers.py
index 2f08f935d9..06b0033b8f 100644
--- a/Lib/lib2to3/tests/test_fixers.py
+++ b/Lib/lib2to3/tests/test_fixers.py
@@ -282,6 +282,65 @@ class Test_apply(FixerTestCase):
b = """f(*args, **kwds)"""
self.check(a, b)
+class Test_reload(FixerTestCase):
+ fixer = "reload"
+
+ def test(self):
+ b = """reload(a)"""
+ a = """import imp\nimp.reload(a)"""
+ self.check(b, a)
+
+ def test_comment(self):
+ b = """reload( a ) # comment"""
+ a = """import imp\nimp.reload( a ) # comment"""
+ self.check(b, a)
+
+ # PEP 8 comments
+ b = """reload( a ) # comment"""
+ a = """import imp\nimp.reload( a ) # comment"""
+ self.check(b, a)
+
+ def test_space(self):
+ b = """reload( a )"""
+ a = """import imp\nimp.reload( a )"""
+ self.check(b, a)
+
+ b = """reload( a)"""
+ a = """import imp\nimp.reload( a)"""
+ self.check(b, a)
+
+ b = """reload(a )"""
+ a = """import imp\nimp.reload(a )"""
+ self.check(b, a)
+
+ def test_unchanged(self):
+ s = """reload(a=1)"""
+ self.unchanged(s)
+
+ s = """reload(f, g)"""
+ self.unchanged(s)
+
+ s = """reload(f, *h)"""
+ self.unchanged(s)
+
+ s = """reload(f, *h, **i)"""
+ self.unchanged(s)
+
+ s = """reload(f, **i)"""
+ self.unchanged(s)
+
+ s = """reload(*h, **i)"""
+ self.unchanged(s)
+
+ s = """reload(*h)"""
+ self.unchanged(s)
+
+ s = """reload(**i)"""
+ self.unchanged(s)
+
+ s = """reload()"""
+ self.unchanged(s)
+
class Test_intern(FixerTestCase):
fixer = "intern"
@@ -4576,3 +4635,53 @@ class Test_exitfunc(FixerTestCase):
def test_unchanged(self):
s = """f(sys.exitfunc)"""
self.unchanged(s)
+
+
+class Test_asserts(FixerTestCase):
+
+ fixer = "asserts"
+
+ def test_deprecated_names(self):
+ tests = [
+ ('self.assert_(True)', 'self.assertTrue(True)'),
+ ('self.assertEquals(2, 2)', 'self.assertEqual(2, 2)'),
+ ('self.assertNotEquals(2, 3)', 'self.assertNotEqual(2, 3)'),
+ ('self.assertAlmostEquals(2, 3)', 'self.assertAlmostEqual(2, 3)'),
+ ('self.assertNotAlmostEquals(2, 8)', 'self.assertNotAlmostEqual(2, 8)'),
+ ('self.failUnlessEqual(2, 2)', 'self.assertEqual(2, 2)'),
+ ('self.failIfEqual(2, 3)', 'self.assertNotEqual(2, 3)'),
+ ('self.failUnlessAlmostEqual(2, 3)', 'self.assertAlmostEqual(2, 3)'),
+ ('self.failIfAlmostEqual(2, 8)', 'self.assertNotAlmostEqual(2, 8)'),
+ ('self.failUnless(True)', 'self.assertTrue(True)'),
+ ('self.failUnlessRaises(foo)', 'self.assertRaises(foo)'),
+ ('self.failIf(False)', 'self.assertFalse(False)'),
+ ]
+ for b, a in tests:
+ self.check(b, a)
+
+ def test_variants(self):
+ b = 'eq = self.assertEquals'
+ a = 'eq = self.assertEqual'
+ self.check(b, a)
+ b = 'self.assertEquals(2, 3, msg="fail")'
+ a = 'self.assertEqual(2, 3, msg="fail")'
+ self.check(b, a)
+ b = 'self.assertEquals(2, 3, msg="fail") # foo'
+ a = 'self.assertEqual(2, 3, msg="fail") # foo'
+ self.check(b, a)
+ b = 'self.assertEquals (2, 3)'
+ a = 'self.assertEqual (2, 3)'
+ self.check(b, a)
+ b = ' self.assertEquals (2, 3)'
+ a = ' self.assertEqual (2, 3)'
+ self.check(b, a)
+ b = 'with self.failUnlessRaises(Explosion): explode()'
+ a = 'with self.assertRaises(Explosion): explode()'
+ self.check(b, a)
+ b = 'with self.failUnlessRaises(Explosion) as cm: explode()'
+ a = 'with self.assertRaises(Explosion) as cm: explode()'
+ self.check(b, a)
+
+ def test_unchanged(self):
+ self.unchanged('self.assertEqualsOnSaturday')
+ self.unchanged('self.assertEqualsOnSaturday(3, 5)')
diff --git a/Lib/linecache.py b/Lib/linecache.py
index c3f2c3fdca..02a9eb5253 100644
--- a/Lib/linecache.py
+++ b/Lib/linecache.py
@@ -59,7 +59,7 @@ def checkcache(filename=None):
continue # no-op for files loaded via a __loader__
try:
stat = os.stat(fullname)
- except os.error:
+ except OSError:
del cache[filename]
continue
if size != stat.st_size or mtime != stat.st_mtime:
@@ -91,7 +91,7 @@ def updatecache(filename, module_globals=None):
if name and get_source:
try:
data = get_source(name)
- except (ImportError, IOError):
+ except (ImportError, OSError):
pass
else:
if data is None:
@@ -118,14 +118,14 @@ def updatecache(filename, module_globals=None):
try:
stat = os.stat(fullname)
break
- except os.error:
+ except OSError:
pass
else:
return []
try:
with tokenize.open(fullname) as fp:
lines = fp.readlines()
- except IOError:
+ except OSError:
return []
if lines and not lines[-1].endswith('\n'):
lines[-1] += '\n'
diff --git a/Lib/locale.py b/Lib/locale.py
index d2a885d519..2e82c952ac 100644
--- a/Lib/locale.py
+++ b/Lib/locale.py
@@ -554,8 +554,8 @@ if sys.platform.startswith("win"):
# On Win32, this will return the ANSI code page
def getpreferredencoding(do_setlocale = True):
"""Return the charset that the user is likely using."""
- import _locale
- return _locale._getdefaultlocale()[1]
+ import _bootlocale
+ return _bootlocale.getpreferredencoding(False)
else:
# On Unix, if CODESET is available, use that.
try:
@@ -574,27 +574,16 @@ else:
def getpreferredencoding(do_setlocale = True):
"""Return the charset that the user is likely using,
according to the system configuration."""
+ import _bootlocale
if do_setlocale:
oldloc = setlocale(LC_CTYPE)
try:
setlocale(LC_CTYPE, "")
except Error:
pass
- result = nl_langinfo(CODESET)
- if not result and sys.platform == 'darwin':
- # nl_langinfo can return an empty string
- # when the setting has an invalid value.
- # Default to UTF-8 in that case because
- # UTF-8 is the default charset on OSX and
- # returning nothing will crash the
- # interpreter.
- result = 'UTF-8'
+ result = _bootlocale.getpreferredencoding(False)
+ if do_setlocale:
setlocale(LC_CTYPE, oldloc)
- else:
- result = nl_langinfo(CODESET)
- if not result and sys.platform == 'darwin':
- # See above for explanation
- result = 'UTF-8'
return result
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index 7f94e39f0f..9b41b9d277 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -67,7 +67,7 @@ else: #pragma: no cover
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
- except:
+ except Exception:
return sys.exc_info()[2].tb_frame.f_back
# _srcfile is only used in conjunction with sys._getframe().
@@ -123,20 +123,22 @@ INFO = 20
DEBUG = 10
NOTSET = 0
-_levelNames = {
- CRITICAL : 'CRITICAL',
- ERROR : 'ERROR',
- WARNING : 'WARNING',
- INFO : 'INFO',
- DEBUG : 'DEBUG',
- NOTSET : 'NOTSET',
- 'CRITICAL' : CRITICAL,
- 'ERROR' : ERROR,
- 'WARN' : WARNING,
- 'WARNING' : WARNING,
- 'INFO' : INFO,
- 'DEBUG' : DEBUG,
- 'NOTSET' : NOTSET,
+_levelToName = {
+ CRITICAL: 'CRITICAL',
+ ERROR: 'ERROR',
+ WARNING: 'WARNING',
+ INFO: 'INFO',
+ DEBUG: 'DEBUG',
+ NOTSET: 'NOTSET',
+}
+_nameToLevel = {
+ 'CRITICAL': CRITICAL,
+ 'ERROR': ERROR,
+ 'WARN': WARNING,
+ 'WARNING': WARNING,
+ 'INFO': INFO,
+ 'DEBUG': DEBUG,
+ 'NOTSET': NOTSET,
}
def getLevelName(level):
@@ -153,7 +155,7 @@ def getLevelName(level):
Otherwise, the string "Level %s" % level is returned.
"""
- return _levelNames.get(level, ("Level %s" % level))
+ return _levelToName.get(level, ("Level %s" % level))
def addLevelName(level, levelName):
"""
@@ -163,8 +165,8 @@ def addLevelName(level, levelName):
"""
_acquireLock()
try: #unlikely to cause an exception, but you never know...
- _levelNames[level] = levelName
- _levelNames[levelName] = level
+ _levelToName[level] = levelName
+ _nameToLevel[levelName] = level
finally:
_releaseLock()
@@ -172,9 +174,9 @@ def _checkLevel(level):
if isinstance(level, int):
rv = level
elif str(level) == level:
- if level not in _levelNames:
+ if level not in _nameToLevel:
raise ValueError("Unknown level: %r" % level)
- rv = _levelNames[level]
+ rv = _nameToLevel[level]
else:
raise TypeError("Level not an integer or a valid string: %r" % level)
return rv
@@ -880,16 +882,30 @@ class Handler(Filterer):
The record which was being processed is passed in to this method.
"""
if raiseExceptions and sys.stderr: # see issue 13807
- ei = sys.exc_info()
+ t, v, tb = sys.exc_info()
try:
- traceback.print_exception(ei[0], ei[1], ei[2],
- None, sys.stderr)
- sys.stderr.write('Logged from file %s, line %s\n' % (
- record.filename, record.lineno))
- except IOError: #pragma: no cover
+ sys.stderr.write('--- Logging error ---\n')
+ traceback.print_exception(t, v, tb, None, sys.stderr)
+ sys.stderr.write('Call stack:\n')
+ # Walk the stack frame up until we're out of logging,
+ # so as to print the calling context.
+ frame = tb.tb_frame
+ while (frame and os.path.dirname(frame.f_code.co_filename) ==
+ __path__[0]):
+ frame = frame.f_back
+ if frame:
+ traceback.print_stack(frame, file=sys.stderr)
+ else:
+ # couldn't find the right stack frame, for some reason
+ sys.stderr.write('Logged from file %s, line %s\n' % (
+ record.filename, record.lineno))
+ # Issue 18671: output logging message and arguments
+ sys.stderr.write('Message: %r\n'
+ 'Arguments: %s\n' % (record.msg, record.args))
+ except OSError: #pragma: no cover
pass # see issue 5971
finally:
- del ei
+ del t, v, tb
class StreamHandler(Handler):
"""
@@ -939,9 +955,7 @@ class StreamHandler(Handler):
stream.write(msg)
stream.write(self.terminator)
self.flush()
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
+ except Exception:
self.handleError(record)
class FileHandler(StreamHandler):
@@ -1833,7 +1847,7 @@ def shutdown(handlerList=_handlerList):
h.acquire()
h.flush()
h.close()
- except (IOError, ValueError):
+ except (OSError, ValueError):
# Ignore errors which might be caused
# because handlers have been closed but
# references to them are still around at
@@ -1841,7 +1855,7 @@ def shutdown(handlerList=_handlerList):
pass
finally:
h.release()
- except:
+ except: # ignore everything, as we're shutting down
if raiseExceptions:
raise
#else, swallow
diff --git a/Lib/logging/config.py b/Lib/logging/config.py
index 188061449d..b882a62ab3 100644
--- a/Lib/logging/config.py
+++ b/Lib/logging/config.py
@@ -61,11 +61,14 @@ def fileConfig(fname, defaults=None, disable_existing_loggers=True):
"""
import configparser
- cp = configparser.ConfigParser(defaults)
- if hasattr(fname, 'readline'):
- cp.read_file(fname)
+ if isinstance(fname, configparser.RawConfigParser):
+ cp = fname
else:
- cp.read(fname)
+ cp = configparser.ConfigParser(defaults)
+ if hasattr(fname, 'readline'):
+ cp.read_file(fname)
+ else:
+ cp.read(fname)
formatters = _create_formatters(cp)
@@ -141,7 +144,7 @@ def _install_handlers(cp, formatters):
h = klass(*args)
if "level" in section:
level = section["level"]
- h.setLevel(logging._levelNames[level])
+ h.setLevel(level)
if len(fmt):
h.setFormatter(formatters[fmt])
if issubclass(klass, logging.handlers.MemoryHandler):
@@ -188,7 +191,7 @@ def _install_loggers(cp, handlers, disable_existing):
log = root
if "level" in section:
level = section["level"]
- log.setLevel(logging._levelNames[level])
+ log.setLevel(level)
for h in root.handlers[:]:
root.removeHandler(h)
hlist = section["handlers"]
@@ -234,7 +237,7 @@ def _install_loggers(cp, handlers, disable_existing):
existing.remove(qn)
if "level" in section:
level = section["level"]
- logger.setLevel(logging._levelNames[level])
+ logger.setLevel(level)
for h in logger.handlers[:]:
logger.removeHandler(h)
logger.propagate = propagate
@@ -729,6 +732,7 @@ class DictConfigurator(BaseConfigurator):
'address' in config:
config['address'] = self.as_tuple(config['address'])
factory = klass
+ props = config.pop('.', None)
kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
try:
result = factory(**kwargs)
@@ -747,6 +751,9 @@ class DictConfigurator(BaseConfigurator):
result.setLevel(logging._checkLevel(level))
if filters:
self.add_filters(result, filters)
+ if props:
+ for name, value in props.items():
+ setattr(result, name, value)
return result
def add_handlers(self, logger, handlers):
@@ -795,7 +802,7 @@ def dictConfig(config):
dictConfigClass(config).configure()
-def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
+def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
"""
Start up a socket server on the specified port, and listen for new
configurations.
@@ -804,6 +811,15 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
Returns a Thread object on which you can call start() to start the server,
and which you can join() when appropriate. To stop the server, call
stopListening().
+
+ Use the ``verify`` argument to verify any bytes received across the wire
+ from a client. If specified, it should be a callable which receives a
+ single argument - the bytes of configuration data received across the
+ network - and it should return either ``None``, to indicate that the
+ passed in bytes could not be verified and should be discarded, or a
+ byte string which is then passed to the configuration machinery as
+ normal. Note that you can return transformed bytes, e.g. by decrypting
+ the bytes passed in.
"""
if not thread: #pragma: no cover
raise NotImplementedError("listen() needs threading to work")
@@ -831,25 +847,26 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + conn.recv(slen - len(chunk))
- chunk = chunk.decode("utf-8")
- try:
- import json
- d =json.loads(chunk)
- assert isinstance(d, dict)
- dictConfig(d)
- except:
- #Apply new configuration.
-
- file = io.StringIO(chunk)
+ if self.server.verify is not None:
+ chunk = self.server.verify(chunk)
+ if chunk is not None: # verified, can process
+ chunk = chunk.decode("utf-8")
try:
- fileConfig(file)
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
- traceback.print_exc()
+ import json
+ d =json.loads(chunk)
+ assert isinstance(d, dict)
+ dictConfig(d)
+ except Exception:
+ #Apply new configuration.
+
+ file = io.StringIO(chunk)
+ try:
+ fileConfig(file)
+ except Exception:
+ traceback.print_exc()
if self.server.ready:
self.server.ready.set()
- except socket.error as e:
+ except OSError as e:
if not isinstance(e.args, tuple):
raise
else:
@@ -865,13 +882,14 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
allow_reuse_address = 1
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
- handler=None, ready=None):
+ handler=None, ready=None, verify=None):
ThreadingTCPServer.__init__(self, (host, port), handler)
logging._acquireLock()
self.abort = 0
logging._releaseLock()
self.timeout = 1
self.ready = ready
+ self.verify = verify
def serve_until_stopped(self):
import select
@@ -889,16 +907,18 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
class Server(threading.Thread):
- def __init__(self, rcvr, hdlr, port):
+ def __init__(self, rcvr, hdlr, port, verify):
super(Server, self).__init__()
self.rcvr = rcvr
self.hdlr = hdlr
self.port = port
+ self.verify = verify
self.ready = threading.Event()
def run(self):
server = self.rcvr(port=self.port, handler=self.hdlr,
- ready=self.ready)
+ ready=self.ready,
+ verify=self.verify)
if self.port == 0:
self.port = server.server_address[1]
self.ready.set()
@@ -908,7 +928,7 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
logging._releaseLock()
server.serve_until_stopped()
- return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
+ return Server(ConfigSocketReceiver, ConfigStreamHandler, port, verify)
def stopListening():
"""
diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py
index ddec7dd9d0..b2e7d445cf 100644
--- a/Lib/logging/handlers.py
+++ b/Lib/logging/handlers.py
@@ -72,9 +72,7 @@ class BaseRotatingHandler(logging.FileHandler):
if self.shouldRollover(record):
self.doRollover()
logging.FileHandler.emit(self, record)
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
+ except Exception:
self.handleError(record)
def rotation_filename(self, default_name):
@@ -201,11 +199,12 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
If backupCount is > 0, when rollover is done, no more than backupCount
files are kept - the oldest ones are deleted.
"""
- def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
+ def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):
BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
self.when = when.upper()
self.backupCount = backupCount
self.utc = utc
+ self.atTime = atTime
# Calculate the real rollover interval, which is just the number of
# seconds between rollovers. Also set the filename suffix used when
# a rollover occurs. Current 'when' events supported:
@@ -275,9 +274,22 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
currentHour = t[3]
currentMinute = t[4]
currentSecond = t[5]
- # r is the number of seconds left between now and midnight
- r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
- currentSecond)
+ currentDay = t[6]
+ # r is the number of seconds left between now and the next rotation
+ if self.atTime is None:
+ rotate_ts = _MIDNIGHT
+ else:
+ rotate_ts = ((self.atTime.hour * 60 + self.atTime.minute)*60 +
+ self.atTime.second)
+
+ r = rotate_ts - ((currentHour * 60 + currentMinute) * 60 +
+ currentSecond)
+ if r < 0:
+ # Rotate time is before the current time (for example when
+ # self.rotateAt is 13:45 and it now 14:15), rotation is
+ # tomorrow.
+ r += _MIDNIGHT
+ currentDay = (currentDay + 1) % 7
result = currentTime + r
# If we are rolling over on a certain day, add in the number of days until
# the next rollover, but offset by 1 since we just calculated the time
@@ -295,7 +307,7 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
# This is because the above time calculation takes us to midnight on this
# day, i.e. the start of the next day.
if self.when.startswith('W'):
- day = t[6] # 0 is Monday
+ day = currentDay # 0 is Monday
if day != self.dayOfWeek:
if day < self.dayOfWeek:
daysToWait = self.dayOfWeek - day
@@ -444,11 +456,8 @@ class WatchedFileHandler(logging.FileHandler):
try:
# stat the file by path, checking for existence
sres = os.stat(self.baseFilename)
- except OSError as err:
- if err.errno == errno.ENOENT:
- sres = None
- else:
- raise
+ except FileNotFoundError:
+ sres = None
# compare file system stat with that of our stream file handle
if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino:
if self.stream is not None:
@@ -485,6 +494,10 @@ class SocketHandler(logging.Handler):
logging.Handler.__init__(self)
self.host = host
self.port = port
+ if port is None:
+ self.address = host
+ else:
+ self.address = (host, port)
self.sock = None
self.closeOnError = False
self.retryTime = None
@@ -500,15 +513,17 @@ class SocketHandler(logging.Handler):
A factory method which allows subclasses to define the precise
type of socket they want.
"""
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- if hasattr(s, 'settimeout'):
- s.settimeout(timeout)
- try:
- s.connect((self.host, self.port))
- return s
- except socket.error:
- s.close()
- raise
+ if self.port is not None:
+ result = socket.create_connection(self.address, timeout=timeout)
+ else:
+ result = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ result.settimeout(timeout)
+ try:
+ result.connect(self.address)
+ except OSError:
+ result.close() # Issue 19182
+ raise
+ return result
def createSocket(self):
"""
@@ -528,7 +543,7 @@ class SocketHandler(logging.Handler):
try:
self.sock = self.makeSocket()
self.retryTime = None # next time, no delay before trying
- except socket.error:
+ except OSError:
#Creation failed, so set the retry time and return.
if self.retryTime is None:
self.retryPeriod = self.retryStart
@@ -552,16 +567,8 @@ class SocketHandler(logging.Handler):
#but are still unable to connect.
if self.sock:
try:
- if hasattr(self.sock, "sendall"):
- self.sock.sendall(s)
- else: #pragma: no cover
- sentsofar = 0
- left = len(s)
- while left > 0:
- sent = self.sock.send(s[sentsofar:])
- sentsofar = sentsofar + sent
- left = left - sent
- except socket.error: #pragma: no cover
+ self.sock.sendall(s)
+ except OSError: #pragma: no cover
self.sock.close()
self.sock = None # so we can call createSocket next time
@@ -611,9 +618,7 @@ class SocketHandler(logging.Handler):
try:
s = self.makePickle(record)
self.send(s)
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
+ except Exception:
self.handleError(record)
def close(self):
@@ -652,7 +657,11 @@ class DatagramHandler(SocketHandler):
The factory method of SocketHandler is here overridden to create
a UDP socket (SOCK_DGRAM).
"""
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ if self.port is None:
+ family = socket.AF_UNIX
+ else:
+ family = socket.AF_INET
+ s = socket.socket(family, socket.SOCK_DGRAM)
return s
def send(self, s):
@@ -665,7 +674,7 @@ class DatagramHandler(SocketHandler):
"""
if self.sock is None:
self.createSocket()
- self.sock.sendto(s, (self.host, self.port))
+ self.sock.sendto(s, self.address)
class SysLogHandler(logging.Handler):
"""
@@ -777,7 +786,11 @@ class SysLogHandler(logging.Handler):
If address is specified as a string, a UNIX socket is used. To log to a
local syslogd, "SysLogHandler(address="/dev/log")" can be used.
- If facility is not specified, LOG_USER is used.
+ If facility is not specified, LOG_USER is used. If socktype is
+ specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific
+ socket type will be used. For Unix sockets, you can also specify a
+ socktype of None, in which case socket.SOCK_DGRAM will be used, falling
+ back to socket.SOCK_STREAM.
"""
logging.Handler.__init__(self)
@@ -807,7 +820,7 @@ class SysLogHandler(logging.Handler):
self.socket.connect(address)
# it worked, so set self.socktype to the used type
self.socktype = use_socktype
- except socket.error:
+ except OSError:
self.socket.close()
if self.socktype is not None:
# user didn't specify falling back, so fail
@@ -818,7 +831,7 @@ class SysLogHandler(logging.Handler):
self.socket.connect(address)
# it worked, so set self.socktype to the used type
self.socktype = use_socktype
- except socket.error:
+ except OSError:
self.socket.close()
raise
@@ -871,10 +884,9 @@ class SysLogHandler(logging.Handler):
msg = self.ident + msg
if self.append_nul:
msg += '\000'
- """
- We need to convert record level to lowercase, maybe this will
- change in the future.
- """
+
+ # We need to convert record level to lowercase, maybe this will
+ # change in the future.
prio = '<%d>' % self.encodePriority(self.facility,
self.mapPriority(record.levelname))
prio = prio.encode('utf-8')
@@ -885,7 +897,7 @@ class SysLogHandler(logging.Handler):
if self.unixsocket:
try:
self.socket.send(msg)
- except socket.error:
+ except OSError:
self.socket.close()
self._connect_unixsocket(self.address)
self.socket.send(msg)
@@ -893,9 +905,7 @@ class SysLogHandler(logging.Handler):
self.socket.sendto(msg, self.address)
else:
self.socket.sendall(msg)
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
+ except Exception:
self.handleError(record)
class SMTPHandler(logging.Handler):
@@ -973,9 +983,7 @@ class SMTPHandler(logging.Handler):
smtp.login(self.username, self.password)
smtp.sendmail(self.fromaddr, self.toaddrs, msg)
smtp.quit()
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
+ except Exception:
self.handleError(record)
class NTEventLogHandler(logging.Handler):
@@ -1060,9 +1068,7 @@ class NTEventLogHandler(logging.Handler):
type = self.getEventType(record)
msg = self.format(record)
self._welu.ReportEvent(self.appname, id, cat, type, [msg])
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
+ except Exception:
self.handleError(record)
def close(self):
@@ -1147,9 +1153,7 @@ class HTTPHandler(logging.Handler):
if self.method == "POST":
h.send(data.encode('utf-8'))
h.getresponse() #can't do anything with the result
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
+ except Exception:
self.handleError(record)
class BufferingHandler(logging.Handler):
@@ -1329,9 +1333,7 @@ class QueueHandler(logging.Handler):
"""
try:
self.enqueue(self.prepare(record))
- except (KeyboardInterrupt, SystemExit): #pragma: no cover
- raise
- except:
+ except Exception:
self.handleError(record)
if threading:
diff --git a/Lib/lzma.py b/Lib/lzma.py
index 1a1b065f8e..011ad271fb 100644
--- a/Lib/lzma.py
+++ b/Lib/lzma.py
@@ -54,9 +54,9 @@ class LZMAFile(io.BufferedIOBase):
bytes object), in which case the named file is opened, or it can
be an existing file object to read from or write to.
- mode can be "r" for reading (default), "w" for (over)writing, or
- "a" for appending. These can equivalently be given as "rb", "wb",
- and "ab" respectively.
+ mode can be "r" for reading (default), "w" for (over)writing,
+ "x" for creating exclusively, or "a" for appending. These can
+ equivalently be given as "rb", "wb", "xb" and "ab" respectively.
format specifies the container format to use for the file.
If mode is "r", this defaults to FORMAT_AUTO. Otherwise, the
@@ -110,8 +110,9 @@ class LZMAFile(io.BufferedIOBase):
# stream will need a separate decompressor object.
self._init_args = {"format":format, "filters":filters}
self._decompressor = LZMADecompressor(**self._init_args)
- self._buffer = None
- elif mode in ("w", "wb", "a", "ab"):
+ self._buffer = b""
+ self._buffer_offset = 0
+ elif mode in ("w", "wb", "a", "ab", "x", "xb"):
if format is None:
format = FORMAT_XZ
mode_code = _MODE_WRITE
@@ -143,7 +144,7 @@ class LZMAFile(io.BufferedIOBase):
try:
if self._mode in (_MODE_READ, _MODE_READ_EOF):
self._decompressor = None
- self._buffer = None
+ self._buffer = b""
elif self._mode == _MODE_WRITE:
self._fp.write(self._compressor.flush())
self._compressor = None
@@ -187,15 +188,18 @@ class LZMAFile(io.BufferedIOBase):
raise ValueError("I/O operation on closed file")
def _check_can_read(self):
- if not self.readable():
+ if self._mode not in (_MODE_READ, _MODE_READ_EOF):
+ self._check_not_closed()
raise io.UnsupportedOperation("File not open for reading")
def _check_can_write(self):
- if not self.writable():
+ if self._mode != _MODE_WRITE:
+ self._check_not_closed()
raise io.UnsupportedOperation("File not open for writing")
def _check_can_seek(self):
- if not self.readable():
+ if self._mode not in (_MODE_READ, _MODE_READ_EOF):
+ self._check_not_closed()
raise io.UnsupportedOperation("Seeking is only supported "
"on files open for reading")
if not self._fp.seekable():
@@ -204,16 +208,13 @@ class LZMAFile(io.BufferedIOBase):
# Fill the readahead buffer if it is empty. Returns False on EOF.
def _fill_buffer(self):
+ if self._mode == _MODE_READ_EOF:
+ return False
# Depending on the input data, our call to the decompressor may not
# return any data. In this case, try again after reading another block.
- while True:
- if self._buffer:
- return True
-
- if self._decompressor.unused_data:
- rawblock = self._decompressor.unused_data
- else:
- rawblock = self._fp.read(_BUFFER_SIZE)
+ while self._buffer_offset == len(self._buffer):
+ rawblock = (self._decompressor.unused_data or
+ self._fp.read(_BUFFER_SIZE))
if not rawblock:
if self._decompressor.eof:
@@ -229,30 +230,48 @@ class LZMAFile(io.BufferedIOBase):
self._decompressor = LZMADecompressor(**self._init_args)
self._buffer = self._decompressor.decompress(rawblock)
+ self._buffer_offset = 0
+ return True
# Read data until EOF.
# If return_data is false, consume the data without returning it.
def _read_all(self, return_data=True):
+ # The loop assumes that _buffer_offset is 0. Ensure that this is true.
+ self._buffer = self._buffer[self._buffer_offset:]
+ self._buffer_offset = 0
+
blocks = []
while self._fill_buffer():
if return_data:
blocks.append(self._buffer)
self._pos += len(self._buffer)
- self._buffer = None
+ self._buffer = b""
if return_data:
return b"".join(blocks)
# Read a block of up to n bytes.
# If return_data is false, consume the data without returning it.
def _read_block(self, n, return_data=True):
+ # If we have enough data buffered, return immediately.
+ end = self._buffer_offset + n
+ if end <= len(self._buffer):
+ data = self._buffer[self._buffer_offset : end]
+ self._buffer_offset = end
+ self._pos += len(data)
+ return data if return_data else None
+
+ # The loop assumes that _buffer_offset is 0. Ensure that this is true.
+ self._buffer = self._buffer[self._buffer_offset:]
+ self._buffer_offset = 0
+
blocks = []
while n > 0 and self._fill_buffer():
if n < len(self._buffer):
data = self._buffer[:n]
- self._buffer = self._buffer[n:]
+ self._buffer_offset = n
else:
data = self._buffer
- self._buffer = None
+ self._buffer = b""
if return_data:
blocks.append(data)
self._pos += len(data)
@@ -267,9 +286,9 @@ class LZMAFile(io.BufferedIOBase):
The exact number of bytes returned is unspecified.
"""
self._check_can_read()
- if self._mode == _MODE_READ_EOF or not self._fill_buffer():
+ if not self._fill_buffer():
return b""
- return self._buffer
+ return self._buffer[self._buffer_offset:]
def read(self, size=-1):
"""Read up to size uncompressed bytes from the file.
@@ -278,7 +297,7 @@ class LZMAFile(io.BufferedIOBase):
Returns b"" if the file is already at EOF.
"""
self._check_can_read()
- if self._mode == _MODE_READ_EOF or size == 0:
+ if size == 0:
return b""
elif size < 0:
return self._read_all()
@@ -295,18 +314,40 @@ class LZMAFile(io.BufferedIOBase):
# this does not give enough data for the decompressor to make progress.
# In this case we make multiple reads, to avoid returning b"".
self._check_can_read()
- if (size == 0 or self._mode == _MODE_READ_EOF or
- not self._fill_buffer()):
+ if (size == 0 or
+ # Only call _fill_buffer() if the buffer is actually empty.
+ # This gives a significant speedup if *size* is small.
+ (self._buffer_offset == len(self._buffer) and not self._fill_buffer())):
return b""
- if 0 < size < len(self._buffer):
- data = self._buffer[:size]
- self._buffer = self._buffer[size:]
+ if size > 0:
+ data = self._buffer[self._buffer_offset :
+ self._buffer_offset + size]
+ self._buffer_offset += len(data)
else:
- data = self._buffer
- self._buffer = None
+ data = self._buffer[self._buffer_offset:]
+ self._buffer = b""
+ self._buffer_offset = 0
self._pos += len(data)
return data
+ def readline(self, size=-1):
+ """Read a line of uncompressed bytes from the file.
+
+ The terminating newline (if present) is retained. If size is
+ non-negative, no more than size bytes will be read (in which
+ case the line may be incomplete). Returns b'' if already at EOF.
+ """
+ self._check_can_read()
+ # Shortcut for the common case - the whole line is in the buffer.
+ if size < 0:
+ end = self._buffer.find(b"\n", self._buffer_offset) + 1
+ if end > 0:
+ line = self._buffer[self._buffer_offset : end]
+ self._buffer_offset = end
+ self._pos += len(line)
+ return line
+ return io.BufferedIOBase.readline(self, size)
+
def write(self, data):
"""Write a bytes object to the file.
@@ -326,7 +367,8 @@ class LZMAFile(io.BufferedIOBase):
self._mode = _MODE_READ
self._pos = 0
self._decompressor = LZMADecompressor(**self._init_args)
- self._buffer = None
+ self._buffer = b""
+ self._buffer_offset = 0
def seek(self, offset, whence=0):
"""Change the file position.
@@ -365,8 +407,7 @@ class LZMAFile(io.BufferedIOBase):
offset -= self._pos
# Read and discard data until we reach the desired position.
- if self._mode != _MODE_READ_EOF:
- self._read_block(offset, return_data=False)
+ self._read_block(offset, return_data=False)
return self._pos
@@ -381,23 +422,25 @@ def open(filename, mode="rb", *,
encoding=None, errors=None, newline=None):
"""Open an LZMA-compressed file in binary or text mode.
- filename can be either an actual file name (given as a str or bytes object),
- in which case the named file is opened, or it can be an existing file object
- to read from or write to.
+ filename can be either an actual file name (given as a str or bytes
+ object), in which case the named file is opened, or it can be an
+ existing file object to read from or write to.
- The mode argument can be "r", "rb" (default), "w", "wb", "a", or "ab" for
- binary mode, or "rt", "wt" or "at" for text mode.
+ The mode argument can be "r", "rb" (default), "w", "wb", "x", "xb",
+ "a", or "ab" for binary mode, or "rt", "wt", "xt", or "at" for text
+ mode.
- The format, check, preset and filters arguments specify the compression
- settings, as for LZMACompressor, LZMADecompressor and LZMAFile.
+ The format, check, preset and filters arguments specify the
+ compression settings, as for LZMACompressor, LZMADecompressor and
+ LZMAFile.
- For binary mode, this function is equivalent to the LZMAFile constructor:
- LZMAFile(filename, mode, ...). In this case, the encoding, errors and
- newline arguments must not be provided.
+ For binary mode, this function is equivalent to the LZMAFile
+ constructor: LZMAFile(filename, mode, ...). In this case, the
+ encoding, errors and newline arguments must not be provided.
For text mode, a LZMAFile object is created, and wrapped in an
- io.TextIOWrapper instance with the specified encoding, error handling
- behavior, and line ending(s).
+ io.TextIOWrapper instance with the specified encoding, error
+ handling behavior, and line ending(s).
"""
if "t" in mode:
@@ -427,7 +470,7 @@ def compress(data, format=FORMAT_XZ, check=-1, preset=None, filters=None):
Refer to LZMACompressor's docstring for a description of the
optional arguments *format*, *check*, *preset* and *filters*.
- For incremental compression, use an LZMACompressor object instead.
+ For incremental compression, use an LZMACompressor instead.
"""
comp = LZMACompressor(format, check, preset, filters)
return comp.compress(data) + comp.flush()
@@ -439,7 +482,7 @@ def decompress(data, format=FORMAT_AUTO, memlimit=None, filters=None):
Refer to LZMADecompressor's docstring for a description of the
optional arguments *format*, *check* and *filters*.
- For incremental decompression, use a LZMADecompressor object instead.
+ For incremental decompression, use an LZMADecompressor instead.
"""
results = []
while True:
diff --git a/Lib/macpath.py b/Lib/macpath.py
index 1615d9122a..d34f9e944c 100644
--- a/Lib/macpath.py
+++ b/Lib/macpath.py
@@ -127,7 +127,7 @@ def lexists(path):
try:
st = os.lstat(path)
- except os.error:
+ except OSError:
return False
return True
diff --git a/Lib/mailbox.py b/Lib/mailbox.py
index d59dae85cd..5f591bc349 100644
--- a/Lib/mailbox.py
+++ b/Lib/mailbox.py
@@ -22,9 +22,6 @@ import email.generator
import io
import contextlib
try:
- if sys.platform == 'os2emx':
- # OS/2 EMX fcntl() not adequate
- raise ImportError
import fcntl
except ImportError:
fcntl = None
@@ -341,11 +338,8 @@ class Maildir(Mailbox):
# This overrides an inapplicable implementation in the superclass.
try:
self.remove(key)
- except KeyError:
+ except (KeyError, FileNotFoundError):
pass
- except OSError as e:
- if e.errno != errno.ENOENT:
- raise
def __setitem__(self, key, message):
"""Replace the keyed message; raise KeyError if it doesn't exist."""
@@ -377,14 +371,11 @@ class Maildir(Mailbox):
def get_message(self, key):
"""Return a Message representation or raise a KeyError."""
subpath = self._lookup(key)
- f = open(os.path.join(self._path, subpath), 'rb')
- try:
+ with open(os.path.join(self._path, subpath), 'rb') as f:
if self._factory:
msg = self._factory(f)
else:
msg = MaildirMessage(f)
- finally:
- f.close()
subdir, name = os.path.split(subpath)
msg.set_subdir(subdir)
if self.colon in name:
@@ -394,11 +385,8 @@ class Maildir(Mailbox):
def get_bytes(self, key):
"""Return a bytes representation or raise a KeyError."""
- f = open(os.path.join(self._path, self._lookup(key)), 'rb')
- try:
+ with open(os.path.join(self._path, self._lookup(key)), 'rb') as f:
return f.read().replace(linesep, b'\n')
- finally:
- f.close()
def get_file(self, key):
"""Return a file-like representation or raise a KeyError."""
@@ -510,16 +498,12 @@ class Maildir(Mailbox):
path = os.path.join(self._path, 'tmp', uniq)
try:
os.stat(path)
- except OSError as e:
- if e.errno == errno.ENOENT:
- Maildir._count += 1
- try:
- return _create_carefully(path)
- except OSError as e:
- if e.errno != errno.EEXIST:
- raise
- else:
- raise
+ except FileNotFoundError:
+ Maildir._count += 1
+ try:
+ return _create_carefully(path)
+ except FileExistsError:
+ pass
# Fall through to here if stat succeeded or open raised EEXIST.
raise ExternalClashError('Name clash prevented file creation: %s' %
@@ -596,7 +580,7 @@ class _singlefileMailbox(Mailbox):
Mailbox.__init__(self, path, factory, create)
try:
f = open(self._path, 'rb+')
- except IOError as e:
+ except OSError as e:
if e.errno == errno.ENOENT:
if create:
f = open(self._path, 'wb+')
@@ -639,8 +623,7 @@ class _singlefileMailbox(Mailbox):
def iterkeys(self):
"""Return an iterator over keys."""
self._lookup()
- for key in self._toc.keys():
- yield key
+ yield from self._toc.keys()
def __contains__(self, key):
"""Return True if the keyed message exists, False otherwise."""
@@ -718,13 +701,9 @@ class _singlefileMailbox(Mailbox):
os.chmod(new_file.name, mode)
try:
os.rename(new_file.name, self._path)
- except OSError as e:
- if e.errno == errno.EEXIST or \
- (os.name == 'os2' and e.errno == errno.EACCES):
- os.remove(self._path)
- os.rename(new_file.name, self._path)
- else:
- raise
+ except FileExistsError:
+ os.remove(self._path)
+ os.rename(new_file.name, self._path)
self._file = open(self._path, 'rb+')
self._toc = new_toc
self._pending = False
@@ -1001,7 +980,7 @@ class MH(Mailbox):
path = os.path.join(self._path, str(key))
try:
f = open(path, 'rb+')
- except IOError as e:
+ except OSError as e:
if e.errno == errno.ENOENT:
raise KeyError('No message with key: %s' % key)
else:
@@ -1015,7 +994,7 @@ class MH(Mailbox):
path = os.path.join(self._path, str(key))
try:
f = open(path, 'rb+')
- except IOError as e:
+ except OSError as e:
if e.errno == errno.ENOENT:
raise KeyError('No message with key: %s' % key)
else:
@@ -1041,12 +1020,12 @@ class MH(Mailbox):
f = open(os.path.join(self._path, str(key)), 'rb+')
else:
f = open(os.path.join(self._path, str(key)), 'rb')
- except IOError as e:
+ except OSError as e:
if e.errno == errno.ENOENT:
raise KeyError('No message with key: %s' % key)
else:
raise
- try:
+ with f:
if self._locked:
_lock_file(f)
try:
@@ -1054,8 +1033,6 @@ class MH(Mailbox):
finally:
if self._locked:
_unlock_file(f)
- finally:
- f.close()
for name, key_list in self.get_sequences().items():
if key in key_list:
msg.add_sequence(name)
@@ -1068,12 +1045,12 @@ class MH(Mailbox):
f = open(os.path.join(self._path, str(key)), 'rb+')
else:
f = open(os.path.join(self._path, str(key)), 'rb')
- except IOError as e:
+ except OSError as e:
if e.errno == errno.ENOENT:
raise KeyError('No message with key: %s' % key)
else:
raise
- try:
+ with f:
if self._locked:
_lock_file(f)
try:
@@ -1081,14 +1058,12 @@ class MH(Mailbox):
finally:
if self._locked:
_unlock_file(f)
- finally:
- f.close()
def get_file(self, key):
"""Return a file-like representation or raise a KeyError."""
try:
f = open(os.path.join(self._path, str(key)), 'rb')
- except IOError as e:
+ except OSError as e:
if e.errno == errno.ENOENT:
raise KeyError('No message with key: %s' % key)
else:
@@ -2081,7 +2056,7 @@ def _lock_file(f, dotlock=True):
if fcntl:
try:
fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
- except IOError as e:
+ except OSError as e:
if e.errno in (errno.EAGAIN, errno.EACCES, errno.EROFS):
raise ExternalClashError('lockf: lock unavailable: %s' %
f.name)
@@ -2091,7 +2066,7 @@ def _lock_file(f, dotlock=True):
try:
pre_lock = _create_temporary(f.name + '.lock')
pre_lock.close()
- except IOError as e:
+ except OSError as e:
if e.errno in (errno.EACCES, errno.EROFS):
return # Without write access, just skip dotlocking.
else:
@@ -2104,14 +2079,10 @@ def _lock_file(f, dotlock=True):
else:
os.rename(pre_lock.name, f.name + '.lock')
dotlock_done = True
- except OSError as e:
- if e.errno == errno.EEXIST or \
- (os.name == 'os2' and e.errno == errno.EACCES):
- os.remove(pre_lock.name)
- raise ExternalClashError('dot lock unavailable: %s' %
- f.name)
- else:
- raise
+ except FileExistsError:
+ os.remove(pre_lock.name)
+ raise ExternalClashError('dot lock unavailable: %s' %
+ f.name)
except:
if fcntl:
fcntl.lockf(f, fcntl.LOCK_UN)
diff --git a/Lib/mailcap.py b/Lib/mailcap.py
index 99f4958bf7..bd61b0b0ae 100644
--- a/Lib/mailcap.py
+++ b/Lib/mailcap.py
@@ -20,7 +20,7 @@ def getcaps():
for mailcap in listmailcapfiles():
try:
fp = open(mailcap, 'r')
- except IOError:
+ except OSError:
continue
morecaps = readmailcapfile(fp)
fp.close()
diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py
index 3e742a755b..d7161f8134 100644
--- a/Lib/mimetypes.py
+++ b/Lib/mimetypes.py
@@ -361,7 +361,7 @@ def init(files=None):
def read_mime_types(file):
try:
f = open(file)
- except IOError:
+ except OSError:
return None
db = MimeTypes()
db.readfp(f, True)
diff --git a/Lib/modulefinder.py b/Lib/modulefinder.py
index a197a0d358..ebd068aaa2 100644
--- a/Lib/modulefinder.py
+++ b/Lib/modulefinder.py
@@ -1,13 +1,16 @@
"""Find modules used by a script, using introspection."""
import dis
-import imp
import importlib.machinery
import marshal
import os
import sys
import types
import struct
+import warnings
+with warnings.catch_warnings():
+ warnings.simplefilter('ignore', PendingDeprecationWarning)
+ import imp
# XXX Clean up once str8's cstor matches bytes.
LOAD_CONST = bytes([dis.opname.index('LOAD_CONST')])
@@ -229,7 +232,7 @@ class ModuleFinder:
for dir in m.__path__:
try:
names = os.listdir(dir)
- except os.error:
+ except OSError:
self.msg(2, "can't list directory", dir)
continue
for name in names:
diff --git a/Lib/multiprocessing/__init__.py b/Lib/multiprocessing/__init__.py
index 1f3e67c9b8..86df638370 100644
--- a/Lib/multiprocessing/__init__.py
+++ b/Lib/multiprocessing/__init__.py
@@ -8,260 +8,31 @@
# subpackage 'multiprocessing.dummy' has the same API but is a simple
# wrapper for 'threading'.
#
-# Try calling `multiprocessing.doc.main()` to read the html
-# documentation in a webbrowser.
-#
-#
# Copyright (c) 2006-2008, R Oudkerk
# Licensed to PSF under a Contributor Agreement.
#
-__version__ = '0.70a1'
-
-__all__ = [
- 'Process', 'current_process', 'active_children', 'freeze_support',
- 'Manager', 'Pipe', 'cpu_count', 'log_to_stderr', 'get_logger',
- 'allow_connection_pickling', 'BufferTooShort', 'TimeoutError',
- 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition',
- 'Event', 'Barrier', 'Queue', 'SimpleQueue', 'JoinableQueue', 'Pool',
- 'Value', 'Array', 'RawValue', 'RawArray', 'SUBDEBUG', 'SUBWARNING',
- ]
-
-__author__ = 'R. Oudkerk (r.m.oudkerk@gmail.com)'
-
-#
-# Imports
-#
-
-import os
import sys
-
-from multiprocessing.process import Process, current_process, active_children
-from multiprocessing.util import SUBDEBUG, SUBWARNING
+from . import context
#
-# Exceptions
+# Copy stuff from default context
#
-class ProcessError(Exception):
- pass
-
-class BufferTooShort(ProcessError):
- pass
-
-class TimeoutError(ProcessError):
- pass
-
-class AuthenticationError(ProcessError):
- pass
-
-import _multiprocessing
-
-#
-# Definitions not depending on native semaphores
-#
-
-def Manager():
- '''
- Returns a manager associated with a running server process
-
- The managers methods such as `Lock()`, `Condition()` and `Queue()`
- can be used to create shared objects.
- '''
- from multiprocessing.managers import SyncManager
- m = SyncManager()
- m.start()
- return m
-
-def Pipe(duplex=True):
- '''
- Returns two connection object connected by a pipe
- '''
- from multiprocessing.connection import Pipe
- return Pipe(duplex)
-
-def cpu_count():
- '''
- Returns the number of CPUs in the system
- '''
- if sys.platform == 'win32':
- try:
- num = int(os.environ['NUMBER_OF_PROCESSORS'])
- except (ValueError, KeyError):
- num = 0
- elif 'bsd' in sys.platform or sys.platform == 'darwin':
- comm = '/sbin/sysctl -n hw.ncpu'
- if sys.platform == 'darwin':
- comm = '/usr' + comm
- try:
- with os.popen(comm) as p:
- num = int(p.read())
- except ValueError:
- num = 0
- else:
- try:
- num = os.sysconf('SC_NPROCESSORS_ONLN')
- except (ValueError, OSError, AttributeError):
- num = 0
-
- if num >= 1:
- return num
- else:
- raise NotImplementedError('cannot determine number of cpus')
-
-def freeze_support():
- '''
- Check whether this is a fake forked process in a frozen executable.
- If so then run code specified by commandline and exit.
- '''
- if sys.platform == 'win32' and getattr(sys, 'frozen', False):
- from multiprocessing.forking import freeze_support
- freeze_support()
-
-def get_logger():
- '''
- Return package logger -- if it does not already exist then it is created
- '''
- from multiprocessing.util import get_logger
- return get_logger()
-
-def log_to_stderr(level=None):
- '''
- Turn on logging and add a handler which prints to stderr
- '''
- from multiprocessing.util import log_to_stderr
- return log_to_stderr(level)
-
-def allow_connection_pickling():
- '''
- Install support for sending connections and sockets between processes
- '''
- # This is undocumented. In previous versions of multiprocessing
- # its only effect was to make socket objects inheritable on Windows.
- import multiprocessing.connection
+globals().update((name, getattr(context._default_context, name))
+ for name in context._default_context.__all__)
+__all__ = context._default_context.__all__
#
-# Definitions depending on native semaphores
+# XXX These should not really be documented or public.
#
-def Lock():
- '''
- Returns a non-recursive lock object
- '''
- from multiprocessing.synchronize import Lock
- return Lock()
-
-def RLock():
- '''
- Returns a recursive lock object
- '''
- from multiprocessing.synchronize import RLock
- return RLock()
-
-def Condition(lock=None):
- '''
- Returns a condition object
- '''
- from multiprocessing.synchronize import Condition
- return Condition(lock)
-
-def Semaphore(value=1):
- '''
- Returns a semaphore object
- '''
- from multiprocessing.synchronize import Semaphore
- return Semaphore(value)
-
-def BoundedSemaphore(value=1):
- '''
- Returns a bounded semaphore object
- '''
- from multiprocessing.synchronize import BoundedSemaphore
- return BoundedSemaphore(value)
-
-def Event():
- '''
- Returns an event object
- '''
- from multiprocessing.synchronize import Event
- return Event()
-
-def Barrier(parties, action=None, timeout=None):
- '''
- Returns a barrier object
- '''
- from multiprocessing.synchronize import Barrier
- return Barrier(parties, action, timeout)
-
-def Queue(maxsize=0):
- '''
- Returns a queue object
- '''
- from multiprocessing.queues import Queue
- return Queue(maxsize)
+SUBDEBUG = 5
+SUBWARNING = 25
-def JoinableQueue(maxsize=0):
- '''
- Returns a queue object
- '''
- from multiprocessing.queues import JoinableQueue
- return JoinableQueue(maxsize)
-
-def SimpleQueue():
- '''
- Returns a queue object
- '''
- from multiprocessing.queues import SimpleQueue
- return SimpleQueue()
-
-def Pool(processes=None, initializer=None, initargs=(), maxtasksperchild=None):
- '''
- Returns a process pool object
- '''
- from multiprocessing.pool import Pool
- return Pool(processes, initializer, initargs, maxtasksperchild)
-
-def RawValue(typecode_or_type, *args):
- '''
- Returns a shared object
- '''
- from multiprocessing.sharedctypes import RawValue
- return RawValue(typecode_or_type, *args)
-
-def RawArray(typecode_or_type, size_or_initializer):
- '''
- Returns a shared array
- '''
- from multiprocessing.sharedctypes import RawArray
- return RawArray(typecode_or_type, size_or_initializer)
-
-def Value(typecode_or_type, *args, lock=True):
- '''
- Returns a synchronized shared object
- '''
- from multiprocessing.sharedctypes import Value
- return Value(typecode_or_type, *args, lock=lock)
-
-def Array(typecode_or_type, size_or_initializer, *, lock=True):
- '''
- Returns a synchronized shared array
- '''
- from multiprocessing.sharedctypes import Array
- return Array(typecode_or_type, size_or_initializer, lock=lock)
-
-#
#
+# Alias for main module -- will be reset by bootstrapping child processes
#
-if sys.platform == 'win32':
-
- def set_executable(executable):
- '''
- Sets the path to a python.exe or pythonw.exe binary used to run
- child processes on Windows instead of sys.executable.
- Useful for people embedding Python.
- '''
- from multiprocessing.forking import set_executable
- set_executable(executable)
-
- __all__ += ['set_executable']
+if '__main__' in sys.modules:
+ sys.modules['__mp_main__'] = sys.modules['__main__']
diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py
index 2a0bc2fa72..8ad6fd5f3e 100644
--- a/Lib/multiprocessing/connection.py
+++ b/Lib/multiprocessing/connection.py
@@ -12,8 +12,6 @@ __all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]
import io
import os
import sys
-import pickle
-import select
import socket
import struct
import errno
@@ -22,9 +20,13 @@ import tempfile
import itertools
import _multiprocessing
-from multiprocessing import current_process, AuthenticationError, BufferTooShort
-from multiprocessing.util import get_temp_dir, Finalize, sub_debug, debug
-from multiprocessing.forking import ForkingPickler
+
+from . import reduction
+from . import util
+
+from . import AuthenticationError, BufferTooShort
+from .reduction import ForkingPickler
+
try:
import _winapi
from _winapi import WAIT_OBJECT_0, WAIT_TIMEOUT, INFINITE
@@ -72,7 +74,7 @@ def arbitrary_address(family):
if family == 'AF_INET':
return ('localhost', 0)
elif family == 'AF_UNIX':
- return tempfile.mktemp(prefix='listener-', dir=get_temp_dir())
+ return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
elif family == 'AF_PIPE':
return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
(os.getpid(), next(_mmap_counter)))
@@ -132,22 +134,22 @@ class _ConnectionBase:
def _check_closed(self):
if self._handle is None:
- raise IOError("handle is closed")
+ raise OSError("handle is closed")
def _check_readable(self):
if not self._readable:
- raise IOError("connection is write-only")
+ raise OSError("connection is write-only")
def _check_writable(self):
if not self._writable:
- raise IOError("connection is read-only")
+ raise OSError("connection is read-only")
def _bad_message_length(self):
if self._writable:
self._readable = False
else:
self.close()
- raise IOError("bad message length")
+ raise OSError("bad message length")
@property
def closed(self):
@@ -202,9 +204,7 @@ class _ConnectionBase:
"""Send a (picklable) object"""
self._check_closed()
self._check_writable()
- buf = io.BytesIO()
- ForkingPickler(buf, pickle.HIGHEST_PROTOCOL).dump(obj)
- self._send_bytes(buf.getbuffer())
+ self._send_bytes(ForkingPickler.dumps(obj))
def recv_bytes(self, maxlength=None):
"""
@@ -249,7 +249,7 @@ class _ConnectionBase:
self._check_closed()
self._check_readable()
buf = self._recv_bytes()
- return pickle.loads(buf.getbuffer())
+ return ForkingPickler.loads(buf.getbuffer())
def poll(self, timeout=0.0):
"""Whether there is any input available to be read"""
@@ -317,7 +317,7 @@ if _winapi:
return f
elif err == _winapi.ERROR_MORE_DATA:
return self._get_more_data(ov, maxsize)
- except IOError as e:
+ except OSError as e:
if e.winerror == _winapi.ERROR_BROKEN_PIPE:
raise EOFError
else:
@@ -389,7 +389,7 @@ class Connection(_ConnectionBase):
if remaining == size:
raise EOFError
else:
- raise IOError("got end of file during message")
+ raise OSError("got end of file during message")
buf.write(chunk)
remaining -= n
return buf
@@ -449,7 +449,7 @@ class Listener(object):
Returns a `Connection` object.
'''
if self._listener is None:
- raise IOError('listener is closed')
+ raise OSError('listener is closed')
c = self._listener.accept()
if self._authkey:
deliver_challenge(c, self._authkey)
@@ -535,7 +535,9 @@ else:
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
_winapi.PIPE_WAIT,
- 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
+ 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
+ # default security descriptor: the handle cannot be inherited
+ _winapi.NULL
)
h2 = _winapi.CreateFile(
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
@@ -580,7 +582,7 @@ class SocketListener(object):
self._last_accepted = None
if family == 'AF_UNIX':
- self._unlink = Finalize(
+ self._unlink = util.Finalize(
self, os.unlink, args=(address,), exitpriority=0
)
else:
@@ -628,8 +630,8 @@ if sys.platform == 'win32':
self._handle_queue = [self._new_handle(first=True)]
self._last_accepted = None
- sub_debug('listener created with address=%r', self._address)
- self.close = Finalize(
+ util.sub_debug('listener created with address=%r', self._address)
+ self.close = util.Finalize(
self, PipeListener._finalize_pipe_listener,
args=(self._handle_queue, self._address), exitpriority=0
)
@@ -671,7 +673,7 @@ if sys.platform == 'win32':
@staticmethod
def _finalize_pipe_listener(queue, address):
- sub_debug('closing listener with address=%r', address)
+ util.sub_debug('closing listener with address=%r', address)
for handle in queue:
_winapi.CloseHandle(handle)
@@ -688,7 +690,7 @@ if sys.platform == 'win32':
0, _winapi.NULL, _winapi.OPEN_EXISTING,
_winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
)
- except WindowsError as e:
+ except OSError as e:
if e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
_winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
raise
@@ -717,7 +719,7 @@ def deliver_challenge(connection, authkey):
assert isinstance(authkey, bytes)
message = os.urandom(MESSAGE_LENGTH)
connection.send_bytes(CHALLENGE + message)
- digest = hmac.new(authkey, message).digest()
+ digest = hmac.new(authkey, message, 'md5').digest()
response = connection.recv_bytes(256) # reject large message
if response == digest:
connection.send_bytes(WELCOME)
@@ -731,7 +733,7 @@ def answer_challenge(connection, authkey):
message = connection.recv_bytes(256) # reject large message
assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message
message = message[len(CHALLENGE):]
- digest = hmac.new(authkey, message).digest()
+ digest = hmac.new(authkey, message, 'md5').digest()
connection.send_bytes(digest)
response = connection.recv_bytes(256) # reject large message
if response != WELCOME:
@@ -874,28 +876,15 @@ if sys.platform == 'win32':
else:
- if hasattr(select, 'poll'):
- def _poll(fds, timeout):
- if timeout is not None:
- timeout = int(timeout * 1000) # timeout is in milliseconds
- fd_map = {}
- pollster = select.poll()
- for fd in fds:
- pollster.register(fd, select.POLLIN)
- if hasattr(fd, 'fileno'):
- fd_map[fd.fileno()] = fd
- else:
- fd_map[fd] = fd
- ls = []
- for fd, event in pollster.poll(timeout):
- if event & select.POLLNVAL:
- raise ValueError('invalid file descriptor %i' % fd)
- ls.append(fd_map[fd])
- return ls
- else:
- def _poll(fds, timeout):
- return select.select(fds, [], [], timeout)[0]
+ import selectors
+ # poll/select have the advantage of not requiring any extra file
+ # descriptor, contrarily to epoll/kqueue (also, they require a single
+ # syscall).
+ if hasattr(selectors, 'PollSelector'):
+ _WaitSelector = selectors.PollSelector
+ else:
+ _WaitSelector = selectors.SelectSelector
def wait(object_list, timeout=None):
'''
@@ -903,34 +892,54 @@ else:
Returns list of those objects in object_list which are ready/readable.
'''
- if timeout is not None:
- if timeout <= 0:
- return _poll(object_list, 0)
- else:
- deadline = time.time() + timeout
- while True:
- try:
- return _poll(object_list, timeout)
- except OSError as e:
- if e.errno != errno.EINTR:
- raise
+ with _WaitSelector() as selector:
+ for obj in object_list:
+ selector.register(obj, selectors.EVENT_READ)
+
if timeout is not None:
- timeout = deadline - time.time()
+ deadline = time.time() + timeout
+
+ while True:
+ ready = selector.select(timeout)
+ if ready:
+ return [key.fileobj for (key, events) in ready]
+ else:
+ if timeout is not None:
+ timeout = deadline - time.time()
+ if timeout < 0:
+ return ready
#
# Make connection and socket objects sharable if possible
#
if sys.platform == 'win32':
- from . import reduction
- ForkingPickler.register(socket.socket, reduction.reduce_socket)
- ForkingPickler.register(Connection, reduction.reduce_connection)
- ForkingPickler.register(PipeConnection, reduction.reduce_pipe_connection)
+ def reduce_connection(conn):
+ handle = conn.fileno()
+ with socket.fromfd(handle, socket.AF_INET, socket.SOCK_STREAM) as s:
+ from . import resource_sharer
+ ds = resource_sharer.DupSocket(s)
+ return rebuild_connection, (ds, conn.readable, conn.writable)
+ def rebuild_connection(ds, readable, writable):
+ sock = ds.detach()
+ return Connection(sock.detach(), readable, writable)
+ reduction.register(Connection, reduce_connection)
+
+ def reduce_pipe_connection(conn):
+ access = ((_winapi.FILE_GENERIC_READ if conn.readable else 0) |
+ (_winapi.FILE_GENERIC_WRITE if conn.writable else 0))
+ dh = reduction.DupHandle(conn.fileno(), access)
+ return rebuild_pipe_connection, (dh, conn.readable, conn.writable)
+ def rebuild_pipe_connection(dh, readable, writable):
+ handle = dh.detach()
+ return PipeConnection(handle, readable, writable)
+ reduction.register(PipeConnection, reduce_pipe_connection)
+
else:
- try:
- from . import reduction
- except ImportError:
- pass
- else:
- ForkingPickler.register(socket.socket, reduction.reduce_socket)
- ForkingPickler.register(Connection, reduction.reduce_connection)
+ def reduce_connection(conn):
+ df = reduction.DupFd(conn.fileno())
+ return rebuild_connection, (df, conn.readable, conn.writable)
+ def rebuild_connection(df, readable, writable):
+ fd = df.detach()
+ return Connection(fd, readable, writable)
+ reduction.register(Connection, reduce_connection)
diff --git a/Lib/multiprocessing/context.py b/Lib/multiprocessing/context.py
new file mode 100644
index 0000000000..63849f9d16
--- /dev/null
+++ b/Lib/multiprocessing/context.py
@@ -0,0 +1,348 @@
+import os
+import sys
+import threading
+
+from . import process
+
+__all__ = [] # things are copied from here to __init__.py
+
+#
+# Exceptions
+#
+
+class ProcessError(Exception):
+ pass
+
+class BufferTooShort(ProcessError):
+ pass
+
+class TimeoutError(ProcessError):
+ pass
+
+class AuthenticationError(ProcessError):
+ pass
+
+#
+# Base type for contexts
+#
+
+class BaseContext(object):
+
+ ProcessError = ProcessError
+ BufferTooShort = BufferTooShort
+ TimeoutError = TimeoutError
+ AuthenticationError = AuthenticationError
+
+ current_process = staticmethod(process.current_process)
+ active_children = staticmethod(process.active_children)
+
+ def cpu_count(self):
+ '''Returns the number of CPUs in the system'''
+ num = os.cpu_count()
+ if num is None:
+ raise NotImplementedError('cannot determine number of cpus')
+ else:
+ return num
+
+ def Manager(self):
+ '''Returns a manager associated with a running server process
+
+ The managers methods such as `Lock()`, `Condition()` and `Queue()`
+ can be used to create shared objects.
+ '''
+ from .managers import SyncManager
+ m = SyncManager(ctx=self.get_context())
+ m.start()
+ return m
+
+ def Pipe(self, duplex=True):
+ '''Returns two connection object connected by a pipe'''
+ from .connection import Pipe
+ return Pipe(duplex)
+
+ def Lock(self):
+ '''Returns a non-recursive lock object'''
+ from .synchronize import Lock
+ return Lock(ctx=self.get_context())
+
+ def RLock(self):
+ '''Returns a recursive lock object'''
+ from .synchronize import RLock
+ return RLock(ctx=self.get_context())
+
+ def Condition(self, lock=None):
+ '''Returns a condition object'''
+ from .synchronize import Condition
+ return Condition(lock, ctx=self.get_context())
+
+ def Semaphore(self, value=1):
+ '''Returns a semaphore object'''
+ from .synchronize import Semaphore
+ return Semaphore(value, ctx=self.get_context())
+
+ def BoundedSemaphore(self, value=1):
+ '''Returns a bounded semaphore object'''
+ from .synchronize import BoundedSemaphore
+ return BoundedSemaphore(value, ctx=self.get_context())
+
+ def Event(self):
+ '''Returns an event object'''
+ from .synchronize import Event
+ return Event(ctx=self.get_context())
+
+ def Barrier(self, parties, action=None, timeout=None):
+ '''Returns a barrier object'''
+ from .synchronize import Barrier
+ return Barrier(parties, action, timeout, ctx=self.get_context())
+
+ def Queue(self, maxsize=0):
+ '''Returns a queue object'''
+ from .queues import Queue
+ return Queue(maxsize, ctx=self.get_context())
+
+ def JoinableQueue(self, maxsize=0):
+ '''Returns a queue object'''
+ from .queues import JoinableQueue
+ return JoinableQueue(maxsize, ctx=self.get_context())
+
+ def SimpleQueue(self):
+ '''Returns a queue object'''
+ from .queues import SimpleQueue
+ return SimpleQueue(ctx=self.get_context())
+
+ def Pool(self, processes=None, initializer=None, initargs=(),
+ maxtasksperchild=None):
+ '''Returns a process pool object'''
+ from .pool import Pool
+ return Pool(processes, initializer, initargs, maxtasksperchild,
+ context=self.get_context())
+
+ def RawValue(self, typecode_or_type, *args):
+ '''Returns a shared object'''
+ from .sharedctypes import RawValue
+ return RawValue(typecode_or_type, *args)
+
+ def RawArray(self, typecode_or_type, size_or_initializer):
+ '''Returns a shared array'''
+ from .sharedctypes import RawArray
+ return RawArray(typecode_or_type, size_or_initializer)
+
+ def Value(self, typecode_or_type, *args, lock=True):
+ '''Returns a synchronized shared object'''
+ from .sharedctypes import Value
+ return Value(typecode_or_type, *args, lock=lock,
+ ctx=self.get_context())
+
+ def Array(self, typecode_or_type, size_or_initializer, *, lock=True):
+ '''Returns a synchronized shared array'''
+ from .sharedctypes import Array
+ return Array(typecode_or_type, size_or_initializer, lock=lock,
+ ctx=self.get_context())
+
+ def freeze_support(self):
+ '''Check whether this is a fake forked process in a frozen executable.
+ If so then run code specified by commandline and exit.
+ '''
+ if sys.platform == 'win32' and getattr(sys, 'frozen', False):
+ from .spawn import freeze_support
+ freeze_support()
+
+ def get_logger(self):
+ '''Return package logger -- if it does not already exist then
+ it is created.
+ '''
+ from .util import get_logger
+ return get_logger()
+
+ def log_to_stderr(self, level=None):
+ '''Turn on logging and add a handler which prints to stderr'''
+ from .util import log_to_stderr
+ return log_to_stderr(level)
+
+ def allow_connection_pickling(self):
+ '''Install support for sending connections and sockets
+ between processes
+ '''
+ # This is undocumented. In previous versions of multiprocessing
+ # its only effect was to make socket objects inheritable on Windows.
+ from . import connection
+
+ def set_executable(self, executable):
+ '''Sets the path to a python.exe or pythonw.exe binary used to run
+ child processes instead of sys.executable when using the 'spawn'
+ start method. Useful for people embedding Python.
+ '''
+ from .spawn import set_executable
+ set_executable(executable)
+
+ def set_forkserver_preload(self, module_names):
+ '''Set list of module names to try to load in forkserver process.
+ This is really just a hint.
+ '''
+ from .forkserver import set_forkserver_preload
+ set_forkserver_preload(module_names)
+
+ def get_context(self, method=None):
+ if method is None:
+ return self
+ try:
+ ctx = _concrete_contexts[method]
+ except KeyError:
+ raise ValueError('cannot find context for %r' % method)
+ ctx._check_available()
+ return ctx
+
+ def get_start_method(self, allow_none=False):
+ return self._name
+
+ def set_start_method(self, method=None):
+ raise ValueError('cannot set start method of concrete context')
+
+ def _check_available(self):
+ pass
+
+#
+# Type of default context -- underlying context can be set at most once
+#
+
+class Process(process.BaseProcess):
+ _start_method = None
+ @staticmethod
+ def _Popen(process_obj):
+ return _default_context.get_context().Process._Popen(process_obj)
+
+class DefaultContext(BaseContext):
+ Process = Process
+
+ def __init__(self, context):
+ self._default_context = context
+ self._actual_context = None
+
+ def get_context(self, method=None):
+ if method is None:
+ if self._actual_context is None:
+ self._actual_context = self._default_context
+ return self._actual_context
+ else:
+ return super().get_context(method)
+
+ def set_start_method(self, method, force=False):
+ if self._actual_context is not None and not force:
+ raise RuntimeError('context has already been set')
+ if method is None and force:
+ self._actual_context = None
+ return
+ self._actual_context = self.get_context(method)
+
+ def get_start_method(self, allow_none=False):
+ if self._actual_context is None:
+ if allow_none:
+ return None
+ self._actual_context = self._default_context
+ return self._actual_context._name
+
+ def get_all_start_methods(self):
+ if sys.platform == 'win32':
+ return ['spawn']
+ else:
+ from . import reduction
+ if reduction.HAVE_SEND_HANDLE:
+ return ['fork', 'spawn', 'forkserver']
+ else:
+ return ['fork', 'spawn']
+
+DefaultContext.__all__ = list(x for x in dir(DefaultContext) if x[0] != '_')
+
+#
+# Context types for fixed start method
+#
+
+if sys.platform != 'win32':
+
+ class ForkProcess(process.BaseProcess):
+ _start_method = 'fork'
+ @staticmethod
+ def _Popen(process_obj):
+ from .popen_fork import Popen
+ return Popen(process_obj)
+
+ class SpawnProcess(process.BaseProcess):
+ _start_method = 'spawn'
+ @staticmethod
+ def _Popen(process_obj):
+ from .popen_spawn_posix import Popen
+ return Popen(process_obj)
+
+ class ForkServerProcess(process.BaseProcess):
+ _start_method = 'forkserver'
+ @staticmethod
+ def _Popen(process_obj):
+ from .popen_forkserver import Popen
+ return Popen(process_obj)
+
+ class ForkContext(BaseContext):
+ _name = 'fork'
+ Process = ForkProcess
+
+ class SpawnContext(BaseContext):
+ _name = 'spawn'
+ Process = SpawnProcess
+
+ class ForkServerContext(BaseContext):
+ _name = 'forkserver'
+ Process = ForkServerProcess
+ def _check_available(self):
+ from . import reduction
+ if not reduction.HAVE_SEND_HANDLE:
+ raise ValueError('forkserver start method not available')
+
+ _concrete_contexts = {
+ 'fork': ForkContext(),
+ 'spawn': SpawnContext(),
+ 'forkserver': ForkServerContext(),
+ }
+ _default_context = DefaultContext(_concrete_contexts['fork'])
+
+else:
+
+ class SpawnProcess(process.BaseProcess):
+ _start_method = 'spawn'
+ @staticmethod
+ def _Popen(process_obj):
+ from .popen_spawn_win32 import Popen
+ return Popen(process_obj)
+
+ class SpawnContext(BaseContext):
+ _name = 'spawn'
+ Process = SpawnProcess
+
+ _concrete_contexts = {
+ 'spawn': SpawnContext(),
+ }
+ _default_context = DefaultContext(_concrete_contexts['spawn'])
+
+#
+# Force the start method
+#
+
+def _force_start_method(method):
+ _default_context._actual_context = _concrete_contexts[method]
+
+#
+# Check that the current thread is spawning a child process
+#
+
+_tls = threading.local()
+
+def get_spawning_popen():
+ return getattr(_tls, 'spawning_popen', None)
+
+def set_spawning_popen(popen):
+ _tls.spawning_popen = popen
+
+def assert_spawning(obj):
+ if get_spawning_popen() is None:
+ raise RuntimeError(
+ '%s objects should only be shared between processes'
+ ' through inheritance' % type(obj).__name__
+ )
diff --git a/Lib/multiprocessing/dummy/__init__.py b/Lib/multiprocessing/dummy/__init__.py
index e31fc61572..97f7af737e 100644
--- a/Lib/multiprocessing/dummy/__init__.py
+++ b/Lib/multiprocessing/dummy/__init__.py
@@ -4,32 +4,7 @@
# multiprocessing/dummy/__init__.py
#
# Copyright (c) 2006-2008, R Oudkerk
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3. Neither the name of author nor the names of any contributors may be
-# used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
+# Licensed to PSF under a Contributor Agreement.
#
__all__ = [
@@ -47,7 +22,7 @@ import sys
import weakref
import array
-from multiprocessing.dummy.connection import Pipe
+from .connection import Pipe
from threading import Lock, RLock, Semaphore, BoundedSemaphore
from threading import Event, Condition, Barrier
from queue import Queue
@@ -138,7 +113,7 @@ def shutdown():
pass
def Pool(processes=None, initializer=None, initargs=()):
- from multiprocessing.pool import ThreadPool
+ from ..pool import ThreadPool
return ThreadPool(processes, initializer, initargs)
JoinableQueue = Queue
diff --git a/Lib/multiprocessing/dummy/connection.py b/Lib/multiprocessing/dummy/connection.py
index 874ec8e432..694ef96215 100644
--- a/Lib/multiprocessing/dummy/connection.py
+++ b/Lib/multiprocessing/dummy/connection.py
@@ -4,32 +4,7 @@
# multiprocessing/dummy/connection.py
#
# Copyright (c) 2006-2008, R Oudkerk
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3. Neither the name of author nor the names of any contributors may be
-# used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
+# Licensed to PSF under a Contributor Agreement.
#
__all__ = [ 'Client', 'Listener', 'Pipe' ]
diff --git a/Lib/multiprocessing/forking.py b/Lib/multiprocessing/forking.py
deleted file mode 100644
index 9793237bef..0000000000
--- a/Lib/multiprocessing/forking.py
+++ /dev/null
@@ -1,474 +0,0 @@
-#
-# Module for starting a process object using os.fork() or CreateProcess()
-#
-# multiprocessing/forking.py
-#
-# Copyright (c) 2006-2008, R Oudkerk
-# Licensed to PSF under a Contributor Agreement.
-#
-
-import os
-import sys
-import signal
-import errno
-
-from multiprocessing import util, process
-
-__all__ = ['Popen', 'assert_spawning', 'duplicate', 'close', 'ForkingPickler']
-
-#
-# Check that the current thread is spawning a child process
-#
-
-def assert_spawning(self):
- if not Popen.thread_is_spawning():
- raise RuntimeError(
- '%s objects should only be shared between processes'
- ' through inheritance' % type(self).__name__
- )
-
-#
-# Try making some callable types picklable
-#
-
-from pickle import Pickler
-from copyreg import dispatch_table
-
-class ForkingPickler(Pickler):
- _extra_reducers = {}
- def __init__(self, *args):
- Pickler.__init__(self, *args)
- self.dispatch_table = dispatch_table.copy()
- self.dispatch_table.update(self._extra_reducers)
- @classmethod
- def register(cls, type, reduce):
- cls._extra_reducers[type] = reduce
-
-def _reduce_method(m):
- if m.__self__ is None:
- return getattr, (m.__class__, m.__func__.__name__)
- else:
- return getattr, (m.__self__, m.__func__.__name__)
-class _C:
- def f(self):
- pass
-ForkingPickler.register(type(_C().f), _reduce_method)
-
-
-def _reduce_method_descriptor(m):
- return getattr, (m.__objclass__, m.__name__)
-ForkingPickler.register(type(list.append), _reduce_method_descriptor)
-ForkingPickler.register(type(int.__add__), _reduce_method_descriptor)
-
-try:
- from functools import partial
-except ImportError:
- pass
-else:
- def _reduce_partial(p):
- return _rebuild_partial, (p.func, p.args, p.keywords or {})
- def _rebuild_partial(func, args, keywords):
- return partial(func, *args, **keywords)
- ForkingPickler.register(partial, _reduce_partial)
-
-#
-# Unix
-#
-
-if sys.platform != 'win32':
- duplicate = os.dup
- close = os.close
-
- #
- # We define a Popen class similar to the one from subprocess, but
- # whose constructor takes a process object as its argument.
- #
-
- class Popen(object):
-
- def __init__(self, process_obj):
- sys.stdout.flush()
- sys.stderr.flush()
- self.returncode = None
-
- r, w = os.pipe()
- self.sentinel = r
-
- self.pid = os.fork()
- if self.pid == 0:
- os.close(r)
- if 'random' in sys.modules:
- import random
- random.seed()
- code = process_obj._bootstrap()
- os._exit(code)
-
- # `w` will be closed when the child exits, at which point `r`
- # will become ready for reading (using e.g. select()).
- os.close(w)
- util.Finalize(self, os.close, (r,))
-
- def poll(self, flag=os.WNOHANG):
- if self.returncode is None:
- while True:
- try:
- pid, sts = os.waitpid(self.pid, flag)
- except os.error as e:
- if e.errno == errno.EINTR:
- continue
- # Child process not yet created. See #1731717
- # e.errno == errno.ECHILD == 10
- return None
- else:
- break
- if pid == self.pid:
- if os.WIFSIGNALED(sts):
- self.returncode = -os.WTERMSIG(sts)
- else:
- assert os.WIFEXITED(sts)
- self.returncode = os.WEXITSTATUS(sts)
- return self.returncode
-
- def wait(self, timeout=None):
- if self.returncode is None:
- if timeout is not None:
- from .connection import wait
- if not wait([self.sentinel], timeout):
- return None
- # This shouldn't block if wait() returned successfully.
- return self.poll(os.WNOHANG if timeout == 0.0 else 0)
- return self.returncode
-
- def terminate(self):
- if self.returncode is None:
- try:
- os.kill(self.pid, signal.SIGTERM)
- except OSError:
- if self.wait(timeout=0.1) is None:
- raise
-
- @staticmethod
- def thread_is_spawning():
- return False
-
-#
-# Windows
-#
-
-else:
- import _thread
- import msvcrt
- import _winapi
-
- from pickle import load, HIGHEST_PROTOCOL
-
- def dump(obj, file, protocol=None):
- ForkingPickler(file, protocol).dump(obj)
-
- #
- #
- #
-
- TERMINATE = 0x10000
- WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
- WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
-
- close = _winapi.CloseHandle
-
- #
- # _python_exe is the assumed path to the python executable.
- # People embedding Python want to modify it.
- #
-
- if WINSERVICE:
- _python_exe = os.path.join(sys.exec_prefix, 'python.exe')
- else:
- _python_exe = sys.executable
-
- def set_executable(exe):
- global _python_exe
- _python_exe = exe
-
- #
- #
- #
-
- def duplicate(handle, target_process=None, inheritable=False):
- if target_process is None:
- target_process = _winapi.GetCurrentProcess()
- return _winapi.DuplicateHandle(
- _winapi.GetCurrentProcess(), handle, target_process,
- 0, inheritable, _winapi.DUPLICATE_SAME_ACCESS
- )
-
- #
- # We define a Popen class similar to the one from subprocess, but
- # whose constructor takes a process object as its argument.
- #
-
- class Popen(object):
- '''
- Start a subprocess to run the code of a process object
- '''
- _tls = _thread._local()
-
- def __init__(self, process_obj):
- cmd = ' '.join('"%s"' % x for x in get_command_line())
- prep_data = get_preparation_data(process_obj._name)
-
- # create pipe for communication with child
- rfd, wfd = os.pipe()
-
- # get handle for read end of the pipe and make it inheritable
- rhandle = duplicate(msvcrt.get_osfhandle(rfd), inheritable=True)
- os.close(rfd)
-
- with open(wfd, 'wb', closefd=True) as to_child:
- # start process
- try:
- hp, ht, pid, tid = _winapi.CreateProcess(
- _python_exe, cmd + (' %s' % rhandle),
- None, None, 1, 0, None, None, None
- )
- _winapi.CloseHandle(ht)
- finally:
- close(rhandle)
-
- # set attributes of self
- self.pid = pid
- self.returncode = None
- self._handle = hp
- self.sentinel = int(hp)
- util.Finalize(self, _winapi.CloseHandle, (self.sentinel,))
-
- # send information to child
- Popen._tls.process_handle = int(hp)
- try:
- dump(prep_data, to_child, HIGHEST_PROTOCOL)
- dump(process_obj, to_child, HIGHEST_PROTOCOL)
- finally:
- del Popen._tls.process_handle
-
- @staticmethod
- def thread_is_spawning():
- return getattr(Popen._tls, 'process_handle', None) is not None
-
- @staticmethod
- def duplicate_for_child(handle):
- return duplicate(handle, Popen._tls.process_handle)
-
- def wait(self, timeout=None):
- if self.returncode is None:
- if timeout is None:
- msecs = _winapi.INFINITE
- else:
- msecs = max(0, int(timeout * 1000 + 0.5))
-
- res = _winapi.WaitForSingleObject(int(self._handle), msecs)
- if res == _winapi.WAIT_OBJECT_0:
- code = _winapi.GetExitCodeProcess(self._handle)
- if code == TERMINATE:
- code = -signal.SIGTERM
- self.returncode = code
-
- return self.returncode
-
- def poll(self):
- return self.wait(timeout=0)
-
- def terminate(self):
- if self.returncode is None:
- try:
- _winapi.TerminateProcess(int(self._handle), TERMINATE)
- except OSError:
- if self.wait(timeout=1.0) is None:
- raise
-
- #
- #
- #
-
- def is_forking(argv):
- '''
- Return whether commandline indicates we are forking
- '''
- if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
- assert len(argv) == 3
- return True
- else:
- return False
-
-
- def freeze_support():
- '''
- Run code for process object if this in not the main process
- '''
- if is_forking(sys.argv):
- main()
- sys.exit()
-
-
- def get_command_line():
- '''
- Returns prefix of command line used for spawning a child process
- '''
- if getattr(process.current_process(), '_inheriting', False):
- raise RuntimeError('''
- Attempt to start a new process before the current process
- has finished its bootstrapping phase.
-
- This probably means that you are on Windows and you have
- forgotten to use the proper idiom in the main module:
-
- if __name__ == '__main__':
- freeze_support()
- ...
-
- The "freeze_support()" line can be omitted if the program
- is not going to be frozen to produce a Windows executable.''')
-
- if getattr(sys, 'frozen', False):
- return [sys.executable, '--multiprocessing-fork']
- else:
- prog = 'from multiprocessing.forking import main; main()'
- opts = util._args_from_interpreter_flags()
- return [_python_exe] + opts + ['-c', prog, '--multiprocessing-fork']
-
-
- def main():
- '''
- Run code specified by data received over pipe
- '''
- assert is_forking(sys.argv)
-
- handle = int(sys.argv[-1])
- fd = msvcrt.open_osfhandle(handle, os.O_RDONLY)
- from_parent = os.fdopen(fd, 'rb')
-
- process.current_process()._inheriting = True
- preparation_data = load(from_parent)
- prepare(preparation_data)
- self = load(from_parent)
- process.current_process()._inheriting = False
-
- from_parent.close()
-
- exitcode = self._bootstrap()
- sys.exit(exitcode)
-
-
- def get_preparation_data(name):
- '''
- Return info about parent needed by child to unpickle process object
- '''
- from .util import _logger, _log_to_stderr
-
- d = dict(
- name=name,
- sys_path=sys.path,
- sys_argv=sys.argv,
- log_to_stderr=_log_to_stderr,
- orig_dir=process.ORIGINAL_DIR,
- authkey=process.current_process().authkey,
- )
-
- if _logger is not None:
- d['log_level'] = _logger.getEffectiveLevel()
-
- if not WINEXE and not WINSERVICE:
- main_path = getattr(sys.modules['__main__'], '__file__', None)
- if not main_path and sys.argv[0] not in ('', '-c'):
- main_path = sys.argv[0]
- if main_path is not None:
- if not os.path.isabs(main_path) and \
- process.ORIGINAL_DIR is not None:
- main_path = os.path.join(process.ORIGINAL_DIR, main_path)
- d['main_path'] = os.path.normpath(main_path)
-
- return d
-
-#
-# Prepare current process
-#
-
-old_main_modules = []
-
-def prepare(data):
- '''
- Try to get current process ready to unpickle process object
- '''
- old_main_modules.append(sys.modules['__main__'])
-
- if 'name' in data:
- process.current_process().name = data['name']
-
- if 'authkey' in data:
- process.current_process()._authkey = data['authkey']
-
- if 'log_to_stderr' in data and data['log_to_stderr']:
- util.log_to_stderr()
-
- if 'log_level' in data:
- util.get_logger().setLevel(data['log_level'])
-
- if 'sys_path' in data:
- sys.path = data['sys_path']
-
- if 'sys_argv' in data:
- sys.argv = data['sys_argv']
-
- if 'dir' in data:
- os.chdir(data['dir'])
-
- if 'orig_dir' in data:
- process.ORIGINAL_DIR = data['orig_dir']
-
- if 'main_path' in data:
- # XXX (ncoghlan): The following code makes several bogus
- # assumptions regarding the relationship between __file__
- # and a module's real name. See PEP 302 and issue #10845
- main_path = data['main_path']
- main_name = os.path.splitext(os.path.basename(main_path))[0]
- if main_name == '__init__':
- main_name = os.path.basename(os.path.dirname(main_path))
-
- if main_name == '__main__':
- main_module = sys.modules['__main__']
- main_module.__file__ = main_path
- elif main_name != 'ipython':
- # Main modules not actually called __main__.py may
- # contain additional code that should still be executed
- import imp
-
- if main_path is None:
- dirs = None
- elif os.path.basename(main_path).startswith('__init__.py'):
- dirs = [os.path.dirname(os.path.dirname(main_path))]
- else:
- dirs = [os.path.dirname(main_path)]
-
- assert main_name not in sys.modules, main_name
- file, path_name, etc = imp.find_module(main_name, dirs)
- try:
- # We would like to do "imp.load_module('__main__', ...)"
- # here. However, that would cause 'if __name__ ==
- # "__main__"' clauses to be executed.
- main_module = imp.load_module(
- '__parents_main__', file, path_name, etc
- )
- finally:
- if file:
- file.close()
-
- sys.modules['__main__'] = main_module
- main_module.__name__ = '__main__'
-
- # Try to make the potentially picklable objects in
- # sys.modules['__main__'] realize they are in the main
- # module -- somewhat ugly.
- for obj in list(main_module.__dict__.values()):
- try:
- if obj.__module__ == '__parents_main__':
- obj.__module__ = '__main__'
- except Exception:
- pass
diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py
new file mode 100644
index 0000000000..387517ebdc
--- /dev/null
+++ b/Lib/multiprocessing/forkserver.py
@@ -0,0 +1,267 @@
+import errno
+import os
+import selectors
+import signal
+import socket
+import struct
+import sys
+import threading
+
+from . import connection
+from . import process
+from . import reduction
+from . import semaphore_tracker
+from . import spawn
+from . import util
+
+__all__ = ['ensure_running', 'get_inherited_fds', 'connect_to_new_process',
+ 'set_forkserver_preload']
+
+#
+#
+#
+
+MAXFDS_TO_SEND = 256
+UNSIGNED_STRUCT = struct.Struct('Q') # large enough for pid_t
+
+#
+# Forkserver class
+#
+
+class ForkServer(object):
+
+ def __init__(self):
+ self._forkserver_address = None
+ self._forkserver_alive_fd = None
+ self._inherited_fds = None
+ self._lock = threading.Lock()
+ self._preload_modules = ['__main__']
+
+ def set_forkserver_preload(self, modules_names):
+ '''Set list of module names to try to load in forkserver process.'''
+ if not all(type(mod) is str for mod in self._preload_modules):
+ raise TypeError('module_names must be a list of strings')
+ self._preload_modules = modules_names
+
+ def get_inherited_fds(self):
+ '''Return list of fds inherited from parent process.
+
+ This returns None if the current process was not started by fork
+ server.
+ '''
+ return self._inherited_fds
+
+ def connect_to_new_process(self, fds):
+ '''Request forkserver to create a child process.
+
+ Returns a pair of fds (status_r, data_w). The calling process can read
+ the child process's pid and (eventually) its returncode from status_r.
+ The calling process should write to data_w the pickled preparation and
+ process data.
+ '''
+ self.ensure_running()
+ if len(fds) + 4 >= MAXFDS_TO_SEND:
+ raise ValueError('too many fds')
+ with socket.socket(socket.AF_UNIX) as client:
+ client.connect(self._forkserver_address)
+ parent_r, child_w = os.pipe()
+ child_r, parent_w = os.pipe()
+ allfds = [child_r, child_w, self._forkserver_alive_fd,
+ semaphore_tracker.getfd()]
+ allfds += fds
+ try:
+ reduction.sendfds(client, allfds)
+ return parent_r, parent_w
+ except:
+ os.close(parent_r)
+ os.close(parent_w)
+ raise
+ finally:
+ os.close(child_r)
+ os.close(child_w)
+
+ def ensure_running(self):
+ '''Make sure that a fork server is running.
+
+ This can be called from any process. Note that usually a child
+ process will just reuse the forkserver started by its parent, so
+ ensure_running() will do nothing.
+ '''
+ with self._lock:
+ semaphore_tracker.ensure_running()
+ if self._forkserver_alive_fd is not None:
+ return
+
+ cmd = ('from multiprocessing.forkserver import main; ' +
+ 'main(%d, %d, %r, **%r)')
+
+ if self._preload_modules:
+ desired_keys = {'main_path', 'sys_path'}
+ data = spawn.get_preparation_data('ignore')
+ data = dict((x,y) for (x,y) in data.items()
+ if x in desired_keys)
+ else:
+ data = {}
+
+ with socket.socket(socket.AF_UNIX) as listener:
+ address = connection.arbitrary_address('AF_UNIX')
+ listener.bind(address)
+ os.chmod(address, 0o600)
+ listener.listen(100)
+
+ # all client processes own the write end of the "alive" pipe;
+ # when they all terminate the read end becomes ready.
+ alive_r, alive_w = os.pipe()
+ try:
+ fds_to_pass = [listener.fileno(), alive_r]
+ cmd %= (listener.fileno(), alive_r, self._preload_modules,
+ data)
+ exe = spawn.get_executable()
+ args = [exe] + util._args_from_interpreter_flags()
+ args += ['-c', cmd]
+ pid = util.spawnv_passfds(exe, args, fds_to_pass)
+ except:
+ os.close(alive_w)
+ raise
+ finally:
+ os.close(alive_r)
+ self._forkserver_address = address
+ self._forkserver_alive_fd = alive_w
+
+#
+#
+#
+
+def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
+ '''Run forkserver.'''
+ if preload:
+ if '__main__' in preload and main_path is not None:
+ process.current_process()._inheriting = True
+ try:
+ spawn.import_main_path(main_path)
+ finally:
+ del process.current_process()._inheriting
+ for modname in preload:
+ try:
+ __import__(modname)
+ except ImportError:
+ pass
+
+ # close sys.stdin
+ if sys.stdin is not None:
+ try:
+ sys.stdin.close()
+ sys.stdin = open(os.devnull)
+ except (OSError, ValueError):
+ pass
+
+ # ignoring SIGCHLD means no need to reap zombie processes
+ handler = signal.signal(signal.SIGCHLD, signal.SIG_IGN)
+ with socket.socket(socket.AF_UNIX, fileno=listener_fd) as listener, \
+ selectors.DefaultSelector() as selector:
+ _forkserver._forkserver_address = listener.getsockname()
+
+ selector.register(listener, selectors.EVENT_READ)
+ selector.register(alive_r, selectors.EVENT_READ)
+
+ while True:
+ try:
+ while True:
+ rfds = [key.fileobj for (key, events) in selector.select()]
+ if rfds:
+ break
+
+ if alive_r in rfds:
+ # EOF because no more client processes left
+ assert os.read(alive_r, 1) == b''
+ raise SystemExit
+
+ assert listener in rfds
+ with listener.accept()[0] as s:
+ code = 1
+ if os.fork() == 0:
+ try:
+ _serve_one(s, listener, alive_r, handler)
+ except Exception:
+ sys.excepthook(*sys.exc_info())
+ sys.stderr.flush()
+ finally:
+ os._exit(code)
+
+ except InterruptedError:
+ pass
+ except OSError as e:
+ if e.errno != errno.ECONNABORTED:
+ raise
+
+def _serve_one(s, listener, alive_r, handler):
+ # close unnecessary stuff and reset SIGCHLD handler
+ listener.close()
+ os.close(alive_r)
+ signal.signal(signal.SIGCHLD, handler)
+
+ # receive fds from parent process
+ fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
+ s.close()
+ assert len(fds) <= MAXFDS_TO_SEND
+ (child_r, child_w, _forkserver._forkserver_alive_fd,
+ stfd, *_forkserver._inherited_fds) = fds
+ semaphore_tracker._semaphore_tracker._fd = stfd
+
+ # send pid to client processes
+ write_unsigned(child_w, os.getpid())
+
+ # reseed random number generator
+ if 'random' in sys.modules:
+ import random
+ random.seed()
+
+ # run process object received over pipe
+ code = spawn._main(child_r)
+
+ # write the exit code to the pipe
+ write_unsigned(child_w, code)
+
+#
+# Read and write unsigned numbers
+#
+
+def read_unsigned(fd):
+ data = b''
+ length = UNSIGNED_STRUCT.size
+ while len(data) < length:
+ while True:
+ try:
+ s = os.read(fd, length - len(data))
+ except InterruptedError:
+ pass
+ else:
+ break
+ if not s:
+ raise EOFError('unexpected EOF')
+ data += s
+ return UNSIGNED_STRUCT.unpack(data)[0]
+
+def write_unsigned(fd, n):
+ msg = UNSIGNED_STRUCT.pack(n)
+ while msg:
+ while True:
+ try:
+ nbytes = os.write(fd, msg)
+ except InterruptedError:
+ pass
+ else:
+ break
+ if nbytes == 0:
+ raise RuntimeError('should not get here')
+ msg = msg[nbytes:]
+
+#
+#
+#
+
+_forkserver = ForkServer()
+ensure_running = _forkserver.ensure_running
+get_inherited_fds = _forkserver.get_inherited_fds
+connect_to_new_process = _forkserver.connect_to_new_process
+set_forkserver_preload = _forkserver.set_forkserver_preload
diff --git a/Lib/multiprocessing/heap.py b/Lib/multiprocessing/heap.py
index e63fdb8755..98bfdc8ee1 100644
--- a/Lib/multiprocessing/heap.py
+++ b/Lib/multiprocessing/heap.py
@@ -8,15 +8,17 @@
#
import bisect
+import itertools
import mmap
import os
import sys
+import tempfile
import threading
-import itertools
-
import _multiprocessing
-from multiprocessing.util import Finalize, info
-from multiprocessing.forking import assert_spawning
+
+from . import context
+from . import reduction
+from . import util
__all__ = ['BufferWrapper']
@@ -30,17 +32,25 @@ if sys.platform == 'win32':
class Arena(object):
- _counter = itertools.count()
+ _rand = tempfile._RandomNameSequence()
def __init__(self, size):
self.size = size
- self.name = 'pym-%d-%d' % (os.getpid(), next(Arena._counter))
- self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
- assert _winapi.GetLastError() == 0, 'tagname already in use'
+ for i in range(100):
+ name = 'pym-%d-%s' % (os.getpid(), next(self._rand))
+ buf = mmap.mmap(-1, size, tagname=name)
+ if _winapi.GetLastError() == 0:
+ break
+ # We have reopened a preexisting mmap.
+ buf.close()
+ else:
+ raise FileExistsError('Cannot find name for new mmap')
+ self.name = name
+ self.buffer = buf
self._state = (self.size, self.name)
def __getstate__(self):
- assert_spawning(self)
+ context.assert_spawning(self)
return self._state
def __setstate__(self, state):
@@ -52,10 +62,28 @@ else:
class Arena(object):
- def __init__(self, size):
- self.buffer = mmap.mmap(-1, size)
+ def __init__(self, size, fd=-1):
self.size = size
- self.name = None
+ self.fd = fd
+ if fd == -1:
+ self.fd, name = tempfile.mkstemp(
+ prefix='pym-%d-'%os.getpid(), dir=util.get_temp_dir())
+ os.unlink(name)
+ util.Finalize(self, os.close, (self.fd,))
+ with open(self.fd, 'wb', closefd=False) as f:
+ f.write(b'\0'*size)
+ self.buffer = mmap.mmap(self.fd, self.size)
+
+ def reduce_arena(a):
+ if a.fd == -1:
+ raise ValueError('Arena is unpicklable because '
+ 'forking was enabled when it was created')
+ return rebuild_arena, (a.size, reduction.DupFd(a.fd))
+
+ def rebuild_arena(size, dupfd):
+ return Arena(size, dupfd.detach())
+
+ reduction.register(Arena, reduce_arena)
#
# Class allowing allocation of chunks of memory from arenas
@@ -90,7 +118,7 @@ class Heap(object):
if i == len(self._lengths):
length = self._roundup(max(self._size, size), mmap.PAGESIZE)
self._size *= 2
- info('allocating a new mmap of length %d', length)
+ util.info('allocating a new mmap of length %d', length)
arena = Arena(length)
self._arenas.append(arena)
return (arena, 0, length)
@@ -216,7 +244,7 @@ class BufferWrapper(object):
assert 0 <= size < sys.maxsize
block = BufferWrapper._heap.malloc(size)
self._state = (block, size)
- Finalize(self, BufferWrapper._heap.free, args=(block,))
+ util.Finalize(self, BufferWrapper._heap.free, args=(block,))
def create_memoryview(self):
(arena, start, stop), size = self._state
diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py
index 96056b04ea..cc87d36612 100644
--- a/Lib/multiprocessing/managers.py
+++ b/Lib/multiprocessing/managers.py
@@ -19,11 +19,16 @@ import threading
import array
import queue
-from traceback import format_exc
-from multiprocessing import Process, current_process, active_children, Pool, util, connection
-from multiprocessing.process import AuthenticationString
-from multiprocessing.forking import Popen, ForkingPickler
from time import time as _time
+from traceback import format_exc
+
+from . import connection
+from . import context
+from . import pool
+from . import process
+from . import reduction
+from . import util
+from . import get_context
#
# Register some things for pickling
@@ -31,16 +36,14 @@ from time import time as _time
def reduce_array(a):
return array.array, (a.typecode, a.tobytes())
-ForkingPickler.register(array.array, reduce_array)
+reduction.register(array.array, reduce_array)
view_types = [type(getattr({}, name)()) for name in ('items','keys','values')]
if view_types[0] is not list: # only needed in Py3.0
def rebuild_as_list(obj):
return list, (list(obj),)
for view_type in view_types:
- ForkingPickler.register(view_type, rebuild_as_list)
- import copyreg
- copyreg.pickle(view_type, rebuild_as_list)
+ reduction.register(view_type, rebuild_as_list)
#
# Type for identifying shared objects
@@ -130,7 +133,7 @@ class Server(object):
def __init__(self, registry, address, authkey, serializer):
assert isinstance(authkey, bytes)
self.registry = registry
- self.authkey = AuthenticationString(authkey)
+ self.authkey = process.AuthenticationString(authkey)
Listener, Client = listener_client[serializer]
# do authentication later
@@ -146,7 +149,7 @@ class Server(object):
Run the server forever
'''
self.stop_event = threading.Event()
- current_process()._manager_server = self
+ process.current_process()._manager_server = self
try:
accepter = threading.Thread(target=self.accepter)
accepter.daemon = True
@@ -167,7 +170,7 @@ class Server(object):
while True:
try:
c = self.listener.accept()
- except (OSError, IOError):
+ except OSError:
continue
t = threading.Thread(target=self.handle_request, args=(c,))
t.daemon = True
@@ -436,15 +439,17 @@ class BaseManager(object):
_registry = {}
_Server = Server
- def __init__(self, address=None, authkey=None, serializer='pickle'):
+ def __init__(self, address=None, authkey=None, serializer='pickle',
+ ctx=None):
if authkey is None:
- authkey = current_process().authkey
+ authkey = process.current_process().authkey
self._address = address # XXX not final address if eg ('', 0)
- self._authkey = AuthenticationString(authkey)
+ self._authkey = process.AuthenticationString(authkey)
self._state = State()
self._state.value = State.INITIAL
self._serializer = serializer
self._Listener, self._Client = listener_client[serializer]
+ self._ctx = ctx or get_context()
def get_server(self):
'''
@@ -476,7 +481,7 @@ class BaseManager(object):
reader, writer = connection.Pipe(duplex=False)
# spawn process which runs a server
- self._process = Process(
+ self._process = self._ctx.Process(
target=type(self)._run_server,
args=(self._registry, self._address, self._authkey,
self._serializer, writer, initializer, initargs),
@@ -691,11 +696,11 @@ class BaseProxy(object):
self._Client = listener_client[serializer][1]
if authkey is not None:
- self._authkey = AuthenticationString(authkey)
+ self._authkey = process.AuthenticationString(authkey)
elif self._manager is not None:
self._authkey = self._manager._authkey
else:
- self._authkey = current_process().authkey
+ self._authkey = process.current_process().authkey
if incref:
self._incref()
@@ -704,7 +709,7 @@ class BaseProxy(object):
def _connect(self):
util.debug('making connection to manager')
- name = current_process().name
+ name = process.current_process().name
if threading.current_thread().name != 'MainThread':
name += '|' + threading.current_thread().name
conn = self._Client(self._token.address, authkey=self._authkey)
@@ -798,7 +803,7 @@ class BaseProxy(object):
def __reduce__(self):
kwds = {}
- if Popen.thread_is_spawning():
+ if context.get_spawning_popen() is not None:
kwds['authkey'] = self._authkey
if getattr(self, '_isauto', False):
@@ -835,14 +840,14 @@ def RebuildProxy(func, token, serializer, kwds):
If possible the shared object is returned, or otherwise a proxy for it.
'''
- server = getattr(current_process(), '_manager_server', None)
+ server = getattr(process.current_process(), '_manager_server', None)
if server and server.address == token.address:
return server.id_to_obj[token.id][0]
else:
incref = (
kwds.pop('incref', True) and
- not getattr(current_process(), '_inheriting', False)
+ not getattr(process.current_process(), '_inheriting', False)
)
return func(token, serializer, incref=incref, **kwds)
@@ -889,7 +894,7 @@ def AutoProxy(token, serializer, manager=None, authkey=None,
if authkey is None and manager is not None:
authkey = manager._authkey
if authkey is None:
- authkey = current_process().authkey
+ authkey = process.current_process().authkey
ProxyType = MakeProxyType('AutoProxy[%s]' % token.typeid, exposed)
proxy = ProxyType(token, serializer, manager=manager, authkey=authkey,
@@ -1109,7 +1114,7 @@ SyncManager.register('BoundedSemaphore', threading.BoundedSemaphore,
AcquirerProxy)
SyncManager.register('Condition', threading.Condition, ConditionProxy)
SyncManager.register('Barrier', threading.Barrier, BarrierProxy)
-SyncManager.register('Pool', Pool, PoolProxy)
+SyncManager.register('Pool', pool.Pool, PoolProxy)
SyncManager.register('list', list, ListProxy)
SyncManager.register('dict', dict, DictProxy)
SyncManager.register('Value', Value, ValueProxy)
diff --git a/Lib/multiprocessing/pool.py b/Lib/multiprocessing/pool.py
index 0f2dab48eb..1cb2d95678 100644
--- a/Lib/multiprocessing/pool.py
+++ b/Lib/multiprocessing/pool.py
@@ -7,7 +7,7 @@
# Licensed to PSF under a Contributor Agreement.
#
-__all__ = ['Pool']
+__all__ = ['Pool', 'ThreadPool']
#
# Imports
@@ -17,10 +17,14 @@ import threading
import queue
import itertools
import collections
+import os
import time
+import traceback
-from multiprocessing import Process, cpu_count, TimeoutError
-from multiprocessing.util import Finalize, debug
+# If threading is available then ThreadPool should be provided. Therefore
+# we avoid top-level imports which are liable to fail on some systems.
+from . import util
+from . import get_context, cpu_count, TimeoutError
#
# Constants representing the state of a pool
@@ -43,6 +47,29 @@ def starmapstar(args):
return list(itertools.starmap(args[0], args[1]))
#
+# Hack to embed stringification of remote traceback in local traceback
+#
+
+class RemoteTraceback(Exception):
+ def __init__(self, tb):
+ self.tb = tb
+ def __str__(self):
+ return self.tb
+
+class ExceptionWithTraceback:
+ def __init__(self, exc, tb):
+ tb = traceback.format_exception(type(exc), exc, tb)
+ tb = ''.join(tb)
+ self.exc = exc
+ self.tb = '\n"""\n%s"""' % tb
+ def __reduce__(self):
+ return rebuild_exc, (self.exc, self.tb)
+
+def rebuild_exc(exc, tb):
+ exc.__cause__ = RemoteTraceback(tb)
+ return exc
+
+#
# Code run by worker processes
#
@@ -78,28 +105,29 @@ def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None):
while maxtasks is None or (maxtasks and completed < maxtasks):
try:
task = get()
- except (EOFError, IOError):
- debug('worker got EOFError or IOError -- exiting')
+ except (EOFError, OSError):
+ util.debug('worker got EOFError or OSError -- exiting')
break
if task is None:
- debug('worker got sentinel -- exiting')
+ util.debug('worker got sentinel -- exiting')
break
job, i, func, args, kwds = task
try:
result = (True, func(*args, **kwds))
except Exception as e:
+ e = ExceptionWithTraceback(e, e.__traceback__)
result = (False, e)
try:
put((job, i, result))
except Exception as e:
wrapped = MaybeEncodingError(e, result[1])
- debug("Possible encoding error while sending result: %s" % (
+ util.debug("Possible encoding error while sending result: %s" % (
wrapped))
put((job, i, (False, wrapped)))
completed += 1
- debug('worker exiting after %d tasks' % completed)
+ util.debug('worker exiting after %d tasks' % completed)
#
# Class representing a process pool
@@ -109,10 +137,12 @@ class Pool(object):
'''
Class which supports an async version of applying functions to arguments.
'''
- Process = Process
+ def Process(self, *args, **kwds):
+ return self._ctx.Process(*args, **kwds)
def __init__(self, processes=None, initializer=None, initargs=(),
- maxtasksperchild=None):
+ maxtasksperchild=None, context=None):
+ self._ctx = context or get_context()
self._setup_queues()
self._taskqueue = queue.Queue()
self._cache = {}
@@ -122,10 +152,7 @@ class Pool(object):
self._initargs = initargs
if processes is None:
- try:
- processes = cpu_count()
- except NotImplementedError:
- processes = 1
+ processes = os.cpu_count() or 1
if processes < 1:
raise ValueError("Number of processes must be at least 1")
@@ -162,7 +189,7 @@ class Pool(object):
self._result_handler._state = RUN
self._result_handler.start()
- self._terminate = Finalize(
+ self._terminate = util.Finalize(
self, self._terminate_pool,
args=(self._taskqueue, self._inqueue, self._outqueue, self._pool,
self._worker_handler, self._task_handler,
@@ -179,7 +206,7 @@ class Pool(object):
worker = self._pool[i]
if worker.exitcode is not None:
# worker exited
- debug('cleaning up worker %d' % i)
+ util.debug('cleaning up worker %d' % i)
worker.join()
cleaned = True
del self._pool[i]
@@ -199,7 +226,7 @@ class Pool(object):
w.name = w.name.replace('Process', 'PoolWorker')
w.daemon = True
w.start()
- debug('added worker')
+ util.debug('added worker')
def _maintain_pool(self):
"""Clean up any exited workers and start replacements for them.
@@ -208,9 +235,8 @@ class Pool(object):
self._repopulate_pool()
def _setup_queues(self):
- from .queues import SimpleQueue
- self._inqueue = SimpleQueue()
- self._outqueue = SimpleQueue()
+ self._inqueue = self._ctx.SimpleQueue()
+ self._outqueue = self._ctx.SimpleQueue()
self._quick_put = self._inqueue._writer.send
self._quick_get = self._outqueue._reader.recv
@@ -336,7 +362,7 @@ class Pool(object):
time.sleep(0.1)
# send sentinel to stop workers
pool._taskqueue.put(None)
- debug('worker handler exiting')
+ util.debug('worker handler exiting')
@staticmethod
def _handle_tasks(taskqueue, put, outqueue, pool, cache):
@@ -346,7 +372,7 @@ class Pool(object):
i = -1
for i, task in enumerate(taskseq):
if thread._state:
- debug('task handler found thread._state != RUN')
+ util.debug('task handler found thread._state != RUN')
break
try:
put(task)
@@ -358,27 +384,27 @@ class Pool(object):
pass
else:
if set_length:
- debug('doing set_length()')
+ util.debug('doing set_length()')
set_length(i+1)
continue
break
else:
- debug('task handler got sentinel')
+ util.debug('task handler got sentinel')
try:
# tell result handler to finish when cache is empty
- debug('task handler sending sentinel to result handler')
+ util.debug('task handler sending sentinel to result handler')
outqueue.put(None)
# tell workers there is no more work
- debug('task handler sending sentinel to workers')
+ util.debug('task handler sending sentinel to workers')
for p in pool:
put(None)
- except IOError:
- debug('task handler got IOError when sending sentinels')
+ except OSError:
+ util.debug('task handler got OSError when sending sentinels')
- debug('task handler exiting')
+ util.debug('task handler exiting')
@staticmethod
def _handle_results(outqueue, get, cache):
@@ -387,17 +413,17 @@ class Pool(object):
while 1:
try:
task = get()
- except (IOError, EOFError):
- debug('result handler got EOFError/IOError -- exiting')
+ except (OSError, EOFError):
+ util.debug('result handler got EOFError/OSError -- exiting')
return
if thread._state:
assert thread._state == TERMINATE
- debug('result handler found thread._state=TERMINATE')
+ util.debug('result handler found thread._state=TERMINATE')
break
if task is None:
- debug('result handler got sentinel')
+ util.debug('result handler got sentinel')
break
job, i, obj = task
@@ -409,12 +435,12 @@ class Pool(object):
while cache and thread._state != TERMINATE:
try:
task = get()
- except (IOError, EOFError):
- debug('result handler got EOFError/IOError -- exiting')
+ except (OSError, EOFError):
+ util.debug('result handler got EOFError/OSError -- exiting')
return
if task is None:
- debug('result handler ignoring extra sentinel')
+ util.debug('result handler ignoring extra sentinel')
continue
job, i, obj = task
try:
@@ -423,7 +449,7 @@ class Pool(object):
pass
if hasattr(outqueue, '_reader'):
- debug('ensuring that outqueue is not full')
+ util.debug('ensuring that outqueue is not full')
# If we don't make room available in outqueue then
# attempts to add the sentinel (None) to outqueue may
# block. There is guaranteed to be no more than 2 sentinels.
@@ -432,10 +458,10 @@ class Pool(object):
if not outqueue._reader.poll():
break
get()
- except (IOError, EOFError):
+ except (OSError, EOFError):
pass
- debug('result handler exiting: len(cache)=%s, thread._state=%s',
+ util.debug('result handler exiting: len(cache)=%s, thread._state=%s',
len(cache), thread._state)
@staticmethod
@@ -453,19 +479,19 @@ class Pool(object):
)
def close(self):
- debug('closing pool')
+ util.debug('closing pool')
if self._state == RUN:
self._state = CLOSE
self._worker_handler._state = CLOSE
def terminate(self):
- debug('terminating pool')
+ util.debug('terminating pool')
self._state = TERMINATE
self._worker_handler._state = TERMINATE
self._terminate()
def join(self):
- debug('joining pool')
+ util.debug('joining pool')
assert self._state in (CLOSE, TERMINATE)
self._worker_handler.join()
self._task_handler.join()
@@ -476,7 +502,7 @@ class Pool(object):
@staticmethod
def _help_stuff_finish(inqueue, task_handler, size):
# task_handler may be blocked trying to put items on inqueue
- debug('removing tasks from inqueue until task handler finished')
+ util.debug('removing tasks from inqueue until task handler finished')
inqueue._rlock.acquire()
while task_handler.is_alive() and inqueue._reader.poll():
inqueue._reader.recv()
@@ -486,12 +512,12 @@ class Pool(object):
def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool,
worker_handler, task_handler, result_handler, cache):
# this is guaranteed to only be called once
- debug('finalizing pool')
+ util.debug('finalizing pool')
worker_handler._state = TERMINATE
task_handler._state = TERMINATE
- debug('helping task handler/workers to finish')
+ util.debug('helping task handler/workers to finish')
cls._help_stuff_finish(inqueue, task_handler, len(pool))
assert result_handler.is_alive() or len(cache) == 0
@@ -501,31 +527,31 @@ class Pool(object):
# We must wait for the worker handler to exit before terminating
# workers because we don't want workers to be restarted behind our back.
- debug('joining worker handler')
+ util.debug('joining worker handler')
if threading.current_thread() is not worker_handler:
worker_handler.join()
# Terminate workers which haven't already finished.
if pool and hasattr(pool[0], 'terminate'):
- debug('terminating workers')
+ util.debug('terminating workers')
for p in pool:
if p.exitcode is None:
p.terminate()
- debug('joining task handler')
+ util.debug('joining task handler')
if threading.current_thread() is not task_handler:
task_handler.join()
- debug('joining result handler')
+ util.debug('joining result handler')
if threading.current_thread() is not result_handler:
result_handler.join()
if pool and hasattr(pool[0], 'terminate'):
- debug('joining pool workers')
+ util.debug('joining pool workers')
for p in pool:
if p.is_alive():
# worker has not yet exited
- debug('cleaning up worker %d' % p.pid)
+ util.debug('cleaning up worker %d' % p.pid)
p.join()
def __enter__(self):
@@ -711,7 +737,10 @@ class IMapUnorderedIterator(IMapIterator):
class ThreadPool(Pool):
- from .dummy import Process
+ @staticmethod
+ def Process(*args, **kwds):
+ from .dummy import Process
+ return Process(*args, **kwds)
def __init__(self, processes=None, initializer=None, initargs=()):
Pool.__init__(self, processes, initializer, initargs)
diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py
new file mode 100644
index 0000000000..463cc18aa3
--- /dev/null
+++ b/Lib/multiprocessing/popen_fork.py
@@ -0,0 +1,83 @@
+import os
+import sys
+import signal
+import errno
+
+from . import util
+
+__all__ = ['Popen']
+
+#
+# Start child process using fork
+#
+
+class Popen(object):
+ method = 'fork'
+
+ def __init__(self, process_obj):
+ sys.stdout.flush()
+ sys.stderr.flush()
+ self.returncode = None
+ self._launch(process_obj)
+
+ def duplicate_for_child(self, fd):
+ return fd
+
+ def poll(self, flag=os.WNOHANG):
+ if self.returncode is None:
+ while True:
+ try:
+ pid, sts = os.waitpid(self.pid, flag)
+ except OSError as e:
+ if e.errno == errno.EINTR:
+ continue
+ # Child process not yet created. See #1731717
+ # e.errno == errno.ECHILD == 10
+ return None
+ else:
+ break
+ if pid == self.pid:
+ if os.WIFSIGNALED(sts):
+ self.returncode = -os.WTERMSIG(sts)
+ else:
+ assert os.WIFEXITED(sts)
+ self.returncode = os.WEXITSTATUS(sts)
+ return self.returncode
+
+ def wait(self, timeout=None):
+ if self.returncode is None:
+ if timeout is not None:
+ from .connection import wait
+ if not wait([self.sentinel], timeout):
+ return None
+ # This shouldn't block if wait() returned successfully.
+ return self.poll(os.WNOHANG if timeout == 0.0 else 0)
+ return self.returncode
+
+ def terminate(self):
+ if self.returncode is None:
+ try:
+ os.kill(self.pid, signal.SIGTERM)
+ except ProcessLookupError:
+ pass
+ except OSError:
+ if self.wait(timeout=0.1) is None:
+ raise
+
+ def _launch(self, process_obj):
+ code = 1
+ parent_r, child_w = os.pipe()
+ self.pid = os.fork()
+ if self.pid == 0:
+ try:
+ os.close(parent_r)
+ if 'random' in sys.modules:
+ import random
+ random.seed()
+ code = process_obj._bootstrap()
+ finally:
+ os._exit(code)
+ else:
+ os.close(child_w)
+ util.Finalize(self, os.close, (parent_r,))
+ self.sentinel = parent_r
diff --git a/Lib/multiprocessing/popen_forkserver.py b/Lib/multiprocessing/popen_forkserver.py
new file mode 100644
index 0000000000..b115f8123c
--- /dev/null
+++ b/Lib/multiprocessing/popen_forkserver.py
@@ -0,0 +1,69 @@
+import io
+import os
+
+from . import reduction
+if not reduction.HAVE_SEND_HANDLE:
+ raise ImportError('No support for sending fds between processes')
+from . import context
+from . import forkserver
+from . import popen_fork
+from . import spawn
+from . import util
+
+
+__all__ = ['Popen']
+
+#
+# Wrapper for an fd used while launching a process
+#
+
+class _DupFd(object):
+ def __init__(self, ind):
+ self.ind = ind
+ def detach(self):
+ return forkserver.get_inherited_fds()[self.ind]
+
+#
+# Start child process using a server process
+#
+
+class Popen(popen_fork.Popen):
+ method = 'forkserver'
+ DupFd = _DupFd
+
+ def __init__(self, process_obj):
+ self._fds = []
+ super().__init__(process_obj)
+
+ def duplicate_for_child(self, fd):
+ self._fds.append(fd)
+ return len(self._fds) - 1
+
+ def _launch(self, process_obj):
+ prep_data = spawn.get_preparation_data(process_obj._name)
+ buf = io.BytesIO()
+ context.set_spawning_popen(self)
+ try:
+ reduction.dump(prep_data, buf)
+ reduction.dump(process_obj, buf)
+ finally:
+ context.set_spawning_popen(None)
+
+ self.sentinel, w = forkserver.connect_to_new_process(self._fds)
+ util.Finalize(self, os.close, (self.sentinel,))
+ with open(w, 'wb', closefd=True) as f:
+ f.write(buf.getbuffer())
+ self.pid = forkserver.read_unsigned(self.sentinel)
+
+ def poll(self, flag=os.WNOHANG):
+ if self.returncode is None:
+ from .connection import wait
+ timeout = 0 if flag == os.WNOHANG else None
+ if not wait([self.sentinel], timeout):
+ return None
+ try:
+ self.returncode = forkserver.read_unsigned(self.sentinel)
+ except (OSError, EOFError):
+ # The process ended abnormally perhaps because of a signal
+ self.returncode = 255
+ return self.returncode
diff --git a/Lib/multiprocessing/popen_spawn_posix.py b/Lib/multiprocessing/popen_spawn_posix.py
new file mode 100644
index 0000000000..8b5dc42aa1
--- /dev/null
+++ b/Lib/multiprocessing/popen_spawn_posix.py
@@ -0,0 +1,72 @@
+import fcntl
+import io
+import os
+
+from . import context
+from . import popen_fork
+from . import reduction
+from . import spawn
+from . import util
+
+from . import current_process
+
+__all__ = ['Popen']
+
+
+#
+# Wrapper for an fd used while launching a process
+#
+
+class _DupFd(object):
+ def __init__(self, fd):
+ self.fd = fd
+ def detach(self):
+ return self.fd
+
+#
+# Start child process using a fresh interpreter
+#
+
+class Popen(popen_fork.Popen):
+ method = 'spawn'
+ DupFd = _DupFd
+
+ def __init__(self, process_obj):
+ self._fds = []
+ super().__init__(process_obj)
+
+ def duplicate_for_child(self, fd):
+ self._fds.append(fd)
+ return fd
+
+ def _launch(self, process_obj):
+ from . import semaphore_tracker
+ tracker_fd = semaphore_tracker.getfd()
+ self._fds.append(tracker_fd)
+ prep_data = spawn.get_preparation_data(process_obj._name)
+ fp = io.BytesIO()
+ context.set_spawning_popen(self)
+ try:
+ reduction.dump(prep_data, fp)
+ reduction.dump(process_obj, fp)
+ finally:
+ context.set_spawning_popen(None)
+
+ parent_r = child_w = child_r = parent_w = None
+ try:
+ parent_r, child_w = os.pipe()
+ child_r, parent_w = os.pipe()
+ cmd = spawn.get_command_line(tracker_fd=tracker_fd,
+ pipe_handle=child_r)
+ self._fds.extend([child_r, child_w])
+ self.pid = util.spawnv_passfds(spawn.get_executable(),
+ cmd, self._fds)
+ self.sentinel = parent_r
+ with open(parent_w, 'wb', closefd=False) as f:
+ f.write(fp.getbuffer())
+ finally:
+ if parent_r is not None:
+ util.Finalize(self, os.close, (parent_r,))
+ for fd in (child_r, child_w, parent_w):
+ if fd is not None:
+ os.close(fd)
diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py
new file mode 100644
index 0000000000..3b53068be4
--- /dev/null
+++ b/Lib/multiprocessing/popen_spawn_win32.py
@@ -0,0 +1,99 @@
+import os
+import msvcrt
+import signal
+import sys
+import _winapi
+
+from . import context
+from . import spawn
+from . import reduction
+from . import util
+
+__all__ = ['Popen']
+
+#
+#
+#
+
+TERMINATE = 0x10000
+WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
+WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
+
+#
+# We define a Popen class similar to the one from subprocess, but
+# whose constructor takes a process object as its argument.
+#
+
+class Popen(object):
+ '''
+ Start a subprocess to run the code of a process object
+ '''
+ method = 'spawn'
+
+ def __init__(self, process_obj):
+ prep_data = spawn.get_preparation_data(process_obj._name)
+
+ # read end of pipe will be "stolen" by the child process
+ # -- see spawn_main() in spawn.py.
+ rhandle, whandle = _winapi.CreatePipe(None, 0)
+ wfd = msvcrt.open_osfhandle(whandle, 0)
+ cmd = spawn.get_command_line(parent_pid=os.getpid(),
+ pipe_handle=rhandle)
+ cmd = ' '.join('"%s"' % x for x in cmd)
+
+ with open(wfd, 'wb', closefd=True) as to_child:
+ # start process
+ try:
+ hp, ht, pid, tid = _winapi.CreateProcess(
+ spawn.get_executable(), cmd,
+ None, None, False, 0, None, None, None)
+ _winapi.CloseHandle(ht)
+ except:
+ _winapi.CloseHandle(rhandle)
+ raise
+
+ # set attributes of self
+ self.pid = pid
+ self.returncode = None
+ self._handle = hp
+ self.sentinel = int(hp)
+ util.Finalize(self, _winapi.CloseHandle, (self.sentinel,))
+
+ # send information to child
+ context.set_spawning_popen(self)
+ try:
+ reduction.dump(prep_data, to_child)
+ reduction.dump(process_obj, to_child)
+ finally:
+ context.set_spawning_popen(None)
+
+ def duplicate_for_child(self, handle):
+ assert self is context.get_spawning_popen()
+ return reduction.duplicate(handle, self.sentinel)
+
+ def wait(self, timeout=None):
+ if self.returncode is None:
+ if timeout is None:
+ msecs = _winapi.INFINITE
+ else:
+ msecs = max(0, int(timeout * 1000 + 0.5))
+
+ res = _winapi.WaitForSingleObject(int(self._handle), msecs)
+ if res == _winapi.WAIT_OBJECT_0:
+ code = _winapi.GetExitCodeProcess(self._handle)
+ if code == TERMINATE:
+ code = -signal.SIGTERM
+ self.returncode = code
+
+ return self.returncode
+
+ def poll(self):
+ return self.wait(timeout=0)
+
+ def terminate(self):
+ if self.returncode is None:
+ try:
+ _winapi.TerminateProcess(int(self._handle), TERMINATE)
+ except OSError:
+ if self.wait(timeout=1.0) is None:
+ raise
diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py
index 3d32add71a..68959bf9f4 100644
--- a/Lib/multiprocessing/process.py
+++ b/Lib/multiprocessing/process.py
@@ -7,7 +7,7 @@
# Licensed to PSF under a Contributor Agreement.
#
-__all__ = ['Process', 'current_process', 'active_children']
+__all__ = ['BaseProcess', 'current_process', 'active_children']
#
# Imports
@@ -43,7 +43,7 @@ def active_children():
Return list of process objects corresponding to live child processes
'''
_cleanup()
- return list(_current_process._children)
+ return list(_children)
#
#
@@ -51,33 +51,29 @@ def active_children():
def _cleanup():
# check for processes which have finished
- for p in list(_current_process._children):
+ for p in list(_children):
if p._popen.poll() is not None:
- _current_process._children.discard(p)
+ _children.discard(p)
#
# The `Process` class
#
-class Process(object):
+class BaseProcess(object):
'''
Process objects represent activity that is run in a separate process
- The class is analagous to `threading.Thread`
+ The class is analogous to `threading.Thread`
'''
- _Popen = None
+ def _Popen(self):
+ raise NotImplementedError
def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
*, daemon=None):
assert group is None, 'group argument must be None for now'
- count = next(_current_process._counter)
+ count = next(_process_counter)
self._identity = _current_process._identity + (count,)
- self._authkey = _current_process._authkey
- if daemon is not None:
- self._daemonic = daemon
- else:
- self._daemonic = _current_process._daemonic
- self._tempdir = _current_process._tempdir
+ self._config = _current_process._config.copy()
self._parent_pid = os.getpid()
self._popen = None
self._target = target
@@ -85,6 +81,8 @@ class Process(object):
self._kwargs = dict(kwargs)
self._name = name or type(self).__name__ + '-' + \
':'.join(str(i) for i in self._identity)
+ if daemon is not None:
+ self.daemon = daemon
_dangling.add(self)
def run(self):
@@ -101,16 +99,12 @@ class Process(object):
assert self._popen is None, 'cannot start a process twice'
assert self._parent_pid == os.getpid(), \
'can only start a process object created by current process'
- assert not _current_process._daemonic, \
+ assert not _current_process._config.get('daemon'), \
'daemonic processes are not allowed to have children'
_cleanup()
- if self._Popen is not None:
- Popen = self._Popen
- else:
- from .forking import Popen
- self._popen = Popen(self)
+ self._popen = self._Popen(self)
self._sentinel = self._popen.sentinel
- _current_process._children.add(self)
+ _children.add(self)
def terminate(self):
'''
@@ -126,7 +120,7 @@ class Process(object):
assert self._popen is not None, 'can only join a started process'
res = self._popen.wait(timeout)
if res is not None:
- _current_process._children.discard(self)
+ _children.discard(self)
def is_alive(self):
'''
@@ -154,7 +148,7 @@ class Process(object):
'''
Return whether process is a daemon
'''
- return self._daemonic
+ return self._config.get('daemon', False)
@daemon.setter
def daemon(self, daemonic):
@@ -162,18 +156,18 @@ class Process(object):
Set whether process is a daemon
'''
assert self._popen is None, 'process has already started'
- self._daemonic = daemonic
+ self._config['daemon'] = daemonic
@property
def authkey(self):
- return self._authkey
+ return self._config['authkey']
@authkey.setter
def authkey(self, authkey):
'''
Set authorization key of process
'''
- self._authkey = AuthenticationString(authkey)
+ self._config['authkey'] = AuthenticationString(authkey)
@property
def exitcode(self):
@@ -227,17 +221,19 @@ class Process(object):
status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
- status, self._daemonic and ' daemon' or '')
+ status, self.daemon and ' daemon' or '')
##
def _bootstrap(self):
- from . import util
- global _current_process
+ from . import util, context
+ global _current_process, _process_counter, _children
try:
- self._children = set()
- self._counter = itertools.count(1)
+ if self._start_method is not None:
+ context._force_start_method(self._start_method)
+ _process_counter = itertools.count(1)
+ _children = set()
if sys.stdin is not None:
try:
sys.stdin.close()
@@ -285,8 +281,8 @@ class Process(object):
class AuthenticationString(bytes):
def __reduce__(self):
- from .forking import Popen
- if not Popen.thread_is_spawning():
+ from .context import get_spawning_popen
+ if get_spawning_popen() is None:
raise TypeError(
'Pickling an AuthenticationString object is '
'disallowed for security reasons'
@@ -297,20 +293,29 @@ class AuthenticationString(bytes):
# Create object representing the main process
#
-class _MainProcess(Process):
+class _MainProcess(BaseProcess):
def __init__(self):
self._identity = ()
- self._daemonic = False
self._name = 'MainProcess'
self._parent_pid = None
self._popen = None
- self._counter = itertools.count(1)
- self._children = set()
- self._authkey = AuthenticationString(os.urandom(32))
- self._tempdir = None
+ self._config = {'authkey': AuthenticationString(os.urandom(32)),
+ 'semprefix': '/mp'}
+ # Note that some versions of FreeBSD only allow named
+ # semaphores to have names of up to 14 characters. Therefore
+ # we choose a short prefix.
+ #
+ # On MacOSX in a sandbox it may be necessary to use a
+ # different prefix -- see #19478.
+ #
+ # Everything in self._config will be inherited by descendant
+ # processes.
+
_current_process = _MainProcess()
+_process_counter = itertools.count(1)
+_children = set()
del _MainProcess
#
diff --git a/Lib/multiprocessing/queues.py b/Lib/multiprocessing/queues.py
index 37271fb4eb..f650771092 100644
--- a/Lib/multiprocessing/queues.py
+++ b/Lib/multiprocessing/queues.py
@@ -18,11 +18,14 @@ import weakref
import errno
from queue import Empty, Full
+
import _multiprocessing
-from multiprocessing.connection import Pipe
-from multiprocessing.synchronize import Lock, BoundedSemaphore, Semaphore, Condition
-from multiprocessing.util import debug, info, Finalize, register_after_fork
-from multiprocessing.forking import assert_spawning
+
+from . import connection
+from . import context
+
+from .util import debug, info, Finalize, register_after_fork, is_exiting
+from .reduction import ForkingPickler
#
# Queue type using a pipe, buffer and thread
@@ -30,18 +33,18 @@ from multiprocessing.forking import assert_spawning
class Queue(object):
- def __init__(self, maxsize=0):
+ def __init__(self, maxsize=0, *, ctx):
if maxsize <= 0:
maxsize = _multiprocessing.SemLock.SEM_VALUE_MAX
self._maxsize = maxsize
- self._reader, self._writer = Pipe(duplex=False)
- self._rlock = Lock()
+ self._reader, self._writer = connection.Pipe(duplex=False)
+ self._rlock = ctx.Lock()
self._opid = os.getpid()
if sys.platform == 'win32':
self._wlock = None
else:
- self._wlock = Lock()
- self._sem = BoundedSemaphore(maxsize)
+ self._wlock = ctx.Lock()
+ self._sem = ctx.BoundedSemaphore(maxsize)
# For use by concurrent.futures
self._ignore_epipe = False
@@ -51,7 +54,7 @@ class Queue(object):
register_after_fork(self, Queue._after_fork)
def __getstate__(self):
- assert_spawning(self)
+ context.assert_spawning(self)
return (self._ignore_epipe, self._maxsize, self._reader, self._writer,
self._rlock, self._wlock, self._sem, self._opid)
@@ -69,8 +72,8 @@ class Queue(object):
self._joincancelled = False
self._closed = False
self._close = None
- self._send = self._writer.send
- self._recv = self._reader.recv
+ self._send_bytes = self._writer.send_bytes
+ self._recv_bytes = self._reader.recv_bytes
self._poll = self._reader.poll
def put(self, obj, block=True, timeout=None):
@@ -89,14 +92,9 @@ class Queue(object):
def get(self, block=True, timeout=None):
if block and timeout is None:
- self._rlock.acquire()
- try:
- res = self._recv()
- self._sem.release()
- return res
- finally:
- self._rlock.release()
-
+ with self._rlock:
+ res = self._recv_bytes()
+ self._sem.release()
else:
if block:
deadline = time.time() + timeout
@@ -109,11 +107,12 @@ class Queue(object):
raise Empty
elif not self._poll():
raise Empty
- res = self._recv()
+ res = self._recv_bytes()
self._sem.release()
- return res
finally:
self._rlock.release()
+ # unserialize the data after having released the lock
+ return ForkingPickler.loads(res)
def qsize(self):
# Raises NotImplementedError on Mac OSX because of broken sem_getvalue()
@@ -158,7 +157,7 @@ class Queue(object):
self._buffer.clear()
self._thread = threading.Thread(
target=Queue._feed,
- args=(self._buffer, self._notempty, self._send,
+ args=(self._buffer, self._notempty, self._send_bytes,
self._wlock, self._writer.close, self._ignore_epipe),
name='QueueFeederThread'
)
@@ -210,10 +209,8 @@ class Queue(object):
notempty.release()
@staticmethod
- def _feed(buffer, notempty, send, writelock, close, ignore_epipe):
+ def _feed(buffer, notempty, send_bytes, writelock, close, ignore_epipe):
debug('starting thread to feed data to pipe')
- from .util import is_exiting
-
nacquire = notempty.acquire
nrelease = notempty.release
nwait = notempty.wait
@@ -241,12 +238,14 @@ class Queue(object):
close()
return
+ # serialize the data before acquiring the lock
+ obj = ForkingPickler.dumps(obj)
if wacquire is None:
- send(obj)
+ send_bytes(obj)
else:
wacquire()
try:
- send(obj)
+ send_bytes(obj)
finally:
wrelease()
except IndexError:
@@ -279,10 +278,10 @@ _sentinel = object()
class JoinableQueue(Queue):
- def __init__(self, maxsize=0):
- Queue.__init__(self, maxsize)
- self._unfinished_tasks = Semaphore(0)
- self._cond = Condition()
+ def __init__(self, maxsize=0, *, ctx):
+ Queue.__init__(self, maxsize, ctx=ctx)
+ self._unfinished_tasks = ctx.Semaphore(0)
+ self._cond = ctx.Condition()
def __getstate__(self):
return Queue.__getstate__(self) + (self._cond, self._unfinished_tasks)
@@ -332,48 +331,37 @@ class JoinableQueue(Queue):
class SimpleQueue(object):
- def __init__(self):
- self._reader, self._writer = Pipe(duplex=False)
- self._rlock = Lock()
+ def __init__(self, *, ctx):
+ self._reader, self._writer = connection.Pipe(duplex=False)
+ self._rlock = ctx.Lock()
self._poll = self._reader.poll
if sys.platform == 'win32':
self._wlock = None
else:
- self._wlock = Lock()
- self._make_methods()
+ self._wlock = ctx.Lock()
def empty(self):
return not self._poll()
def __getstate__(self):
- assert_spawning(self)
+ context.assert_spawning(self)
return (self._reader, self._writer, self._rlock, self._wlock)
def __setstate__(self, state):
(self._reader, self._writer, self._rlock, self._wlock) = state
- self._make_methods()
- def _make_methods(self):
- recv = self._reader.recv
- racquire, rrelease = self._rlock.acquire, self._rlock.release
- def get():
- racquire()
- try:
- return recv()
- finally:
- rrelease()
- self.get = get
+ def get(self):
+ with self._rlock:
+ res = self._reader.recv_bytes()
+ # unserialize the data after having released the lock
+ return ForkingPickler.loads(res)
+ def put(self, obj):
+ # serialize the data before acquiring the lock
+ obj = ForkingPickler.dumps(obj)
if self._wlock is None:
# writes to a message oriented win32 pipe are atomic
- self.put = self._writer.send
+ self._writer.send_bytes(obj)
else:
- send = self._writer.send
- wacquire, wrelease = self._wlock.acquire, self._wlock.release
- def put(obj):
- wacquire()
- try:
- return send(obj)
- finally:
- wrelease()
- self.put = put
+ with self._wlock:
+ self._writer.send_bytes(obj)
diff --git a/Lib/multiprocessing/reduction.py b/Lib/multiprocessing/reduction.py
index 656fa8ff6b..01e6de2f96 100644
--- a/Lib/multiprocessing/reduction.py
+++ b/Lib/multiprocessing/reduction.py
@@ -1,6 +1,5 @@
#
-# Module to allow connection and socket objects to be transferred
-# between processes
+# Module which deals with pickling of objects.
#
# multiprocessing/reduction.py
#
@@ -8,27 +7,57 @@
# Licensed to PSF under a Contributor Agreement.
#
-__all__ = ['reduce_socket', 'reduce_connection', 'send_handle', 'recv_handle']
-
+import copyreg
+import functools
+import io
import os
-import sys
+import pickle
import socket
-import threading
-import struct
-import signal
+import sys
-from multiprocessing import current_process
-from multiprocessing.util import register_after_fork, debug, sub_debug
-from multiprocessing.util import is_exiting, sub_warning
+from . import context
+from . import util
+__all__ = ['send_handle', 'recv_handle', 'ForkingPickler', 'register', 'dump']
+
+
+HAVE_SEND_HANDLE = (sys.platform == 'win32' or
+ (hasattr(socket, 'CMSG_LEN') and
+ hasattr(socket, 'SCM_RIGHTS') and
+ hasattr(socket.socket, 'sendmsg')))
#
+# Pickler subclass
#
-#
-if not(sys.platform == 'win32' or (hasattr(socket, 'CMSG_LEN') and
- hasattr(socket, 'SCM_RIGHTS'))):
- raise ImportError('pickling of connections not supported')
+class ForkingPickler(pickle.Pickler):
+ '''Pickler subclass used by multiprocessing.'''
+ _extra_reducers = {}
+ _copyreg_dispatch_table = copyreg.dispatch_table
+
+ def __init__(self, *args):
+ super().__init__(*args)
+ self.dispatch_table = self._copyreg_dispatch_table.copy()
+ self.dispatch_table.update(self._extra_reducers)
+
+ @classmethod
+ def register(cls, type, reduce):
+ '''Register a reduce function for a type.'''
+ cls._extra_reducers[type] = reduce
+
+ @classmethod
+ def dumps(cls, obj, protocol=None):
+ buf = io.BytesIO()
+ cls(buf, protocol).dump(obj)
+ return buf.getbuffer()
+
+ loads = pickle.loads
+
+register = ForkingPickler.register
+
+def dump(obj, file, protocol=None):
+ '''Replacement for pickle.dump() using ForkingPickler.'''
+ ForkingPickler(file, protocol).dump(obj)
#
# Platform specific definitions
@@ -36,20 +65,44 @@ if not(sys.platform == 'win32' or (hasattr(socket, 'CMSG_LEN') and
if sys.platform == 'win32':
# Windows
- __all__ += ['reduce_pipe_connection']
+ __all__ += ['DupHandle', 'duplicate', 'steal_handle']
import _winapi
+ def duplicate(handle, target_process=None, inheritable=False):
+ '''Duplicate a handle. (target_process is a handle not a pid!)'''
+ if target_process is None:
+ target_process = _winapi.GetCurrentProcess()
+ return _winapi.DuplicateHandle(
+ _winapi.GetCurrentProcess(), handle, target_process,
+ 0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)
+
+ def steal_handle(source_pid, handle):
+ '''Steal a handle from process identified by source_pid.'''
+ source_process_handle = _winapi.OpenProcess(
+ _winapi.PROCESS_DUP_HANDLE, False, source_pid)
+ try:
+ return _winapi.DuplicateHandle(
+ source_process_handle, handle,
+ _winapi.GetCurrentProcess(), 0, False,
+ _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE)
+ finally:
+ _winapi.CloseHandle(source_process_handle)
+
def send_handle(conn, handle, destination_pid):
+ '''Send a handle over a local connection.'''
dh = DupHandle(handle, _winapi.DUPLICATE_SAME_ACCESS, destination_pid)
conn.send(dh)
def recv_handle(conn):
+ '''Receive a handle over a local connection.'''
return conn.recv().detach()
class DupHandle(object):
+ '''Picklable wrapper for a handle.'''
def __init__(self, handle, access, pid=None):
- # duplicate handle for process with given pid
if pid is None:
+ # We just duplicate the handle in the current process and
+ # let the receiving process steal the handle.
pid = os.getpid()
proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False, pid)
try:
@@ -62,9 +115,12 @@ if sys.platform == 'win32':
self._pid = pid
def detach(self):
+ '''Get the handle. This should only be called once.'''
# retrieve handle from process which currently owns it
if self._pid == os.getpid():
+ # The handle has already been duplicated for this process.
return self._handle
+ # We must steal the handle from the process whose pid is self._pid.
proc = _winapi.OpenProcess(_winapi.PROCESS_DUP_HANDLE, False,
self._pid)
try:
@@ -74,207 +130,112 @@ if sys.platform == 'win32':
finally:
_winapi.CloseHandle(proc)
- class DupSocket(object):
- def __init__(self, sock):
- new_sock = sock.dup()
- def send(conn, pid):
- share = new_sock.share(pid)
- conn.send_bytes(share)
- self._id = resource_sharer.register(send, new_sock.close)
-
- def detach(self):
- conn = resource_sharer.get_connection(self._id)
- try:
- share = conn.recv_bytes()
- return socket.fromshare(share)
- finally:
- conn.close()
-
- def reduce_socket(s):
- return rebuild_socket, (DupSocket(s),)
-
- def rebuild_socket(ds):
- return ds.detach()
-
- def reduce_connection(conn):
- handle = conn.fileno()
- with socket.fromfd(handle, socket.AF_INET, socket.SOCK_STREAM) as s:
- ds = DupSocket(s)
- return rebuild_connection, (ds, conn.readable, conn.writable)
-
- def rebuild_connection(ds, readable, writable):
- from .connection import Connection
- sock = ds.detach()
- return Connection(sock.detach(), readable, writable)
-
- def reduce_pipe_connection(conn):
- access = ((_winapi.FILE_GENERIC_READ if conn.readable else 0) |
- (_winapi.FILE_GENERIC_WRITE if conn.writable else 0))
- dh = DupHandle(conn.fileno(), access)
- return rebuild_pipe_connection, (dh, conn.readable, conn.writable)
-
- def rebuild_pipe_connection(dh, readable, writable):
- from .connection import PipeConnection
- handle = dh.detach()
- return PipeConnection(handle, readable, writable)
-
else:
# Unix
+ __all__ += ['DupFd', 'sendfds', 'recvfds']
+ import array
# On MacOSX we should acknowledge receipt of fds -- see Issue14669
ACKNOWLEDGE = sys.platform == 'darwin'
+ def sendfds(sock, fds):
+ '''Send an array of fds over an AF_UNIX socket.'''
+ fds = array.array('i', fds)
+ msg = bytes([len(fds) % 256])
+ sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
+ if ACKNOWLEDGE and sock.recv(1) != b'A':
+ raise RuntimeError('did not receive acknowledgement of fd')
+
+ def recvfds(sock, size):
+ '''Receive an array of fds over an AF_UNIX socket.'''
+ a = array.array('i')
+ bytes_size = a.itemsize * size
+ msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_LEN(bytes_size))
+ if not msg and not ancdata:
+ raise EOFError
+ try:
+ if ACKNOWLEDGE:
+ sock.send(b'A')
+ if len(ancdata) != 1:
+ raise RuntimeError('received %d items of ancdata' %
+ len(ancdata))
+ cmsg_level, cmsg_type, cmsg_data = ancdata[0]
+ if (cmsg_level == socket.SOL_SOCKET and
+ cmsg_type == socket.SCM_RIGHTS):
+ if len(cmsg_data) % a.itemsize != 0:
+ raise ValueError
+ a.frombytes(cmsg_data)
+ assert len(a) % 256 == msg[0]
+ return list(a)
+ except (ValueError, IndexError):
+ pass
+ raise RuntimeError('Invalid data received')
+
def send_handle(conn, handle, destination_pid):
+ '''Send a handle over a local connection.'''
with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
- s.sendmsg([b'x'], [(socket.SOL_SOCKET, socket.SCM_RIGHTS,
- struct.pack("@i", handle))])
- if ACKNOWLEDGE and conn.recv_bytes() != b'ACK':
- raise RuntimeError('did not receive acknowledgement of fd')
+ sendfds(s, [handle])
def recv_handle(conn):
- size = struct.calcsize("@i")
+ '''Receive a handle over a local connection.'''
with socket.fromfd(conn.fileno(), socket.AF_UNIX, socket.SOCK_STREAM) as s:
- msg, ancdata, flags, addr = s.recvmsg(1, socket.CMSG_LEN(size))
- try:
- if ACKNOWLEDGE:
- conn.send_bytes(b'ACK')
- cmsg_level, cmsg_type, cmsg_data = ancdata[0]
- if (cmsg_level == socket.SOL_SOCKET and
- cmsg_type == socket.SCM_RIGHTS):
- return struct.unpack("@i", cmsg_data[:size])[0]
- except (ValueError, IndexError, struct.error):
- pass
- raise RuntimeError('Invalid data received')
-
- class DupFd(object):
- def __init__(self, fd):
- new_fd = os.dup(fd)
- def send(conn, pid):
- send_handle(conn, new_fd, pid)
- def close():
- os.close(new_fd)
- self._id = resource_sharer.register(send, close)
+ return recvfds(s, 1)[0]
+
+ def DupFd(fd):
+ '''Return a wrapper for an fd.'''
+ popen_obj = context.get_spawning_popen()
+ if popen_obj is not None:
+ return popen_obj.DupFd(popen_obj.duplicate_for_child(fd))
+ elif HAVE_SEND_HANDLE:
+ from . import resource_sharer
+ return resource_sharer.DupFd(fd)
+ else:
+ raise ValueError('SCM_RIGHTS appears not to be available')
- def detach(self):
- conn = resource_sharer.get_connection(self._id)
- try:
- return recv_handle(conn)
- finally:
- conn.close()
+#
+# Try making some callable types picklable
+#
- def reduce_socket(s):
- df = DupFd(s.fileno())
- return rebuild_socket, (df, s.family, s.type, s.proto)
+def _reduce_method(m):
+ if m.__self__ is None:
+ return getattr, (m.__class__, m.__func__.__name__)
+ else:
+ return getattr, (m.__self__, m.__func__.__name__)
+class _C:
+ def f(self):
+ pass
+register(type(_C().f), _reduce_method)
- def rebuild_socket(df, family, type, proto):
- fd = df.detach()
- s = socket.fromfd(fd, family, type, proto)
- os.close(fd)
- return s
- def reduce_connection(conn):
- df = DupFd(conn.fileno())
- return rebuild_connection, (df, conn.readable, conn.writable)
+def _reduce_method_descriptor(m):
+ return getattr, (m.__objclass__, m.__name__)
+register(type(list.append), _reduce_method_descriptor)
+register(type(int.__add__), _reduce_method_descriptor)
- def rebuild_connection(df, readable, writable):
- from .connection import Connection
- fd = df.detach()
- return Connection(fd, readable, writable)
+
+def _reduce_partial(p):
+ return _rebuild_partial, (p.func, p.args, p.keywords or {})
+def _rebuild_partial(func, args, keywords):
+ return functools.partial(func, *args, **keywords)
+register(functools.partial, _reduce_partial)
#
-# Server which shares registered resources with clients
+# Make sockets picklable
#
-class ResourceSharer(object):
- def __init__(self):
- self._key = 0
- self._cache = {}
- self._old_locks = []
- self._lock = threading.Lock()
- self._listener = None
- self._address = None
- self._thread = None
- register_after_fork(self, ResourceSharer._afterfork)
-
- def register(self, send, close):
- with self._lock:
- if self._address is None:
- self._start()
- self._key += 1
- self._cache[self._key] = (send, close)
- return (self._address, self._key)
-
- @staticmethod
- def get_connection(ident):
- from .connection import Client
- address, key = ident
- c = Client(address, authkey=current_process().authkey)
- c.send((key, os.getpid()))
- return c
-
- def stop(self, timeout=None):
- from .connection import Client
- with self._lock:
- if self._address is not None:
- c = Client(self._address, authkey=current_process().authkey)
- c.send(None)
- c.close()
- self._thread.join(timeout)
- if self._thread.is_alive():
- sub_warn('ResourceSharer thread did not stop when asked')
- self._listener.close()
- self._thread = None
- self._address = None
- self._listener = None
- for key, (send, close) in self._cache.items():
- close()
- self._cache.clear()
-
- def _afterfork(self):
- for key, (send, close) in self._cache.items():
- close()
- self._cache.clear()
- # If self._lock was locked at the time of the fork, it may be broken
- # -- see issue 6721. Replace it without letting it be gc'ed.
- self._old_locks.append(self._lock)
- self._lock = threading.Lock()
- if self._listener is not None:
- self._listener.close()
- self._listener = None
- self._address = None
- self._thread = None
-
- def _start(self):
- from .connection import Listener
- assert self._listener is None
- debug('starting listener and thread for sending handles')
- self._listener = Listener(authkey=current_process().authkey)
- self._address = self._listener.address
- t = threading.Thread(target=self._serve)
- t.daemon = True
- t.start()
- self._thread = t
-
- def _serve(self):
- if hasattr(signal, 'pthread_sigmask'):
- signal.pthread_sigmask(signal.SIG_BLOCK, range(1, signal.NSIG))
- while 1:
- try:
- conn = self._listener.accept()
- msg = conn.recv()
- if msg is None:
- break
- key, destination_pid = msg
- send, close = self._cache.pop(key)
- send(conn, destination_pid)
- close()
- conn.close()
- except:
- if not is_exiting():
- import traceback
- sub_warning(
- 'thread for sharing handles raised exception :\n' +
- '-'*79 + '\n' + traceback.format_exc() + '-'*79
- )
-
-resource_sharer = ResourceSharer()
+if sys.platform == 'win32':
+ def _reduce_socket(s):
+ from .resource_sharer import DupSocket
+ return _rebuild_socket, (DupSocket(s),)
+ def _rebuild_socket(ds):
+ return ds.detach()
+ register(socket.socket, _reduce_socket)
+
+else:
+ def _reduce_socket(s):
+ df = DupFd(s.fileno())
+ return _rebuild_socket, (df, s.family, s.type, s.proto)
+ def _rebuild_socket(df, family, type, proto):
+ fd = df.detach()
+ return socket.socket(family, type, proto, fileno=fd)
+ register(socket.socket, _reduce_socket)
diff --git a/Lib/multiprocessing/resource_sharer.py b/Lib/multiprocessing/resource_sharer.py
new file mode 100644
index 0000000000..5e46fc65b4
--- /dev/null
+++ b/Lib/multiprocessing/resource_sharer.py
@@ -0,0 +1,158 @@
+#
+# We use a background thread for sharing fds on Unix, and for sharing sockets on
+# Windows.
+#
+# A client which wants to pickle a resource registers it with the resource
+# sharer and gets an identifier in return. The unpickling process will connect
+# to the resource sharer, sends the identifier and its pid, and then receives
+# the resource.
+#
+
+import os
+import signal
+import socket
+import sys
+import threading
+
+from . import process
+from . import reduction
+from . import util
+
+__all__ = ['stop']
+
+
+if sys.platform == 'win32':
+ __all__ += ['DupSocket']
+
+ class DupSocket(object):
+ '''Picklable wrapper for a socket.'''
+ def __init__(self, sock):
+ new_sock = sock.dup()
+ def send(conn, pid):
+ share = new_sock.share(pid)
+ conn.send_bytes(share)
+ self._id = _resource_sharer.register(send, new_sock.close)
+
+ def detach(self):
+ '''Get the socket. This should only be called once.'''
+ with _resource_sharer.get_connection(self._id) as conn:
+ share = conn.recv_bytes()
+ return socket.fromshare(share)
+
+else:
+ __all__ += ['DupFd']
+
+ class DupFd(object):
+ '''Wrapper for fd which can be used at any time.'''
+ def __init__(self, fd):
+ new_fd = os.dup(fd)
+ def send(conn, pid):
+ reduction.send_handle(conn, new_fd, pid)
+ def close():
+ os.close(new_fd)
+ self._id = _resource_sharer.register(send, close)
+
+ def detach(self):
+ '''Get the fd. This should only be called once.'''
+ with _resource_sharer.get_connection(self._id) as conn:
+ return reduction.recv_handle(conn)
+
+
+class _ResourceSharer(object):
+ '''Manager for resouces using background thread.'''
+ def __init__(self):
+ self._key = 0
+ self._cache = {}
+ self._old_locks = []
+ self._lock = threading.Lock()
+ self._listener = None
+ self._address = None
+ self._thread = None
+ util.register_after_fork(self, _ResourceSharer._afterfork)
+
+ def register(self, send, close):
+ '''Register resource, returning an identifier.'''
+ with self._lock:
+ if self._address is None:
+ self._start()
+ self._key += 1
+ self._cache[self._key] = (send, close)
+ return (self._address, self._key)
+
+ @staticmethod
+ def get_connection(ident):
+ '''Return connection from which to receive identified resource.'''
+ from .connection import Client
+ address, key = ident
+ c = Client(address, authkey=process.current_process().authkey)
+ c.send((key, os.getpid()))
+ return c
+
+ def stop(self, timeout=None):
+ '''Stop the background thread and clear registered resources.'''
+ from .connection import Client
+ with self._lock:
+ if self._address is not None:
+ c = Client(self._address,
+ authkey=process.current_process().authkey)
+ c.send(None)
+ c.close()
+ self._thread.join(timeout)
+ if self._thread.is_alive():
+ util.sub_warning('_ResourceSharer thread did '
+ 'not stop when asked')
+ self._listener.close()
+ self._thread = None
+ self._address = None
+ self._listener = None
+ for key, (send, close) in self._cache.items():
+ close()
+ self._cache.clear()
+
+ def _afterfork(self):
+ for key, (send, close) in self._cache.items():
+ close()
+ self._cache.clear()
+ # If self._lock was locked at the time of the fork, it may be broken
+ # -- see issue 6721. Replace it without letting it be gc'ed.
+ self._old_locks.append(self._lock)
+ self._lock = threading.Lock()
+ if self._listener is not None:
+ self._listener.close()
+ self._listener = None
+ self._address = None
+ self._thread = None
+
+ def _start(self):
+ from .connection import Listener
+ assert self._listener is None
+ util.debug('starting listener and thread for sending handles')
+ self._listener = Listener(authkey=process.current_process().authkey)
+ self._address = self._listener.address
+ t = threading.Thread(target=self._serve)
+ t.daemon = True
+ t.start()
+ self._thread = t
+
+ def _serve(self):
+ if hasattr(signal, 'pthread_sigmask'):
+ signal.pthread_sigmask(signal.SIG_BLOCK, range(1, signal.NSIG))
+ while 1:
+ try:
+ with self._listener.accept() as conn:
+ msg = conn.recv()
+ if msg is None:
+ break
+ key, destination_pid = msg
+ send, close = self._cache.pop(key)
+ try:
+ send(conn, destination_pid)
+ finally:
+ close()
+ except:
+ if not util.is_exiting():
+ sys.excepthook(*sys.exc_info())
+
+
+_resource_sharer = _ResourceSharer()
+stop = _resource_sharer.stop
diff --git a/Lib/multiprocessing/semaphore_tracker.py b/Lib/multiprocessing/semaphore_tracker.py
new file mode 100644
index 0000000000..ddb2b520f5
--- /dev/null
+++ b/Lib/multiprocessing/semaphore_tracker.py
@@ -0,0 +1,145 @@
+#
+# On Unix we run a server process which keeps track of unlinked
+# semaphores. The server ignores SIGINT and SIGTERM and reads from a
+# pipe. Every other process of the program has a copy of the writable
+# end of the pipe, so we get EOF when all other processes have exited.
+# Then the server process unlinks any remaining semaphore names.
+#
+# This is important because the system only supports a limited number
+# of named semaphores, and they will not be automatically removed till
+# the next reboot. Without this semaphore tracker process, "killall
+# python" would probably leave unlinked semaphores.
+#
+
+import errno
+import os
+import signal
+import sys
+import threading
+import warnings
+import _multiprocessing
+
+from . import spawn
+from . import util
+from . import current_process
+
+__all__ = ['ensure_running', 'register', 'unregister']
+
+
+class SemaphoreTracker(object):
+
+ def __init__(self):
+ self._lock = threading.Lock()
+ self._fd = None
+
+ def getfd(self):
+ self.ensure_running()
+ return self._fd
+
+ def ensure_running(self):
+ '''Make sure that semaphore tracker process is running.
+
+ This can be run from any process. Usually a child process will use
+ the semaphore created by its parent.'''
+ with self._lock:
+ if self._fd is not None:
+ return
+ fds_to_pass = []
+ try:
+ fds_to_pass.append(sys.stderr.fileno())
+ except Exception:
+ pass
+ cmd = 'from multiprocessing.semaphore_tracker import main;main(%d)'
+ r, w = os.pipe()
+ try:
+ fds_to_pass.append(r)
+ # process will out live us, so no need to wait on pid
+ exe = spawn.get_executable()
+ args = [exe] + util._args_from_interpreter_flags()
+ args += ['-c', cmd % r]
+ util.spawnv_passfds(exe, args, fds_to_pass)
+ except:
+ os.close(w)
+ raise
+ else:
+ self._fd = w
+ finally:
+ os.close(r)
+
+ def register(self, name):
+ '''Register name of semaphore with semaphore tracker.'''
+ self._send('REGISTER', name)
+
+ def unregister(self, name):
+ '''Unregister name of semaphore with semaphore tracker.'''
+ self._send('UNREGISTER', name)
+
+ def _send(self, cmd, name):
+ self.ensure_running()
+ msg = '{0}:{1}\n'.format(cmd, name).encode('ascii')
+ if len(name) > 512:
+ # posix guarantees that writes to a pipe of less than PIPE_BUF
+ # bytes are atomic, and that PIPE_BUF >= 512
+ raise ValueError('name too long')
+ nbytes = os.write(self._fd, msg)
+ assert nbytes == len(msg)
+
+
+_semaphore_tracker = SemaphoreTracker()
+ensure_running = _semaphore_tracker.ensure_running
+register = _semaphore_tracker.register
+unregister = _semaphore_tracker.unregister
+getfd = _semaphore_tracker.getfd
+
+
+def main(fd):
+ '''Run semaphore tracker.'''
+ # protect the process from ^C and "killall python" etc
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
+
+ for f in (sys.stdin, sys.stdout):
+ try:
+ f.close()
+ except Exception:
+ pass
+
+ cache = set()
+ try:
+ # keep track of registered/unregistered semaphores
+ with open(fd, 'rb') as f:
+ for line in f:
+ try:
+ cmd, name = line.strip().split(b':')
+ if cmd == b'REGISTER':
+ cache.add(name)
+ elif cmd == b'UNREGISTER':
+ cache.remove(name)
+ else:
+ raise RuntimeError('unrecognized command %r' % cmd)
+ except Exception:
+ try:
+ sys.excepthook(*sys.exc_info())
+ except:
+ pass
+ finally:
+ # all processes have terminated; cleanup any remaining semaphores
+ if cache:
+ try:
+ warnings.warn('semaphore_tracker: There appear to be %d '
+ 'leaked semaphores to clean up at shutdown' %
+ len(cache))
+ except Exception:
+ pass
+ for name in cache:
+ # For some reason the process which created and registered this
+ # semaphore has failed to unregister it. Presumably it has died.
+ # We therefore unlink it.
+ try:
+ name = name.decode('ascii')
+ try:
+ _multiprocessing.sem_unlink(name)
+ except Exception as e:
+ warnings.warn('semaphore_tracker: %r: %s' % (name, e))
+ finally:
+ pass
diff --git a/Lib/multiprocessing/sharedctypes.py b/Lib/multiprocessing/sharedctypes.py
index a358ed4f12..0c178252d5 100644
--- a/Lib/multiprocessing/sharedctypes.py
+++ b/Lib/multiprocessing/sharedctypes.py
@@ -10,8 +10,11 @@
import ctypes
import weakref
-from multiprocessing import heap, RLock
-from multiprocessing.forking import assert_spawning, ForkingPickler
+from . import heap
+from . import get_context
+
+from .context import assert_spawning
+from .reduction import ForkingPickler
__all__ = ['RawValue', 'RawArray', 'Value', 'Array', 'copy', 'synchronized']
@@ -63,7 +66,7 @@ def RawArray(typecode_or_type, size_or_initializer):
result.__init__(*size_or_initializer)
return result
-def Value(typecode_or_type, *args, lock=True):
+def Value(typecode_or_type, *args, lock=True, ctx=None):
'''
Return a synchronization wrapper for a Value
'''
@@ -71,12 +74,13 @@ def Value(typecode_or_type, *args, lock=True):
if lock is False:
return obj
if lock in (True, None):
- lock = RLock()
+ ctx = ctx or get_context()
+ lock = ctx.RLock()
if not hasattr(lock, 'acquire'):
raise AttributeError("'%r' has no method 'acquire'" % lock)
- return synchronized(obj, lock)
+ return synchronized(obj, lock, ctx=ctx)
-def Array(typecode_or_type, size_or_initializer, *, lock=True):
+def Array(typecode_or_type, size_or_initializer, *, lock=True, ctx=None):
'''
Return a synchronization wrapper for a RawArray
'''
@@ -84,25 +88,27 @@ def Array(typecode_or_type, size_or_initializer, *, lock=True):
if lock is False:
return obj
if lock in (True, None):
- lock = RLock()
+ ctx = ctx or get_context()
+ lock = ctx.RLock()
if not hasattr(lock, 'acquire'):
raise AttributeError("'%r' has no method 'acquire'" % lock)
- return synchronized(obj, lock)
+ return synchronized(obj, lock, ctx=ctx)
def copy(obj):
new_obj = _new_value(type(obj))
ctypes.pointer(new_obj)[0] = obj
return new_obj
-def synchronized(obj, lock=None):
+def synchronized(obj, lock=None, ctx=None):
assert not isinstance(obj, SynchronizedBase), 'object already synchronized'
+ ctx = ctx or get_context()
if isinstance(obj, ctypes._SimpleCData):
- return Synchronized(obj, lock)
+ return Synchronized(obj, lock, ctx)
elif isinstance(obj, ctypes.Array):
if obj._type_ is ctypes.c_char:
- return SynchronizedString(obj, lock)
- return SynchronizedArray(obj, lock)
+ return SynchronizedString(obj, lock, ctx)
+ return SynchronizedArray(obj, lock, ctx)
else:
cls = type(obj)
try:
@@ -112,7 +118,7 @@ def synchronized(obj, lock=None):
d = dict((name, make_property(name)) for name in names)
classname = 'Synchronized' + cls.__name__
scls = class_cache[cls] = type(classname, (SynchronizedBase,), d)
- return scls(obj, lock)
+ return scls(obj, lock, ctx)
#
# Functions for pickling/unpickling
@@ -172,9 +178,13 @@ class_cache = weakref.WeakKeyDictionary()
class SynchronizedBase(object):
- def __init__(self, obj, lock=None):
+ def __init__(self, obj, lock=None, ctx=None):
self._obj = obj
- self._lock = lock or RLock()
+ if lock:
+ self._lock = lock
+ else:
+ ctx = ctx or get_context(force=True)
+ self._lock = ctx.RLock()
self.acquire = self._lock.acquire
self.release = self._lock.release
diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py
new file mode 100644
index 0000000000..364b53f499
--- /dev/null
+++ b/Lib/multiprocessing/spawn.py
@@ -0,0 +1,258 @@
+#
+# Code used to start processes when using the spawn or forkserver
+# start methods.
+#
+# multiprocessing/spawn.py
+#
+# Copyright (c) 2006-2008, R Oudkerk
+# Licensed to PSF under a Contributor Agreement.
+#
+
+import os
+import pickle
+import sys
+
+from . import get_start_method, set_start_method
+from . import process
+from . import util
+
+__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable',
+ 'get_preparation_data', 'get_command_line', 'import_main_path']
+
+#
+# _python_exe is the assumed path to the python executable.
+# People embedding Python want to modify it.
+#
+
+if sys.platform != 'win32':
+ WINEXE = False
+ WINSERVICE = False
+else:
+ WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
+ WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
+
+if WINSERVICE:
+ _python_exe = os.path.join(sys.exec_prefix, 'python.exe')
+else:
+ _python_exe = sys.executable
+
+def set_executable(exe):
+ global _python_exe
+ _python_exe = exe
+
+def get_executable():
+ return _python_exe
+
+#
+#
+#
+
+def is_forking(argv):
+ '''
+ Return whether commandline indicates we are forking
+ '''
+ if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
+ return True
+ else:
+ return False
+
+
+def freeze_support():
+ '''
+ Run code for process object if this in not the main process
+ '''
+ if is_forking(sys.argv):
+ main()
+ sys.exit()
+
+
+def get_command_line(**kwds):
+ '''
+ Returns prefix of command line used for spawning a child process
+ '''
+ if getattr(sys, 'frozen', False):
+ return [sys.executable, '--multiprocessing-fork']
+ else:
+ prog = 'from multiprocessing.spawn import spawn_main; spawn_main(%s)'
+ prog %= ', '.join('%s=%r' % item for item in kwds.items())
+ opts = util._args_from_interpreter_flags()
+ return [_python_exe] + opts + ['-c', prog, '--multiprocessing-fork']
+
+
+def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
+ '''
+ Run code specifed by data received over pipe
+ '''
+ assert is_forking(sys.argv)
+ if sys.platform == 'win32':
+ import msvcrt
+ from .reduction import steal_handle
+ new_handle = steal_handle(parent_pid, pipe_handle)
+ fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
+ else:
+ from . import semaphore_tracker
+ semaphore_tracker._semaphore_tracker._fd = tracker_fd
+ fd = pipe_handle
+ exitcode = _main(fd)
+ sys.exit(exitcode)
+
+
+def _main(fd):
+ with os.fdopen(fd, 'rb', closefd=True) as from_parent:
+ process.current_process()._inheriting = True
+ try:
+ preparation_data = pickle.load(from_parent)
+ prepare(preparation_data)
+ self = pickle.load(from_parent)
+ finally:
+ del process.current_process()._inheriting
+ return self._bootstrap()
+
+
+def _check_not_importing_main():
+ if getattr(process.current_process(), '_inheriting', False):
+ raise RuntimeError('''
+ An attempt has been made to start a new process before the
+ current process has finished its bootstrapping phase.
+
+ This probably means that you are not using fork to start your
+ child processes and you have forgotten to use the proper idiom
+ in the main module:
+
+ if __name__ == '__main__':
+ freeze_support()
+ ...
+
+ The "freeze_support()" line can be omitted if the program
+ is not going to be frozen to produce an executable.''')
+
+
+def get_preparation_data(name):
+ '''
+ Return info about parent needed by child to unpickle process object
+ '''
+ _check_not_importing_main()
+ d = dict(
+ log_to_stderr=util._log_to_stderr,
+ authkey=process.current_process().authkey,
+ )
+
+ if util._logger is not None:
+ d['log_level'] = util._logger.getEffectiveLevel()
+
+ sys_path=sys.path.copy()
+ try:
+ i = sys_path.index('')
+ except ValueError:
+ pass
+ else:
+ sys_path[i] = process.ORIGINAL_DIR
+
+ d.update(
+ name=name,
+ sys_path=sys_path,
+ sys_argv=sys.argv,
+ orig_dir=process.ORIGINAL_DIR,
+ dir=os.getcwd(),
+ start_method=get_start_method(),
+ )
+
+ if sys.platform != 'win32' or (not WINEXE and not WINSERVICE):
+ main_path = getattr(sys.modules['__main__'], '__file__', None)
+ if not main_path and sys.argv[0] not in ('', '-c'):
+ main_path = sys.argv[0]
+ if main_path is not None:
+ if (not os.path.isabs(main_path) and
+ process.ORIGINAL_DIR is not None):
+ main_path = os.path.join(process.ORIGINAL_DIR, main_path)
+ d['main_path'] = os.path.normpath(main_path)
+
+ return d
+
+#
+# Prepare current process
+#
+
+old_main_modules = []
+
+def prepare(data):
+ '''
+ Try to get current process ready to unpickle process object
+ '''
+ if 'name' in data:
+ process.current_process().name = data['name']
+
+ if 'authkey' in data:
+ process.current_process().authkey = data['authkey']
+
+ if 'log_to_stderr' in data and data['log_to_stderr']:
+ util.log_to_stderr()
+
+ if 'log_level' in data:
+ util.get_logger().setLevel(data['log_level'])
+
+ if 'sys_path' in data:
+ sys.path = data['sys_path']
+
+ if 'sys_argv' in data:
+ sys.argv = data['sys_argv']
+
+ if 'dir' in data:
+ os.chdir(data['dir'])
+
+ if 'orig_dir' in data:
+ process.ORIGINAL_DIR = data['orig_dir']
+
+ if 'start_method' in data:
+ set_start_method(data['start_method'])
+
+ if 'main_path' in data:
+ import_main_path(data['main_path'])
+
+
+def import_main_path(main_path):
+ '''
+ Set sys.modules['__main__'] to module at main_path
+ '''
+ # XXX (ncoghlan): The following code makes several bogus
+ # assumptions regarding the relationship between __file__
+ # and a module's real name. See PEP 302 and issue #10845
+ if getattr(sys.modules['__main__'], '__file__', None) == main_path:
+ return
+
+ main_name = os.path.splitext(os.path.basename(main_path))[0]
+ if main_name == '__init__':
+ main_name = os.path.basename(os.path.dirname(main_path))
+
+ if main_name == '__main__':
+ main_module = sys.modules['__main__']
+ main_module.__file__ = main_path
+ elif main_name != 'ipython':
+ # Main modules not actually called __main__.py may
+ # contain additional code that should still be executed
+ import importlib
+ import types
+
+ if main_path is None:
+ dirs = None
+ elif os.path.basename(main_path).startswith('__init__.py'):
+ dirs = [os.path.dirname(os.path.dirname(main_path))]
+ else:
+ dirs = [os.path.dirname(main_path)]
+
+ assert main_name not in sys.modules, main_name
+ sys.modules.pop('__mp_main__', None)
+ # We should not try to load __main__
+ # since that would execute 'if __name__ == "__main__"'
+ # clauses, potentially causing a psuedo fork bomb.
+ main_module = types.ModuleType(main_name)
+ # XXX Use a target of main_module?
+ spec = importlib.find_spec(main_name, path=dirs)
+ methods = importlib._bootstrap._SpecMethods(spec)
+ methods.init_module_attrs(main_module)
+ main_module.__name__ = '__mp_main__'
+ code = spec.loader.get_code(main_name)
+ exec(code, main_module.__dict__)
+
+ old_main_modules.append(sys.modules['__main__'])
+ sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module
diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py
index 0faca78412..9d8e282477 100644
--- a/Lib/multiprocessing/synchronize.py
+++ b/Lib/multiprocessing/synchronize.py
@@ -11,20 +11,24 @@ __all__ = [
'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event'
]
+import os
import threading
import sys
-
+import itertools
+import tempfile
import _multiprocessing
-from multiprocessing.process import current_process
-from multiprocessing.util import register_after_fork, debug
-from multiprocessing.forking import assert_spawning, Popen
+
from time import time as _time
+from . import context
+from . import process
+from . import util
+
# Try to import the mp.synchronize module cleanly, if it fails
# raise ImportError for platforms lacking a working sem_open implementation.
# See issue 3770
try:
- from _multiprocessing import SemLock
+ from _multiprocessing import SemLock, sem_unlink
except (ImportError):
raise ImportError("This platform lacks a functioning sem_open" +
" implementation, therefore, the required" +
@@ -44,15 +48,46 @@ SEM_VALUE_MAX = _multiprocessing.SemLock.SEM_VALUE_MAX
class SemLock(object):
- def __init__(self, kind, value, maxvalue):
- sl = self._semlock = _multiprocessing.SemLock(kind, value, maxvalue)
- debug('created semlock with handle %s' % sl.handle)
+ _rand = tempfile._RandomNameSequence()
+
+ def __init__(self, kind, value, maxvalue, *, ctx):
+ ctx = ctx or get_context()
+ ctx = ctx.get_context()
+ unlink_now = sys.platform == 'win32' or ctx._name == 'fork'
+ for i in range(100):
+ try:
+ sl = self._semlock = _multiprocessing.SemLock(
+ kind, value, maxvalue, self._make_name(),
+ unlink_now)
+ except FileExistsError:
+ pass
+ else:
+ break
+ else:
+ raise FileExistsError('cannot find name for semaphore')
+
+ util.debug('created semlock with handle %s' % sl.handle)
self._make_methods()
if sys.platform != 'win32':
def _after_fork(obj):
obj._semlock._after_fork()
- register_after_fork(self, _after_fork)
+ util.register_after_fork(self, _after_fork)
+
+ if self._semlock.name is not None:
+ # We only get here if we are on Unix with forking
+ # disabled. When the object is garbage collected or the
+ # process shuts down we unlink the semaphore name
+ from .semaphore_tracker import register
+ register(self._semlock.name)
+ util.Finalize(self, SemLock._cleanup, (self._semlock.name,),
+ exitpriority=0)
+
+ @staticmethod
+ def _cleanup(name):
+ from .semaphore_tracker import unregister
+ sem_unlink(name)
+ unregister(name)
def _make_methods(self):
self.acquire = self._semlock.acquire
@@ -65,23 +100,32 @@ class SemLock(object):
return self._semlock.__exit__(*args)
def __getstate__(self):
- assert_spawning(self)
+ context.assert_spawning(self)
sl = self._semlock
- return (Popen.duplicate_for_child(sl.handle), sl.kind, sl.maxvalue)
+ if sys.platform == 'win32':
+ h = context.get_spawning_popen().duplicate_for_child(sl.handle)
+ else:
+ h = sl.handle
+ return (h, sl.kind, sl.maxvalue, sl.name)
def __setstate__(self, state):
self._semlock = _multiprocessing.SemLock._rebuild(*state)
- debug('recreated blocker with handle %r' % state[0])
+ util.debug('recreated blocker with handle %r' % state[0])
self._make_methods()
+ @staticmethod
+ def _make_name():
+ return '%s-%s' % (process.current_process()._config['semprefix'],
+ next(SemLock._rand))
+
#
# Semaphore
#
class Semaphore(SemLock):
- def __init__(self, value=1):
- SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX)
+ def __init__(self, value=1, *, ctx):
+ SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)
def get_value(self):
return self._semlock._get_value()
@@ -99,8 +143,8 @@ class Semaphore(SemLock):
class BoundedSemaphore(Semaphore):
- def __init__(self, value=1):
- SemLock.__init__(self, SEMAPHORE, value, value)
+ def __init__(self, value=1, *, ctx):
+ SemLock.__init__(self, SEMAPHORE, value, value, ctx=ctx)
def __repr__(self):
try:
@@ -116,13 +160,13 @@ class BoundedSemaphore(Semaphore):
class Lock(SemLock):
- def __init__(self):
- SemLock.__init__(self, SEMAPHORE, 1, 1)
+ def __init__(self, *, ctx):
+ SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)
def __repr__(self):
try:
if self._semlock._is_mine():
- name = current_process().name
+ name = process.current_process().name
if threading.current_thread().name != 'MainThread':
name += '|' + threading.current_thread().name
elif self._semlock._get_value() == 1:
@@ -141,13 +185,13 @@ class Lock(SemLock):
class RLock(SemLock):
- def __init__(self):
- SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1)
+ def __init__(self, *, ctx):
+ SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1, ctx=ctx)
def __repr__(self):
try:
if self._semlock._is_mine():
- name = current_process().name
+ name = process.current_process().name
if threading.current_thread().name != 'MainThread':
name += '|' + threading.current_thread().name
count = self._semlock._count()
@@ -167,15 +211,15 @@ class RLock(SemLock):
class Condition(object):
- def __init__(self, lock=None):
- self._lock = lock or RLock()
- self._sleeping_count = Semaphore(0)
- self._woken_count = Semaphore(0)
- self._wait_semaphore = Semaphore(0)
+ def __init__(self, lock=None, *, ctx):
+ self._lock = lock or ctx.RLock()
+ self._sleeping_count = ctx.Semaphore(0)
+ self._woken_count = ctx.Semaphore(0)
+ self._wait_semaphore = ctx.Semaphore(0)
self._make_methods()
def __getstate__(self):
- assert_spawning(self)
+ context.assert_spawning(self)
return (self._lock, self._sleeping_count,
self._woken_count, self._wait_semaphore)
@@ -289,9 +333,9 @@ class Condition(object):
class Event(object):
- def __init__(self):
- self._cond = Condition(Lock())
- self._flag = Semaphore(0)
+ def __init__(self, *, ctx):
+ self._cond = ctx.Condition(ctx.Lock())
+ self._flag = ctx.Semaphore(0)
def is_set(self):
self._cond.acquire()
@@ -340,11 +384,11 @@ class Event(object):
class Barrier(threading.Barrier):
- def __init__(self, parties, action=None, timeout=None):
+ def __init__(self, parties, action=None, timeout=None, *, ctx):
import struct
- from multiprocessing.heap import BufferWrapper
+ from .heap import BufferWrapper
wrapper = BufferWrapper(struct.calcsize('i') * 2)
- cond = Condition()
+ cond = ctx.Condition()
self.__setstate__((parties, action, timeout, cond, wrapper))
self._state = 0
self._count = 0
diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py
index f5862b49a4..e476e85382 100644
--- a/Lib/multiprocessing/util.py
+++ b/Lib/multiprocessing/util.py
@@ -17,13 +17,13 @@ import threading # we want threading to install it's
# cleanup function before multiprocessing does
from subprocess import _args_from_interpreter_flags
-from multiprocessing.process import current_process, active_children
+from . import process
__all__ = [
'sub_debug', 'debug', 'info', 'sub_warning', 'get_logger',
'log_to_stderr', 'get_temp_dir', 'register_after_fork',
'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
- 'SUBDEBUG', 'SUBWARNING',
+ 'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
]
#
@@ -71,8 +71,6 @@ def get_logger():
_logger = logging.getLogger(LOGGER_NAME)
_logger.propagate = 0
- logging.addLevelName(SUBDEBUG, 'SUBDEBUG')
- logging.addLevelName(SUBWARNING, 'SUBWARNING')
# XXX multiprocessing should cleanup before logging
if hasattr(atexit, 'unregister'):
@@ -111,13 +109,14 @@ def log_to_stderr(level=None):
def get_temp_dir():
# get name of a temp directory which will be automatically cleaned up
- if current_process()._tempdir is None:
+ tempdir = process.current_process()._config.get('tempdir')
+ if tempdir is None:
import shutil, tempfile
tempdir = tempfile.mkdtemp(prefix='pymp-')
info('created temp directory %s', tempdir)
Finalize(None, shutil.rmtree, args=[tempdir], exitpriority=-100)
- current_process()._tempdir = tempdir
- return current_process()._tempdir
+ process.current_process()._config['tempdir'] = tempdir
+ return tempdir
#
# Support for reinitialization of objects when bootstrapping a child process
@@ -273,8 +272,8 @@ def is_exiting():
_exiting = False
def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
- active_children=active_children,
- current_process=current_process):
+ active_children=process.active_children,
+ current_process=process.current_process):
# We hold on to references to functions in the arglist due to the
# situation described below, where this function is called after this
# module's globals are destroyed.
@@ -303,7 +302,7 @@ def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
# #9207.
for p in active_children():
- if p._daemonic:
+ if p.daemon:
info('calling terminate() for daemon %s', p.name)
p._popen.terminate()
@@ -335,3 +334,36 @@ class ForkAwareLocal(threading.local):
register_after_fork(self, lambda obj : obj.__dict__.clear())
def __reduce__(self):
return type(self), ()
+
+#
+# Close fds except those specified
+#
+
+try:
+ MAXFD = os.sysconf("SC_OPEN_MAX")
+except Exception:
+ MAXFD = 256
+
+def close_all_fds_except(fds):
+ fds = list(fds) + [-1, MAXFD]
+ fds.sort()
+ assert fds[-1] == MAXFD, 'fd too large'
+ for i in range(len(fds) - 1):
+ os.closerange(fds[i]+1, fds[i+1])
+
+#
+# Start a program with only specified fds kept open
+#
+
+def spawnv_passfds(path, args, passfds):
+ import _posixsubprocess
+ passfds = sorted(passfds)
+ errpipe_read, errpipe_write = os.pipe()
+ try:
+ return _posixsubprocess.fork_exec(
+ args, [os.fsencode(path)], True, passfds, None, None,
+ -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
+ False, False, None)
+ finally:
+ os.close(errpipe_read)
+ os.close(errpipe_write)
diff --git a/Lib/netrc.py b/Lib/netrc.py
index 2aa48f32cd..bbb3d23b54 100644
--- a/Lib/netrc.py
+++ b/Lib/netrc.py
@@ -3,8 +3,6 @@
# Module and documentation by Eric S. Raymond, 21 Dec 1998
import os, shlex, stat
-if os.name == 'posix':
- import pwd
__all__ = ["netrc", "NetrcParseError"]
@@ -28,7 +26,7 @@ class netrc:
try:
file = os.path.join(os.environ['HOME'], ".netrc")
except KeyError:
- raise IOError("Could not find .netrc: $HOME is not set")
+ raise OSError("Could not find .netrc: $HOME is not set")
self.hosts = {}
self.macros = {}
with open(file) as fp:
@@ -92,6 +90,7 @@ class netrc:
if os.name == 'posix' and default_netrc:
prop = os.fstat(fp.fileno())
if prop.st_uid != os.getuid():
+ import pwd
try:
fowner = pwd.getpwuid(prop.st_uid)[0]
except KeyError:
diff --git a/Lib/nntplib.py b/Lib/nntplib.py
index 02cc37c1e2..046f48343d 100644
--- a/Lib/nntplib.py
+++ b/Lib/nntplib.py
@@ -288,9 +288,7 @@ if _have_ssl:
"""
# Generate a default SSL context if none was passed.
if context is None:
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- # SSLv2 considered harmful.
- context.options |= ssl.OP_NO_SSLv2
+ context = ssl._create_stdlib_context()
return context.wrap_socket(sock)
@@ -366,7 +364,7 @@ class _NNTPBase:
if is_connected():
try:
self.quit()
- except (socket.error, EOFError):
+ except (OSError, EOFError):
pass
finally:
if is_connected():
@@ -956,7 +954,7 @@ class _NNTPBase:
if auth:
user = auth[0]
password = auth[2]
- except IOError:
+ except OSError:
pass
# Perform NNTP authentication if needed.
if not user:
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index 826be87d3c..6b656976a4 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -17,7 +17,7 @@ __all__ = ["normcase","isabs","join","splitdrive","split","splitext",
"ismount", "expanduser","expandvars","normpath","abspath",
"splitunc","curdir","pardir","sep","pathsep","defpath","altsep",
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
- "samefile", "sameopenfile",]
+ "samefile", "sameopenfile", "samestat",]
# strings representing various path-related bits and pieces
# These are primarily for export; internally, they are hardcoded.
@@ -30,9 +30,6 @@ altsep = '/'
defpath = '.;C:\\bin'
if 'ce' in sys.builtin_module_names:
defpath = '\\Windows'
-elif 'os2' in sys.builtin_module_names:
- # OS/2 w/ VACPP
- altsep = '/'
devnull = 'nul'
def _get_empty(path):
@@ -320,12 +317,11 @@ def dirname(p):
def islink(path):
"""Test whether a path is a symbolic link.
- This will always return false for Windows prior to 6.0
- and for OS/2.
+ This will always return false for Windows prior to 6.0.
"""
try:
st = os.lstat(path)
- except (os.error, AttributeError):
+ except (OSError, AttributeError):
return False
return stat.S_ISLNK(st.st_mode)
@@ -335,20 +331,39 @@ def lexists(path):
"""Test whether a path exists. Returns True for broken symbolic links"""
try:
st = os.lstat(path)
- except (os.error, WindowsError):
+ except OSError:
return False
return True
-# Is a path a mount point? Either a root (with or without drive letter)
-# or an UNC path with at most a / or \ after the mount point.
-
+# Is a path a mount point?
+# Any drive letter root (eg c:\)
+# Any share UNC (eg \\server\share)
+# Any volume mounted on a filesystem folder
+#
+# No one method detects all three situations. Historically we've lexically
+# detected drive letter roots and share UNCs. The canonical approach to
+# detecting mounted volumes (querying the reparse tag) fails for the most
+# common case: drive letter roots. The alternative which uses GetVolumePathName
+# fails if the drive letter is the result of a SUBST.
+try:
+ from nt import _getvolumepathname
+except ImportError:
+ _getvolumepathname = None
def ismount(path):
- """Test whether a path is a mount point (defined as root of drive)"""
+ """Test whether a path is a mount point (a drive root, the root of a
+ share, or a mounted volume)"""
seps = _get_bothseps(path)
+ path = abspath(path)
root, rest = splitdrive(path)
if root and root[0] in seps:
return (not rest) or (rest in seps)
- return rest in seps
+ if rest in seps:
+ return True
+
+ if _getvolumepathname:
+ return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps)
+ else:
+ return False
# Expand paths beginning with '~' or '~user'.
@@ -588,7 +603,7 @@ else: # use native Windows method on Windows
if path: # Empty path must return current working directory.
try:
path = _getfullpathname(path)
- except WindowsError:
+ except OSError:
pass # Bad path - return unchanged.
elif isinstance(path, bytes):
path = os.getcwdb()
@@ -656,23 +671,6 @@ except (AttributeError, ImportError):
def _getfinalpathname(f):
return normcase(abspath(f))
-def samefile(f1, f2):
- "Test whether two pathnames reference the same actual file"
- return _getfinalpathname(f1) == _getfinalpathname(f2)
-
-
-try:
- from nt import _getfileinformation
-except ImportError:
- # On other operating systems, just return the fd and see that
- # it compares equal in sameopenfile.
- def _getfileinformation(fd):
- return fd
-
-def sameopenfile(f1, f2):
- """Test whether two file objects reference the same file"""
- return _getfileinformation(f1) == _getfileinformation(f2)
-
try:
# The genericpath.isdir implementation uses os.stat and checks the mode
diff --git a/Lib/nturl2path.py b/Lib/nturl2path.py
index 511dcec5d6..5a6d44a659 100644
--- a/Lib/nturl2path.py
+++ b/Lib/nturl2path.py
@@ -23,7 +23,7 @@ def url2pathname(url):
comp = url.split('|')
if len(comp) != 2 or comp[0][-1] not in string.ascii_letters:
error = 'Bad URL: ' + url
- raise IOError(error)
+ raise OSError(error)
drive = comp[0][-1].upper()
components = comp[1].split('/')
path = drive + ':'
@@ -55,7 +55,7 @@ def pathname2url(p):
comp = p.split(':')
if len(comp) != 2 or len(comp[0]) > 1:
error = 'Bad path: ' + p
- raise IOError(error)
+ raise OSError(error)
drive = urllib.parse.quote(comp[0].upper())
components = comp[1].split('\\')
diff --git a/Lib/opcode.py b/Lib/opcode.py
index a639fe322b..0bd1ee679c 100644
--- a/Lib/opcode.py
+++ b/Lib/opcode.py
@@ -8,6 +8,19 @@ __all__ = ["cmp_op", "hasconst", "hasname", "hasjrel", "hasjabs",
"haslocal", "hascompare", "hasfree", "opname", "opmap",
"HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"]
+# It's a chicken-and-egg I'm afraid:
+# We're imported before _opcode's made.
+# With exception unheeded
+# (stack_effect is not needed)
+# Both our chickens and eggs are allayed.
+# --Larry Hastings, 2013/11/23
+
+try:
+ from _opcode import stack_effect
+ __all__.append('stack_effect')
+except ImportError:
+ pass
+
cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is',
'is not', 'exception match', 'BAD')
@@ -84,7 +97,6 @@ def_op('BINARY_XOR', 65)
def_op('BINARY_OR', 66)
def_op('INPLACE_POWER', 67)
def_op('GET_ITER', 68)
-def_op('STORE_LOCALS', 69)
def_op('PRINT_EXPR', 70)
def_op('LOAD_BUILD_CLASS', 71)
@@ -179,6 +191,9 @@ def_op('LIST_APPEND', 145)
def_op('SET_ADD', 146)
def_op('MAP_ADD', 147)
+def_op('LOAD_CLASSDEREF', 148)
+hasfree.append(148)
+
def_op('EXTENDED_ARG', 144)
EXTENDED_ARG = 144
diff --git a/Lib/operator.py b/Lib/operator.py
new file mode 100644
index 0000000000..d31a9a4537
--- /dev/null
+++ b/Lib/operator.py
@@ -0,0 +1,412 @@
+#!/usr/bin/env python3
+"""
+Operator Interface
+
+This module exports a set of functions corresponding to the intrinsic
+operators of Python. For example, operator.add(x, y) is equivalent
+to the expression x+y. The function names are those used for special
+methods; variants without leading and trailing '__' are also provided
+for convenience.
+
+This is the pure Python implementation of the module.
+"""
+
+__all__ = ['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', 'countOf',
+ 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand',
+ 'iconcat', 'ifloordiv', 'ilshift', 'imod', 'imul', 'index',
+ 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', 'is_',
+ 'is_not', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le',
+ 'length_hint', 'lshift', 'lt', 'methodcaller', 'mod', 'mul', 'ne',
+ 'neg', 'not_', 'or_', 'pos', 'pow', 'rshift', 'setitem', 'sub',
+ 'truediv', 'truth', 'xor']
+
+from builtins import abs as _abs
+
+
+# Comparison Operations *******************************************************#
+
+def lt(a, b):
+ "Same as a < b."
+ return a < b
+
+def le(a, b):
+ "Same as a <= b."
+ return a <= b
+
+def eq(a, b):
+ "Same as a == b."
+ return a == b
+
+def ne(a, b):
+ "Same as a != b."
+ return a != b
+
+def ge(a, b):
+ "Same as a >= b."
+ return a >= b
+
+def gt(a, b):
+ "Same as a > b."
+ return a > b
+
+# Logical Operations **********************************************************#
+
+def not_(a):
+ "Same as not a."
+ return not a
+
+def truth(a):
+ "Return True if a is true, False otherwise."
+ return True if a else False
+
+def is_(a, b):
+ "Same as a is b."
+ return a is b
+
+def is_not(a, b):
+ "Same as a is not b."
+ return a is not b
+
+# Mathematical/Bitwise Operations *********************************************#
+
+def abs(a):
+ "Same as abs(a)."
+ return _abs(a)
+
+def add(a, b):
+ "Same as a + b."
+ return a + b
+
+def and_(a, b):
+ "Same as a & b."
+ return a & b
+
+def floordiv(a, b):
+ "Same as a // b."
+ return a // b
+
+def index(a):
+ "Same as a.__index__()."
+ return a.__index__()
+
+def inv(a):
+ "Same as ~a."
+ return ~a
+invert = inv
+
+def lshift(a, b):
+ "Same as a << b."
+ return a << b
+
+def mod(a, b):
+ "Same as a % b."
+ return a % b
+
+def mul(a, b):
+ "Same as a * b."
+ return a * b
+
+def neg(a):
+ "Same as -a."
+ return -a
+
+def or_(a, b):
+ "Same as a | b."
+ return a | b
+
+def pos(a):
+ "Same as +a."
+ return +a
+
+def pow(a, b):
+ "Same as a ** b."
+ return a ** b
+
+def rshift(a, b):
+ "Same as a >> b."
+ return a >> b
+
+def sub(a, b):
+ "Same as a - b."
+ return a - b
+
+def truediv(a, b):
+ "Same as a / b."
+ return a / b
+
+def xor(a, b):
+ "Same as a ^ b."
+ return a ^ b
+
+# Sequence Operations *********************************************************#
+
+def concat(a, b):
+ "Same as a + b, for a and b sequences."
+ if not hasattr(a, '__getitem__'):
+ msg = "'%s' object can't be concatenated" % type(a).__name__
+ raise TypeError(msg)
+ return a + b
+
+def contains(a, b):
+ "Same as b in a (note reversed operands)."
+ return b in a
+
+def countOf(a, b):
+ "Return the number of times b occurs in a."
+ count = 0
+ for i in a:
+ if i == b:
+ count += 1
+ return count
+
+def delitem(a, b):
+ "Same as del a[b]."
+ del a[b]
+
+def getitem(a, b):
+ "Same as a[b]."
+ return a[b]
+
+def indexOf(a, b):
+ "Return the first index of b in a."
+ for i, j in enumerate(a):
+ if j == b:
+ return i
+ else:
+ raise ValueError('sequence.index(x): x not in sequence')
+
+def setitem(a, b, c):
+ "Same as a[b] = c."
+ a[b] = c
+
+def length_hint(obj, default=0):
+ """
+ Return an estimate of the number of items in obj.
+ This is useful for presizing containers when building from an iterable.
+
+ If the object supports len(), the result will be exact. Otherwise, it may
+ over- or under-estimate by an arbitrary amount. The result will be an
+ integer >= 0.
+ """
+ if not isinstance(default, int):
+ msg = ("'%s' object cannot be interpreted as an integer" %
+ type(default).__name__)
+ raise TypeError(msg)
+
+ try:
+ return len(obj)
+ except TypeError:
+ pass
+
+ try:
+ hint = type(obj).__length_hint__
+ except AttributeError:
+ return default
+
+ try:
+ val = hint(obj)
+ except TypeError:
+ return default
+ if val is NotImplemented:
+ return default
+ if not isinstance(val, int):
+ msg = ('__length_hint__ must be integer, not %s' %
+ type(val).__name__)
+ raise TypeError(msg)
+ if val < 0:
+ msg = '__length_hint__() should return >= 0'
+ raise ValueError(msg)
+ return val
+
+# Generalized Lookup Objects **************************************************#
+
+class attrgetter:
+ """
+ Return a callable object that fetches the given attribute(s) from its operand.
+ After f = attrgetter('name'), the call f(r) returns r.name.
+ After g = attrgetter('name', 'date'), the call g(r) returns (r.name, r.date).
+ After h = attrgetter('name.first', 'name.last'), the call h(r) returns
+ (r.name.first, r.name.last).
+ """
+ def __init__(self, attr, *attrs):
+ if not attrs:
+ if not isinstance(attr, str):
+ raise TypeError('attribute name must be a string')
+ names = attr.split('.')
+ def func(obj):
+ for name in names:
+ obj = getattr(obj, name)
+ return obj
+ self._call = func
+ else:
+ getters = tuple(map(attrgetter, (attr,) + attrs))
+ def func(obj):
+ return tuple(getter(obj) for getter in getters)
+ self._call = func
+
+ def __call__(self, obj):
+ return self._call(obj)
+
+class itemgetter:
+ """
+ Return a callable object that fetches the given item(s) from its operand.
+ After f = itemgetter(2), the call f(r) returns r[2].
+ After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3])
+ """
+ def __init__(self, item, *items):
+ if not items:
+ def func(obj):
+ return obj[item]
+ self._call = func
+ else:
+ items = (item,) + items
+ def func(obj):
+ return tuple(obj[i] for i in items)
+ self._call = func
+
+ def __call__(self, obj):
+ return self._call(obj)
+
+class methodcaller:
+ """
+ Return a callable object that calls the given method on its operand.
+ After f = methodcaller('name'), the call f(r) returns r.name().
+ After g = methodcaller('name', 'date', foo=1), the call g(r) returns
+ r.name('date', foo=1).
+ """
+
+ def __init__(*args, **kwargs):
+ if len(args) < 2:
+ msg = "methodcaller needs at least one argument, the method name"
+ raise TypeError(msg)
+ self = args[0]
+ self._name = args[1]
+ self._args = args[2:]
+ self._kwargs = kwargs
+
+ def __call__(self, obj):
+ return getattr(obj, self._name)(*self._args, **self._kwargs)
+
+# In-place Operations *********************************************************#
+
+def iadd(a, b):
+ "Same as a += b."
+ a += b
+ return a
+
+def iand(a, b):
+ "Same as a &= b."
+ a &= b
+ return a
+
+def iconcat(a, b):
+ "Same as a += b, for a and b sequences."
+ if not hasattr(a, '__getitem__'):
+ msg = "'%s' object can't be concatenated" % type(a).__name__
+ raise TypeError(msg)
+ a += b
+ return a
+
+def ifloordiv(a, b):
+ "Same as a //= b."
+ a //= b
+ return a
+
+def ilshift(a, b):
+ "Same as a <<= b."
+ a <<= b
+ return a
+
+def imod(a, b):
+ "Same as a %= b."
+ a %= b
+ return a
+
+def imul(a, b):
+ "Same as a *= b."
+ a *= b
+ return a
+
+def ior(a, b):
+ "Same as a |= b."
+ a |= b
+ return a
+
+def ipow(a, b):
+ "Same as a **= b."
+ a **=b
+ return a
+
+def irshift(a, b):
+ "Same as a >>= b."
+ a >>= b
+ return a
+
+def isub(a, b):
+ "Same as a -= b."
+ a -= b
+ return a
+
+def itruediv(a, b):
+ "Same as a /= b."
+ a /= b
+ return a
+
+def ixor(a, b):
+ "Same as a ^= b."
+ a ^= b
+ return a
+
+
+try:
+ from _operator import *
+except ImportError:
+ pass
+else:
+ from _operator import __doc__
+
+# All of these "__func__ = func" assignments have to happen after importing
+# from _operator to make sure they're set to the right function
+__lt__ = lt
+__le__ = le
+__eq__ = eq
+__ne__ = ne
+__ge__ = ge
+__gt__ = gt
+__not__ = not_
+__abs__ = abs
+__add__ = add
+__and__ = and_
+__floordiv__ = floordiv
+__index__ = index
+__inv__ = inv
+__invert__ = invert
+__lshift__ = lshift
+__mod__ = mod
+__mul__ = mul
+__neg__ = neg
+__or__ = or_
+__pos__ = pos
+__pow__ = pow
+__rshift__ = rshift
+__sub__ = sub
+__truediv__ = truediv
+__xor__ = xor
+__concat__ = concat
+__contains__ = contains
+__delitem__ = delitem
+__getitem__ = getitem
+__setitem__ = setitem
+__iadd__ = iadd
+__iand__ = iand
+__iconcat__ = iconcat
+__ifloordiv__ = ifloordiv
+__ilshift__ = ilshift
+__imod__ = imod
+__imul__ = imul
+__ior__ = ior
+__ipow__ = ipow
+__irshift__ = irshift
+__isub__ = isub
+__itruediv__ = itruediv
+__ixor__ = ixor
diff --git a/Lib/os.py b/Lib/os.py
index 87689cc269..e9880a1e4c 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -1,9 +1,9 @@
r"""OS routines for Mac, NT, or Posix depending on what system we're on.
This exports:
- - all functions from posix, nt, os2, or ce, e.g. unlink, stat, etc.
+ - all functions from posix, nt or ce, e.g. unlink, stat, etc.
- os.path is either posixpath or ntpath
- - os.name is either 'posix', 'nt', 'os2' or 'ce'.
+ - os.name is either 'posix', 'nt' or 'ce'.
- os.curdir is a string representing the current directory ('.' or ':')
- os.pardir is a string representing the parent directory ('..' or '::')
- os.sep is the (or a most common) pathname separator ('/' or ':' or '\\')
@@ -81,30 +81,6 @@ elif 'nt' in _names:
except ImportError:
pass
-elif 'os2' in _names:
- name = 'os2'
- linesep = '\r\n'
- from os2 import *
- try:
- from os2 import _exit
- __all__.append('_exit')
- except ImportError:
- pass
- if sys.version.find('EMX GCC') == -1:
- import ntpath as path
- else:
- import os2emxpath as path
- from _emx_link import link
-
- import os2
- __all__.extend(_get_exports_list(os2))
- del os2
-
- try:
- from os2 import _have_functions
- except ImportError:
- pass
-
elif 'ce' in _names:
name = 'ce'
linesep = '\r\n'
@@ -256,10 +232,9 @@ def makedirs(name, mode=0o777, exist_ok=False):
if head and tail and not path.exists(head):
try:
makedirs(head, mode, exist_ok)
- except OSError as e:
+ except FileExistsError:
# be happy if someone already created the path
- if e.errno != errno.EEXIST:
- raise
+ pass
cdir = curdir
if isinstance(tail, bytes):
cdir = bytes(curdir, 'ASCII')
@@ -302,7 +277,7 @@ def removedirs(name):
while head and tail:
try:
rmdir(head)
- except error:
+ except OSError:
break
head, tail = path.split(head)
@@ -329,7 +304,7 @@ def renames(old, new):
if head and tail:
try:
removedirs(head)
- except error:
+ except OSError:
pass
__all__.extend(["makedirs", "removedirs", "renames"])
@@ -365,7 +340,7 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
By default errors from the os.listdir() call are ignored. If
optional arg 'onerror' is specified, it should be a function; it
- will be called with one argument, an os.error instance. It can
+ will be called with one argument, an OSError instance. It can
report the error to continue with the walk, or raise the exception
to abort the walk. Note that the filename is available as the
filename attribute of the exception object.
@@ -399,10 +374,10 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
# minor reason when (say) a thousand readable directories are still
# left to visit. That logic is copied here.
try:
- # Note that listdir and error are globals in this module due
+ # Note that listdir is global in this module due
# to earlier import-*.
names = listdir(top)
- except error as err:
+ except OSError as err:
if onerror is not None:
onerror(err)
return
@@ -504,7 +479,7 @@ if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd:
try:
orig_st = stat(name, dir_fd=topfd, follow_symlinks=follow_symlinks)
dirfd = open(name, O_RDONLY, dir_fd=topfd)
- except error as err:
+ except OSError as err:
if onerror is not None:
onerror(err)
return
@@ -599,7 +574,7 @@ def _execvpe(file, args, env=None):
fullname = path.join(dir, file)
try:
exec_func(fullname, *argrest)
- except error as e:
+ except OSError as e:
last_exc = e
tb = sys.exc_info()[2]
if (e.errno != errno.ENOENT and e.errno != errno.ENOTDIR
@@ -656,7 +631,7 @@ def get_exec_path(env=None):
# Change environ to automatically call putenv(), unsetenv if they exist.
-from collections.abc import MutableMapping
+from _collections_abc import MutableMapping
class _Environ(MutableMapping):
def __init__(self, data, encodekey, decodekey, encodevalue, decodevalue, putenv, unsetenv):
@@ -716,17 +691,19 @@ try:
except NameError:
_putenv = lambda key, value: None
else:
- __all__.append("putenv")
+ if "putenv" not in __all__:
+ __all__.append("putenv")
try:
_unsetenv = unsetenv
except NameError:
_unsetenv = lambda key: _putenv(key, "")
else:
- __all__.append("unsetenv")
+ if "unsetenv" not in __all__:
+ __all__.append("unsetenv")
def _createenviron():
- if name in ('os2', 'nt'):
+ if name == 'nt':
# Where Env Var Names Must Be UPPERCASE
def check_str(value):
if not isinstance(value, str):
@@ -766,7 +743,7 @@ def getenv(key, default=None):
key, default and the result are str."""
return environ.get(key, default)
-supports_bytes_environ = name not in ('os2', 'nt')
+supports_bytes_environ = (name != 'nt')
__all__.extend(("getenv", "supports_bytes_environ"))
if supports_bytes_environ:
@@ -865,7 +842,7 @@ if _exists("fork") and not _exists("spawnv") and _exists("execv"):
elif WIFEXITED(sts):
return WEXITSTATUS(sts)
else:
- raise error("Not stopped, signaled or exited???")
+ raise OSError("Not stopped, signaled or exited???")
def spawnv(mode, file, args):
"""spawnv(mode, file, args) -> integer
@@ -908,6 +885,10 @@ If mode == P_WAIT return the process's exit code if it exits normally;
otherwise return -SIG, where SIG is the signal that killed it. """
return _spawnvef(mode, file, args, env, execvpe)
+
+ __all__.extend(["spawnv", "spawnve", "spawnvp", "spawnvpe"])
+
+
if _exists("spawnv"):
# These aren't supplied by the basic Windows code
# but can be easily implemented in Python
@@ -933,7 +914,7 @@ otherwise return -SIG, where SIG is the signal that killed it. """
return spawnve(mode, file, args[:-1], env)
- __all__.extend(["spawnv", "spawnve", "spawnl", "spawnle",])
+ __all__.extend(["spawnl", "spawnle"])
if _exists("spawnvp"):
@@ -961,34 +942,8 @@ otherwise return -SIG, where SIG is the signal that killed it. """
return spawnvpe(mode, file, args[:-1], env)
- __all__.extend(["spawnvp", "spawnvpe", "spawnlp", "spawnlpe",])
-
-import copyreg as _copyreg
-
-def _make_stat_result(tup, dict):
- return stat_result(tup, dict)
+ __all__.extend(["spawnlp", "spawnlpe"])
-def _pickle_stat_result(sr):
- (type, args) = sr.__reduce__()
- return (_make_stat_result, args)
-
-try:
- _copyreg.pickle(stat_result, _pickle_stat_result, _make_stat_result)
-except NameError: # stat_result may not exist
- pass
-
-def _make_statvfs_result(tup, dict):
- return statvfs_result(tup, dict)
-
-def _pickle_statvfs_result(sr):
- (type, args) = sr.__reduce__()
- return (_make_statvfs_result, args)
-
-try:
- _copyreg.pickle(statvfs_result, _pickle_statvfs_result,
- _make_statvfs_result)
-except NameError: # statvfs_result may not exist
- pass
# Supply os.popen()
def popen(cmd, mode="r", buffering=-1):
diff --git a/Lib/os2emxpath.py b/Lib/os2emxpath.py
deleted file mode 100644
index 0ccbf8ae7b..0000000000
--- a/Lib/os2emxpath.py
+++ /dev/null
@@ -1,158 +0,0 @@
-# Module 'os2emxpath' -- common operations on OS/2 pathnames
-"""Common pathname manipulations, OS/2 EMX version.
-
-Instead of importing this module directly, import os and refer to this
-module as os.path.
-"""
-
-import os
-import stat
-from genericpath import *
-from ntpath import (expanduser, expandvars, isabs, islink, splitdrive,
- splitext, split)
-
-__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
- "basename","dirname","commonprefix","getsize","getmtime",
- "getatime","getctime", "islink","exists","lexists","isdir","isfile",
- "ismount","expanduser","expandvars","normpath","abspath",
- "splitunc","curdir","pardir","sep","pathsep","defpath","altsep",
- "extsep","devnull","realpath","supports_unicode_filenames"]
-
-# strings representing various path-related bits and pieces
-curdir = '.'
-pardir = '..'
-extsep = '.'
-sep = '/'
-altsep = '\\'
-pathsep = ';'
-defpath = '.;C:\\bin'
-devnull = 'nul'
-
-# Normalize the case of a pathname and map slashes to backslashes.
-# Other normalizations (such as optimizing '../' away) are not done
-# (this is done by normpath).
-
-def normcase(s):
- """Normalize case of pathname.
-
- Makes all characters lowercase and all altseps into seps."""
- if not isinstance(s, (bytes, str)):
- raise TypeError("normcase() argument must be str or bytes, "
- "not '{}'".format(s.__class__.__name__))
- return s.replace('\\', '/').lower()
-
-
-# Join two (or more) paths.
-
-def join(a, *p):
- """Join two or more pathname components, inserting sep as needed"""
- path = a
- for b in p:
- if isabs(b):
- path = b
- elif path == '' or path[-1:] in '/\\:':
- path = path + b
- else:
- path = path + '/' + b
- return path
-
-
-# Parse UNC paths
-def splitunc(p):
- """Split a pathname into UNC mount point and relative path specifiers.
-
- Return a 2-tuple (unc, rest); either part may be empty.
- If unc is not empty, it has the form '//host/mount' (or similar
- using backslashes). unc+rest is always the input path.
- Paths containing drive letters never have an UNC part.
- """
- if p[1:2] == ':':
- return '', p # Drive letter present
- firstTwo = p[0:2]
- if firstTwo == '/' * 2 or firstTwo == '\\' * 2:
- # is a UNC path:
- # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter
- # \\machine\mountpoint\directories...
- # directory ^^^^^^^^^^^^^^^
- normp = normcase(p)
- index = normp.find('/', 2)
- if index == -1:
- ##raise RuntimeError, 'illegal UNC path: "' + p + '"'
- return ("", p)
- index = normp.find('/', index + 1)
- if index == -1:
- index = len(p)
- return p[:index], p[index:]
- return '', p
-
-
-# Return the tail (basename) part of a path.
-
-def basename(p):
- """Returns the final component of a pathname"""
- return split(p)[1]
-
-
-# Return the head (dirname) part of a path.
-
-def dirname(p):
- """Returns the directory component of a pathname"""
- return split(p)[0]
-
-
-# alias exists to lexists
-lexists = exists
-
-
-# Is a path a directory?
-
-# Is a path a mount point? Either a root (with or without drive letter)
-# or an UNC path with at most a / or \ after the mount point.
-
-def ismount(path):
- """Test whether a path is a mount point (defined as root of drive)"""
- unc, rest = splitunc(path)
- if unc:
- return rest in ("", "/", "\\")
- p = splitdrive(path)[1]
- return len(p) == 1 and p[0] in '/\\'
-
-
-# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
-
-def normpath(path):
- """Normalize path, eliminating double slashes, etc."""
- path = path.replace('\\', '/')
- prefix, path = splitdrive(path)
- while path[:1] == '/':
- prefix = prefix + '/'
- path = path[1:]
- comps = path.split('/')
- i = 0
- while i < len(comps):
- if comps[i] == '.':
- del comps[i]
- elif comps[i] == '..' and i > 0 and comps[i-1] not in ('', '..'):
- del comps[i-1:i+1]
- i = i - 1
- elif comps[i] == '' and i > 0 and comps[i-1] != '':
- del comps[i]
- else:
- i = i + 1
- # If the path is now empty, substitute '.'
- if not prefix and not comps:
- comps.append('.')
- return prefix + '/'.join(comps)
-
-
-# Return an absolute path.
-def abspath(path):
- """Return the absolute version of a path"""
- if not isabs(path):
- path = join(os.getcwd(), path)
- return normpath(path)
-
-# realpath is a no-op on systems without islink support
-realpath = abspath
-
-supports_unicode_filenames = False
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
new file mode 100644
index 0000000000..e73eca7ec9
--- /dev/null
+++ b/Lib/pathlib.py
@@ -0,0 +1,1284 @@
+import fnmatch
+import functools
+import io
+import ntpath
+import os
+import posixpath
+import re
+import sys
+import weakref
+try:
+ import threading
+except ImportError:
+ import dummy_threading as threading
+
+from collections import Sequence, defaultdict
+from contextlib import contextmanager
+from errno import EINVAL, ENOENT
+from itertools import chain, count
+from operator import attrgetter
+from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes
+
+
+supports_symlinks = True
+try:
+ import nt
+except ImportError:
+ nt = None
+else:
+ if sys.getwindowsversion()[:2] >= (6, 0):
+ from nt import _getfinalpathname
+ else:
+ supports_symlinks = False
+ _getfinalpathname = None
+
+
+__all__ = [
+ "PurePath", "PurePosixPath", "PureWindowsPath",
+ "Path", "PosixPath", "WindowsPath",
+ ]
+
+#
+# Internals
+#
+
+def _is_wildcard_pattern(pat):
+ # Whether this pattern needs actual matching using fnmatch, or can
+ # be looked up directly as a file.
+ return "*" in pat or "?" in pat or "[" in pat
+
+
+class _Flavour(object):
+ """A flavour implements a particular (platform-specific) set of path
+ semantics."""
+
+ def __init__(self):
+ self.join = self.sep.join
+
+ def parse_parts(self, parts):
+ parsed = []
+ sep = self.sep
+ altsep = self.altsep
+ drv = root = ''
+ it = reversed(parts)
+ for part in it:
+ if not part:
+ continue
+ if altsep:
+ part = part.replace(altsep, sep)
+ drv, root, rel = self.splitroot(part)
+ if sep in rel:
+ for x in reversed(rel.split(sep)):
+ if x and x != '.':
+ parsed.append(sys.intern(x))
+ else:
+ if rel and rel != '.':
+ parsed.append(sys.intern(rel))
+ if drv or root:
+ if not drv:
+ # If no drive is present, try to find one in the previous
+ # parts. This makes the result of parsing e.g.
+ # ("C:", "/", "a") reasonably intuitive.
+ for part in it:
+ drv = self.splitroot(part)[0]
+ if drv:
+ break
+ break
+ if drv or root:
+ parsed.append(drv + root)
+ parsed.reverse()
+ return drv, root, parsed
+
+ def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
+ """
+ Join the two paths represented by the respective
+ (drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
+ """
+ if root2:
+ parts = parts2
+ root = root2
+ else:
+ parts = parts + parts2
+ # XXX raise error if drv and drv2 are different?
+ return drv2 or drv, root, parts
+
+
+class _WindowsFlavour(_Flavour):
+ # Reference for Windows paths can be found at
+ # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
+
+ sep = '\\'
+ altsep = '/'
+ has_drv = True
+ pathmod = ntpath
+
+ is_supported = (nt is not None)
+
+ drive_letters = (
+ set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
+ set(chr(x) for x in range(ord('A'), ord('Z') + 1))
+ )
+ ext_namespace_prefix = '\\\\?\\'
+
+ reserved_names = (
+ {'CON', 'PRN', 'AUX', 'NUL'} |
+ {'COM%d' % i for i in range(1, 10)} |
+ {'LPT%d' % i for i in range(1, 10)}
+ )
+
+ # Interesting findings about extended paths:
+ # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
+ # but '\\?\c:/a' is not
+ # - extended paths are always absolute; "relative" extended paths will
+ # fail.
+
+ def splitroot(self, part, sep=sep):
+ first = part[0:1]
+ second = part[1:2]
+ if (second == sep and first == sep):
+ # XXX extended paths should also disable the collapsing of "."
+ # components (according to MSDN docs).
+ prefix, part = self._split_extended_path(part)
+ first = part[0:1]
+ second = part[1:2]
+ else:
+ prefix = ''
+ third = part[2:3]
+ if (second == sep and first == sep and third != sep):
+ # is a UNC path:
+ # vvvvvvvvvvvvvvvvvvvvv root
+ # \\machine\mountpoint\directory\etc\...
+ # directory ^^^^^^^^^^^^^^
+ index = part.find(sep, 2)
+ if index != -1:
+ index2 = part.find(sep, index + 1)
+ # a UNC path can't have two slashes in a row
+ # (after the initial two)
+ if index2 != index + 1:
+ if index2 == -1:
+ index2 = len(part)
+ if prefix:
+ return prefix + part[1:index2], sep, part[index2+1:]
+ else:
+ return part[:index2], sep, part[index2+1:]
+ drv = root = ''
+ if second == ':' and first in self.drive_letters:
+ drv = part[:2]
+ part = part[2:]
+ first = third
+ if first == sep:
+ root = first
+ part = part.lstrip(sep)
+ return prefix + drv, root, part
+
+ def casefold(self, s):
+ return s.lower()
+
+ def casefold_parts(self, parts):
+ return [p.lower() for p in parts]
+
+ def resolve(self, path):
+ s = str(path)
+ if not s:
+ return os.getcwd()
+ if _getfinalpathname is not None:
+ return self._ext_to_normal(_getfinalpathname(s))
+ # Means fallback on absolute
+ return None
+
+ def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
+ prefix = ''
+ if s.startswith(ext_prefix):
+ prefix = s[:4]
+ s = s[4:]
+ if s.startswith('UNC\\'):
+ prefix += s[:3]
+ s = '\\' + s[3:]
+ return prefix, s
+
+ def _ext_to_normal(self, s):
+ # Turn back an extended path into a normal DOS-like path
+ return self._split_extended_path(s)[1]
+
+ def is_reserved(self, parts):
+ # NOTE: the rules for reserved names seem somewhat complicated
+ # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
+ # We err on the side of caution and return True for paths which are
+ # not considered reserved by Windows.
+ if not parts:
+ return False
+ if parts[0].startswith('\\\\'):
+ # UNC paths are never reserved
+ return False
+ return parts[-1].partition('.')[0].upper() in self.reserved_names
+
+ def make_uri(self, path):
+ # Under Windows, file URIs use the UTF-8 encoding.
+ drive = path.drive
+ if len(drive) == 2 and drive[1] == ':':
+ # It's a path on a local drive => 'file:///c:/a/b'
+ rest = path.as_posix()[2:].lstrip('/')
+ return 'file:///%s/%s' % (
+ drive, urlquote_from_bytes(rest.encode('utf-8')))
+ else:
+ # It's a path on a network drive => 'file://host/share/a/b'
+ return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
+
+
+class _PosixFlavour(_Flavour):
+ sep = '/'
+ altsep = ''
+ has_drv = False
+ pathmod = posixpath
+
+ is_supported = (os.name != 'nt')
+
+ def splitroot(self, part, sep=sep):
+ if part and part[0] == sep:
+ stripped_part = part.lstrip(sep)
+ # According to POSIX path resolution:
+ # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
+ # "A pathname that begins with two successive slashes may be
+ # interpreted in an implementation-defined manner, although more
+ # than two leading slashes shall be treated as a single slash".
+ if len(part) - len(stripped_part) == 2:
+ return '', sep * 2, stripped_part
+ else:
+ return '', sep, stripped_part
+ else:
+ return '', '', part
+
+ def casefold(self, s):
+ return s
+
+ def casefold_parts(self, parts):
+ return parts
+
+ def resolve(self, path):
+ sep = self.sep
+ def split(p):
+ return [x for x in p.split(sep) if x]
+ def absparts(p):
+ # Our own abspath(), since the posixpath one makes
+ # the mistake of "normalizing" the path without resolving the
+ # symlinks first.
+ if not p.startswith(sep):
+ return split(os.getcwd()) + split(p)
+ else:
+ return split(p)
+ parts = absparts(str(path))[::-1]
+ accessor = path._accessor
+ resolved = cur = ""
+ symlinks = {}
+ while parts:
+ part = parts.pop()
+ cur = resolved + sep + part
+ if cur in symlinks and symlinks[cur] <= len(parts):
+ # We've already seen the symlink and there's not less
+ # work to do than the last time.
+ raise RuntimeError("Symlink loop from %r" % cur)
+ try:
+ target = accessor.readlink(cur)
+ except OSError as e:
+ if e.errno != EINVAL:
+ raise
+ # Not a symlink
+ resolved = cur
+ else:
+ # Take note of remaining work from this symlink
+ symlinks[cur] = len(parts)
+ if target.startswith(sep):
+ # Symlink points to absolute path
+ resolved = ""
+ parts.extend(split(target)[::-1])
+ return resolved or sep
+
+ def is_reserved(self, parts):
+ return False
+
+ def make_uri(self, path):
+ # We represent the path using the local filesystem encoding,
+ # for portability to other applications.
+ bpath = bytes(path)
+ return 'file://' + urlquote_from_bytes(bpath)
+
+
+_windows_flavour = _WindowsFlavour()
+_posix_flavour = _PosixFlavour()
+
+
+class _Accessor:
+ """An accessor implements a particular (system-specific or not) way of
+ accessing paths on the filesystem."""
+
+
+class _NormalAccessor(_Accessor):
+
+ def _wrap_strfunc(strfunc):
+ @functools.wraps(strfunc)
+ def wrapped(pathobj, *args):
+ return strfunc(str(pathobj), *args)
+ return staticmethod(wrapped)
+
+ def _wrap_binary_strfunc(strfunc):
+ @functools.wraps(strfunc)
+ def wrapped(pathobjA, pathobjB, *args):
+ return strfunc(str(pathobjA), str(pathobjB), *args)
+ return staticmethod(wrapped)
+
+ stat = _wrap_strfunc(os.stat)
+
+ lstat = _wrap_strfunc(os.lstat)
+
+ open = _wrap_strfunc(os.open)
+
+ listdir = _wrap_strfunc(os.listdir)
+
+ chmod = _wrap_strfunc(os.chmod)
+
+ if hasattr(os, "lchmod"):
+ lchmod = _wrap_strfunc(os.lchmod)
+ else:
+ def lchmod(self, pathobj, mode):
+ raise NotImplementedError("lchmod() not available on this system")
+
+ mkdir = _wrap_strfunc(os.mkdir)
+
+ unlink = _wrap_strfunc(os.unlink)
+
+ rmdir = _wrap_strfunc(os.rmdir)
+
+ rename = _wrap_binary_strfunc(os.rename)
+
+ replace = _wrap_binary_strfunc(os.replace)
+
+ if nt:
+ if supports_symlinks:
+ symlink = _wrap_binary_strfunc(os.symlink)
+ else:
+ def symlink(a, b, target_is_directory):
+ raise NotImplementedError("symlink() not available on this system")
+ else:
+ # Under POSIX, os.symlink() takes two args
+ @staticmethod
+ def symlink(a, b, target_is_directory):
+ return os.symlink(str(a), str(b))
+
+ utime = _wrap_strfunc(os.utime)
+
+ # Helper for resolve()
+ def readlink(self, path):
+ return os.readlink(path)
+
+
+_normal_accessor = _NormalAccessor()
+
+
+#
+# Globbing helpers
+#
+
+@contextmanager
+def _cached(func):
+ try:
+ func.__cached__
+ yield func
+ except AttributeError:
+ cache = {}
+ def wrapper(*args):
+ try:
+ return cache[args]
+ except KeyError:
+ value = cache[args] = func(*args)
+ return value
+ wrapper.__cached__ = True
+ try:
+ yield wrapper
+ finally:
+ cache.clear()
+
+def _make_selector(pattern_parts):
+ pat = pattern_parts[0]
+ child_parts = pattern_parts[1:]
+ if pat == '**':
+ cls = _RecursiveWildcardSelector
+ elif '**' in pat:
+ raise ValueError("Invalid pattern: '**' can only be an entire path component")
+ elif _is_wildcard_pattern(pat):
+ cls = _WildcardSelector
+ else:
+ cls = _PreciseSelector
+ return cls(pat, child_parts)
+
+if hasattr(functools, "lru_cache"):
+ _make_selector = functools.lru_cache()(_make_selector)
+
+
+class _Selector:
+ """A selector matches a specific glob pattern part against the children
+ of a given path."""
+
+ def __init__(self, child_parts):
+ self.child_parts = child_parts
+ if child_parts:
+ self.successor = _make_selector(child_parts)
+ else:
+ self.successor = _TerminatingSelector()
+
+ def select_from(self, parent_path):
+ """Iterate over all child paths of `parent_path` matched by this
+ selector. This can contain parent_path itself."""
+ path_cls = type(parent_path)
+ is_dir = path_cls.is_dir
+ exists = path_cls.exists
+ listdir = parent_path._accessor.listdir
+ return self._select_from(parent_path, is_dir, exists, listdir)
+
+
+class _TerminatingSelector:
+
+ def _select_from(self, parent_path, is_dir, exists, listdir):
+ yield parent_path
+
+
+class _PreciseSelector(_Selector):
+
+ def __init__(self, name, child_parts):
+ self.name = name
+ _Selector.__init__(self, child_parts)
+
+ def _select_from(self, parent_path, is_dir, exists, listdir):
+ if not is_dir(parent_path):
+ return
+ path = parent_path._make_child_relpath(self.name)
+ if exists(path):
+ for p in self.successor._select_from(path, is_dir, exists, listdir):
+ yield p
+
+
+class _WildcardSelector(_Selector):
+
+ def __init__(self, pat, child_parts):
+ self.pat = re.compile(fnmatch.translate(pat))
+ _Selector.__init__(self, child_parts)
+
+ def _select_from(self, parent_path, is_dir, exists, listdir):
+ if not is_dir(parent_path):
+ return
+ cf = parent_path._flavour.casefold
+ for name in listdir(parent_path):
+ casefolded = cf(name)
+ if self.pat.match(casefolded):
+ path = parent_path._make_child_relpath(name)
+ for p in self.successor._select_from(path, is_dir, exists, listdir):
+ yield p
+
+
+class _RecursiveWildcardSelector(_Selector):
+
+ def __init__(self, pat, child_parts):
+ _Selector.__init__(self, child_parts)
+
+ def _iterate_directories(self, parent_path, is_dir, listdir):
+ yield parent_path
+ for name in listdir(parent_path):
+ path = parent_path._make_child_relpath(name)
+ if is_dir(path):
+ for p in self._iterate_directories(path, is_dir, listdir):
+ yield p
+
+ def _select_from(self, parent_path, is_dir, exists, listdir):
+ if not is_dir(parent_path):
+ return
+ with _cached(listdir) as listdir:
+ yielded = set()
+ try:
+ successor_select = self.successor._select_from
+ for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
+ for p in successor_select(starting_point, is_dir, exists, listdir):
+ if p not in yielded:
+ yield p
+ yielded.add(p)
+ finally:
+ yielded.clear()
+
+
+#
+# Public API
+#
+
+class _PathParents(Sequence):
+ """This object provides sequence-like access to the logical ancestors
+ of a path. Don't try to construct it yourself."""
+ __slots__ = ('_pathcls', '_drv', '_root', '_parts')
+
+ def __init__(self, path):
+ # We don't store the instance to avoid reference cycles
+ self._pathcls = type(path)
+ self._drv = path._drv
+ self._root = path._root
+ self._parts = path._parts
+
+ def __len__(self):
+ if self._drv or self._root:
+ return len(self._parts) - 1
+ else:
+ return len(self._parts)
+
+ def __getitem__(self, idx):
+ if idx < 0 or idx >= len(self):
+ raise IndexError(idx)
+ return self._pathcls._from_parsed_parts(self._drv, self._root,
+ self._parts[:-idx - 1])
+
+ def __repr__(self):
+ return "<{}.parents>".format(self._pathcls.__name__)
+
+
+class PurePath(object):
+ """PurePath represents a filesystem path and offers operations which
+ don't imply any actual filesystem I/O. Depending on your system,
+ instantiating a PurePath will return either a PurePosixPath or a
+ PureWindowsPath object. You can also instantiate either of these classes
+ directly, regardless of your system.
+ """
+ __slots__ = (
+ '_drv', '_root', '_parts',
+ '_str', '_hash', '_pparts', '_cached_cparts',
+ )
+
+ def __new__(cls, *args):
+ """Construct a PurePath from one or several strings and or existing
+ PurePath objects. The strings and path objects are combined so as
+ to yield a canonicalized path, which is incorporated into the
+ new PurePath object.
+ """
+ if cls is PurePath:
+ cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+ return cls._from_parts(args)
+
+ def __reduce__(self):
+ # Using the parts tuple helps share interned path parts
+ # when pickling related paths.
+ return (self.__class__, tuple(self._parts))
+
+ @classmethod
+ def _parse_args(cls, args):
+ # This is useful when you don't want to create an instance, just
+ # canonicalize some constructor arguments.
+ parts = []
+ for a in args:
+ if isinstance(a, PurePath):
+ parts += a._parts
+ elif isinstance(a, str):
+ # Assuming a str
+ parts.append(a)
+ else:
+ raise TypeError(
+ "argument should be a path or str object, not %r"
+ % type(a))
+ return cls._flavour.parse_parts(parts)
+
+ @classmethod
+ def _from_parts(cls, args, init=True):
+ # We need to call _parse_args on the instance, so as to get the
+ # right flavour.
+ self = object.__new__(cls)
+ drv, root, parts = self._parse_args(args)
+ self._drv = drv
+ self._root = root
+ self._parts = parts
+ if init:
+ self._init()
+ return self
+
+ @classmethod
+ def _from_parsed_parts(cls, drv, root, parts, init=True):
+ self = object.__new__(cls)
+ self._drv = drv
+ self._root = root
+ self._parts = parts
+ if init:
+ self._init()
+ return self
+
+ @classmethod
+ def _format_parsed_parts(cls, drv, root, parts):
+ if drv or root:
+ return drv + root + cls._flavour.join(parts[1:])
+ else:
+ return cls._flavour.join(parts)
+
+ def _init(self):
+ # Overriden in concrete Path
+ pass
+
+ def _make_child(self, args):
+ drv, root, parts = self._parse_args(args)
+ drv, root, parts = self._flavour.join_parsed_parts(
+ self._drv, self._root, self._parts, drv, root, parts)
+ return self._from_parsed_parts(drv, root, parts)
+
+ def __str__(self):
+ """Return the string representation of the path, suitable for
+ passing to system calls."""
+ try:
+ return self._str
+ except AttributeError:
+ self._str = self._format_parsed_parts(self._drv, self._root,
+ self._parts) or '.'
+ return self._str
+
+ def as_posix(self):
+ """Return the string representation of the path with forward (/)
+ slashes."""
+ f = self._flavour
+ return str(self).replace(f.sep, '/')
+
+ def __bytes__(self):
+ """Return the bytes representation of the path. This is only
+ recommended to use under Unix."""
+ return os.fsencode(str(self))
+
+ def __repr__(self):
+ return "{}({!r})".format(self.__class__.__name__, self.as_posix())
+
+ def as_uri(self):
+ """Return the path as a 'file' URI."""
+ if not self.is_absolute():
+ raise ValueError("relative path can't be expressed as a file URI")
+ return self._flavour.make_uri(self)
+
+ @property
+ def _cparts(self):
+ # Cached casefolded parts, for hashing and comparison
+ try:
+ return self._cached_cparts
+ except AttributeError:
+ self._cached_cparts = self._flavour.casefold_parts(self._parts)
+ return self._cached_cparts
+
+ def __eq__(self, other):
+ if not isinstance(other, PurePath):
+ return NotImplemented
+ return self._cparts == other._cparts and self._flavour is other._flavour
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ try:
+ return self._hash
+ except AttributeError:
+ self._hash = hash(tuple(self._cparts))
+ return self._hash
+
+ def __lt__(self, other):
+ if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+ return NotImplemented
+ return self._cparts < other._cparts
+
+ def __le__(self, other):
+ if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+ return NotImplemented
+ return self._cparts <= other._cparts
+
+ def __gt__(self, other):
+ if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+ return NotImplemented
+ return self._cparts > other._cparts
+
+ def __ge__(self, other):
+ if not isinstance(other, PurePath) or self._flavour is not other._flavour:
+ return NotImplemented
+ return self._cparts >= other._cparts
+
+ drive = property(attrgetter('_drv'),
+ doc="""The drive prefix (letter or UNC path), if any.""")
+
+ root = property(attrgetter('_root'),
+ doc="""The root of the path, if any.""")
+
+ @property
+ def anchor(self):
+ """The concatenation of the drive and root, or ''."""
+ anchor = self._drv + self._root
+ return anchor
+
+ @property
+ def name(self):
+ """The final path component, if any."""
+ parts = self._parts
+ if len(parts) == (1 if (self._drv or self._root) else 0):
+ return ''
+ return parts[-1]
+
+ @property
+ def suffix(self):
+ """The final component's last suffix, if any."""
+ name = self.name
+ i = name.rfind('.')
+ if 0 < i < len(name) - 1:
+ return name[i:]
+ else:
+ return ''
+
+ @property
+ def suffixes(self):
+ """A list of the final component's suffixes, if any."""
+ name = self.name
+ if name.endswith('.'):
+ return []
+ name = name.lstrip('.')
+ return ['.' + suffix for suffix in name.split('.')[1:]]
+
+ @property
+ def stem(self):
+ """The final path component, minus its last suffix."""
+ name = self.name
+ i = name.rfind('.')
+ if 0 < i < len(name) - 1:
+ return name[:i]
+ else:
+ return name
+
+ def with_name(self, name):
+ """Return a new path with the file name changed."""
+ if not self.name:
+ raise ValueError("%r has an empty name" % (self,))
+ return self._from_parsed_parts(self._drv, self._root,
+ self._parts[:-1] + [name])
+
+ def with_suffix(self, suffix):
+ """Return a new path with the file suffix changed (or added, if none)."""
+ # XXX if suffix is None, should the current suffix be removed?
+ name = self.name
+ if not name:
+ raise ValueError("%r has an empty name" % (self,))
+ old_suffix = self.suffix
+ if not old_suffix:
+ name = name + suffix
+ else:
+ name = name[:-len(old_suffix)] + suffix
+ return self._from_parsed_parts(self._drv, self._root,
+ self._parts[:-1] + [name])
+
+ def relative_to(self, *other):
+ """Return the relative path to another path identified by the passed
+ arguments. If the operation is not possible (because this is not
+ a subpath of the other path), raise ValueError.
+ """
+ # For the purpose of this method, drive and root are considered
+ # separate parts, i.e.:
+ # Path('c:/').relative_to('c:') gives Path('/')
+ # Path('c:/').relative_to('/') raise ValueError
+ if not other:
+ raise TypeError("need at least one argument")
+ parts = self._parts
+ drv = self._drv
+ root = self._root
+ if drv or root:
+ if root:
+ abs_parts = [drv, root] + parts[1:]
+ else:
+ abs_parts = [drv] + parts[1:]
+ else:
+ abs_parts = parts
+ to_drv, to_root, to_parts = self._parse_args(other)
+ if to_drv or to_root:
+ if to_root:
+ to_abs_parts = [to_drv, to_root] + to_parts[1:]
+ else:
+ to_abs_parts = [to_drv] + to_parts[1:]
+ else:
+ to_abs_parts = to_parts
+ n = len(to_abs_parts)
+ if n == 0 and (drv or root) or abs_parts[:n] != to_abs_parts:
+ formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
+ raise ValueError("{!r} does not start with {!r}"
+ .format(str(self), str(formatted)))
+ return self._from_parsed_parts('', '', abs_parts[n:])
+
+ @property
+ def parts(self):
+ """An object providing sequence-like access to the
+ components in the filesystem path."""
+ # We cache the tuple to avoid building a new one each time .parts
+ # is accessed. XXX is this necessary?
+ try:
+ return self._pparts
+ except AttributeError:
+ self._pparts = tuple(self._parts)
+ return self._pparts
+
+ def joinpath(self, *args):
+ """Combine this path with one or several arguments, and return a
+ new path representing either a subpath (if all arguments are relative
+ paths) or a totally different path (if one of the arguments is
+ anchored).
+ """
+ return self._make_child(args)
+
+ def __truediv__(self, key):
+ return self._make_child((key,))
+
+ def __rtruediv__(self, key):
+ return self._from_parts([key] + self._parts)
+
+ @property
+ def parent(self):
+ """The logical parent of the path."""
+ drv = self._drv
+ root = self._root
+ parts = self._parts
+ if len(parts) == 1 and (drv or root):
+ return self
+ return self._from_parsed_parts(drv, root, parts[:-1])
+
+ @property
+ def parents(self):
+ """A sequence of this path's logical parents."""
+ return _PathParents(self)
+
+ def is_absolute(self):
+ """True if the path is absolute (has both a root and, if applicable,
+ a drive)."""
+ if not self._root:
+ return False
+ return not self._flavour.has_drv or bool(self._drv)
+
+ def is_reserved(self):
+ """Return True if the path contains one of the special names reserved
+ by the system, if any."""
+ return self._flavour.is_reserved(self._parts)
+
+ def match(self, path_pattern):
+ """
+ Return True if this path matches the given pattern.
+ """
+ cf = self._flavour.casefold
+ path_pattern = cf(path_pattern)
+ drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+ if not pat_parts:
+ raise ValueError("empty pattern")
+ if drv and drv != cf(self._drv):
+ return False
+ if root and root != cf(self._root):
+ return False
+ parts = self._cparts
+ if drv or root:
+ if len(pat_parts) != len(parts):
+ return False
+ pat_parts = pat_parts[1:]
+ elif len(pat_parts) > len(parts):
+ return False
+ for part, pat in zip(reversed(parts), reversed(pat_parts)):
+ if not fnmatch.fnmatchcase(part, pat):
+ return False
+ return True
+
+
+class PurePosixPath(PurePath):
+ _flavour = _posix_flavour
+ __slots__ = ()
+
+
+class PureWindowsPath(PurePath):
+ _flavour = _windows_flavour
+ __slots__ = ()
+
+
+# Filesystem-accessing classes
+
+
+class Path(PurePath):
+ __slots__ = (
+ '_accessor',
+ '_closed',
+ )
+
+ def __new__(cls, *args, **kwargs):
+ if cls is Path:
+ cls = WindowsPath if os.name == 'nt' else PosixPath
+ self = cls._from_parts(args, init=False)
+ if not self._flavour.is_supported:
+ raise NotImplementedError("cannot instantiate %r on your system"
+ % (cls.__name__,))
+ self._init()
+ return self
+
+ def _init(self,
+ # Private non-constructor arguments
+ template=None,
+ ):
+ self._closed = False
+ if template is not None:
+ self._accessor = template._accessor
+ else:
+ self._accessor = _normal_accessor
+
+ def _make_child_relpath(self, part):
+ # This is an optimization used for dir walking. `part` must be
+ # a single part relative to this path.
+ parts = self._parts + [part]
+ return self._from_parsed_parts(self._drv, self._root, parts)
+
+ def __enter__(self):
+ if self._closed:
+ self._raise_closed()
+ return self
+
+ def __exit__(self, t, v, tb):
+ self._closed = True
+
+ def _raise_closed(self):
+ raise ValueError("I/O operation on closed path")
+
+ def _opener(self, name, flags, mode=0o666):
+ # A stub for the opener argument to built-in open()
+ return self._accessor.open(self, flags, mode)
+
+ # Public API
+
+ @classmethod
+ def cwd(cls):
+ """Return a new path pointing to the current working directory
+ (as returned by os.getcwd()).
+ """
+ return cls(os.getcwd())
+
+ def iterdir(self):
+ """Iterate over the files in this directory. Does not yield any
+ result for the special paths '.' and '..'.
+ """
+ if self._closed:
+ self._raise_closed()
+ for name in self._accessor.listdir(self):
+ if name in {'.', '..'}:
+ # Yielding a path object for these makes little sense
+ continue
+ yield self._make_child_relpath(name)
+ if self._closed:
+ self._raise_closed()
+
+ def glob(self, pattern):
+ """Iterate over this subtree and yield all existing files (of any
+ kind, including directories) matching the given pattern.
+ """
+ pattern = self._flavour.casefold(pattern)
+ drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+ if drv or root:
+ raise NotImplementedError("Non-relative patterns are unsupported")
+ selector = _make_selector(tuple(pattern_parts))
+ for p in selector.select_from(self):
+ yield p
+
+ def rglob(self, pattern):
+ """Recursively yield all existing files (of any kind, including
+ directories) matching the given pattern, anywhere in this subtree.
+ """
+ pattern = self._flavour.casefold(pattern)
+ drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
+ if drv or root:
+ raise NotImplementedError("Non-relative patterns are unsupported")
+ selector = _make_selector(("**",) + tuple(pattern_parts))
+ for p in selector.select_from(self):
+ yield p
+
+ def absolute(self):
+ """Return an absolute version of this path. This function works
+ even if the path doesn't point to anything.
+
+ No normalization is done, i.e. all '.' and '..' will be kept along.
+ Use resolve() to get the canonical path to a file.
+ """
+ # XXX untested yet!
+ if self._closed:
+ self._raise_closed()
+ if self.is_absolute():
+ return self
+ # FIXME this must defer to the specific flavour (and, under Windows,
+ # use nt._getfullpathname())
+ obj = self._from_parts([os.getcwd()] + self._parts, init=False)
+ obj._init(template=self)
+ return obj
+
+ def resolve(self):
+ """
+ Make the path absolute, resolving all symlinks on the way and also
+ normalizing it (for example turning slashes into backslashes under
+ Windows).
+ """
+ if self._closed:
+ self._raise_closed()
+ s = self._flavour.resolve(self)
+ if s is None:
+ # No symlink resolution => for consistency, raise an error if
+ # the path doesn't exist or is forbidden
+ self.stat()
+ s = str(self.absolute())
+ # Now we have no symlinks in the path, it's safe to normalize it.
+ normed = self._flavour.pathmod.normpath(s)
+ obj = self._from_parts((normed,), init=False)
+ obj._init(template=self)
+ return obj
+
+ def stat(self):
+ """
+ Return the result of the stat() system call on this path, like
+ os.stat() does.
+ """
+ return self._accessor.stat(self)
+
+ def owner(self):
+ """
+ Return the login name of the file owner.
+ """
+ import pwd
+ return pwd.getpwuid(self.stat().st_uid).pw_name
+
+ def group(self):
+ """
+ Return the group name of the file gid.
+ """
+ import grp
+ return grp.getgrgid(self.stat().st_gid).gr_name
+
+ def _raw_open(self, flags, mode=0o777):
+ """
+ Open the file pointed by this path and return a file descriptor,
+ as os.open() does.
+ """
+ if self._closed:
+ self._raise_closed()
+ return self._accessor.open(self, flags, mode)
+
+ def open(self, mode='r', buffering=-1, encoding=None,
+ errors=None, newline=None):
+ """
+ Open the file pointed by this path and return a file object, as
+ the built-in open() function does.
+ """
+ if self._closed:
+ self._raise_closed()
+ return io.open(str(self), mode, buffering, encoding, errors, newline,
+ opener=self._opener)
+
+ def touch(self, mode=0o666, exist_ok=True):
+ """
+ Create this file with the given access mode, if it doesn't exist.
+ """
+ if self._closed:
+ self._raise_closed()
+ if exist_ok:
+ # First try to bump modification time
+ # Implementation note: GNU touch uses the UTIME_NOW option of
+ # the utimensat() / futimens() functions.
+ try:
+ self._accessor.utime(self, None)
+ except OSError:
+ # Avoid exception chaining
+ pass
+ else:
+ return
+ flags = os.O_CREAT | os.O_WRONLY
+ if not exist_ok:
+ flags |= os.O_EXCL
+ fd = self._raw_open(flags, mode)
+ os.close(fd)
+
+ def mkdir(self, mode=0o777, parents=False):
+ if self._closed:
+ self._raise_closed()
+ if not parents:
+ self._accessor.mkdir(self, mode)
+ else:
+ try:
+ self._accessor.mkdir(self, mode)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ self.parent.mkdir(mode, True)
+ self._accessor.mkdir(self, mode)
+
+ def chmod(self, mode):
+ """
+ Change the permissions of the path, like os.chmod().
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.chmod(self, mode)
+
+ def lchmod(self, mode):
+ """
+ Like chmod(), except if the path points to a symlink, the symlink's
+ permissions are changed, rather than its target's.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.lchmod(self, mode)
+
+ def unlink(self):
+ """
+ Remove this file or link.
+ If the path is a directory, use rmdir() instead.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.unlink(self)
+
+ def rmdir(self):
+ """
+ Remove this directory. The directory must be empty.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.rmdir(self)
+
+ def lstat(self):
+ """
+ Like stat(), except if the path points to a symlink, the symlink's
+ status information is returned, rather than its target's.
+ """
+ if self._closed:
+ self._raise_closed()
+ return self._accessor.lstat(self)
+
+ def rename(self, target):
+ """
+ Rename this path to the given path.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.rename(self, target)
+
+ def replace(self, target):
+ """
+ Rename this path to the given path, clobbering the existing
+ destination if it exists.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.replace(self, target)
+
+ def symlink_to(self, target, target_is_directory=False):
+ """
+ Make this path a symlink pointing to the given path.
+ Note the order of arguments (self, target) is the reverse of os.symlink's.
+ """
+ if self._closed:
+ self._raise_closed()
+ self._accessor.symlink(target, self, target_is_directory)
+
+ # Convenience functions for querying the stat results
+
+ def exists(self):
+ """
+ Whether this path exists.
+ """
+ try:
+ self.stat()
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ return False
+ return True
+
+ def is_dir(self):
+ """
+ Whether this path is a directory.
+ """
+ try:
+ return S_ISDIR(self.stat().st_mode)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+
+ def is_file(self):
+ """
+ Whether this path is a regular file (also True for symlinks pointing
+ to regular files).
+ """
+ try:
+ return S_ISREG(self.stat().st_mode)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+
+ def is_symlink(self):
+ """
+ Whether this path is a symbolic link.
+ """
+ try:
+ return S_ISLNK(self.lstat().st_mode)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ # Path doesn't exist
+ return False
+
+ def is_block_device(self):
+ """
+ Whether this path is a block device.
+ """
+ try:
+ return S_ISBLK(self.stat().st_mode)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+
+ def is_char_device(self):
+ """
+ Whether this path is a character device.
+ """
+ try:
+ return S_ISCHR(self.stat().st_mode)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+
+ def is_fifo(self):
+ """
+ Whether this path is a FIFO.
+ """
+ try:
+ return S_ISFIFO(self.stat().st_mode)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+
+ def is_socket(self):
+ """
+ Whether this path is a socket.
+ """
+ try:
+ return S_ISSOCK(self.stat().st_mode)
+ except OSError as e:
+ if e.errno != ENOENT:
+ raise
+ # Path doesn't exist or is a broken symlink
+ # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
+ return False
+
+
+class PosixPath(Path, PurePosixPath):
+ __slots__ = ()
+
+class WindowsPath(Path, PureWindowsPath):
+ __slots__ = ()
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 80cba9d36c..dd7ceb8927 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -92,21 +92,14 @@ def find_function(funcname, filename):
cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname))
try:
fp = open(filename)
- except IOError:
+ except OSError:
return None
# consumer of this info expects the first line to be 1
- lineno = 1
- answer = None
- while True:
- line = fp.readline()
- if line == '':
- break
- if cre.match(line):
- answer = funcname, filename, lineno
- break
- lineno += 1
- fp.close()
- return answer
+ with fp:
+ for lineno, line in enumerate(fp, start=1):
+ if cre.match(line):
+ return funcname, filename, lineno
+ return None
def getsourcelines(obj):
lines, lineno = inspect.findsource(obj)
@@ -170,12 +163,12 @@ class Pdb(bdb.Bdb, cmd.Cmd):
try:
with open(os.path.join(envHome, ".pdbrc")) as rcFile:
self.rcLines.extend(rcFile)
- except IOError:
+ except OSError:
pass
try:
with open(".pdbrc") as rcFile:
self.rcLines.extend(rcFile)
- except IOError:
+ except OSError:
pass
self.commands = {} # associates a command list to breakpoint numbers
@@ -304,8 +297,16 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return
exc_type, exc_value, exc_traceback = exc_info
frame.f_locals['__exception__'] = exc_type, exc_value
- self.message(traceback.format_exception_only(exc_type,
- exc_value)[-1].strip())
+
+ # An 'Internal StopIteration' exception is an exception debug event
+ # issued by the interpreter when handling a subgenerator run with
+ # 'yield from' or a generator controled by a for loop. No exception has
+ # actually occured in this case. The debugger uses this debug event to
+ # stop when the debuggee is returning from such generators.
+ prefix = 'Internal ' if (not exc_traceback
+ and exc_type is StopIteration) else ''
+ self.message('%s%s' % (prefix,
+ traceback.format_exception_only(exc_type, exc_value)[-1].strip()))
self.interaction(frame, exc_traceback)
# General interaction function
@@ -1163,15 +1164,13 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return _rstr('** raised %s **' % err)
def do_p(self, arg):
- """p(rint) expression
+ """p expression
Print the value of the expression.
"""
try:
self.message(repr(self._getval(arg)))
except:
pass
- # make "print" an alias of "p" since print isn't a Python statement anymore
- do_print = do_p
def do_pp(self, arg):
"""pp expression
@@ -1245,7 +1244,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
breaklist = self.get_file_breaks(filename)
try:
lines, lineno = getsourcelines(self.curframe)
- except IOError as err:
+ except OSError as err:
self.error(err)
return
self._print_lines(lines, lineno, breaklist, self.curframe)
@@ -1261,7 +1260,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return
try:
lines, lineno = getsourcelines(obj)
- except (IOError, TypeError) as err:
+ except (OSError, TypeError) as err:
self.error(err)
return
self._print_lines(lines, lineno)
@@ -1392,7 +1391,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
placed in the .pdbrc file):
# Print instance variables (usage "pi classInst")
- alias pi for k in %1.__dict__.keys(): print "%1.",k,"=",%1.__dict__[k]
+ alias pi for k in %1.__dict__.keys(): print("%1.",k,"=",%1.__dict__[k])
# Print instance variables in self
alias ps pi self
"""
@@ -1550,7 +1549,7 @@ if __doc__ is not None:
'help', 'where', 'down', 'up', 'break', 'tbreak', 'clear', 'disable',
'enable', 'ignore', 'condition', 'commands', 'step', 'next', 'until',
'jump', 'return', 'retval', 'run', 'continue', 'list', 'longlist',
- 'args', 'print', 'pp', 'whatis', 'source', 'display', 'undisplay',
+ 'args', 'p', 'pp', 'whatis', 'source', 'display', 'undisplay',
'interact', 'alias', 'unalias', 'debug', 'quit',
]
diff --git a/Lib/pickle.py b/Lib/pickle.py
index 386ffba7e2..c57149a393 100644
--- a/Lib/pickle.py
+++ b/Lib/pickle.py
@@ -23,12 +23,13 @@ Misc variables:
"""
-from types import FunctionType, BuiltinFunctionType
+from types import FunctionType, ModuleType
from copyreg import dispatch_table
from copyreg import _extension_registry, _inverted_registry, _extension_cache
-import marshal
+from itertools import islice
import sys
-import struct
+from sys import maxsize
+from struct import pack, unpack
import re
import io
import codecs
@@ -41,28 +42,24 @@ __all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler",
bytes_types = (bytes, bytearray)
# These are purely informational; no code uses these.
-format_version = "3.0" # File format version we write
+format_version = "4.0" # File format version we write
compatible_formats = ["1.0", # Original protocol 0
"1.1", # Protocol 0 with INST added
"1.2", # Original protocol 1
"1.3", # Protocol 1 with BINFLOAT added
"2.0", # Protocol 2
"3.0", # Protocol 3
+ "4.0", # Protocol 4
] # Old format versions we can read
# This is the highest protocol number we know how to read.
-HIGHEST_PROTOCOL = 3
+HIGHEST_PROTOCOL = 4
# The protocol we write by default. May be less than HIGHEST_PROTOCOL.
# We intentionally write a protocol that Python 2.x cannot read;
# there are too many issues with that.
DEFAULT_PROTOCOL = 3
-# Why use struct.pack() for pickling but marshal.loads() for
-# unpickling? struct.pack() is 40% faster than marshal.dumps(), but
-# marshal.loads() is twice as fast as struct.unpack()!
-mloads = marshal.loads
-
class PickleError(Exception):
"""A common base class for the other pickling exceptions."""
pass
@@ -168,7 +165,181 @@ _tuplesize2code = [EMPTY_TUPLE, TUPLE1, TUPLE2, TUPLE3]
BINBYTES = b'B' # push bytes; counted binary string argument
SHORT_BINBYTES = b'C' # " " ; " " " " < 256 bytes
-__all__.extend([x for x in dir() if re.match("[A-Z][A-Z0-9_]+$",x)])
+# Protocol 4
+SHORT_BINUNICODE = b'\x8c' # push short string; UTF-8 length < 256 bytes
+BINUNICODE8 = b'\x8d' # push very long string
+BINBYTES8 = b'\x8e' # push very long bytes string
+EMPTY_SET = b'\x8f' # push empty set on the stack
+ADDITEMS = b'\x90' # modify set by adding topmost stack items
+FROZENSET = b'\x91' # build frozenset from topmost stack items
+NEWOBJ_EX = b'\x92' # like NEWOBJ but work with keyword only arguments
+STACK_GLOBAL = b'\x93' # same as GLOBAL but using names on the stacks
+MEMOIZE = b'\x94' # store top of the stack in memo
+FRAME = b'\x95' # indicate the beginning of a new frame
+
+__all__.extend([x for x in dir() if re.match("[A-Z][A-Z0-9_]+$", x)])
+
+
+class _Framer:
+
+ _FRAME_SIZE_TARGET = 64 * 1024
+
+ def __init__(self, file_write):
+ self.file_write = file_write
+ self.current_frame = None
+
+ def start_framing(self):
+ self.current_frame = io.BytesIO()
+
+ def end_framing(self):
+ if self.current_frame and self.current_frame.tell() > 0:
+ self.commit_frame(force=True)
+ self.current_frame = None
+
+ def commit_frame(self, force=False):
+ if self.current_frame:
+ f = self.current_frame
+ if f.tell() >= self._FRAME_SIZE_TARGET or force:
+ with f.getbuffer() as data:
+ n = len(data)
+ write = self.file_write
+ write(FRAME)
+ write(pack("<Q", n))
+ write(data)
+ f.seek(0)
+ f.truncate()
+
+ def write(self, data):
+ if self.current_frame:
+ return self.current_frame.write(data)
+ else:
+ return self.file_write(data)
+
+
+class _Unframer:
+
+ def __init__(self, file_read, file_readline, file_tell=None):
+ self.file_read = file_read
+ self.file_readline = file_readline
+ self.current_frame = None
+
+ def read(self, n):
+ if self.current_frame:
+ data = self.current_frame.read(n)
+ if not data and n != 0:
+ self.current_frame = None
+ return self.file_read(n)
+ if len(data) < n:
+ raise UnpicklingError(
+ "pickle exhausted before end of frame")
+ return data
+ else:
+ return self.file_read(n)
+
+ def readline(self):
+ if self.current_frame:
+ data = self.current_frame.readline()
+ if not data:
+ self.current_frame = None
+ return self.file_readline()
+ if data[-1] != b'\n':
+ raise UnpicklingError(
+ "pickle exhausted before end of frame")
+ return data
+ else:
+ return self.file_readline()
+
+ def load_frame(self, frame_size):
+ if self.current_frame and self.current_frame.read() != b'':
+ raise UnpicklingError(
+ "beginning of a new frame before end of current frame")
+ self.current_frame = io.BytesIO(self.file_read(frame_size))
+
+
+# Tools used for pickling.
+
+def _getattribute(obj, name, allow_qualname=False):
+ dotted_path = name.split(".")
+ if not allow_qualname and len(dotted_path) > 1:
+ raise AttributeError("Can't get qualified attribute {!r} on {!r}; " +
+ "use protocols >= 4 to enable support"
+ .format(name, obj))
+ for subpath in dotted_path:
+ if subpath == '<locals>':
+ raise AttributeError("Can't get local attribute {!r} on {!r}"
+ .format(name, obj))
+ try:
+ obj = getattr(obj, subpath)
+ except AttributeError:
+ raise AttributeError("Can't get attribute {!r} on {!r}"
+ .format(name, obj))
+ return obj
+
+def whichmodule(obj, name, allow_qualname=False):
+ """Find the module an object belong to."""
+ module_name = getattr(obj, '__module__', None)
+ if module_name is not None:
+ return module_name
+ for module_name, module in sys.modules.items():
+ if module_name == '__main__' or module is None:
+ continue
+ try:
+ if _getattribute(module, name, allow_qualname) is obj:
+ return module_name
+ except AttributeError:
+ pass
+ return '__main__'
+
+def encode_long(x):
+ r"""Encode a long to a two's complement little-endian binary string.
+ Note that 0 is a special case, returning an empty string, to save a
+ byte in the LONG1 pickling context.
+
+ >>> encode_long(0)
+ b''
+ >>> encode_long(255)
+ b'\xff\x00'
+ >>> encode_long(32767)
+ b'\xff\x7f'
+ >>> encode_long(-256)
+ b'\x00\xff'
+ >>> encode_long(-32768)
+ b'\x00\x80'
+ >>> encode_long(-128)
+ b'\x80'
+ >>> encode_long(127)
+ b'\x7f'
+ >>>
+ """
+ if x == 0:
+ return b''
+ nbytes = (x.bit_length() >> 3) + 1
+ result = x.to_bytes(nbytes, byteorder='little', signed=True)
+ if x < 0 and nbytes > 1:
+ if result[-1] == 0xff and (result[-2] & 0x80) != 0:
+ result = result[:-1]
+ return result
+
+def decode_long(data):
+ r"""Decode a long from a two's complement little-endian binary string.
+
+ >>> decode_long(b'')
+ 0
+ >>> decode_long(b"\xff\x00")
+ 255
+ >>> decode_long(b"\xff\x7f")
+ 32767
+ >>> decode_long(b"\x00\xff")
+ -256
+ >>> decode_long(b"\x00\x80")
+ -32768
+ >>> decode_long(b"\x80")
+ -128
+ >>> decode_long(b"\x7f")
+ 127
+ """
+ return int.from_bytes(data, byteorder='little', signed=True)
+
# Pickling machinery
@@ -178,9 +349,9 @@ class _Pickler:
"""This takes a binary file for writing a pickle data stream.
The optional protocol argument tells the pickler to use the
- given protocol; supported protocols are 0, 1, 2, 3. The default
- protocol is 3; a backward-incompatible protocol designed for
- Python 3.0.
+ given protocol; supported protocols are 0, 1, 2, 3 and 4. The
+ default protocol is 3; a backward-incompatible protocol designed for
+ Python 3.
Specifying a negative protocol version selects the highest
protocol version supported. The higher the protocol used, the
@@ -193,8 +364,8 @@ class _Pickler:
meets this interface.
If fix_imports is True and protocol is less than 3, pickle will try to
- map the new Python 3.x names to the old module names used in Python
- 2.x, so that the pickle data stream is readable with Python 2.x.
+ map the new Python 3 names to the old module names used in Python 2,
+ so that the pickle data stream is readable with Python 2.
"""
if protocol is None:
protocol = DEFAULT_PROTOCOL
@@ -203,9 +374,11 @@ class _Pickler:
elif not 0 <= protocol <= HIGHEST_PROTOCOL:
raise ValueError("pickle protocol must be <= %d" % HIGHEST_PROTOCOL)
try:
- self.write = file.write
+ self._file_write = file.write
except AttributeError:
raise TypeError("file must have a 'write' attribute")
+ self.framer = _Framer(self._file_write)
+ self.write = self.framer.write
self.memo = {}
self.proto = int(protocol)
self.bin = protocol >= 1
@@ -227,13 +400,16 @@ class _Pickler:
"""Write a pickled representation of obj to the open file."""
# Check whether Pickler was initialized correctly. This is
# only needed to mimic the behavior of _pickle.Pickler.dump().
- if not hasattr(self, "write"):
+ if not hasattr(self, "_file_write"):
raise PicklingError("Pickler.__init__() was not called by "
"%s.__init__()" % (self.__class__.__name__,))
if self.proto >= 2:
- self.write(PROTO + bytes([self.proto]))
+ self.write(PROTO + pack("<B", self.proto))
+ if self.proto >= 4:
+ self.framer.start_framing()
self.save(obj)
self.write(STOP)
+ self.framer.end_framing()
def memoize(self, obj):
"""Store an object in the memo."""
@@ -253,31 +429,35 @@ class _Pickler:
if self.fast:
return
assert id(obj) not in self.memo
- memo_len = len(self.memo)
- self.write(self.put(memo_len))
- self.memo[id(obj)] = memo_len, obj
+ idx = len(self.memo)
+ self.write(self.put(idx))
+ self.memo[id(obj)] = idx, obj
# Return a PUT (BINPUT, LONG_BINPUT) opcode string, with argument i.
- def put(self, i, pack=struct.pack):
- if self.bin:
- if i < 256:
- return BINPUT + bytes([i])
+ def put(self, idx):
+ if self.proto >= 4:
+ return MEMOIZE
+ elif self.bin:
+ if idx < 256:
+ return BINPUT + pack("<B", idx)
else:
- return LONG_BINPUT + pack("<I", i)
-
- return PUT + repr(i).encode("ascii") + b'\n'
+ return LONG_BINPUT + pack("<I", idx)
+ else:
+ return PUT + repr(idx).encode("ascii") + b'\n'
# Return a GET (BINGET, LONG_BINGET) opcode string, with argument i.
- def get(self, i, pack=struct.pack):
+ def get(self, i):
if self.bin:
if i < 256:
- return BINGET + bytes([i])
+ return BINGET + pack("<B", i)
else:
return LONG_BINGET + pack("<I", i)
return GET + repr(i).encode("ascii") + b'\n'
def save(self, obj, save_persistent_id=True):
+ self.framer.commit_frame()
+
# Check for persistent id (defined by a subclass)
pid = self.persistent_id(obj)
if pid is not None and save_persistent_id:
@@ -286,20 +466,20 @@ class _Pickler:
# Check the memo
x = self.memo.get(id(obj))
- if x:
+ if x is not None:
self.write(self.get(x[0]))
return
# Check the type dispatch table
t = type(obj)
f = self.dispatch.get(t)
- if f:
+ if f is not None:
f(self, obj) # Call unbound method with explicit self
return
# Check private dispatch table if any, or else copyreg.dispatch_table
reduce = getattr(self, 'dispatch_table', dispatch_table).get(t)
- if reduce:
+ if reduce is not None:
rv = reduce(obj)
else:
# Check for a class with a custom metaclass; treat as regular class
@@ -313,11 +493,11 @@ class _Pickler:
# Check for a __reduce_ex__ method, fall back to __reduce__
reduce = getattr(obj, "__reduce_ex__", None)
- if reduce:
+ if reduce is not None:
rv = reduce(self.proto)
else:
reduce = getattr(obj, "__reduce__", None)
- if reduce:
+ if reduce is not None:
rv = reduce()
else:
raise PicklingError("Can't pickle %r object: %r" %
@@ -353,24 +533,33 @@ class _Pickler:
else:
self.write(PERSID + str(pid).encode("ascii") + b'\n')
- def save_reduce(self, func, args, state=None,
- listitems=None, dictitems=None, obj=None):
+ def save_reduce(self, func, args, state=None, listitems=None,
+ dictitems=None, obj=None):
# This API is called by some subclasses
- # Assert that args is a tuple
if not isinstance(args, tuple):
- raise PicklingError("args from save_reduce() should be a tuple")
-
- # Assert that func is callable
+ raise PicklingError("args from save_reduce() must be a tuple")
if not callable(func):
- raise PicklingError("func from save_reduce() should be callable")
+ raise PicklingError("func from save_reduce() must be callable")
save = self.save
write = self.write
- # Protocol 2 special case: if func's name is __newobj__, use NEWOBJ
- if self.proto >= 2 and getattr(func, "__name__", "") == "__newobj__":
- # A __reduce__ implementation can direct protocol 2 to
+ func_name = getattr(func, "__name__", "")
+ if self.proto >= 4 and func_name == "__newobj_ex__":
+ cls, args, kwargs = args
+ if not hasattr(cls, "__new__"):
+ raise PicklingError("args[0] from {} args has no __new__"
+ .format(func_name))
+ if obj is not None and cls is not obj.__class__:
+ raise PicklingError("args[0] from {} args has the wrong class"
+ .format(func_name))
+ save(cls)
+ save(args)
+ save(kwargs)
+ write(NEWOBJ_EX)
+ elif self.proto >= 2 and func_name == "__newobj__":
+ # A __reduce__ implementation can direct protocol 2 or newer to
# use the more efficient NEWOBJ opcode, while still
# allowing protocol 0 and 1 to work normally. For this to
# work, the function returned by __reduce__ should be
@@ -413,7 +602,13 @@ class _Pickler:
write(REDUCE)
if obj is not None:
- self.memoize(obj)
+ # If the object is already in the memo, this means it is
+ # recursive. In this case, throw away everything we put on the
+ # stack, and fetch the object back from the memo.
+ if id(obj) in self.memo:
+ write(POP + self.get(self.memo[id(obj)][0]))
+ else:
+ self.memoize(obj)
# More new special cases (that work with older protocols as
# well): when __reduce__ returns a tuple with 4 or 5 items,
@@ -438,22 +633,14 @@ class _Pickler:
self.write(NONE)
dispatch[type(None)] = save_none
- def save_ellipsis(self, obj):
- self.save_global(Ellipsis, 'Ellipsis')
- dispatch[type(Ellipsis)] = save_ellipsis
-
- def save_notimplemented(self, obj):
- self.save_global(NotImplemented, 'NotImplemented')
- dispatch[type(NotImplemented)] = save_notimplemented
-
def save_bool(self, obj):
if self.proto >= 2:
- self.write(obj and NEWTRUE or NEWFALSE)
+ self.write(NEWTRUE if obj else NEWFALSE)
else:
- self.write(obj and TRUE or FALSE)
+ self.write(TRUE if obj else FALSE)
dispatch[bool] = save_bool
- def save_long(self, obj, pack=struct.pack):
+ def save_long(self, obj):
if self.bin:
# If the int is small enough to fit in a signed 4-byte 2's-comp
# format, we can store it more efficiently than the general
@@ -461,93 +648,95 @@ class _Pickler:
# First one- and two-byte unsigned ints:
if obj >= 0:
if obj <= 0xff:
- self.write(BININT1 + bytes([obj]))
+ self.write(BININT1 + pack("<B", obj))
return
if obj <= 0xffff:
- self.write(BININT2 + bytes([obj&0xff, obj>>8]))
+ self.write(BININT2 + pack("<H", obj))
return
# Next check for 4-byte signed ints:
- high_bits = obj >> 31 # note that Python shift sign-extends
- if high_bits == 0 or high_bits == -1:
- # All high bits are copies of bit 2**31, so the value
- # fits in a 4-byte signed int.
+ if -0x80000000 <= obj <= 0x7fffffff:
self.write(BININT + pack("<i", obj))
return
if self.proto >= 2:
encoded = encode_long(obj)
n = len(encoded)
if n < 256:
- self.write(LONG1 + bytes([n]) + encoded)
+ self.write(LONG1 + pack("<B", n) + encoded)
else:
self.write(LONG4 + pack("<i", n) + encoded)
return
self.write(LONG + repr(obj).encode("ascii") + b'L\n')
dispatch[int] = save_long
- def save_float(self, obj, pack=struct.pack):
+ def save_float(self, obj):
if self.bin:
self.write(BINFLOAT + pack('>d', obj))
else:
self.write(FLOAT + repr(obj).encode("ascii") + b'\n')
dispatch[float] = save_float
- def save_bytes(self, obj, pack=struct.pack):
+ def save_bytes(self, obj):
if self.proto < 3:
- if len(obj) == 0:
+ if not obj: # bytes object is empty
self.save_reduce(bytes, (), obj=obj)
else:
self.save_reduce(codecs.encode,
(str(obj, 'latin1'), 'latin1'), obj=obj)
return
n = len(obj)
- if n < 256:
- self.write(SHORT_BINBYTES + bytes([n]) + bytes(obj))
+ if n <= 0xff:
+ self.write(SHORT_BINBYTES + pack("<B", n) + obj)
+ elif n > 0xffffffff and self.proto >= 4:
+ self.write(BINBYTES8 + pack("<Q", n) + obj)
else:
- self.write(BINBYTES + pack("<I", n) + bytes(obj))
+ self.write(BINBYTES + pack("<I", n) + obj)
self.memoize(obj)
dispatch[bytes] = save_bytes
- def save_str(self, obj, pack=struct.pack):
+ def save_str(self, obj):
if self.bin:
encoded = obj.encode('utf-8', 'surrogatepass')
n = len(encoded)
- self.write(BINUNICODE + pack("<I", n) + encoded)
+ if n <= 0xff and self.proto >= 4:
+ self.write(SHORT_BINUNICODE + pack("<B", n) + encoded)
+ elif n > 0xffffffff and self.proto >= 4:
+ self.write(BINUNICODE8 + pack("<Q", n) + encoded)
+ else:
+ self.write(BINUNICODE + pack("<I", n) + encoded)
else:
obj = obj.replace("\\", "\\u005c")
obj = obj.replace("\n", "\\u000a")
- self.write(UNICODE + bytes(obj.encode('raw-unicode-escape')) +
+ self.write(UNICODE + obj.encode('raw-unicode-escape') +
b'\n')
self.memoize(obj)
dispatch[str] = save_str
def save_tuple(self, obj):
- write = self.write
- proto = self.proto
-
- n = len(obj)
- if n == 0:
- if proto:
- write(EMPTY_TUPLE)
+ if not obj: # tuple is empty
+ if self.bin:
+ self.write(EMPTY_TUPLE)
else:
- write(MARK + TUPLE)
+ self.write(MARK + TUPLE)
return
+ n = len(obj)
save = self.save
memo = self.memo
- if n <= 3 and proto >= 2:
+ if n <= 3 and self.proto >= 2:
for element in obj:
save(element)
# Subtle. Same as in the big comment below.
if id(obj) in memo:
get = self.get(memo[id(obj)][0])
- write(POP * n + get)
+ self.write(POP * n + get)
else:
- write(_tuplesize2code[n])
+ self.write(_tuplesize2code[n])
self.memoize(obj)
return
# proto 0 or proto 1 and tuple isn't empty, or proto > 1 and tuple
# has more than 3 elements.
+ write = self.write
write(MARK)
for element in obj:
save(element)
@@ -561,25 +750,23 @@ class _Pickler:
# could have been done in the "for element" loop instead, but
# recursive tuples are a rare thing.
get = self.get(memo[id(obj)][0])
- if proto:
+ if self.bin:
write(POP_MARK + get)
else: # proto 0 -- POP_MARK not available
write(POP * (n+1) + get)
return
# No recursion.
- self.write(TUPLE)
+ write(TUPLE)
self.memoize(obj)
dispatch[tuple] = save_tuple
def save_list(self, obj):
- write = self.write
-
if self.bin:
- write(EMPTY_LIST)
+ self.write(EMPTY_LIST)
else: # proto 0 -- can't use EMPTY_LIST
- write(MARK + LIST)
+ self.write(MARK + LIST)
self.memoize(obj)
self._batch_appends(obj)
@@ -599,17 +786,9 @@ class _Pickler:
write(APPEND)
return
- items = iter(items)
- r = range(self._BATCHSIZE)
- while items is not None:
- tmp = []
- for i in r:
- try:
- x = next(items)
- tmp.append(x)
- except StopIteration:
- items = None
- break
+ it = iter(items)
+ while True:
+ tmp = list(islice(it, self._BATCHSIZE))
n = len(tmp)
if n > 1:
write(MARK)
@@ -620,14 +799,14 @@ class _Pickler:
save(tmp[0])
write(APPEND)
# else tmp is empty, and we're done
+ if n < self._BATCHSIZE:
+ return
def save_dict(self, obj):
- write = self.write
-
if self.bin:
- write(EMPTY_DICT)
+ self.write(EMPTY_DICT)
else: # proto 0 -- can't use EMPTY_DICT
- write(MARK + DICT)
+ self.write(MARK + DICT)
self.memoize(obj)
self._batch_setitems(obj.items())
@@ -648,16 +827,9 @@ class _Pickler:
write(SETITEM)
return
- items = iter(items)
- r = range(self._BATCHSIZE)
- while items is not None:
- tmp = []
- for i in r:
- try:
- tmp.append(next(items))
- except StopIteration:
- items = None
- break
+ it = iter(items)
+ while True:
+ tmp = list(islice(it, self._BATCHSIZE))
n = len(tmp)
if n > 1:
write(MARK)
@@ -671,55 +843,109 @@ class _Pickler:
save(v)
write(SETITEM)
# else tmp is empty, and we're done
+ if n < self._BATCHSIZE:
+ return
+
+ def save_set(self, obj):
+ save = self.save
+ write = self.write
+
+ if self.proto < 4:
+ self.save_reduce(set, (list(obj),), obj=obj)
+ return
+
+ write(EMPTY_SET)
+ self.memoize(obj)
- def save_global(self, obj, name=None, pack=struct.pack):
+ it = iter(obj)
+ while True:
+ batch = list(islice(it, self._BATCHSIZE))
+ n = len(batch)
+ if n > 0:
+ write(MARK)
+ for item in batch:
+ save(item)
+ write(ADDITEMS)
+ if n < self._BATCHSIZE:
+ return
+ dispatch[set] = save_set
+
+ def save_frozenset(self, obj):
+ save = self.save
+ write = self.write
+
+ if self.proto < 4:
+ self.save_reduce(frozenset, (list(obj),), obj=obj)
+ return
+
+ write(MARK)
+ for item in obj:
+ save(item)
+
+ if id(obj) in self.memo:
+ # If the object is already in the memo, this means it is
+ # recursive. In this case, throw away everything we put on the
+ # stack, and fetch the object back from the memo.
+ write(POP_MARK + self.get(self.memo[id(obj)][0]))
+ return
+
+ write(FROZENSET)
+ self.memoize(obj)
+ dispatch[frozenset] = save_frozenset
+
+ def save_global(self, obj, name=None):
write = self.write
memo = self.memo
+ if name is None and self.proto >= 4:
+ name = getattr(obj, '__qualname__', None)
if name is None:
name = obj.__name__
- module = getattr(obj, "__module__", None)
- if module is None:
- module = whichmodule(obj, name)
-
+ module_name = whichmodule(obj, name, allow_qualname=self.proto >= 4)
try:
- __import__(module, level=0)
- mod = sys.modules[module]
- klass = getattr(mod, name)
+ __import__(module_name, level=0)
+ module = sys.modules[module_name]
+ obj2 = _getattribute(module, name, allow_qualname=self.proto >= 4)
except (ImportError, KeyError, AttributeError):
raise PicklingError(
"Can't pickle %r: it's not found as %s.%s" %
- (obj, module, name))
+ (obj, module_name, name))
else:
- if klass is not obj:
+ if obj2 is not obj:
raise PicklingError(
"Can't pickle %r: it's not the same object as %s.%s" %
- (obj, module, name))
+ (obj, module_name, name))
if self.proto >= 2:
- code = _extension_registry.get((module, name))
+ code = _extension_registry.get((module_name, name))
if code:
assert code > 0
if code <= 0xff:
- write(EXT1 + bytes([code]))
+ write(EXT1 + pack("<B", code))
elif code <= 0xffff:
- write(EXT2 + bytes([code&0xff, code>>8]))
+ write(EXT2 + pack("<H", code))
else:
write(EXT4 + pack("<i", code))
return
# Non-ASCII identifiers are supported only with protocols >= 3.
- if self.proto >= 3:
- write(GLOBAL + bytes(module, "utf-8") + b'\n' +
+ if self.proto >= 4:
+ self.save(module_name)
+ self.save(name)
+ write(STACK_GLOBAL)
+ elif self.proto >= 3:
+ write(GLOBAL + bytes(module_name, "utf-8") + b'\n' +
bytes(name, "utf-8") + b'\n')
else:
if self.fix_imports:
- if (module, name) in _compat_pickle.REVERSE_NAME_MAPPING:
- module, name = _compat_pickle.REVERSE_NAME_MAPPING[(module, name)]
- if module in _compat_pickle.REVERSE_IMPORT_MAPPING:
- module = _compat_pickle.REVERSE_IMPORT_MAPPING[module]
+ r_name_mapping = _compat_pickle.REVERSE_NAME_MAPPING
+ r_import_mapping = _compat_pickle.REVERSE_IMPORT_MAPPING
+ if (module_name, name) in r_name_mapping:
+ module_name, name = r_name_mapping[(module_name, name)]
+ if module_name in r_import_mapping:
+ module_name = r_import_mapping[module_name]
try:
- write(GLOBAL + bytes(module, "ascii") + b'\n' +
+ write(GLOBAL + bytes(module_name, "ascii") + b'\n' +
bytes(name, "ascii") + b'\n')
except UnicodeEncodeError:
raise PicklingError(
@@ -738,58 +964,8 @@ class _Pickler:
return self.save_global(obj)
dispatch[FunctionType] = save_global
- dispatch[BuiltinFunctionType] = save_global
dispatch[type] = save_type
-# Pickling helpers
-
-def _keep_alive(x, memo):
- """Keeps a reference to the object x in the memo.
-
- Because we remember objects by their id, we have
- to assure that possibly temporary objects are kept
- alive by referencing them.
- We store a reference at the id of the memo, which should
- normally not be used unless someone tries to deepcopy
- the memo itself...
- """
- try:
- memo[id(memo)].append(x)
- except KeyError:
- # aha, this is the first one :-)
- memo[id(memo)]=[x]
-
-
-# A cache for whichmodule(), mapping a function object to the name of
-# the module in which the function was found.
-
-classmap = {} # called classmap for backwards compatibility
-
-def whichmodule(func, funcname):
- """Figure out the module in which a function occurs.
-
- Search sys.modules for the module.
- Cache in classmap.
- Return a module name.
- If the function cannot be found, return "__main__".
- """
- # Python functions should always get an __module__ from their globals.
- mod = getattr(func, "__module__", None)
- if mod is not None:
- return mod
- if func in classmap:
- return classmap[func]
-
- for name, module in list(sys.modules.items()):
- if module is None:
- continue # skip dummy package entries
- if name != '__main__' and getattr(module, funcname, None) is func:
- break
- else:
- name = '__main__'
- classmap[func] = name
- return name
-
# Unpickling machinery
@@ -817,8 +993,8 @@ class _Unpickler:
instances pickled by Python 2.x; these default to 'ASCII' and
'strict', respectively.
"""
- self.readline = file.readline
- self.read = file.read
+ self._file_readline = file.readline
+ self._file_read = file.read
self.memo = {}
self.encoding = encoding
self.errors = errors
@@ -832,16 +1008,20 @@ class _Unpickler:
"""
# Check whether Unpickler was initialized correctly. This is
# only needed to mimic the behavior of _pickle.Unpickler.dump().
- if not hasattr(self, "read"):
+ if not hasattr(self, "_file_read"):
raise UnpicklingError("Unpickler.__init__() was not called by "
"%s.__init__()" % (self.__class__.__name__,))
+ self._unframer = _Unframer(self._file_read, self._file_readline)
+ self.read = self._unframer.read
+ self.readline = self._unframer.readline
self.mark = object() # any new unique object
self.stack = []
self.append = self.stack.append
+ self.proto = 0
read = self.read
dispatch = self.dispatch
try:
- while 1:
+ while True:
key = read(1)
if not key:
raise EOFError
@@ -871,12 +1051,19 @@ class _Unpickler:
dispatch = {}
def load_proto(self):
- proto = ord(self.read(1))
+ proto = self.read(1)[0]
if not 0 <= proto <= HIGHEST_PROTOCOL:
raise ValueError("unsupported pickle protocol: %d" % proto)
self.proto = proto
dispatch[PROTO[0]] = load_proto
+ def load_frame(self):
+ frame_size, = unpack('<Q', self.read(8))
+ if frame_size > sys.maxsize:
+ raise ValueError("frame size > sys.maxsize: %d" % frame_size)
+ self._unframer.load_frame(frame_size)
+ dispatch[FRAME[0]] = load_frame
+
def load_persid(self):
pid = self.readline()[:-1].decode("ascii")
self.append(self.persistent_load(pid))
@@ -906,43 +1093,40 @@ class _Unpickler:
elif data == TRUE[1:]:
val = True
else:
- try:
- val = int(data, 0)
- except ValueError:
- val = int(data, 0)
+ val = int(data, 0)
self.append(val)
dispatch[INT[0]] = load_int
def load_binint(self):
- self.append(mloads(b'i' + self.read(4)))
+ self.append(unpack('<i', self.read(4))[0])
dispatch[BININT[0]] = load_binint
def load_binint1(self):
- self.append(ord(self.read(1)))
+ self.append(self.read(1)[0])
dispatch[BININT1[0]] = load_binint1
def load_binint2(self):
- self.append(mloads(b'i' + self.read(2) + b'\000\000'))
+ self.append(unpack('<H', self.read(2))[0])
dispatch[BININT2[0]] = load_binint2
def load_long(self):
- val = self.readline()[:-1].decode("ascii")
- if val and val[-1] == 'L':
+ val = self.readline()[:-1]
+ if val and val[-1] == b'L'[0]:
val = val[:-1]
self.append(int(val, 0))
dispatch[LONG[0]] = load_long
def load_long1(self):
- n = ord(self.read(1))
+ n = self.read(1)[0]
data = self.read(n)
self.append(decode_long(data))
dispatch[LONG1[0]] = load_long1
def load_long4(self):
- n = mloads(b'i' + self.read(4))
+ n, = unpack('<i', self.read(4))
if n < 0:
# Corrupt or hostile pickle -- we never write one like this
- raise UnpicklingError("LONG pickle has negative byte count");
+ raise UnpicklingError("LONG pickle has negative byte count")
data = self.read(n)
self.append(decode_long(data))
dispatch[LONG4[0]] = load_long4
@@ -951,39 +1135,36 @@ class _Unpickler:
self.append(float(self.readline()[:-1]))
dispatch[FLOAT[0]] = load_float
- def load_binfloat(self, unpack=struct.unpack):
+ def load_binfloat(self):
self.append(unpack('>d', self.read(8))[0])
dispatch[BINFLOAT[0]] = load_binfloat
def load_string(self):
- orig = self.readline()
- rep = orig[:-1]
- for q in (b'"', b"'"): # double or single quote
- if rep.startswith(q):
- if len(rep) < 2 or not rep.endswith(q):
- raise ValueError("insecure string pickle")
- rep = rep[len(q):-len(q)]
- break
+ data = self.readline()[:-1]
+ # Strip outermost quotes
+ if len(data) >= 2 and data[0] == data[-1] and data[0] in b'"\'':
+ data = data[1:-1]
else:
- raise ValueError("insecure string pickle: %r" % orig)
- self.append(codecs.escape_decode(rep)[0]
+ raise UnpicklingError("the STRING opcode argument must be quoted")
+ self.append(codecs.escape_decode(data)[0]
.decode(self.encoding, self.errors))
dispatch[STRING[0]] = load_string
def load_binstring(self):
# Deprecated BINSTRING uses signed 32-bit length
- len = mloads(b'i' + self.read(4))
+ len, = unpack('<i', self.read(4))
if len < 0:
- raise UnpicklingError("BINSTRING pickle has negative byte count");
+ raise UnpicklingError("BINSTRING pickle has negative byte count")
data = self.read(len)
value = str(data, self.encoding, self.errors)
self.append(value)
dispatch[BINSTRING[0]] = load_binstring
- def load_binbytes(self, unpack=struct.unpack, maxsize=sys.maxsize):
+ def load_binbytes(self):
len, = unpack('<I', self.read(4))
if len > maxsize:
- raise UnpicklingError("BINBYTES exceeds system's maximum size of %d bytes" % maxsize);
+ raise UnpicklingError("BINBYTES exceeds system's maximum size "
+ "of %d bytes" % maxsize)
self.append(self.read(len))
dispatch[BINBYTES[0]] = load_binbytes
@@ -991,25 +1172,39 @@ class _Unpickler:
self.append(str(self.readline()[:-1], 'raw-unicode-escape'))
dispatch[UNICODE[0]] = load_unicode
- def load_binunicode(self, unpack=struct.unpack, maxsize=sys.maxsize):
+ def load_binunicode(self):
len, = unpack('<I', self.read(4))
if len > maxsize:
- raise UnpicklingError("BINUNICODE exceeds system's maximum size of %d bytes" % maxsize);
+ raise UnpicklingError("BINUNICODE exceeds system's maximum size "
+ "of %d bytes" % maxsize)
self.append(str(self.read(len), 'utf-8', 'surrogatepass'))
dispatch[BINUNICODE[0]] = load_binunicode
+ def load_binunicode8(self):
+ len, = unpack('<Q', self.read(8))
+ if len > maxsize:
+ raise UnpicklingError("BINUNICODE8 exceeds system's maximum size "
+ "of %d bytes" % maxsize)
+ self.append(str(self.read(len), 'utf-8', 'surrogatepass'))
+ dispatch[BINUNICODE8[0]] = load_binunicode8
+
def load_short_binstring(self):
- len = ord(self.read(1))
- data = bytes(self.read(len))
+ len = self.read(1)[0]
+ data = self.read(len)
value = str(data, self.encoding, self.errors)
self.append(value)
dispatch[SHORT_BINSTRING[0]] = load_short_binstring
def load_short_binbytes(self):
- len = ord(self.read(1))
- self.append(bytes(self.read(len)))
+ len = self.read(1)[0]
+ self.append(self.read(len))
dispatch[SHORT_BINBYTES[0]] = load_short_binbytes
+ def load_short_binunicode(self):
+ len = self.read(1)[0]
+ self.append(str(self.read(len), 'utf-8', 'surrogatepass'))
+ dispatch[SHORT_BINUNICODE[0]] = load_short_binunicode
+
def load_tuple(self):
k = self.marker()
self.stack[k:] = [tuple(self.stack[k+1:])]
@@ -1039,6 +1234,15 @@ class _Unpickler:
self.append({})
dispatch[EMPTY_DICT[0]] = load_empty_dictionary
+ def load_empty_set(self):
+ self.append(set())
+ dispatch[EMPTY_SET[0]] = load_empty_set
+
+ def load_frozenset(self):
+ k = self.marker()
+ self.stack[k:] = [frozenset(self.stack[k+1:])]
+ dispatch[FROZENSET[0]] = load_frozenset
+
def load_list(self):
k = self.marker()
self.stack[k:] = [self.stack[k+1:]]
@@ -1046,12 +1250,9 @@ class _Unpickler:
def load_dict(self):
k = self.marker()
- d = {}
items = self.stack[k+1:]
- for i in range(0, len(items), 2):
- key = items[i]
- value = items[i+1]
- d[key] = value
+ d = {items[i]: items[i+1]
+ for i in range(0, len(items), 2)}
self.stack[k:] = [d]
dispatch[DICT[0]] = load_dict
@@ -1090,11 +1291,19 @@ class _Unpickler:
def load_newobj(self):
args = self.stack.pop()
- cls = self.stack[-1]
+ cls = self.stack.pop()
obj = cls.__new__(cls, *args)
- self.stack[-1] = obj
+ self.append(obj)
dispatch[NEWOBJ[0]] = load_newobj
+ def load_newobj_ex(self):
+ kwargs = self.stack.pop()
+ args = self.stack.pop()
+ cls = self.stack.pop()
+ obj = cls.__new__(cls, *args, **kwargs)
+ self.append(obj)
+ dispatch[NEWOBJ_EX[0]] = load_newobj_ex
+
def load_global(self):
module = self.readline()[:-1].decode("utf-8")
name = self.readline()[:-1].decode("utf-8")
@@ -1102,18 +1311,26 @@ class _Unpickler:
self.append(klass)
dispatch[GLOBAL[0]] = load_global
+ def load_stack_global(self):
+ name = self.stack.pop()
+ module = self.stack.pop()
+ if type(name) is not str or type(module) is not str:
+ raise UnpicklingError("STACK_GLOBAL requires str")
+ self.append(self.find_class(module, name))
+ dispatch[STACK_GLOBAL[0]] = load_stack_global
+
def load_ext1(self):
- code = ord(self.read(1))
+ code = self.read(1)[0]
self.get_extension(code)
dispatch[EXT1[0]] = load_ext1
def load_ext2(self):
- code = mloads(b'i' + self.read(2) + b'\000\000')
+ code, = unpack('<H', self.read(2))
self.get_extension(code)
dispatch[EXT2[0]] = load_ext2
def load_ext4(self):
- code = mloads(b'i' + self.read(4))
+ code, = unpack('<i', self.read(4))
self.get_extension(code)
dispatch[EXT4[0]] = load_ext4
@@ -1127,7 +1344,7 @@ class _Unpickler:
if not key:
if code <= 0: # note that 0 is forbidden
# Corrupt or hostile pickle.
- raise UnpicklingError("EXT specifies code <= 0");
+ raise UnpicklingError("EXT specifies code <= 0")
raise ValueError("unregistered extension code %d" % code)
obj = self.find_class(*key)
_extension_cache[code] = obj
@@ -1141,9 +1358,8 @@ class _Unpickler:
if module in _compat_pickle.IMPORT_MAPPING:
module = _compat_pickle.IMPORT_MAPPING[module]
__import__(module, level=0)
- mod = sys.modules[module]
- klass = getattr(mod, name)
- return klass
+ return _getattribute(sys.modules[module], name,
+ allow_qualname=self.proto >= 4)
def load_reduce(self):
stack = self.stack
@@ -1181,7 +1397,7 @@ class _Unpickler:
self.append(self.memo[i])
dispatch[BINGET[0]] = load_binget
- def load_long_binget(self, unpack=struct.unpack):
+ def load_long_binget(self):
i, = unpack('<I', self.read(4))
self.append(self.memo[i])
dispatch[LONG_BINGET[0]] = load_long_binget
@@ -1200,13 +1416,18 @@ class _Unpickler:
self.memo[i] = self.stack[-1]
dispatch[BINPUT[0]] = load_binput
- def load_long_binput(self, unpack=struct.unpack, maxsize=sys.maxsize):
+ def load_long_binput(self):
i, = unpack('<I', self.read(4))
if i > maxsize:
raise ValueError("negative LONG_BINPUT argument")
self.memo[i] = self.stack[-1]
dispatch[LONG_BINPUT[0]] = load_long_binput
+ def load_memoize(self):
+ memo = self.memo
+ memo[len(memo)] = self.stack[-1]
+ dispatch[MEMOIZE[0]] = load_memoize
+
def load_append(self):
stack = self.stack
value = stack.pop()
@@ -1246,12 +1467,26 @@ class _Unpickler:
del stack[mark:]
dispatch[SETITEMS[0]] = load_setitems
+ def load_additems(self):
+ stack = self.stack
+ mark = self.marker()
+ set_obj = stack[mark - 1]
+ items = stack[mark + 1:]
+ if isinstance(set_obj, set):
+ set_obj.update(items)
+ else:
+ add = set_obj.add
+ for item in items:
+ add(item)
+ del stack[mark:]
+ dispatch[ADDITEMS[0]] = load_additems
+
def load_build(self):
stack = self.stack
state = stack.pop()
inst = stack[-1]
setstate = getattr(inst, "__setstate__", None)
- if setstate:
+ if setstate is not None:
setstate(state)
return
slotstate = None
@@ -1279,86 +1514,46 @@ class _Unpickler:
raise _Stop(value)
dispatch[STOP[0]] = load_stop
-# Encode/decode ints.
-
-def encode_long(x):
- r"""Encode a long to a two's complement little-endian binary string.
- Note that 0 is a special case, returning an empty string, to save a
- byte in the LONG1 pickling context.
-
- >>> encode_long(0)
- b''
- >>> encode_long(255)
- b'\xff\x00'
- >>> encode_long(32767)
- b'\xff\x7f'
- >>> encode_long(-256)
- b'\x00\xff'
- >>> encode_long(-32768)
- b'\x00\x80'
- >>> encode_long(-128)
- b'\x80'
- >>> encode_long(127)
- b'\x7f'
- >>>
- """
- if x == 0:
- return b''
- nbytes = (x.bit_length() >> 3) + 1
- result = x.to_bytes(nbytes, byteorder='little', signed=True)
- if x < 0 and nbytes > 1:
- if result[-1] == 0xff and (result[-2] & 0x80) != 0:
- result = result[:-1]
- return result
-
-def decode_long(data):
- r"""Decode an int from a two's complement little-endian binary string.
-
- >>> decode_long(b'')
- 0
- >>> decode_long(b"\xff\x00")
- 255
- >>> decode_long(b"\xff\x7f")
- 32767
- >>> decode_long(b"\x00\xff")
- -256
- >>> decode_long(b"\x00\x80")
- -32768
- >>> decode_long(b"\x80")
- -128
- >>> decode_long(b"\x7f")
- 127
- """
- return int.from_bytes(data, byteorder='little', signed=True)
# Shorthands
-def dump(obj, file, protocol=None, *, fix_imports=True):
- Pickler(file, protocol, fix_imports=fix_imports).dump(obj)
+def _dump(obj, file, protocol=None, *, fix_imports=True):
+ _Pickler(file, protocol, fix_imports=fix_imports).dump(obj)
-def dumps(obj, protocol=None, *, fix_imports=True):
+def _dumps(obj, protocol=None, *, fix_imports=True):
f = io.BytesIO()
- Pickler(f, protocol, fix_imports=fix_imports).dump(obj)
+ _Pickler(f, protocol, fix_imports=fix_imports).dump(obj)
res = f.getvalue()
assert isinstance(res, bytes_types)
return res
-def load(file, *, fix_imports=True, encoding="ASCII", errors="strict"):
- return Unpickler(file, fix_imports=fix_imports,
+def _load(file, *, fix_imports=True, encoding="ASCII", errors="strict"):
+ return _Unpickler(file, fix_imports=fix_imports,
encoding=encoding, errors=errors).load()
-def loads(s, *, fix_imports=True, encoding="ASCII", errors="strict"):
+def _loads(s, *, fix_imports=True, encoding="ASCII", errors="strict"):
if isinstance(s, str):
raise TypeError("Can't load pickle from unicode string")
file = io.BytesIO(s)
- return Unpickler(file, fix_imports=fix_imports,
- encoding=encoding, errors=errors).load()
+ return _Unpickler(file, fix_imports=fix_imports,
+ encoding=encoding, errors=errors).load()
# Use the faster _pickle if possible
try:
- from _pickle import *
+ from _pickle import (
+ PickleError,
+ PicklingError,
+ UnpicklingError,
+ Pickler,
+ Unpickler,
+ dump,
+ dumps,
+ load,
+ loads
+ )
except ImportError:
Pickler, Unpickler = _Pickler, _Unpickler
+ dump, dumps, load, loads = _dump, _dumps, _load, _loads
# Doctest
def _test():
diff --git a/Lib/pickletools.py b/Lib/pickletools.py
index 612fa8f27e..a2480f6510 100644
--- a/Lib/pickletools.py
+++ b/Lib/pickletools.py
@@ -11,6 +11,7 @@ dis(pickle, out=None, memo=None, indentlevel=4)
'''
import codecs
+import io
import pickle
import re
import sys
@@ -34,119 +35,118 @@ bytes_types = pickle.bytes_types
# by a later GET.
-"""
-"A pickle" is a program for a virtual pickle machine (PM, but more accurately
-called an unpickling machine). It's a sequence of opcodes, interpreted by the
-PM, building an arbitrarily complex Python object.
-
-For the most part, the PM is very simple: there are no looping, testing, or
-conditional instructions, no arithmetic and no function calls. Opcodes are
-executed once each, from first to last, until a STOP opcode is reached.
-
-The PM has two data areas, "the stack" and "the memo".
-
-Many opcodes push Python objects onto the stack; e.g., INT pushes a Python
-integer object on the stack, whose value is gotten from a decimal string
-literal immediately following the INT opcode in the pickle bytestream. Other
-opcodes take Python objects off the stack. The result of unpickling is
-whatever object is left on the stack when the final STOP opcode is executed.
-
-The memo is simply an array of objects, or it can be implemented as a dict
-mapping little integers to objects. The memo serves as the PM's "long term
-memory", and the little integers indexing the memo are akin to variable
-names. Some opcodes pop a stack object into the memo at a given index,
-and others push a memo object at a given index onto the stack again.
-
-At heart, that's all the PM has. Subtleties arise for these reasons:
-
-+ Object identity. Objects can be arbitrarily complex, and subobjects
- may be shared (for example, the list [a, a] refers to the same object a
- twice). It can be vital that unpickling recreate an isomorphic object
- graph, faithfully reproducing sharing.
-
-+ Recursive objects. For example, after "L = []; L.append(L)", L is a
- list, and L[0] is the same list. This is related to the object identity
- point, and some sequences of pickle opcodes are subtle in order to
- get the right result in all cases.
-
-+ Things pickle doesn't know everything about. Examples of things pickle
- does know everything about are Python's builtin scalar and container
- types, like ints and tuples. They generally have opcodes dedicated to
- them. For things like module references and instances of user-defined
- classes, pickle's knowledge is limited. Historically, many enhancements
- have been made to the pickle protocol in order to do a better (faster,
- and/or more compact) job on those.
-
-+ Backward compatibility and micro-optimization. As explained below,
- pickle opcodes never go away, not even when better ways to do a thing
- get invented. The repertoire of the PM just keeps growing over time.
- For example, protocol 0 had two opcodes for building Python integers (INT
- and LONG), protocol 1 added three more for more-efficient pickling of short
- integers, and protocol 2 added two more for more-efficient pickling of
- long integers (before protocol 2, the only ways to pickle a Python long
- took time quadratic in the number of digits, for both pickling and
- unpickling). "Opcode bloat" isn't so much a subtlety as a source of
- wearying complication.
-
-
-Pickle protocols:
-
-For compatibility, the meaning of a pickle opcode never changes. Instead new
-pickle opcodes get added, and each version's unpickler can handle all the
-pickle opcodes in all protocol versions to date. So old pickles continue to
-be readable forever. The pickler can generally be told to restrict itself to
-the subset of opcodes available under previous protocol versions too, so that
-users can create pickles under the current version readable by older
-versions. However, a pickle does not contain its version number embedded
-within it. If an older unpickler tries to read a pickle using a later
-protocol, the result is most likely an exception due to seeing an unknown (in
-the older unpickler) opcode.
-
-The original pickle used what's now called "protocol 0", and what was called
-"text mode" before Python 2.3. The entire pickle bytestream is made up of
-printable 7-bit ASCII characters, plus the newline character, in protocol 0.
-That's why it was called text mode. Protocol 0 is small and elegant, but
-sometimes painfully inefficient.
-
-The second major set of additions is now called "protocol 1", and was called
-"binary mode" before Python 2.3. This added many opcodes with arguments
-consisting of arbitrary bytes, including NUL bytes and unprintable "high bit"
-bytes. Binary mode pickles can be substantially smaller than equivalent
-text mode pickles, and sometimes faster too; e.g., BININT represents a 4-byte
-int as 4 bytes following the opcode, which is cheaper to unpickle than the
-(perhaps) 11-character decimal string attached to INT. Protocol 1 also added
-a number of opcodes that operate on many stack elements at once (like APPENDS
-and SETITEMS), and "shortcut" opcodes (like EMPTY_DICT and EMPTY_TUPLE).
-
-The third major set of additions came in Python 2.3, and is called "protocol
-2". This added:
-
-- A better way to pickle instances of new-style classes (NEWOBJ).
-
-- A way for a pickle to identify its protocol (PROTO).
-
-- Time- and space- efficient pickling of long ints (LONG{1,4}).
-
-- Shortcuts for small tuples (TUPLE{1,2,3}}.
-
-- Dedicated opcodes for bools (NEWTRUE, NEWFALSE).
-
-- The "extension registry", a vector of popular objects that can be pushed
- efficiently by index (EXT{1,2,4}). This is akin to the memo and GET, but
- the registry contents are predefined (there's nothing akin to the memo's
- PUT).
-
-Another independent change with Python 2.3 is the abandonment of any
-pretense that it might be safe to load pickles received from untrusted
-parties -- no sufficient security analysis has been done to guarantee
-this and there isn't a use case that warrants the expense of such an
-analysis.
-
-To this end, all tests for __safe_for_unpickling__ or for
-copyreg.safe_constructors are removed from the unpickling code.
-References to these variables in the descriptions below are to be seen
-as describing unpickling in Python 2.2 and before.
-"""
+# "A pickle" is a program for a virtual pickle machine (PM, but more accurately
+# called an unpickling machine). It's a sequence of opcodes, interpreted by the
+# PM, building an arbitrarily complex Python object.
+#
+# For the most part, the PM is very simple: there are no looping, testing, or
+# conditional instructions, no arithmetic and no function calls. Opcodes are
+# executed once each, from first to last, until a STOP opcode is reached.
+#
+# The PM has two data areas, "the stack" and "the memo".
+#
+# Many opcodes push Python objects onto the stack; e.g., INT pushes a Python
+# integer object on the stack, whose value is gotten from a decimal string
+# literal immediately following the INT opcode in the pickle bytestream. Other
+# opcodes take Python objects off the stack. The result of unpickling is
+# whatever object is left on the stack when the final STOP opcode is executed.
+#
+# The memo is simply an array of objects, or it can be implemented as a dict
+# mapping little integers to objects. The memo serves as the PM's "long term
+# memory", and the little integers indexing the memo are akin to variable
+# names. Some opcodes pop a stack object into the memo at a given index,
+# and others push a memo object at a given index onto the stack again.
+#
+# At heart, that's all the PM has. Subtleties arise for these reasons:
+#
+# + Object identity. Objects can be arbitrarily complex, and subobjects
+# may be shared (for example, the list [a, a] refers to the same object a
+# twice). It can be vital that unpickling recreate an isomorphic object
+# graph, faithfully reproducing sharing.
+#
+# + Recursive objects. For example, after "L = []; L.append(L)", L is a
+# list, and L[0] is the same list. This is related to the object identity
+# point, and some sequences of pickle opcodes are subtle in order to
+# get the right result in all cases.
+#
+# + Things pickle doesn't know everything about. Examples of things pickle
+# does know everything about are Python's builtin scalar and container
+# types, like ints and tuples. They generally have opcodes dedicated to
+# them. For things like module references and instances of user-defined
+# classes, pickle's knowledge is limited. Historically, many enhancements
+# have been made to the pickle protocol in order to do a better (faster,
+# and/or more compact) job on those.
+#
+# + Backward compatibility and micro-optimization. As explained below,
+# pickle opcodes never go away, not even when better ways to do a thing
+# get invented. The repertoire of the PM just keeps growing over time.
+# For example, protocol 0 had two opcodes for building Python integers (INT
+# and LONG), protocol 1 added three more for more-efficient pickling of short
+# integers, and protocol 2 added two more for more-efficient pickling of
+# long integers (before protocol 2, the only ways to pickle a Python long
+# took time quadratic in the number of digits, for both pickling and
+# unpickling). "Opcode bloat" isn't so much a subtlety as a source of
+# wearying complication.
+#
+#
+# Pickle protocols:
+#
+# For compatibility, the meaning of a pickle opcode never changes. Instead new
+# pickle opcodes get added, and each version's unpickler can handle all the
+# pickle opcodes in all protocol versions to date. So old pickles continue to
+# be readable forever. The pickler can generally be told to restrict itself to
+# the subset of opcodes available under previous protocol versions too, so that
+# users can create pickles under the current version readable by older
+# versions. However, a pickle does not contain its version number embedded
+# within it. If an older unpickler tries to read a pickle using a later
+# protocol, the result is most likely an exception due to seeing an unknown (in
+# the older unpickler) opcode.
+#
+# The original pickle used what's now called "protocol 0", and what was called
+# "text mode" before Python 2.3. The entire pickle bytestream is made up of
+# printable 7-bit ASCII characters, plus the newline character, in protocol 0.
+# That's why it was called text mode. Protocol 0 is small and elegant, but
+# sometimes painfully inefficient.
+#
+# The second major set of additions is now called "protocol 1", and was called
+# "binary mode" before Python 2.3. This added many opcodes with arguments
+# consisting of arbitrary bytes, including NUL bytes and unprintable "high bit"
+# bytes. Binary mode pickles can be substantially smaller than equivalent
+# text mode pickles, and sometimes faster too; e.g., BININT represents a 4-byte
+# int as 4 bytes following the opcode, which is cheaper to unpickle than the
+# (perhaps) 11-character decimal string attached to INT. Protocol 1 also added
+# a number of opcodes that operate on many stack elements at once (like APPENDS
+# and SETITEMS), and "shortcut" opcodes (like EMPTY_DICT and EMPTY_TUPLE).
+#
+# The third major set of additions came in Python 2.3, and is called "protocol
+# 2". This added:
+#
+# - A better way to pickle instances of new-style classes (NEWOBJ).
+#
+# - A way for a pickle to identify its protocol (PROTO).
+#
+# - Time- and space- efficient pickling of long ints (LONG{1,4}).
+#
+# - Shortcuts for small tuples (TUPLE{1,2,3}}.
+#
+# - Dedicated opcodes for bools (NEWTRUE, NEWFALSE).
+#
+# - The "extension registry", a vector of popular objects that can be pushed
+# efficiently by index (EXT{1,2,4}). This is akin to the memo and GET, but
+# the registry contents are predefined (there's nothing akin to the memo's
+# PUT).
+#
+# Another independent change with Python 2.3 is the abandonment of any
+# pretense that it might be safe to load pickles received from untrusted
+# parties -- no sufficient security analysis has been done to guarantee
+# this and there isn't a use case that warrants the expense of such an
+# analysis.
+#
+# To this end, all tests for __safe_for_unpickling__ or for
+# copyreg.safe_constructors are removed from the unpickling code.
+# References to these variables in the descriptions below are to be seen
+# as describing unpickling in Python 2.2 and before.
+
# Meta-rule: Descriptions are stored in instances of descriptor objects,
# with plain constructors. No meta-language is defined from which
@@ -169,6 +169,7 @@ UP_TO_NEWLINE = -1
TAKEN_FROM_ARGUMENT1 = -2 # num bytes is 1-byte unsigned int
TAKEN_FROM_ARGUMENT4 = -3 # num bytes is 4-byte signed little-endian int
TAKEN_FROM_ARGUMENT4U = -4 # num bytes is 4-byte unsigned little-endian int
+TAKEN_FROM_ARGUMENT8U = -5 # num bytes is 8-byte unsigned little-endian int
class ArgumentDescriptor(object):
__slots__ = (
@@ -176,7 +177,7 @@ class ArgumentDescriptor(object):
'name',
# length of argument, in bytes; an int; UP_TO_NEWLINE and
- # TAKEN_FROM_ARGUMENT{1,4} are negative values for variable-length
+ # TAKEN_FROM_ARGUMENT{1,4,8} are negative values for variable-length
# cases
'n',
@@ -197,7 +198,8 @@ class ArgumentDescriptor(object):
n in (UP_TO_NEWLINE,
TAKEN_FROM_ARGUMENT1,
TAKEN_FROM_ARGUMENT4,
- TAKEN_FROM_ARGUMENT4U))
+ TAKEN_FROM_ARGUMENT4U,
+ TAKEN_FROM_ARGUMENT8U))
self.n = n
self.reader = reader
@@ -289,6 +291,27 @@ uint4 = ArgumentDescriptor(
doc="Four-byte unsigned integer, little-endian.")
+def read_uint8(f):
+ r"""
+ >>> import io
+ >>> read_uint8(io.BytesIO(b'\xff\x00\x00\x00\x00\x00\x00\x00'))
+ 255
+ >>> read_uint8(io.BytesIO(b'\xff' * 8)) == 2**64-1
+ True
+ """
+
+ data = f.read(8)
+ if len(data) == 8:
+ return _unpack("<Q", data)[0]
+ raise ValueError("not enough data in stream to read uint8")
+
+uint8 = ArgumentDescriptor(
+ name='uint8',
+ n=8,
+ reader=read_uint8,
+ doc="Eight-byte unsigned integer, little-endian.")
+
+
def read_stringnl(f, decode=True, stripquotes=True):
r"""
>>> import io
@@ -382,6 +405,36 @@ stringnl_noescape_pair = ArgumentDescriptor(
a single blank separating the two strings.
""")
+
+def read_string1(f):
+ r"""
+ >>> import io
+ >>> read_string1(io.BytesIO(b"\x00"))
+ ''
+ >>> read_string1(io.BytesIO(b"\x03abcdef"))
+ 'abc'
+ """
+
+ n = read_uint1(f)
+ assert n >= 0
+ data = f.read(n)
+ if len(data) == n:
+ return data.decode("latin-1")
+ raise ValueError("expected %d bytes in a string1, but only %d remain" %
+ (n, len(data)))
+
+string1 = ArgumentDescriptor(
+ name="string1",
+ n=TAKEN_FROM_ARGUMENT1,
+ reader=read_string1,
+ doc="""A counted string.
+
+ The first argument is a 1-byte unsigned int giving the number
+ of bytes in the string, and the second argument is that many
+ bytes.
+ """)
+
+
def read_string4(f):
r"""
>>> import io
@@ -416,28 +469,28 @@ string4 = ArgumentDescriptor(
""")
-def read_string1(f):
+def read_bytes1(f):
r"""
>>> import io
- >>> read_string1(io.BytesIO(b"\x00"))
- ''
- >>> read_string1(io.BytesIO(b"\x03abcdef"))
- 'abc'
+ >>> read_bytes1(io.BytesIO(b"\x00"))
+ b''
+ >>> read_bytes1(io.BytesIO(b"\x03abcdef"))
+ b'abc'
"""
n = read_uint1(f)
assert n >= 0
data = f.read(n)
if len(data) == n:
- return data.decode("latin-1")
- raise ValueError("expected %d bytes in a string1, but only %d remain" %
+ return data
+ raise ValueError("expected %d bytes in a bytes1, but only %d remain" %
(n, len(data)))
-string1 = ArgumentDescriptor(
- name="string1",
+bytes1 = ArgumentDescriptor(
+ name="bytes1",
n=TAKEN_FROM_ARGUMENT1,
- reader=read_string1,
- doc="""A counted string.
+ reader=read_bytes1,
+ doc="""A counted bytes string.
The first argument is a 1-byte unsigned int giving the number
of bytes in the string, and the second argument is that many
@@ -487,6 +540,7 @@ def read_bytes4(f):
"""
n = read_uint4(f)
+ assert n >= 0
if n > sys.maxsize:
raise ValueError("bytes4 byte count > sys.maxsize: %d" % n)
data = f.read(n)
@@ -506,6 +560,40 @@ bytes4 = ArgumentDescriptor(
""")
+def read_bytes8(f):
+ r"""
+ >>> import io, struct, sys
+ >>> read_bytes8(io.BytesIO(b"\x00\x00\x00\x00\x00\x00\x00\x00abc"))
+ b''
+ >>> read_bytes8(io.BytesIO(b"\x03\x00\x00\x00\x00\x00\x00\x00abcdef"))
+ b'abc'
+ >>> bigsize8 = struct.pack("<Q", sys.maxsize//3)
+ >>> read_bytes8(io.BytesIO(bigsize8 + b"abcdef")) #doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ ValueError: expected ... bytes in a bytes8, but only 6 remain
+ """
+
+ n = read_uint8(f)
+ assert n >= 0
+ if n > sys.maxsize:
+ raise ValueError("bytes8 byte count > sys.maxsize: %d" % n)
+ data = f.read(n)
+ if len(data) == n:
+ return data
+ raise ValueError("expected %d bytes in a bytes8, but only %d remain" %
+ (n, len(data)))
+
+bytes8 = ArgumentDescriptor(
+ name="bytes8",
+ n=TAKEN_FROM_ARGUMENT8U,
+ reader=read_bytes8,
+ doc="""A counted bytes string.
+
+ The first argument is a 8-byte little-endian unsigned int giving
+ the number of bytes, and the second argument is that many bytes.
+ """)
+
def read_unicodestringnl(f):
r"""
>>> import io
@@ -531,6 +619,46 @@ unicodestringnl = ArgumentDescriptor(
escape sequences.
""")
+
+def read_unicodestring1(f):
+ r"""
+ >>> import io
+ >>> s = 'abcd\uabcd'
+ >>> enc = s.encode('utf-8')
+ >>> enc
+ b'abcd\xea\xaf\x8d'
+ >>> n = bytes([len(enc)]) # little-endian 1-byte length
+ >>> t = read_unicodestring1(io.BytesIO(n + enc + b'junk'))
+ >>> s == t
+ True
+
+ >>> read_unicodestring1(io.BytesIO(n + enc[:-1]))
+ Traceback (most recent call last):
+ ...
+ ValueError: expected 7 bytes in a unicodestring1, but only 6 remain
+ """
+
+ n = read_uint1(f)
+ assert n >= 0
+ data = f.read(n)
+ if len(data) == n:
+ return str(data, 'utf-8', 'surrogatepass')
+ raise ValueError("expected %d bytes in a unicodestring1, but only %d "
+ "remain" % (n, len(data)))
+
+unicodestring1 = ArgumentDescriptor(
+ name="unicodestring1",
+ n=TAKEN_FROM_ARGUMENT1,
+ reader=read_unicodestring1,
+ doc="""A counted Unicode string.
+
+ The first argument is a 1-byte little-endian signed int
+ giving the number of bytes in the string, and the second
+ argument-- the UTF-8 encoding of the Unicode string --
+ contains that many bytes.
+ """)
+
+
def read_unicodestring4(f):
r"""
>>> import io
@@ -550,6 +678,7 @@ def read_unicodestring4(f):
"""
n = read_uint4(f)
+ assert n >= 0
if n > sys.maxsize:
raise ValueError("unicodestring4 byte count > sys.maxsize: %d" % n)
data = f.read(n)
@@ -571,6 +700,47 @@ unicodestring4 = ArgumentDescriptor(
""")
+def read_unicodestring8(f):
+ r"""
+ >>> import io
+ >>> s = 'abcd\uabcd'
+ >>> enc = s.encode('utf-8')
+ >>> enc
+ b'abcd\xea\xaf\x8d'
+ >>> n = bytes([len(enc)]) + bytes(7) # little-endian 8-byte length
+ >>> t = read_unicodestring8(io.BytesIO(n + enc + b'junk'))
+ >>> s == t
+ True
+
+ >>> read_unicodestring8(io.BytesIO(n + enc[:-1]))
+ Traceback (most recent call last):
+ ...
+ ValueError: expected 7 bytes in a unicodestring8, but only 6 remain
+ """
+
+ n = read_uint8(f)
+ assert n >= 0
+ if n > sys.maxsize:
+ raise ValueError("unicodestring8 byte count > sys.maxsize: %d" % n)
+ data = f.read(n)
+ if len(data) == n:
+ return str(data, 'utf-8', 'surrogatepass')
+ raise ValueError("expected %d bytes in a unicodestring8, but only %d "
+ "remain" % (n, len(data)))
+
+unicodestring8 = ArgumentDescriptor(
+ name="unicodestring8",
+ n=TAKEN_FROM_ARGUMENT8U,
+ reader=read_unicodestring8,
+ doc="""A counted Unicode string.
+
+ The first argument is a 8-byte little-endian signed int
+ giving the number of bytes in the string, and the second
+ argument-- the UTF-8 encoding of the Unicode string --
+ contains that many bytes.
+ """)
+
+
def read_decimalnl_short(f):
r"""
>>> import io
@@ -860,6 +1030,16 @@ pydict = StackObject(
obtype=dict,
doc="A Python dict object.")
+pyset = StackObject(
+ name="set",
+ obtype=set,
+ doc="A Python set object.")
+
+pyfrozenset = StackObject(
+ name="frozenset",
+ obtype=set,
+ doc="A Python frozenset object.")
+
anyobject = StackObject(
name='any',
obtype=object,
@@ -1143,6 +1323,19 @@ opcodes = [
literally as the string content.
"""),
+ I(name='BINBYTES8',
+ code='\x8e',
+ arg=bytes8,
+ stack_before=[],
+ stack_after=[pybytes],
+ proto=4,
+ doc="""Push a Python bytes object.
+
+ There are two arguments: the first is a 8-byte unsigned int giving
+ the number of bytes in the string, and the second is that many bytes,
+ which are taken literally as the string content.
+ """),
+
# Ways to spell None.
I(name='NONE',
@@ -1191,6 +1384,19 @@ opcodes = [
until the next newline character.
"""),
+ I(name='SHORT_BINUNICODE',
+ code='\x8c',
+ arg=unicodestring1,
+ stack_before=[],
+ stack_after=[pyunicode],
+ proto=4,
+ doc="""Push a Python Unicode string object.
+
+ There are two arguments: the first is a 1-byte little-endian signed int
+ giving the number of bytes in the string. The second is that many
+ bytes, and is the UTF-8 encoding of the Unicode string.
+ """),
+
I(name='BINUNICODE',
code='X',
arg=unicodestring4,
@@ -1204,6 +1410,19 @@ opcodes = [
bytes, and is the UTF-8 encoding of the Unicode string.
"""),
+ I(name='BINUNICODE8',
+ code='\x8d',
+ arg=unicodestring8,
+ stack_before=[],
+ stack_after=[pyunicode],
+ proto=4,
+ doc="""Push a Python Unicode string object.
+
+ There are two arguments: the first is a 8-byte little-endian signed int
+ giving the number of bytes in the string. The second is that many
+ bytes, and is the UTF-8 encoding of the Unicode string.
+ """),
+
# Ways to spell floats.
I(name='FLOAT',
@@ -1429,6 +1648,54 @@ opcodes = [
1, 2, ..., n, and in that order.
"""),
+ # Ways to build sets
+
+ I(name='EMPTY_SET',
+ code='\x8f',
+ arg=None,
+ stack_before=[],
+ stack_after=[pyset],
+ proto=4,
+ doc="Push an empty set."),
+
+ I(name='ADDITEMS',
+ code='\x90',
+ arg=None,
+ stack_before=[pyset, markobject, stackslice],
+ stack_after=[pyset],
+ proto=4,
+ doc="""Add an arbitrary number of items to an existing set.
+
+ The slice of the stack following the topmost markobject is taken as
+ a sequence of items, added to the set immediately under the topmost
+ markobject. Everything at and after the topmost markobject is popped,
+ leaving the mutated set at the top of the stack.
+
+ Stack before: ... pyset markobject item_1 ... item_n
+ Stack after: ... pyset
+
+ where pyset has been modified via pyset.add(item_i) = item_i for i in
+ 1, 2, ..., n, and in that order.
+ """),
+
+ # Way to build frozensets
+
+ I(name='FROZENSET',
+ code='\x91',
+ arg=None,
+ stack_before=[markobject, stackslice],
+ stack_after=[pyfrozenset],
+ proto=4,
+ doc="""Build a frozenset out of the topmost slice, after markobject.
+
+ All the stack entries following the topmost markobject are placed into
+ a single Python frozenset, which single frozenset object replaces all
+ of the stack from the topmost markobject onward. For example,
+
+ Stack before: ... markobject 1 2 3
+ Stack after: ... frozenset({1, 2, 3})
+ """),
+
# Stack manipulation.
I(name='POP',
@@ -1550,6 +1817,18 @@ opcodes = [
unsigned little-endian integer following.
"""),
+ I(name='MEMOIZE',
+ code='\x94',
+ arg=None,
+ stack_before=[anyobject],
+ stack_after=[anyobject],
+ proto=4,
+ doc="""Store the stack top into the memo. The stack is not popped.
+
+ The index of the memo location to write is the number of
+ elements currently present in the memo.
+ """),
+
# Access the extension registry (predefined objects). Akin to the GET
# family.
@@ -1615,6 +1894,15 @@ opcodes = [
stack, so unpickling subclasses can override this form of lookup.
"""),
+ I(name='STACK_GLOBAL',
+ code='\x93',
+ arg=None,
+ stack_before=[pyunicode, pyunicode],
+ stack_after=[anyobject],
+ proto=0,
+ doc="""Push a global object (module.attr) on the stack.
+ """),
+
# Ways to build objects of classes pickle doesn't know about directly
# (user-defined classes). I despair of documenting this accurately
# and comprehensibly -- you really have to read the pickle code to
@@ -1771,6 +2059,21 @@ opcodes = [
onto the stack.
"""),
+ I(name='NEWOBJ_EX',
+ code='\x92',
+ arg=None,
+ stack_before=[anyobject, anyobject, anyobject],
+ stack_after=[anyobject],
+ proto=4,
+ doc="""Build an object instance.
+
+ The stack before should be thought of as containing a class
+ object followed by an argument tuple and by a keyword argument dict
+ (the dict being the stack top). Call these cls and args. They are
+ popped off the stack, and the value returned by
+ cls.__new__(cls, *args, *kwargs) is pushed back onto the stack.
+ """),
+
# Machine control.
I(name='PROTO',
@@ -1798,6 +2101,20 @@ opcodes = [
empty then.
"""),
+ # Framing support.
+
+ I(name='FRAME',
+ code='\x95',
+ arg=uint8,
+ stack_before=[],
+ stack_after=[],
+ proto=4,
+ doc="""Indicate the beginning of a new frame.
+
+ The unpickler may use this opcode to safely prefetch data from its
+ underlying stream.
+ """),
+
# Ways to deal with persistent IDs.
I(name='PERSID',
@@ -1904,6 +2221,38 @@ del assure_pickle_consistency
##############################################################################
# A pickle opcode generator.
+def _genops(data, yield_end_pos=False):
+ if isinstance(data, bytes_types):
+ data = io.BytesIO(data)
+
+ if hasattr(data, "tell"):
+ getpos = data.tell
+ else:
+ getpos = lambda: None
+
+ while True:
+ pos = getpos()
+ code = data.read(1)
+ opcode = code2op.get(code.decode("latin-1"))
+ if opcode is None:
+ if code == b"":
+ raise ValueError("pickle exhausted before seeing STOP")
+ else:
+ raise ValueError("at position %s, opcode %r unknown" % (
+ "<unknown>" if pos is None else pos,
+ code))
+ if opcode.arg is None:
+ arg = None
+ else:
+ arg = opcode.arg.reader(data)
+ if yield_end_pos:
+ yield opcode, arg, pos, getpos()
+ else:
+ yield opcode, arg, pos
+ if code == b'.':
+ assert opcode.name == 'STOP'
+ break
+
def genops(pickle):
"""Generate all the opcodes in a pickle.
@@ -1927,62 +2276,48 @@ def genops(pickle):
used. Else (the pickle doesn't have a tell(), and it's not obvious how
to query its current position) pos is None.
"""
-
- if isinstance(pickle, bytes_types):
- import io
- pickle = io.BytesIO(pickle)
-
- if hasattr(pickle, "tell"):
- getpos = pickle.tell
- else:
- getpos = lambda: None
-
- while True:
- pos = getpos()
- code = pickle.read(1)
- opcode = code2op.get(code.decode("latin-1"))
- if opcode is None:
- if code == b"":
- raise ValueError("pickle exhausted before seeing STOP")
- else:
- raise ValueError("at position %s, opcode %r unknown" % (
- pos is None and "<unknown>" or pos,
- code))
- if opcode.arg is None:
- arg = None
- else:
- arg = opcode.arg.reader(pickle)
- yield opcode, arg, pos
- if code == b'.':
- assert opcode.name == 'STOP'
- break
+ return _genops(pickle)
##############################################################################
# A pickle optimizer.
def optimize(p):
'Optimize a pickle string by removing unused PUT opcodes'
- gets = set() # set of args used by a GET opcode
- puts = [] # (arg, startpos, stoppos) for the PUT opcodes
- prevpos = None # set to pos if previous opcode was a PUT
- for opcode, arg, pos in genops(p):
- if prevpos is not None:
- puts.append((prevarg, prevpos, pos))
- prevpos = None
+ not_a_put = object()
+ gets = { not_a_put } # set of args used by a GET opcode
+ opcodes = [] # (startpos, stoppos, putid)
+ proto = 0
+ for opcode, arg, pos, end_pos in _genops(p, yield_end_pos=True):
if 'PUT' in opcode.name:
- prevarg, prevpos = arg, pos
- elif 'GET' in opcode.name:
- gets.add(arg)
-
- # Copy the pickle string except for PUTS without a corresponding GET
- s = []
- i = 0
- for arg, start, stop in puts:
- j = stop if (arg in gets) else start
- s.append(p[i:j])
- i = stop
- s.append(p[i:])
- return b''.join(s)
+ opcodes.append((pos, end_pos, arg))
+ elif 'FRAME' in opcode.name:
+ pass
+ else:
+ if 'GET' in opcode.name:
+ gets.add(arg)
+ elif opcode.name == 'PROTO':
+ assert pos == 0, pos
+ proto = arg
+ opcodes.append((pos, end_pos, not_a_put))
+ prevpos, prevarg = pos, None
+
+ # Copy the opcodes except for PUTS without a corresponding GET
+ out = io.BytesIO()
+ opcodes = iter(opcodes)
+ if proto >= 2:
+ # Write the PROTO header before any framing
+ start, stop, _ = next(opcodes)
+ out.write(p[start:stop])
+ buf = pickle._Framer(out.write)
+ if proto >= 4:
+ buf.start_framing()
+ for start, stop, putid in opcodes:
+ if putid in gets:
+ buf.commit_frame()
+ buf.write(p[start:stop])
+ if proto >= 4:
+ buf.end_framing()
+ return out.getvalue()
##############################################################################
# A symbolic pickle disassembler.
@@ -2082,17 +2417,20 @@ def dis(pickle, out=None, memo=None, indentlevel=4, annotate=0):
errormsg = markmsg = "no MARK exists on stack"
# Check for correct memo usage.
- if opcode.name in ("PUT", "BINPUT", "LONG_BINPUT"):
- assert arg is not None
- if arg in memo:
+ if opcode.name in ("PUT", "BINPUT", "LONG_BINPUT", "MEMOIZE"):
+ if opcode.name == "MEMOIZE":
+ memo_idx = len(memo)
+ else:
+ assert arg is not None
+ memo_idx = arg
+ if memo_idx in memo:
errormsg = "memo key %r already defined" % arg
elif not stack:
errormsg = "stack is empty -- can't store into memo"
elif stack[-1] is markobject:
errormsg = "can't store markobject in the memo"
else:
- memo[arg] = stack[-1]
-
+ memo[memo_idx] = stack[-1]
elif opcode.name in ("GET", "BINGET", "LONG_BINGET"):
if arg in memo:
assert len(after) == 1
diff --git a/Lib/pkgutil.py b/Lib/pkgutil.py
index 20e6498942..4682d22366 100644
--- a/Lib/pkgutil.py
+++ b/Lib/pkgutil.py
@@ -1,12 +1,14 @@
"""Utilities to support packages."""
-import os
-import sys
+from functools import singledispatch as simplegeneric
import importlib
-import imp
+import importlib.util
+import importlib.machinery
+import os
import os.path
-from warnings import warn
+import sys
from types import ModuleType
+import warnings
__all__ = [
'get_importer', 'iter_importers', 'get_loader', 'find_loader',
@@ -20,53 +22,13 @@ def read_code(stream):
import marshal
magic = stream.read(4)
- if magic != imp.get_magic():
+ if magic != importlib.util.MAGIC_NUMBER:
return None
stream.read(8) # Skip timestamp and size
return marshal.load(stream)
-def simplegeneric(func):
- """Make a trivial single-dispatch generic function"""
- registry = {}
- def wrapper(*args, **kw):
- ob = args[0]
- try:
- cls = ob.__class__
- except AttributeError:
- cls = type(ob)
- try:
- mro = cls.__mro__
- except AttributeError:
- try:
- class cls(cls, object):
- pass
- mro = cls.__mro__[1:]
- except TypeError:
- mro = object, # must be an ExtensionClass or some such :(
- for t in mro:
- if t in registry:
- return registry[t](*args, **kw)
- else:
- return func(*args, **kw)
- try:
- wrapper.__name__ = func.__name__
- except (TypeError, AttributeError):
- pass # Python 2.3 doesn't allow functions to be renamed
-
- def register(typ, func=None):
- if func is None:
- return lambda f: register(typ, f)
- registry[typ] = func
- return func
-
- wrapper.__dict__ = func.__dict__
- wrapper.__doc__ = func.__doc__
- wrapper.register = register
- return wrapper
-
-
def walk_packages(path=None, prefix='', onerror=None):
"""Yields (module_loader, name, ispkg) for all modules recursively
on path, or, if path is None, all accessible modules.
@@ -121,8 +83,7 @@ def walk_packages(path=None, prefix='', onerror=None):
# don't traverse path items we've seen before
path = [p for p in path if not seen(p)]
- for item in walk_packages(path, name+'.', onerror):
- yield item
+ yield from walk_packages(path, name+'.', onerror)
def iter_modules(path=None, prefix=''):
@@ -149,13 +110,12 @@ def iter_modules(path=None, prefix=''):
yield i, name, ispkg
-#@simplegeneric
+@simplegeneric
def iter_importer_modules(importer, prefix=''):
if not hasattr(importer, 'iter_modules'):
return []
return importer.iter_modules(prefix)
-iter_importer_modules = simplegeneric(iter_importer_modules)
# Implement a file walker for the normal importlib path hook
def _iter_file_finder_modules(importer, prefix=''):
@@ -201,6 +161,13 @@ def _iter_file_finder_modules(importer, prefix=''):
iter_importer_modules.register(
importlib.machinery.FileFinder, _iter_file_finder_modules)
+
+def _import_imp():
+ global imp
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', PendingDeprecationWarning)
+ imp = importlib.import_module('imp')
+
class ImpImporter:
"""PEP 302 Importer that wraps Python's "classic" import algorithm
@@ -213,8 +180,10 @@ class ImpImporter:
"""
def __init__(self, path=None):
- warn("This emulation is deprecated, use 'importlib' instead",
+ global imp
+ warnings.warn("This emulation is deprecated, use 'importlib' instead",
DeprecationWarning)
+ _import_imp()
self.path = path
def find_module(self, fullname, path=None):
@@ -279,8 +248,9 @@ class ImpLoader:
code = source = None
def __init__(self, fullname, file, filename, etc):
- warn("This emulation is deprecated, use 'importlib' instead",
- DeprecationWarning)
+ warnings.warn("This emulation is deprecated, use 'importlib' instead",
+ DeprecationWarning)
+ _import_imp()
self.file = file
self.filename = filename
self.fullname = fullname
@@ -350,9 +320,8 @@ class ImpLoader:
self.file.close()
elif mod_type==imp.PY_COMPILED:
if os.path.exists(self.filename[:-1]):
- f = open(self.filename[:-1], 'r')
- self.source = f.read()
- f.close()
+ with open(self.filename[:-1], 'r') as f:
+ self.source = f.read()
elif mod_type==imp.PKG_DIRECTORY:
self.source = self._get_delegate().get_source()
return self.source
@@ -456,12 +425,12 @@ def iter_importers(fullname=""):
if path is None:
return
else:
- for importer in sys.meta_path:
- yield importer
+ yield from sys.meta_path
path = sys.path
for item in path:
yield get_importer(item)
+
def get_loader(module_or_name):
"""Get a PEP 302 "loader" object for module_or_name
@@ -589,19 +558,20 @@ def extend_path(path, name):
if os.path.isfile(pkgfile):
try:
f = open(pkgfile)
- except IOError as msg:
+ except OSError as msg:
sys.stderr.write("Can't open %s: %s\n" %
(pkgfile, msg))
else:
- for line in f:
- line = line.rstrip('\n')
- if not line or line.startswith('#'):
- continue
- path.append(line) # Don't check for existence!
- f.close()
+ with f:
+ for line in f:
+ line = line.rstrip('\n')
+ if not line or line.startswith('#'):
+ continue
+ path.append(line) # Don't check for existence!
return path
+
def get_data(package, resource):
"""Get a resource from a package.
@@ -624,10 +594,15 @@ def get_data(package, resource):
which does not support get_data(), then None is returned.
"""
- loader = get_loader(package)
+ spec = importlib.find_spec(package)
+ if spec is None:
+ return None
+ loader = spec.loader
if loader is None or not hasattr(loader, 'get_data'):
return None
- mod = sys.modules.get(package) or loader.load_module(package)
+ # XXX needs test
+ mod = (sys.modules.get(package) or
+ importlib._bootstrap._SpecMethods(spec).load())
if mod is None or not hasattr(mod, '__file__'):
return None
diff --git a/Lib/plat-os2emx/IN.py b/Lib/plat-os2emx/IN.py
deleted file mode 100644
index 753ae24883..0000000000
--- a/Lib/plat-os2emx/IN.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# Generated by h2py from f:/emx/include/netinet/in.h
-
-# Included from sys/param.h
-PAGE_SIZE = 0x1000
-HZ = 100
-MAXNAMLEN = 260
-MAXPATHLEN = 260
-def htonl(X): return _swapl(X)
-
-def ntohl(X): return _swapl(X)
-
-def htons(X): return _swaps(X)
-
-def ntohs(X): return _swaps(X)
-
-IPPROTO_IP = 0
-IPPROTO_ICMP = 1
-IPPROTO_IGMP = 2
-IPPROTO_GGP = 3
-IPPROTO_TCP = 6
-IPPROTO_EGP = 8
-IPPROTO_PUP = 12
-IPPROTO_UDP = 17
-IPPROTO_IDP = 22
-IPPROTO_TP = 29
-IPPROTO_EON = 80
-IPPROTO_RAW = 255
-IPPROTO_MAX = 256
-IPPORT_RESERVED = 1024
-IPPORT_USERRESERVED = 5000
-def IN_CLASSA(i): return (((int)(i) & 0x80000000) == 0)
-
-IN_CLASSA_NET = 0xff000000
-IN_CLASSA_NSHIFT = 24
-IN_CLASSA_HOST = 0x00ffffff
-IN_CLASSA_MAX = 128
-def IN_CLASSB(i): return (((int)(i) & 0xc0000000) == 0x80000000)
-
-IN_CLASSB_NET = 0xffff0000
-IN_CLASSB_NSHIFT = 16
-IN_CLASSB_HOST = 0x0000ffff
-IN_CLASSB_MAX = 65536
-def IN_CLASSC(i): return (((int)(i) & 0xe0000000) == 0xc0000000)
-
-IN_CLASSC_NET = 0xffffff00
-IN_CLASSC_NSHIFT = 8
-IN_CLASSC_HOST = 0x000000ff
-def IN_CLASSD(i): return (((int)(i) & 0xf0000000) == 0xe0000000)
-
-IN_CLASSD_NET = 0xf0000000
-IN_CLASSD_NSHIFT = 28
-IN_CLASSD_HOST = 0x0fffffff
-def IN_MULTICAST(i): return IN_CLASSD(i)
-
-def IN_EXPERIMENTAL(i): return (((int)(i) & 0xe0000000) == 0xe0000000)
-
-def IN_BADCLASS(i): return (((int)(i) & 0xf0000000) == 0xf0000000)
-
-INADDR_ANY = 0x00000000
-INADDR_LOOPBACK = 0x7f000001
-INADDR_BROADCAST = 0xffffffff
-INADDR_NONE = 0xffffffff
-INADDR_UNSPEC_GROUP = 0xe0000000
-INADDR_ALLHOSTS_GROUP = 0xe0000001
-INADDR_MAX_LOCAL_GROUP = 0xe00000ff
-IN_LOOPBACKNET = 127
-IP_OPTIONS = 1
-IP_MULTICAST_IF = 2
-IP_MULTICAST_TTL = 3
-IP_MULTICAST_LOOP = 4
-IP_ADD_MEMBERSHIP = 5
-IP_DROP_MEMBERSHIP = 6
-IP_HDRINCL = 2
-IP_TOS = 3
-IP_TTL = 4
-IP_RECVOPTS = 5
-IP_RECVRETOPTS = 6
-IP_RECVDSTADDR = 7
-IP_RETOPTS = 8
-IP_DEFAULT_MULTICAST_TTL = 1
-IP_DEFAULT_MULTICAST_LOOP = 1
-IP_MAX_MEMBERSHIPS = 20
diff --git a/Lib/plat-os2emx/SOCKET.py b/Lib/plat-os2emx/SOCKET.py
deleted file mode 100644
index dac594ad7f..0000000000
--- a/Lib/plat-os2emx/SOCKET.py
+++ /dev/null
@@ -1,106 +0,0 @@
-# Generated by h2py from f:/emx/include/sys/socket.h
-
-# Included from sys/types.h
-FD_SETSIZE = 256
-
-# Included from sys/uio.h
-FREAD = 1
-FWRITE = 2
-SOCK_STREAM = 1
-SOCK_DGRAM = 2
-SOCK_RAW = 3
-SOCK_RDM = 4
-SOCK_SEQPACKET = 5
-SO_DEBUG = 0x0001
-SO_ACCEPTCONN = 0x0002
-SO_REUSEADDR = 0x0004
-SO_KEEPALIVE = 0x0008
-SO_DONTROUTE = 0x0010
-SO_BROADCAST = 0x0020
-SO_USELOOPBACK = 0x0040
-SO_LINGER = 0x0080
-SO_OOBINLINE = 0x0100
-SO_L_BROADCAST = 0x0200
-SO_RCV_SHUTDOWN = 0x0400
-SO_SND_SHUTDOWN = 0x0800
-SO_SNDBUF = 0x1001
-SO_RCVBUF = 0x1002
-SO_SNDLOWAT = 0x1003
-SO_RCVLOWAT = 0x1004
-SO_SNDTIMEO = 0x1005
-SO_RCVTIMEO = 0x1006
-SO_ERROR = 0x1007
-SO_TYPE = 0x1008
-SO_OPTIONS = 0x1010
-SOL_SOCKET = 0xffff
-AF_UNSPEC = 0
-AF_UNIX = 1
-AF_INET = 2
-AF_IMPLINK = 3
-AF_PUP = 4
-AF_CHAOS = 5
-AF_NS = 6
-AF_NBS = 7
-AF_ISO = 7
-AF_OSI = AF_ISO
-AF_ECMA = 8
-AF_DATAKIT = 9
-AF_CCITT = 10
-AF_SNA = 11
-AF_DECnet = 12
-AF_DLI = 13
-AF_LAT = 14
-AF_HYLINK = 15
-AF_APPLETALK = 16
-AF_NB = 17
-AF_NETBIOS = AF_NB
-AF_OS2 = AF_UNIX
-AF_MAX = 18
-PF_UNSPEC = AF_UNSPEC
-PF_UNIX = AF_UNIX
-PF_INET = AF_INET
-PF_IMPLINK = AF_IMPLINK
-PF_PUP = AF_PUP
-PF_CHAOS = AF_CHAOS
-PF_NS = AF_NS
-PF_NBS = AF_NBS
-PF_ISO = AF_ISO
-PF_OSI = AF_ISO
-PF_ECMA = AF_ECMA
-PF_DATAKIT = AF_DATAKIT
-PF_CCITT = AF_CCITT
-PF_SNA = AF_SNA
-PF_DECnet = AF_DECnet
-PF_DLI = AF_DLI
-PF_LAT = AF_LAT
-PF_HYLINK = AF_HYLINK
-PF_APPLETALK = AF_APPLETALK
-PF_NB = AF_NB
-PF_NETBIOS = AF_NB
-PF_OS2 = AF_UNIX
-PF_MAX = AF_MAX
-SOMAXCONN = 5
-MSG_OOB = 0x1
-MSG_PEEK = 0x2
-MSG_DONTROUTE = 0x4
-MSG_EOR = 0x8
-MSG_TRUNC = 0x10
-MSG_CTRUNC = 0x20
-MSG_WAITALL = 0x40
-MSG_MAXIOVLEN = 16
-SCM_RIGHTS = 0x01
-MT_FREE = 0
-MT_DATA = 1
-MT_HEADER = 2
-MT_SOCKET = 3
-MT_PCB = 4
-MT_RTABLE = 5
-MT_HTABLE = 6
-MT_ATABLE = 7
-MT_SONAME = 8
-MT_ZOMBIE = 9
-MT_SOOPTS = 10
-MT_FTABLE = 11
-MT_RIGHTS = 12
-MT_IFADDR = 13
-MAXSOCKETS = 2048
diff --git a/Lib/plat-os2emx/_emx_link.py b/Lib/plat-os2emx/_emx_link.py
deleted file mode 100644
index 01e6b54c8d..0000000000
--- a/Lib/plat-os2emx/_emx_link.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# _emx_link.py
-
-# Written by Andrew I MacIntyre, December 2002.
-
-"""_emx_link.py is a simplistic emulation of the Unix link(2) library routine
-for creating so-called hard links. It is intended to be imported into
-the os module in place of the unimplemented (on OS/2) Posix link()
-function (os.link()).
-
-We do this on OS/2 by implementing a file copy, with link(2) semantics:-
- - the target cannot already exist;
- - we hope that the actual file open (if successful) is actually
- atomic...
-
-Limitations of this approach/implementation include:-
- - no support for correct link counts (EMX stat(target).st_nlink
- is always 1);
- - thread safety undefined;
- - default file permissions (r+w) used, can't be over-ridden;
- - implemented in Python so comparatively slow, especially for large
- source files;
- - need sufficient free disk space to store the copy.
-
-Behaviour:-
- - any exception should propagate to the caller;
- - want target to be an exact copy of the source, so use binary mode;
- - returns None, same as os.link() which is implemented in posixmodule.c;
- - target removed in the event of a failure where possible;
- - given the motivation to write this emulation came from trying to
- support a Unix resource lock implementation, where minimal overhead
- during creation of the target is desirable and the files are small,
- we read a source block before attempting to create the target so that
- we're ready to immediately write some data into it.
-"""
-
-import os
-import errno
-
-__all__ = ['link']
-
-def link(source, target):
- """link(source, target) -> None
-
- Attempt to hard link the source file to the target file name.
- On OS/2, this creates a complete copy of the source file.
- """
-
- s = os.open(source, os.O_RDONLY | os.O_BINARY)
- if os.isatty(s):
- raise OSError(errno.EXDEV, 'Cross-device link')
- data = os.read(s, 1024)
-
- try:
- t = os.open(target, os.O_WRONLY | os.O_BINARY | os.O_CREAT | os.O_EXCL)
- except OSError:
- os.close(s)
- raise
-
- try:
- while data:
- os.write(t, data)
- data = os.read(s, 1024)
- except OSError:
- os.close(s)
- os.close(t)
- os.unlink(target)
- raise
-
- os.close(s)
- os.close(t)
-
-if __name__ == '__main__':
- import sys
- try:
- link(sys.argv[1], sys.argv[2])
- except IndexError:
- print('Usage: emx_link <source> <target>')
- except OSError:
- print('emx_link: %s' % str(sys.exc_info()[1]))
diff --git a/Lib/plat-os2emx/grp.py b/Lib/plat-os2emx/grp.py
deleted file mode 100644
index ee63ef8ae2..0000000000
--- a/Lib/plat-os2emx/grp.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# this module is an OS/2 oriented replacement for the grp standard
-# extension module.
-
-# written by Andrew MacIntyre, April 2001.
-# updated July 2003, adding field accessor support
-
-# note that this implementation checks whether ":" or ";" as used as
-# the field separator character.
-
-"""Replacement for grp standard extension module, intended for use on
-OS/2 and similar systems which don't normally have an /etc/group file.
-
-The standard Unix group database is an ASCII text file with 4 fields per
-record (line), separated by a colon:
- - group name (string)
- - group password (optional encrypted string)
- - group id (integer)
- - group members (comma delimited list of userids, with no spaces)
-
-Note that members are only included in the group file for groups that
-aren't their primary groups.
-(see the section 8.2 of the Python Library Reference)
-
-This implementation differs from the standard Unix implementation by
-allowing use of the platform's native path separator character - ';' on OS/2,
-DOS and MS-Windows - as the field separator in addition to the Unix
-standard ":".
-
-The module looks for the group database at the following locations
-(in order first to last):
- - ${ETC_GROUP} (or %ETC_GROUP%)
- - ${ETC}/group (or %ETC%/group)
- - ${PYTHONHOME}/Etc/group (or %PYTHONHOME%/Etc/group)
-
-Classes
--------
-
-None
-
-Functions
----------
-
-getgrgid(gid) - return the record for group-id gid as a 4-tuple
-
-getgrnam(name) - return the record for group 'name' as a 4-tuple
-
-getgrall() - return a list of 4-tuples, each tuple being one record
- (NOTE: the order is arbitrary)
-
-Attributes
-----------
-
-group_file - the path of the group database file
-
-"""
-
-import os
-
-# try and find the group file
-__group_path = []
-if 'ETC_GROUP' in os.environ:
- __group_path.append(os.environ['ETC_GROUP'])
-if 'ETC' in os.environ:
- __group_path.append('%s/group' % os.environ['ETC'])
-if 'PYTHONHOME' in os.environ:
- __group_path.append('%s/Etc/group' % os.environ['PYTHONHOME'])
-
-group_file = None
-for __i in __group_path:
- try:
- __f = open(__i, 'r')
- __f.close()
- group_file = __i
- break
- except:
- pass
-
-# decide what field separator we can try to use - Unix standard, with
-# the platform's path separator as an option. No special field conversion
-# handlers are required for the group file.
-__field_sep = [':']
-if os.pathsep:
- if os.pathsep != ':':
- __field_sep.append(os.pathsep)
-
-# helper routine to identify which separator character is in use
-def __get_field_sep(record):
- fs = None
- for c in __field_sep:
- # there should be 3 delimiter characters (for 4 fields)
- if record.count(c) == 3:
- fs = c
- break
- if fs:
- return fs
- else:
- raise KeyError('>> group database fields not delimited <<')
-
-# class to match the new record field name accessors.
-# the resulting object is intended to behave like a read-only tuple,
-# with each member also accessible by a field name.
-class Group:
- def __init__(self, name, passwd, gid, mem):
- self.__dict__['gr_name'] = name
- self.__dict__['gr_passwd'] = passwd
- self.__dict__['gr_gid'] = gid
- self.__dict__['gr_mem'] = mem
- self.__dict__['_record'] = (self.gr_name, self.gr_passwd,
- self.gr_gid, self.gr_mem)
-
- def __len__(self):
- return 4
-
- def __getitem__(self, key):
- return self._record[key]
-
- def __setattr__(self, name, value):
- raise AttributeError('attribute read-only: %s' % name)
-
- def __repr__(self):
- return str(self._record)
-
- def __cmp__(self, other):
- this = str(self._record)
- if this == other:
- return 0
- elif this < other:
- return -1
- else:
- return 1
-
-
-# read the whole file, parsing each entry into tuple form
-# with dictionaries to speed recall by GID or group name
-def __read_group_file():
- if group_file:
- group = open(group_file, 'r')
- else:
- raise KeyError('>> no group database <<')
- gidx = {}
- namx = {}
- sep = None
- while 1:
- entry = group.readline().strip()
- if len(entry) > 3:
- if sep is None:
- sep = __get_field_sep(entry)
- fields = entry.split(sep)
- fields[2] = int(fields[2])
- fields[3] = [f.strip() for f in fields[3].split(',')]
- record = Group(*fields)
- if fields[2] not in gidx:
- gidx[fields[2]] = record
- if fields[0] not in namx:
- namx[fields[0]] = record
- elif len(entry) > 0:
- pass # skip empty or malformed records
- else:
- break
- group.close()
- if len(gidx) == 0:
- raise KeyError
- return (gidx, namx)
-
-# return the group database entry by GID
-def getgrgid(gid):
- g, n = __read_group_file()
- return g[gid]
-
-# return the group database entry by group name
-def getgrnam(name):
- g, n = __read_group_file()
- return n[name]
-
-# return all the group database entries
-def getgrall():
- g, n = __read_group_file()
- return g.values()
-
-# test harness
-if __name__ == '__main__':
- getgrall()
diff --git a/Lib/plat-os2emx/pwd.py b/Lib/plat-os2emx/pwd.py
deleted file mode 100644
index 2cb077f5ab..0000000000
--- a/Lib/plat-os2emx/pwd.py
+++ /dev/null
@@ -1,208 +0,0 @@
-# this module is an OS/2 oriented replacement for the pwd standard
-# extension module.
-
-# written by Andrew MacIntyre, April 2001.
-# updated July 2003, adding field accessor support
-
-# note that this implementation checks whether ":" or ";" as used as
-# the field separator character. Path conversions are are applied when
-# the database uses ":" as the field separator character.
-
-"""Replacement for pwd standard extension module, intended for use on
-OS/2 and similar systems which don't normally have an /etc/passwd file.
-
-The standard Unix password database is an ASCII text file with 7 fields
-per record (line), separated by a colon:
- - user name (string)
- - password (encrypted string, or "*" or "")
- - user id (integer)
- - group id (integer)
- - description (usually user's name)
- - home directory (path to user's home directory)
- - shell (path to the user's login shell)
-
-(see the section 8.1 of the Python Library Reference)
-
-This implementation differs from the standard Unix implementation by
-allowing use of the platform's native path separator character - ';' on OS/2,
-DOS and MS-Windows - as the field separator in addition to the Unix
-standard ":". Additionally, when ":" is the separator path conversions
-are applied to deal with any munging of the drive letter reference.
-
-The module looks for the password database at the following locations
-(in order first to last):
- - ${ETC_PASSWD} (or %ETC_PASSWD%)
- - ${ETC}/passwd (or %ETC%/passwd)
- - ${PYTHONHOME}/Etc/passwd (or %PYTHONHOME%/Etc/passwd)
-
-Classes
--------
-
-None
-
-Functions
----------
-
-getpwuid(uid) - return the record for user-id uid as a 7-tuple
-
-getpwnam(name) - return the record for user 'name' as a 7-tuple
-
-getpwall() - return a list of 7-tuples, each tuple being one record
- (NOTE: the order is arbitrary)
-
-Attributes
-----------
-
-passwd_file - the path of the password database file
-
-"""
-
-import os
-
-# try and find the passwd file
-__passwd_path = []
-if 'ETC_PASSWD' in os.environ:
- __passwd_path.append(os.environ['ETC_PASSWD'])
-if 'ETC' in os.environ:
- __passwd_path.append('%s/passwd' % os.environ['ETC'])
-if 'PYTHONHOME' in os.environ:
- __passwd_path.append('%s/Etc/passwd' % os.environ['PYTHONHOME'])
-
-passwd_file = None
-for __i in __passwd_path:
- try:
- __f = open(__i, 'r')
- __f.close()
- passwd_file = __i
- break
- except:
- pass
-
-# path conversion handlers
-def __nullpathconv(path):
- return path.replace(os.altsep, os.sep)
-
-def __unixpathconv(path):
- # two known drive letter variations: "x;" and "$x"
- if path[0] == '$':
- conv = path[1] + ':' + path[2:]
- elif path[1] == ';':
- conv = path[0] + ':' + path[2:]
- else:
- conv = path
- return conv.replace(os.altsep, os.sep)
-
-# decide what field separator we can try to use - Unix standard, with
-# the platform's path separator as an option. No special field conversion
-# handler is required when using the platform's path separator as field
-# separator, but are required for the home directory and shell fields when
-# using the standard Unix (":") field separator.
-__field_sep = {':': __unixpathconv}
-if os.pathsep:
- if os.pathsep != ':':
- __field_sep[os.pathsep] = __nullpathconv
-
-# helper routine to identify which separator character is in use
-def __get_field_sep(record):
- fs = None
- for c in __field_sep.keys():
- # there should be 6 delimiter characters (for 7 fields)
- if record.count(c) == 6:
- fs = c
- break
- if fs:
- return fs
- else:
- raise KeyError('>> passwd database fields not delimited <<')
-
-# class to match the new record field name accessors.
-# the resulting object is intended to behave like a read-only tuple,
-# with each member also accessible by a field name.
-class Passwd:
- def __init__(self, name, passwd, uid, gid, gecos, dir, shell):
- self.__dict__['pw_name'] = name
- self.__dict__['pw_passwd'] = passwd
- self.__dict__['pw_uid'] = uid
- self.__dict__['pw_gid'] = gid
- self.__dict__['pw_gecos'] = gecos
- self.__dict__['pw_dir'] = dir
- self.__dict__['pw_shell'] = shell
- self.__dict__['_record'] = (self.pw_name, self.pw_passwd,
- self.pw_uid, self.pw_gid,
- self.pw_gecos, self.pw_dir,
- self.pw_shell)
-
- def __len__(self):
- return 7
-
- def __getitem__(self, key):
- return self._record[key]
-
- def __setattr__(self, name, value):
- raise AttributeError('attribute read-only: %s' % name)
-
- def __repr__(self):
- return str(self._record)
-
- def __cmp__(self, other):
- this = str(self._record)
- if this == other:
- return 0
- elif this < other:
- return -1
- else:
- return 1
-
-
-# read the whole file, parsing each entry into tuple form
-# with dictionaries to speed recall by UID or passwd name
-def __read_passwd_file():
- if passwd_file:
- passwd = open(passwd_file, 'r')
- else:
- raise KeyError('>> no password database <<')
- uidx = {}
- namx = {}
- sep = None
- while True:
- entry = passwd.readline().strip()
- if len(entry) > 6:
- if sep is None:
- sep = __get_field_sep(entry)
- fields = entry.split(sep)
- for i in (2, 3):
- fields[i] = int(fields[i])
- for i in (5, 6):
- fields[i] = __field_sep[sep](fields[i])
- record = Passwd(*fields)
- if fields[2] not in uidx:
- uidx[fields[2]] = record
- if fields[0] not in namx:
- namx[fields[0]] = record
- elif len(entry) > 0:
- pass # skip empty or malformed records
- else:
- break
- passwd.close()
- if len(uidx) == 0:
- raise KeyError
- return (uidx, namx)
-
-# return the passwd database entry by UID
-def getpwuid(uid):
- u, n = __read_passwd_file()
- return u[uid]
-
-# return the passwd database entry by passwd name
-def getpwnam(name):
- u, n = __read_passwd_file()
- return n[name]
-
-# return all the passwd database entries
-def getpwall():
- u, n = __read_passwd_file()
- return n.values()
-
-# test harness
-if __name__ == '__main__':
- getpwall()
diff --git a/Lib/plat-os2emx/regen b/Lib/plat-os2emx/regen
deleted file mode 100644
index 3ecd2a8605..0000000000
--- a/Lib/plat-os2emx/regen
+++ /dev/null
@@ -1,7 +0,0 @@
-#! /bin/sh
-export INCLUDE=$C_INCLUDE_PATH
-set -v
-python.exe ../../Tools/scripts/h2py.py $C_INCLUDE_PATH/fcntl.h
-python.exe ../../Tools/scripts/h2py.py $C_INCLUDE_PATH/sys/socket.h
-python.exe ../../Tools/scripts/h2py.py -i '(u_long)' $C_INCLUDE_PATH/netinet/in.h
-#python.exe ../../Tools/scripts/h2py.py $C_INCLUDE_PATH/termios.h
diff --git a/Lib/platform.py b/Lib/platform.py
index ec5df85d6f..c3c4b328e1 100755
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -122,7 +122,7 @@ try:
except AttributeError:
# os.devnull was added in Python 2.4, so emulate it for earlier
# Python versions
- if sys.platform in ('dos','win32','win16','os2'):
+ if sys.platform in ('dos','win32','win16'):
# Use the old CP/M NUL as device name
DEV_NULL = 'NUL'
else:
@@ -316,7 +316,7 @@ def linux_distribution(distname='', version='', id='',
"""
try:
etc = os.listdir('/etc')
- except os.error:
+ except OSError:
# Probably not a Unix system
return distname,version,id
etc.sort()
@@ -403,13 +403,13 @@ _ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) '
def _syscmd_ver(system='', release='', version='',
- supported_platforms=('win32','win16','dos','os2')):
+ supported_platforms=('win32','win16','dos')):
""" Tries to figure out the OS version used and returns
a tuple (system,release,version).
It uses the "ver" shell command for this which is known
- to exists on Windows, DOS and OS/2. XXX Others too ?
+ to exists on Windows, DOS. XXX Others too ?
In case this fails, the given parameters are used as
defaults.
@@ -424,13 +424,10 @@ def _syscmd_ver(system='', release='', version='',
pipe = popen(cmd)
info = pipe.read()
if pipe.close():
- raise os.error('command failed')
+ raise OSError('command failed')
# XXX How can I suppress shell errors from being written
# to stderr ?
- except os.error as why:
- #print 'Command %s failed: %s' % (cmd,why)
- continue
- except IOError as why:
+ except OSError as why:
#print 'Command %s failed: %s' % (cmd,why)
continue
else:
@@ -581,7 +578,7 @@ def win32_ver(release='',version='',csd='',ptype=''):
# Discard any type that isn't REG_SZ
if type == REG_SZ and name.find("Server") != -1:
product_type = VER_NT_SERVER
- except WindowsError:
+ except OSError:
# Use default of VER_NT_WORKSTATION
pass
@@ -637,62 +634,6 @@ def win32_ver(release='',version='',csd='',ptype=''):
RegCloseKey(keyCurVer)
return release,version,csd,ptype
-def _mac_ver_lookup(selectors,default=None):
-
- from _gestalt import gestalt
- l = []
- append = l.append
- for selector in selectors:
- try:
- append(gestalt(selector))
- except (RuntimeError, OSError):
- append(default)
- return l
-
-def _bcd2str(bcd):
-
- return hex(bcd)[2:]
-
-def _mac_ver_gestalt():
- """
- Thanks to Mark R. Levinson for mailing documentation links and
- code examples for this function. Documentation for the
- gestalt() API is available online at:
-
- http://www.rgaros.nl/gestalt/
- """
- # Check whether the version info module is available
- try:
- import _gestalt
- except ImportError:
- return None
- # Get the infos
- sysv, sysa = _mac_ver_lookup(('sysv','sysa'))
- # Decode the infos
- if sysv:
- major = (sysv & 0xFF00) >> 8
- minor = (sysv & 0x00F0) >> 4
- patch = (sysv & 0x000F)
-
- if (major, minor) >= (10, 4):
- # the 'sysv' gestald cannot return patchlevels
- # higher than 9. Apple introduced 3 new
- # gestalt codes in 10.4 to deal with this
- # issue (needed because patch levels can
- # run higher than 9, such as 10.4.11)
- major,minor,patch = _mac_ver_lookup(('sys1','sys2','sys3'))
- release = '%i.%i.%i' %(major, minor, patch)
- else:
- release = '%s.%i.%i' % (_bcd2str(major),minor,patch)
-
- if sysa:
- machine = {0x1: '68k',
- 0x2: 'PowerPC',
- 0xa: 'i386'}.get(sysa,'')
-
- versioninfo=('', '', '')
- return release,versioninfo,machine
-
def _mac_ver_xml():
fn = '/System/Library/CoreServices/SystemVersion.plist'
if not os.path.exists(fn):
@@ -708,7 +649,7 @@ def _mac_ver_xml():
versioninfo=('', '', '')
machine = os.uname().machine
if machine in ('ppc', 'Power Macintosh'):
- # for compatibility with the gestalt based code
+ # Canonical name
machine = 'PowerPC'
return release,versioninfo,machine
@@ -730,12 +671,6 @@ def mac_ver(release='',versioninfo=('','',''),machine=''):
if info is not None:
return info
- # If that doesn't work for some reason fall back to reading the
- # information using gestalt calls.
- info = _mac_ver_gestalt()
- if info is not None:
- return info
-
# If that also doesn't work return the default values
return release,versioninfo,machine
@@ -882,7 +817,7 @@ def _node(default=''):
return default
try:
return socket.gethostname()
- except socket.error:
+ except OSError:
# Still not working...
return default
@@ -901,12 +836,12 @@ def _syscmd_uname(option,default=''):
""" Interface to the system's uname command.
"""
- if sys.platform in ('dos','win32','win16','os2'):
+ if sys.platform in ('dos','win32','win16'):
# XXX Others too ?
return default
try:
f = os.popen('uname %s 2> %s' % (option, DEV_NULL))
- except (AttributeError,os.error):
+ except (AttributeError, OSError):
return default
output = f.read().strip()
rc = f.close()
@@ -924,7 +859,7 @@ def _syscmd_file(target,default=''):
default in case the command should fail.
"""
- if sys.platform in ('dos','win32','win16','os2'):
+ if sys.platform in ('dos','win32','win16'):
# XXX Others too ?
return default
target = _follow_symlinks(target)
@@ -932,7 +867,7 @@ def _syscmd_file(target,default=''):
proc = subprocess.Popen(['file', target],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- except (AttributeError,os.error):
+ except (AttributeError, OSError):
return default
output = proc.communicate()[0].decode('latin-1')
rc = proc.wait()
diff --git a/Lib/plistlib.py b/Lib/plistlib.py
index 2b0b634100..277ce622d0 100644
--- a/Lib/plistlib.py
+++ b/Lib/plistlib.py
@@ -4,25 +4,20 @@ The property list (.plist) file format is a simple XML pickle supporting
basic object types, like dictionaries, lists, numbers and strings.
Usually the top level object is a dictionary.
-To write out a plist file, use the writePlist(rootObject, pathOrFile)
-function. 'rootObject' is the top level object, 'pathOrFile' is a
-filename or a (writable) file object.
+To write out a plist file, use the dump(value, file)
+function. 'value' is the top level object, 'file' is
+a (writable) file object.
-To parse a plist from a file, use the readPlist(pathOrFile) function,
-with a file name or a (readable) file object as the only argument. It
+To parse a plist from a file, use the load(file) function,
+with a (readable) file object as the only argument. It
returns the top level object (again, usually a dictionary).
-To work with plist data in bytes objects, you can use readPlistFromBytes()
-and writePlistToBytes().
+To work with plist data in bytes objects, you can use loads()
+and dumps().
Values can be strings, integers, floats, booleans, tuples, lists,
-dictionaries (but only with string keys), Data or datetime.datetime objects.
-String values (including dictionary keys) have to be unicode strings -- they
-will be written out as UTF-8.
-
-The <data> plist type is supported through the Data class. This is a
-thin wrapper around a Python bytes object. Use 'Data' if your strings
-contain control characters.
+dictionaries (but only with string keys), Data, bytes, bytearray, or
+datetime.datetime objects.
Generate Plist example:
@@ -37,226 +32,48 @@ Generate Plist example:
aTrueValue = True,
aFalseValue = False,
),
- someData = Data(b"<binary gunk>"),
- someMoreData = Data(b"<lots of binary gunk>" * 10),
+ someData = b"<binary gunk>",
+ someMoreData = b"<lots of binary gunk>" * 10,
aDate = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())),
)
- writePlist(pl, fileName)
+ with open(fileName, 'wb') as fp:
+ dump(pl, fp)
Parse Plist example:
- pl = readPlist(pathOrFile)
- print pl["aKey"]
+ with open(fileName, 'rb') as fp:
+ pl = load(fp)
+ print(pl["aKey"])
"""
-
-
__all__ = [
"readPlist", "writePlist", "readPlistFromBytes", "writePlistToBytes",
- "Plist", "Data", "Dict"
+ "Plist", "Data", "Dict", "FMT_XML", "FMT_BINARY",
+ "load", "dump", "loads", "dumps"
]
-# Note: the Plist and Dict classes have been deprecated.
import binascii
+import codecs
+import contextlib
import datetime
+import enum
from io import BytesIO
+import itertools
+import os
import re
+import struct
+from warnings import warn
+from xml.parsers.expat import ParserCreate
-def readPlist(pathOrFile):
- """Read a .plist file. 'pathOrFile' may either be a file name or a
- (readable) file object. Return the unpacked root object (which
- usually is a dictionary).
- """
- didOpen = False
- try:
- if isinstance(pathOrFile, str):
- pathOrFile = open(pathOrFile, 'rb')
- didOpen = True
- p = PlistParser()
- rootObject = p.parse(pathOrFile)
- finally:
- if didOpen:
- pathOrFile.close()
- return rootObject
-
-
-def writePlist(rootObject, pathOrFile):
- """Write 'rootObject' to a .plist file. 'pathOrFile' may either be a
- file name or a (writable) file object.
- """
- didOpen = False
- try:
- if isinstance(pathOrFile, str):
- pathOrFile = open(pathOrFile, 'wb')
- didOpen = True
- writer = PlistWriter(pathOrFile)
- writer.writeln("<plist version=\"1.0\">")
- writer.writeValue(rootObject)
- writer.writeln("</plist>")
- finally:
- if didOpen:
- pathOrFile.close()
-
-
-def readPlistFromBytes(data):
- """Read a plist data from a bytes object. Return the root object.
- """
- return readPlist(BytesIO(data))
-
-
-def writePlistToBytes(rootObject):
- """Return 'rootObject' as a plist-formatted bytes object.
- """
- f = BytesIO()
- writePlist(rootObject, f)
- return f.getvalue()
-
-
-class DumbXMLWriter:
- def __init__(self, file, indentLevel=0, indent="\t"):
- self.file = file
- self.stack = []
- self.indentLevel = indentLevel
- self.indent = indent
-
- def beginElement(self, element):
- self.stack.append(element)
- self.writeln("<%s>" % element)
- self.indentLevel += 1
-
- def endElement(self, element):
- assert self.indentLevel > 0
- assert self.stack.pop() == element
- self.indentLevel -= 1
- self.writeln("</%s>" % element)
-
- def simpleElement(self, element, value=None):
- if value is not None:
- value = _escape(value)
- self.writeln("<%s>%s</%s>" % (element, value, element))
- else:
- self.writeln("<%s/>" % element)
-
- def writeln(self, line):
- if line:
- # plist has fixed encoding of utf-8
- if isinstance(line, str):
- line = line.encode('utf-8')
- self.file.write(self.indentLevel * self.indent)
- self.file.write(line)
- self.file.write(b'\n')
-
-
-# Contents should conform to a subset of ISO 8601
-# (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. Smaller units may be omitted with
-# a loss of precision)
-_dateParser = re.compile(r"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z", re.ASCII)
-
-def _dateFromString(s):
- order = ('year', 'month', 'day', 'hour', 'minute', 'second')
- gd = _dateParser.match(s).groupdict()
- lst = []
- for key in order:
- val = gd[key]
- if val is None:
- break
- lst.append(int(val))
- return datetime.datetime(*lst)
-
-def _dateToString(d):
- return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
- d.year, d.month, d.day,
- d.hour, d.minute, d.second
- )
-
-
-# Regex to find any control chars, except for \t \n and \r
-_controlCharPat = re.compile(
- r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
- r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
-
-def _escape(text):
- m = _controlCharPat.search(text)
- if m is not None:
- raise ValueError("strings can't contains control characters; "
- "use plistlib.Data instead")
- text = text.replace("\r\n", "\n") # convert DOS line endings
- text = text.replace("\r", "\n") # convert Mac line endings
- text = text.replace("&", "&amp;") # escape '&'
- text = text.replace("<", "&lt;") # escape '<'
- text = text.replace(">", "&gt;") # escape '>'
- return text
-
-
-PLISTHEADER = b"""\
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-"""
-
-class PlistWriter(DumbXMLWriter):
-
- def __init__(self, file, indentLevel=0, indent=b"\t", writeHeader=1):
- if writeHeader:
- file.write(PLISTHEADER)
- DumbXMLWriter.__init__(self, file, indentLevel, indent)
-
- def writeValue(self, value):
- if isinstance(value, str):
- self.simpleElement("string", value)
- elif isinstance(value, bool):
- # must switch for bool before int, as bool is a
- # subclass of int...
- if value:
- self.simpleElement("true")
- else:
- self.simpleElement("false")
- elif isinstance(value, int):
- self.simpleElement("integer", "%d" % value)
- elif isinstance(value, float):
- self.simpleElement("real", repr(value))
- elif isinstance(value, dict):
- self.writeDict(value)
- elif isinstance(value, Data):
- self.writeData(value)
- elif isinstance(value, datetime.datetime):
- self.simpleElement("date", _dateToString(value))
- elif isinstance(value, (tuple, list)):
- self.writeArray(value)
- else:
- raise TypeError("unsupported type: %s" % type(value))
-
- def writeData(self, data):
- self.beginElement("data")
- self.indentLevel -= 1
- maxlinelength = max(16, 76 - len(self.indent.replace(b"\t", b" " * 8) *
- self.indentLevel))
- for line in data.asBase64(maxlinelength).split(b"\n"):
- if line:
- self.writeln(line)
- self.indentLevel += 1
- self.endElement("data")
+PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)
+globals().update(PlistFormat.__members__)
- def writeDict(self, d):
- if d:
- self.beginElement("dict")
- items = sorted(d.items())
- for key, value in items:
- if not isinstance(key, str):
- raise TypeError("keys must be strings")
- self.simpleElement("key", key)
- self.writeValue(value)
- self.endElement("dict")
- else:
- self.simpleElement("dict")
- def writeArray(self, array):
- if array:
- self.beginElement("array")
- for value in array:
- self.writeValue(value)
- self.endElement("array")
- else:
- self.simpleElement("array")
+#
+#
+# Deprecated functionality
+#
+#
class _InternalDict(dict):
@@ -264,19 +81,18 @@ class _InternalDict(dict):
# This class is needed while Dict is scheduled for deprecation:
# we only need to warn when a *user* instantiates Dict or when
# the "attribute notation for dict keys" is used.
+ __slots__ = ()
def __getattr__(self, attr):
try:
value = self[attr]
except KeyError:
raise AttributeError(attr)
- from warnings import warn
warn("Attribute access from plist dicts is deprecated, use d[key] "
"notation instead", DeprecationWarning, 2)
return value
def __setattr__(self, attr, value):
- from warnings import warn
warn("Attribute access from plist dicts is deprecated, use d[key] "
"notation instead", DeprecationWarning, 2)
self[attr] = value
@@ -286,56 +102,111 @@ class _InternalDict(dict):
del self[attr]
except KeyError:
raise AttributeError(attr)
- from warnings import warn
warn("Attribute access from plist dicts is deprecated, use d[key] "
"notation instead", DeprecationWarning, 2)
+
class Dict(_InternalDict):
def __init__(self, **kwargs):
- from warnings import warn
warn("The plistlib.Dict class is deprecated, use builtin dict instead",
DeprecationWarning, 2)
super().__init__(**kwargs)
-class Plist(_InternalDict):
+@contextlib.contextmanager
+def _maybe_open(pathOrFile, mode):
+ if isinstance(pathOrFile, str):
+ with open(pathOrFile, mode) as fp:
+ yield fp
+
+ else:
+ yield pathOrFile
+
- """This class has been deprecated. Use readPlist() and writePlist()
+class Plist(_InternalDict):
+ """This class has been deprecated. Use dump() and load()
functions instead, together with regular dict objects.
"""
def __init__(self, **kwargs):
- from warnings import warn
- warn("The Plist class is deprecated, use the readPlist() and "
- "writePlist() functions instead", DeprecationWarning, 2)
+ warn("The Plist class is deprecated, use the load() and "
+ "dump() functions instead", DeprecationWarning, 2)
super().__init__(**kwargs)
+ @classmethod
def fromFile(cls, pathOrFile):
- """Deprecated. Use the readPlist() function instead."""
- rootObject = readPlist(pathOrFile)
+ """Deprecated. Use the load() function instead."""
+ with _maybe_open(pathOrFile, 'rb') as fp:
+ value = load(fp)
plist = cls()
- plist.update(rootObject)
+ plist.update(value)
return plist
- fromFile = classmethod(fromFile)
def write(self, pathOrFile):
- """Deprecated. Use the writePlist() function instead."""
- writePlist(self, pathOrFile)
+ """Deprecated. Use the dump() function instead."""
+ with _maybe_open(pathOrFile, 'wb') as fp:
+ dump(self, fp)
-def _encodeBase64(s, maxlinelength=76):
- # copied from base64.encodebytes(), with added maxlinelength argument
- maxbinsize = (maxlinelength//4)*3
- pieces = []
- for i in range(0, len(s), maxbinsize):
- chunk = s[i : i + maxbinsize]
- pieces.append(binascii.b2a_base64(chunk))
- return b''.join(pieces)
+def readPlist(pathOrFile):
+ """
+ Read a .plist from a path or file. pathOrFile should either
+ be a file name, or a readable binary file object.
+
+ This function is deprecated, use load instead.
+ """
+ warn("The readPlist function is deprecated, use load() instead",
+ DeprecationWarning, 2)
+
+ with _maybe_open(pathOrFile, 'rb') as fp:
+ return load(fp, fmt=None, use_builtin_types=False,
+ dict_type=_InternalDict)
+
+def writePlist(value, pathOrFile):
+ """
+ Write 'value' to a .plist file. 'pathOrFile' may either be a
+ file name or a (writable) file object.
+
+ This function is deprecated, use dump instead.
+ """
+ warn("The writePlist function is deprecated, use dump() instead",
+ DeprecationWarning, 2)
+ with _maybe_open(pathOrFile, 'wb') as fp:
+ dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False)
+
+
+def readPlistFromBytes(data):
+ """
+ Read a plist data from a bytes object. Return the root object.
+
+ This function is deprecated, use loads instead.
+ """
+ warn("The readPlistFromBytes function is deprecated, use loads() instead",
+ DeprecationWarning, 2)
+ return load(BytesIO(data), fmt=None, use_builtin_types=False,
+ dict_type=_InternalDict)
+
+
+def writePlistToBytes(value):
+ """
+ Return 'value' as a plist-formatted bytes object.
+
+ This function is deprecated, use dumps instead.
+ """
+ warn("The writePlistToBytes function is deprecated, use dumps() instead",
+ DeprecationWarning, 2)
+ f = BytesIO()
+ dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False)
+ return f.getvalue()
+
class Data:
+ """
+ Wrapper for binary data.
- """Wrapper for binary data."""
+ This class is deprecated, use a bytes object instead.
+ """
def __init__(self, data):
if not isinstance(data, bytes):
@@ -346,10 +217,10 @@ class Data:
def fromBase64(cls, data):
# base64.decodebytes just calls binascii.a2b_base64;
# it seems overkill to use both base64 and binascii.
- return cls(binascii.a2b_base64(data))
+ return cls(_decode_base64(data))
def asBase64(self, maxlinelength=76):
- return _encodeBase64(self.data, maxlinelength)
+ return _encode_base64(self.data, maxlinelength)
def __eq__(self, other):
if isinstance(other, self.__class__):
@@ -362,43 +233,119 @@ class Data:
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, repr(self.data))
-class PlistParser:
+#
+#
+# End of deprecated functionality
+#
+#
+
+
+#
+# XML support
+#
+
+
+# XML 'header'
+PLISTHEADER = b"""\
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+"""
+
+
+# Regex to find any control chars, except for \t \n and \r
+_controlCharPat = re.compile(
+ r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
+ r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
+
+def _encode_base64(s, maxlinelength=76):
+ # copied from base64.encodebytes(), with added maxlinelength argument
+ maxbinsize = (maxlinelength//4)*3
+ pieces = []
+ for i in range(0, len(s), maxbinsize):
+ chunk = s[i : i + maxbinsize]
+ pieces.append(binascii.b2a_base64(chunk))
+ return b''.join(pieces)
+
+def _decode_base64(s):
+ if isinstance(s, str):
+ return binascii.a2b_base64(s.encode("utf-8"))
+
+ else:
+ return binascii.a2b_base64(s)
- def __init__(self):
+# Contents should conform to a subset of ISO 8601
+# (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. Smaller units
+# may be omitted with # a loss of precision)
+_dateParser = re.compile(r"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z", re.ASCII)
+
+
+def _date_from_string(s):
+ order = ('year', 'month', 'day', 'hour', 'minute', 'second')
+ gd = _dateParser.match(s).groupdict()
+ lst = []
+ for key in order:
+ val = gd[key]
+ if val is None:
+ break
+ lst.append(int(val))
+ return datetime.datetime(*lst)
+
+
+def _date_to_string(d):
+ return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
+ d.year, d.month, d.day,
+ d.hour, d.minute, d.second
+ )
+
+def _escape(text):
+ m = _controlCharPat.search(text)
+ if m is not None:
+ raise ValueError("strings can't contains control characters; "
+ "use bytes instead")
+ text = text.replace("\r\n", "\n") # convert DOS line endings
+ text = text.replace("\r", "\n") # convert Mac line endings
+ text = text.replace("&", "&amp;") # escape '&'
+ text = text.replace("<", "&lt;") # escape '<'
+ text = text.replace(">", "&gt;") # escape '>'
+ return text
+
+class _PlistParser:
+ def __init__(self, use_builtin_types, dict_type):
self.stack = []
- self.currentKey = None
+ self.current_key = None
self.root = None
+ self._use_builtin_types = use_builtin_types
+ self._dict_type = dict_type
def parse(self, fileobj):
- from xml.parsers.expat import ParserCreate
self.parser = ParserCreate()
- self.parser.StartElementHandler = self.handleBeginElement
- self.parser.EndElementHandler = self.handleEndElement
- self.parser.CharacterDataHandler = self.handleData
+ self.parser.StartElementHandler = self.handle_begin_element
+ self.parser.EndElementHandler = self.handle_end_element
+ self.parser.CharacterDataHandler = self.handle_data
self.parser.ParseFile(fileobj)
return self.root
- def handleBeginElement(self, element, attrs):
+ def handle_begin_element(self, element, attrs):
self.data = []
handler = getattr(self, "begin_" + element, None)
if handler is not None:
handler(attrs)
- def handleEndElement(self, element):
+ def handle_end_element(self, element):
handler = getattr(self, "end_" + element, None)
if handler is not None:
handler()
- def handleData(self, data):
+ def handle_data(self, data):
self.data.append(data)
- def addObject(self, value):
- if self.currentKey is not None:
+ def add_object(self, value):
+ if self.current_key is not None:
if not isinstance(self.stack[-1], type({})):
raise ValueError("unexpected element at line %d" %
self.parser.CurrentLineNumber)
- self.stack[-1][self.currentKey] = value
- self.currentKey = None
+ self.stack[-1][self.current_key] = value
+ self.current_key = None
elif not self.stack:
# this is the root object
self.root = value
@@ -408,7 +355,7 @@ class PlistParser:
self.parser.CurrentLineNumber)
self.stack[-1].append(value)
- def getData(self):
+ def get_data(self):
data = ''.join(self.data)
self.data = []
return data
@@ -416,39 +363,648 @@ class PlistParser:
# element handlers
def begin_dict(self, attrs):
- d = _InternalDict()
- self.addObject(d)
+ d = self._dict_type()
+ self.add_object(d)
self.stack.append(d)
+
def end_dict(self):
- if self.currentKey:
+ if self.current_key:
raise ValueError("missing value for key '%s' at line %d" %
- (self.currentKey,self.parser.CurrentLineNumber))
+ (self.current_key,self.parser.CurrentLineNumber))
self.stack.pop()
def end_key(self):
- if self.currentKey or not isinstance(self.stack[-1], type({})):
+ if self.current_key or not isinstance(self.stack[-1], type({})):
raise ValueError("unexpected key at line %d" %
self.parser.CurrentLineNumber)
- self.currentKey = self.getData()
+ self.current_key = self.get_data()
def begin_array(self, attrs):
a = []
- self.addObject(a)
+ self.add_object(a)
self.stack.append(a)
+
def end_array(self):
self.stack.pop()
def end_true(self):
- self.addObject(True)
+ self.add_object(True)
+
def end_false(self):
- self.addObject(False)
+ self.add_object(False)
+
def end_integer(self):
- self.addObject(int(self.getData()))
+ self.add_object(int(self.get_data()))
+
def end_real(self):
- self.addObject(float(self.getData()))
+ self.add_object(float(self.get_data()))
+
def end_string(self):
- self.addObject(self.getData())
+ self.add_object(self.get_data())
+
def end_data(self):
- self.addObject(Data.fromBase64(self.getData().encode("utf-8")))
+ if self._use_builtin_types:
+ self.add_object(_decode_base64(self.get_data()))
+
+ else:
+ self.add_object(Data.fromBase64(self.get_data()))
+
def end_date(self):
- self.addObject(_dateFromString(self.getData()))
+ self.add_object(_date_from_string(self.get_data()))
+
+
+class _DumbXMLWriter:
+ def __init__(self, file, indent_level=0, indent="\t"):
+ self.file = file
+ self.stack = []
+ self._indent_level = indent_level
+ self.indent = indent
+
+ def begin_element(self, element):
+ self.stack.append(element)
+ self.writeln("<%s>" % element)
+ self._indent_level += 1
+
+ def end_element(self, element):
+ assert self._indent_level > 0
+ assert self.stack.pop() == element
+ self._indent_level -= 1
+ self.writeln("</%s>" % element)
+
+ def simple_element(self, element, value=None):
+ if value is not None:
+ value = _escape(value)
+ self.writeln("<%s>%s</%s>" % (element, value, element))
+
+ else:
+ self.writeln("<%s/>" % element)
+
+ def writeln(self, line):
+ if line:
+ # plist has fixed encoding of utf-8
+
+ # XXX: is this test needed?
+ if isinstance(line, str):
+ line = line.encode('utf-8')
+ self.file.write(self._indent_level * self.indent)
+ self.file.write(line)
+ self.file.write(b'\n')
+
+
+class _PlistWriter(_DumbXMLWriter):
+ def __init__(
+ self, file, indent_level=0, indent=b"\t", writeHeader=1,
+ sort_keys=True, skipkeys=False):
+
+ if writeHeader:
+ file.write(PLISTHEADER)
+ _DumbXMLWriter.__init__(self, file, indent_level, indent)
+ self._sort_keys = sort_keys
+ self._skipkeys = skipkeys
+
+ def write(self, value):
+ self.writeln("<plist version=\"1.0\">")
+ self.write_value(value)
+ self.writeln("</plist>")
+
+ def write_value(self, value):
+ if isinstance(value, str):
+ self.simple_element("string", value)
+
+ elif value is True:
+ self.simple_element("true")
+
+ elif value is False:
+ self.simple_element("false")
+
+ elif isinstance(value, int):
+ self.simple_element("integer", "%d" % value)
+
+ elif isinstance(value, float):
+ self.simple_element("real", repr(value))
+
+ elif isinstance(value, dict):
+ self.write_dict(value)
+
+ elif isinstance(value, Data):
+ self.write_data(value)
+
+ elif isinstance(value, (bytes, bytearray)):
+ self.write_bytes(value)
+
+ elif isinstance(value, datetime.datetime):
+ self.simple_element("date", _date_to_string(value))
+
+ elif isinstance(value, (tuple, list)):
+ self.write_array(value)
+
+ else:
+ raise TypeError("unsupported type: %s" % type(value))
+
+ def write_data(self, data):
+ self.write_bytes(data.data)
+
+ def write_bytes(self, data):
+ self.begin_element("data")
+ self._indent_level -= 1
+ maxlinelength = max(
+ 16,
+ 76 - len(self.indent.replace(b"\t", b" " * 8) * self._indent_level))
+
+ for line in _encode_base64(data, maxlinelength).split(b"\n"):
+ if line:
+ self.writeln(line)
+ self._indent_level += 1
+ self.end_element("data")
+
+ def write_dict(self, d):
+ if d:
+ self.begin_element("dict")
+ if self._sort_keys:
+ items = sorted(d.items())
+ else:
+ items = d.items()
+
+ for key, value in items:
+ if not isinstance(key, str):
+ if self._skipkeys:
+ continue
+ raise TypeError("keys must be strings")
+ self.simple_element("key", key)
+ self.write_value(value)
+ self.end_element("dict")
+
+ else:
+ self.simple_element("dict")
+
+ def write_array(self, array):
+ if array:
+ self.begin_element("array")
+ for value in array:
+ self.write_value(value)
+ self.end_element("array")
+
+ else:
+ self.simple_element("array")
+
+
+def _is_fmt_xml(header):
+ prefixes = (b'<?xml', b'<plist')
+
+ for pfx in prefixes:
+ if header.startswith(pfx):
+ return True
+
+ # Also check for alternative XML encodings, this is slightly
+ # overkill because the Apple tools (and plistlib) will not
+ # generate files with these encodings.
+ for bom, encoding in (
+ (codecs.BOM_UTF8, "utf-8"),
+ (codecs.BOM_UTF16_BE, "utf-16-be"),
+ (codecs.BOM_UTF16_LE, "utf-16-le"),
+ # expat does not support utf-32
+ #(codecs.BOM_UTF32_BE, "utf-32-be"),
+ #(codecs.BOM_UTF32_LE, "utf-32-le"),
+ ):
+ if not header.startswith(bom):
+ continue
+
+ for start in prefixes:
+ prefix = bom + start.decode('ascii').encode(encoding)
+ if header[:len(prefix)] == prefix:
+ return True
+
+ return False
+
+#
+# Binary Plist
+#
+
+
+class InvalidFileException (ValueError):
+ def __init__(self, message="Invalid file"):
+ ValueError.__init__(self, message)
+
+_BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'}
+
+class _BinaryPlistParser:
+ """
+ Read or write a binary plist file, following the description of the binary
+ format. Raise InvalidFileException in case of error, otherwise return the
+ root object.
+
+ see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
+ """
+ def __init__(self, use_builtin_types, dict_type):
+ self._use_builtin_types = use_builtin_types
+ self._dict_type = dict_type
+
+ def parse(self, fp):
+ try:
+ # The basic file format:
+ # HEADER
+ # object...
+ # refid->offset...
+ # TRAILER
+ self._fp = fp
+ self._fp.seek(-32, os.SEEK_END)
+ trailer = self._fp.read(32)
+ if len(trailer) != 32:
+ raise InvalidFileException()
+ (
+ offset_size, self._ref_size, num_objects, top_object,
+ offset_table_offset
+ ) = struct.unpack('>6xBBQQQ', trailer)
+ self._fp.seek(offset_table_offset)
+ offset_format = '>' + _BINARY_FORMAT[offset_size] * num_objects
+ self._ref_format = _BINARY_FORMAT[self._ref_size]
+ self._object_offsets = struct.unpack(
+ offset_format, self._fp.read(offset_size * num_objects))
+ return self._read_object(self._object_offsets[top_object])
+
+ except (OSError, IndexError, struct.error):
+ raise InvalidFileException()
+
+ def _get_size(self, tokenL):
+ """ return the size of the next object."""
+ if tokenL == 0xF:
+ m = self._fp.read(1)[0] & 0x3
+ s = 1 << m
+ f = '>' + _BINARY_FORMAT[s]
+ return struct.unpack(f, self._fp.read(s))[0]
+
+ return tokenL
+
+ def _read_refs(self, n):
+ return struct.unpack(
+ '>' + self._ref_format * n, self._fp.read(n * self._ref_size))
+
+ def _read_object(self, offset):
+ """
+ read the object at offset.
+
+ May recursively read sub-objects (content of an array/dict/set)
+ """
+ self._fp.seek(offset)
+ token = self._fp.read(1)[0]
+ tokenH, tokenL = token & 0xF0, token & 0x0F
+
+ if token == 0x00:
+ return None
+
+ elif token == 0x08:
+ return False
+
+ elif token == 0x09:
+ return True
+
+ # The referenced source code also mentions URL (0x0c, 0x0d) and
+ # UUID (0x0e), but neither can be generated using the Cocoa libraries.
+
+ elif token == 0x0f:
+ return b''
+
+ elif tokenH == 0x10: # int
+ return int.from_bytes(self._fp.read(1 << tokenL), 'big')
+
+ elif token == 0x22: # real
+ return struct.unpack('>f', self._fp.read(4))[0]
+
+ elif token == 0x23: # real
+ return struct.unpack('>d', self._fp.read(8))[0]
+
+ elif token == 0x33: # date
+ f = struct.unpack('>d', self._fp.read(8))[0]
+ # timestamp 0 of binary plists corresponds to 1/1/2001
+ # (year of Mac OS X 10.0), instead of 1/1/1970.
+ return datetime.datetime.utcfromtimestamp(f + (31 * 365 + 8) * 86400)
+
+ elif tokenH == 0x40: # data
+ s = self._get_size(tokenL)
+ if self._use_builtin_types:
+ return self._fp.read(s)
+ else:
+ return Data(self._fp.read(s))
+
+ elif tokenH == 0x50: # ascii string
+ s = self._get_size(tokenL)
+ result = self._fp.read(s).decode('ascii')
+ return result
+
+ elif tokenH == 0x60: # unicode string
+ s = self._get_size(tokenL)
+ return self._fp.read(s * 2).decode('utf-16be')
+
+ # tokenH == 0x80 is documented as 'UID' and appears to be used for
+ # keyed-archiving, not in plists.
+
+ elif tokenH == 0xA0: # array
+ s = self._get_size(tokenL)
+ obj_refs = self._read_refs(s)
+ return [self._read_object(self._object_offsets[x])
+ for x in obj_refs]
+
+ # tokenH == 0xB0 is documented as 'ordset', but is not actually
+ # implemented in the Apple reference code.
+
+ # tokenH == 0xC0 is documented as 'set', but sets cannot be used in
+ # plists.
+
+ elif tokenH == 0xD0: # dict
+ s = self._get_size(tokenL)
+ key_refs = self._read_refs(s)
+ obj_refs = self._read_refs(s)
+ result = self._dict_type()
+ for k, o in zip(key_refs, obj_refs):
+ result[self._read_object(self._object_offsets[k])
+ ] = self._read_object(self._object_offsets[o])
+ return result
+
+ raise InvalidFileException()
+
+def _count_to_size(count):
+ if count < 1 << 8:
+ return 1
+
+ elif count < 1 << 16:
+ return 2
+
+ elif count << 1 << 32:
+ return 4
+
+ else:
+ return 8
+
+class _BinaryPlistWriter (object):
+ def __init__(self, fp, sort_keys, skipkeys):
+ self._fp = fp
+ self._sort_keys = sort_keys
+ self._skipkeys = skipkeys
+
+ def write(self, value):
+
+ # Flattened object list:
+ self._objlist = []
+
+ # Mappings from object->objectid
+ # First dict has (type(object), object) as the key,
+ # second dict is used when object is not hashable and
+ # has id(object) as the key.
+ self._objtable = {}
+ self._objidtable = {}
+
+ # Create list of all objects in the plist
+ self._flatten(value)
+
+ # Size of object references in serialized containers
+ # depends on the number of objects in the plist.
+ num_objects = len(self._objlist)
+ self._object_offsets = [0]*num_objects
+ self._ref_size = _count_to_size(num_objects)
+
+ self._ref_format = _BINARY_FORMAT[self._ref_size]
+
+ # Write file header
+ self._fp.write(b'bplist00')
+
+ # Write object list
+ for obj in self._objlist:
+ self._write_object(obj)
+
+ # Write refnum->object offset table
+ top_object = self._getrefnum(value)
+ offset_table_offset = self._fp.tell()
+ offset_size = _count_to_size(offset_table_offset)
+ offset_format = '>' + _BINARY_FORMAT[offset_size] * num_objects
+ self._fp.write(struct.pack(offset_format, *self._object_offsets))
+
+ # Write trailer
+ sort_version = 0
+ trailer = (
+ sort_version, offset_size, self._ref_size, num_objects,
+ top_object, offset_table_offset
+ )
+ self._fp.write(struct.pack('>5xBBBQQQ', *trailer))
+
+ def _flatten(self, value):
+ # First check if the object is in the object table, not used for
+ # containers to ensure that two subcontainers with the same contents
+ # will be serialized as distinct values.
+ if isinstance(value, (
+ str, int, float, datetime.datetime, bytes, bytearray)):
+ if (type(value), value) in self._objtable:
+ return
+
+ elif isinstance(value, Data):
+ if (type(value.data), value.data) in self._objtable:
+ return
+
+ # Add to objectreference map
+ refnum = len(self._objlist)
+ self._objlist.append(value)
+ try:
+ if isinstance(value, Data):
+ self._objtable[(type(value.data), value.data)] = refnum
+ else:
+ self._objtable[(type(value), value)] = refnum
+ except TypeError:
+ self._objidtable[id(value)] = refnum
+
+ # And finally recurse into containers
+ if isinstance(value, dict):
+ keys = []
+ values = []
+ items = value.items()
+ if self._sort_keys:
+ items = sorted(items)
+
+ for k, v in items:
+ if not isinstance(k, str):
+ if self._skipkeys:
+ continue
+ raise TypeError("keys must be strings")
+ keys.append(k)
+ values.append(v)
+
+ for o in itertools.chain(keys, values):
+ self._flatten(o)
+
+ elif isinstance(value, (list, tuple)):
+ for o in value:
+ self._flatten(o)
+
+ def _getrefnum(self, value):
+ try:
+ if isinstance(value, Data):
+ return self._objtable[(type(value.data), value.data)]
+ else:
+ return self._objtable[(type(value), value)]
+ except TypeError:
+ return self._objidtable[id(value)]
+
+ def _write_size(self, token, size):
+ if size < 15:
+ self._fp.write(struct.pack('>B', token | size))
+
+ elif size < 1 << 8:
+ self._fp.write(struct.pack('>BBB', token | 0xF, 0x10, size))
+
+ elif size < 1 << 16:
+ self._fp.write(struct.pack('>BBH', token | 0xF, 0x11, size))
+
+ elif size < 1 << 32:
+ self._fp.write(struct.pack('>BBL', token | 0xF, 0x12, size))
+
+ else:
+ self._fp.write(struct.pack('>BBQ', token | 0xF, 0x13, size))
+
+ def _write_object(self, value):
+ ref = self._getrefnum(value)
+ self._object_offsets[ref] = self._fp.tell()
+ if value is None:
+ self._fp.write(b'\x00')
+
+ elif value is False:
+ self._fp.write(b'\x08')
+
+ elif value is True:
+ self._fp.write(b'\x09')
+
+ elif isinstance(value, int):
+ if value < 1 << 8:
+ self._fp.write(struct.pack('>BB', 0x10, value))
+ elif value < 1 << 16:
+ self._fp.write(struct.pack('>BH', 0x11, value))
+ elif value < 1 << 32:
+ self._fp.write(struct.pack('>BL', 0x12, value))
+ else:
+ self._fp.write(struct.pack('>BQ', 0x13, value))
+
+ elif isinstance(value, float):
+ self._fp.write(struct.pack('>Bd', 0x23, value))
+
+ elif isinstance(value, datetime.datetime):
+ f = (value - datetime.datetime(2001, 1, 1)).total_seconds()
+ self._fp.write(struct.pack('>Bd', 0x33, f))
+
+ elif isinstance(value, Data):
+ self._write_size(0x40, len(value.data))
+ self._fp.write(value.data)
+
+ elif isinstance(value, (bytes, bytearray)):
+ self._write_size(0x40, len(value))
+ self._fp.write(value)
+
+ elif isinstance(value, str):
+ try:
+ t = value.encode('ascii')
+ self._write_size(0x50, len(value))
+ except UnicodeEncodeError:
+ t = value.encode('utf-16be')
+ self._write_size(0x60, len(value))
+
+ self._fp.write(t)
+
+ elif isinstance(value, (list, tuple)):
+ refs = [self._getrefnum(o) for o in value]
+ s = len(refs)
+ self._write_size(0xA0, s)
+ self._fp.write(struct.pack('>' + self._ref_format * s, *refs))
+
+ elif isinstance(value, dict):
+ keyRefs, valRefs = [], []
+
+ if self._sort_keys:
+ rootItems = sorted(value.items())
+ else:
+ rootItems = value.items()
+
+ for k, v in rootItems:
+ if not isinstance(k, str):
+ if self._skipkeys:
+ continue
+ raise TypeError("keys must be strings")
+ keyRefs.append(self._getrefnum(k))
+ valRefs.append(self._getrefnum(v))
+
+ s = len(keyRefs)
+ self._write_size(0xD0, s)
+ self._fp.write(struct.pack('>' + self._ref_format * s, *keyRefs))
+ self._fp.write(struct.pack('>' + self._ref_format * s, *valRefs))
+
+ else:
+ raise InvalidFileException()
+
+
+def _is_fmt_binary(header):
+ return header[:8] == b'bplist00'
+
+
+#
+# Generic bits
+#
+
+_FORMATS={
+ FMT_XML: dict(
+ detect=_is_fmt_xml,
+ parser=_PlistParser,
+ writer=_PlistWriter,
+ ),
+ FMT_BINARY: dict(
+ detect=_is_fmt_binary,
+ parser=_BinaryPlistParser,
+ writer=_BinaryPlistWriter,
+ )
+}
+
+
+def load(fp, *, fmt=None, use_builtin_types=True, dict_type=dict):
+ """Read a .plist file. 'fp' should be (readable) file object.
+ Return the unpacked root object (which usually is a dictionary).
+ """
+ if fmt is None:
+ header = fp.read(32)
+ fp.seek(0)
+ for info in _FORMATS.values():
+ if info['detect'](header):
+ p = info['parser'](
+ use_builtin_types=use_builtin_types,
+ dict_type=dict_type,
+ )
+ break
+
+ else:
+ raise InvalidFileException()
+
+ else:
+ p = _FORMATS[fmt]['parser'](use_builtin_types=use_builtin_types)
+
+ return p.parse(fp)
+
+
+def loads(value, *, fmt=None, use_builtin_types=True, dict_type=dict):
+ """Read a .plist file from a bytes object.
+ Return the unpacked root object (which usually is a dictionary).
+ """
+ fp = BytesIO(value)
+ return load(
+ fp, fmt=fmt, use_builtin_types=use_builtin_types, dict_type=dict_type)
+
+
+def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False):
+ """Write 'value' to a .plist file. 'fp' should be a (writable)
+ file object.
+ """
+ if fmt not in _FORMATS:
+ raise ValueError("Unsupported format: %r"%(fmt,))
+
+ writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys)
+ writer.write(value)
+
+
+def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True):
+ """Return a bytes object with the contents for a .plist file.
+ """
+ fp = BytesIO()
+ dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys)
+ return fp.getvalue()
diff --git a/Lib/poplib.py b/Lib/poplib.py
index 43f8305c48..00ffbcb166 100644
--- a/Lib/poplib.py
+++ b/Lib/poplib.py
@@ -13,7 +13,15 @@ Based on the J. Myers POP3 draft, Jan. 96
# Imports
-import re, socket
+import errno
+import re
+import socket
+
+try:
+ import ssl
+ HAVE_SSL = True
+except ImportError:
+ HAVE_SSL = False
__all__ = ["POP3","error_proto"]
@@ -61,6 +69,8 @@ class POP3:
APOP name digest apop(name, digest)
TOP msg n top(msg, n)
UIDL [msg] uidl(msg = None)
+ CAPA capa()
+ STLS stls()
Raises one exception: 'error_proto'.
@@ -87,6 +97,7 @@ class POP3:
timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
self.host = host
self.port = port
+ self._tls_established = False
self.sock = self._create_socket(timeout)
self.file = self.sock.makefile('rb')
self._debugging = 0
@@ -268,7 +279,14 @@ class POP3:
if self.file is not None:
self.file.close()
if self.sock is not None:
- self.sock.close()
+ try:
+ self.sock.shutdown(socket.SHUT_RDWR)
+ except OSError as e:
+ # The server might already have closed the connection
+ if e.errno != errno.ENOTCONN:
+ raise
+ finally:
+ self.sock.close()
self.file = self.sock = None
#__del__ = quit
@@ -324,21 +342,70 @@ class POP3:
return self._shortcmd('UIDL %s' % which)
return self._longcmd('UIDL')
-try:
- import ssl
-except ImportError:
- pass
-else:
+
+ def capa(self):
+ """Return server capabilities (RFC 2449) as a dictionary
+ >>> c=poplib.POP3('localhost')
+ >>> c.capa()
+ {'IMPLEMENTATION': ['Cyrus', 'POP3', 'server', 'v2.2.12'],
+ 'TOP': [], 'LOGIN-DELAY': ['0'], 'AUTH-RESP-CODE': [],
+ 'EXPIRE': ['NEVER'], 'USER': [], 'STLS': [], 'PIPELINING': [],
+ 'UIDL': [], 'RESP-CODES': []}
+ >>>
+
+ Really, according to RFC 2449, the cyrus folks should avoid
+ having the implementation split into multiple arguments...
+ """
+ def _parsecap(line):
+ lst = line.decode('ascii').split()
+ return lst[0], lst[1:]
+
+ caps = {}
+ try:
+ resp = self._longcmd('CAPA')
+ rawcaps = resp[1]
+ for capline in rawcaps:
+ capnm, capargs = _parsecap(capline)
+ caps[capnm] = capargs
+ except error_proto as _err:
+ raise error_proto('-ERR CAPA not supported by server')
+ return caps
+
+
+ def stls(self, context=None):
+ """Start a TLS session on the active connection as specified in RFC 2595.
+
+ context - a ssl.SSLContext
+ """
+ if not HAVE_SSL:
+ raise error_proto('-ERR TLS support missing')
+ if self._tls_established:
+ raise error_proto('-ERR TLS session already established')
+ caps = self.capa()
+ if not 'STLS' in caps:
+ raise error_proto('-ERR STLS not supported by server')
+ if context is None:
+ context = ssl._create_stdlib_context()
+ resp = self._shortcmd('STLS')
+ self.sock = context.wrap_socket(self.sock)
+ self.file = self.sock.makefile('rb')
+ self._tls_established = True
+ return resp
+
+
+if HAVE_SSL:
class POP3_SSL(POP3):
"""POP3 client class over SSL connection
- Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
+ Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None,
+ context=None)
hostname - the hostname of the pop3 over ssl server
port - port number
keyfile - PEM formatted file that contains your private key
certfile - PEM formatted certificate chain file
+ context - a ssl.SSLContext
See the methods of the parent class POP3 for more documentation.
"""
@@ -353,17 +420,24 @@ else:
"exclusive")
self.keyfile = keyfile
self.certfile = certfile
+ if context is None:
+ context = ssl._create_stdlib_context(certfile=certfile,
+ keyfile=keyfile)
self.context = context
POP3.__init__(self, host, port, timeout)
def _create_socket(self, timeout):
sock = POP3._create_socket(self, timeout)
- if self.context is not None:
- sock = self.context.wrap_socket(sock)
- else:
- sock = ssl.wrap_socket(sock, self.keyfile, self.certfile)
+ sock = self.context.wrap_socket(sock)
return sock
+ def stls(self, keyfile=None, certfile=None, context=None):
+ """The method unconditionally raises an exception since the
+ STLS command doesn't make any sense on an already established
+ SSL/TLS session.
+ """
+ raise error_proto('-ERR TLS session already established')
+
__all__.append("POP3_SSL")
if __name__ == "__main__":
diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index b1e1a9255e..492c415aa5 100644
--- a/Lib/posixpath.py
+++ b/Lib/posixpath.py
@@ -162,7 +162,7 @@ def islink(path):
"""Test whether a path is a symbolic link"""
try:
st = os.lstat(path)
- except (os.error, AttributeError):
+ except (OSError, AttributeError):
return False
return stat.S_ISLNK(st.st_mode)
@@ -172,56 +172,35 @@ def lexists(path):
"""Test whether a path exists. Returns True for broken symbolic links"""
try:
os.lstat(path)
- except os.error:
+ except OSError:
return False
return True
-# Are two filenames really pointing to the same file?
-
-def samefile(f1, f2):
- """Test whether two pathnames reference the same actual file"""
- s1 = os.stat(f1)
- s2 = os.stat(f2)
- return samestat(s1, s2)
-
-
-# Are two open files really referencing the same file?
-# (Not necessarily the same file descriptor!)
-
-def sameopenfile(fp1, fp2):
- """Test whether two open file objects reference the same file"""
- s1 = os.fstat(fp1)
- s2 = os.fstat(fp2)
- return samestat(s1, s2)
-
-
-# Are two stat buffers (obtained from stat, fstat or lstat)
-# describing the same file?
-
-def samestat(s1, s2):
- """Test whether two stat buffers reference the same file"""
- return s1.st_ino == s2.st_ino and \
- s1.st_dev == s2.st_dev
-
-
# Is a path a mount point?
# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
def ismount(path):
"""Test whether a path is a mount point"""
- if islink(path):
- # A symlink can never be a mount point
- return False
try:
s1 = os.lstat(path)
- if isinstance(path, bytes):
- parent = join(path, b'..')
- else:
- parent = join(path, '..')
+ except OSError:
+ # It doesn't exist -- so not a mount point. :-)
+ return False
+ else:
+ # A symlink can never be a mount point
+ if stat.S_ISLNK(s1.st_mode):
+ return False
+
+ if isinstance(path, bytes):
+ parent = join(path, b'..')
+ else:
+ parent = join(path, '..')
+ try:
s2 = os.lstat(parent)
- except os.error:
- return False # It doesn't exist -- so not a mount point :-)
+ except OSError:
+ return False
+
dev1 = s1.st_dev
dev2 = s2.st_dev
if dev1 != dev2:
diff --git a/Lib/pprint.py b/Lib/pprint.py
index 22be0b4bdb..3be9c36254 100644
--- a/Lib/pprint.py
+++ b/Lib/pprint.py
@@ -34,6 +34,7 @@ saferepr()
"""
+import re
import sys as _sys
from collections import OrderedDict as _OrderedDict
from io import StringIO as _StringIO
@@ -41,22 +42,19 @@ from io import StringIO as _StringIO
__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
"PrettyPrinter"]
-# cache these for faster access:
-_commajoin = ", ".join
-_id = id
-_len = len
-_type = type
-
-def pprint(object, stream=None, indent=1, width=80, depth=None):
+def pprint(object, stream=None, indent=1, width=80, depth=None, *,
+ compact=False):
"""Pretty-print a Python object to a stream [default is sys.stdout]."""
printer = PrettyPrinter(
- stream=stream, indent=indent, width=width, depth=depth)
+ stream=stream, indent=indent, width=width, depth=depth,
+ compact=compact)
printer.pprint(object)
-def pformat(object, indent=1, width=80, depth=None):
+def pformat(object, indent=1, width=80, depth=None, *, compact=False):
"""Format a Python object into a pretty-printed representation."""
- return PrettyPrinter(indent=indent, width=width, depth=depth).pformat(object)
+ return PrettyPrinter(indent=indent, width=width, depth=depth,
+ compact=compact).pformat(object)
def saferepr(object):
"""Version of repr() which can handle recursive data structures."""
@@ -101,7 +99,8 @@ def _safe_tuple(t):
return _safe_key(t[0]), _safe_key(t[1])
class PrettyPrinter:
- def __init__(self, indent=1, width=80, depth=None, stream=None):
+ def __init__(self, indent=1, width=80, depth=None, stream=None, *,
+ compact=False):
"""Handle pretty printing operations onto a stream using a set of
configured parameters.
@@ -118,6 +117,9 @@ class PrettyPrinter:
The desired output stream. If omitted (or false), the standard
output stream available at construction will be used.
+ compact
+ If true, several items will be combined in one line.
+
"""
indent = int(indent)
width = int(width)
@@ -131,6 +133,7 @@ class PrettyPrinter:
self._stream = stream
else:
self._stream = _sys.stdout
+ self._compact = bool(compact)
def pprint(self, object):
self._format(object, self._stream, 0, 0, {}, 0)
@@ -150,28 +153,25 @@ class PrettyPrinter:
def _format(self, object, stream, indent, allowance, context, level):
level = level + 1
- objid = _id(object)
+ objid = id(object)
if objid in context:
stream.write(_recursion(object))
self._recursive = True
self._readable = False
return
rep = self._repr(object, context, level - 1)
- typ = _type(object)
- sepLines = _len(rep) > (self._width - 1 - indent - allowance)
+ typ = type(object)
+ max_width = self._width - 1 - indent - allowance
+ sepLines = len(rep) > max_width
write = stream.write
- if self._depth and level > self._depth:
- write(rep)
- return
-
if sepLines:
r = getattr(typ, "__repr__", None)
if issubclass(typ, dict):
write('{')
if self._indent_per_level > 1:
write((self._indent_per_level - 1) * ' ')
- length = _len(object)
+ length = len(object)
if length:
context[objid] = 1
indent = indent + self._indent_per_level
@@ -183,13 +183,13 @@ class PrettyPrinter:
rep = self._repr(key, context, level)
write(rep)
write(': ')
- self._format(ent, stream, indent + _len(rep) + 2,
+ self._format(ent, stream, indent + len(rep) + 2,
allowance + 1, context, level)
if length > 1:
for key, ent in items[1:]:
rep = self._repr(key, context, level)
write(',\n%s%s: ' % (' '*indent, rep))
- self._format(ent, stream, indent + _len(rep) + 2,
+ self._format(ent, stream, indent + len(rep) + 2,
allowance + 1, context, level)
indent = indent - self._indent_per_level
del context[objid]
@@ -201,7 +201,7 @@ class PrettyPrinter:
(issubclass(typ, set) and r is set.__repr__) or
(issubclass(typ, frozenset) and r is frozenset.__repr__)
):
- length = _len(object)
+ length = len(object)
if issubclass(typ, list):
write('[')
endchar = ']'
@@ -225,23 +225,71 @@ class PrettyPrinter:
write((self._indent_per_level - 1) * ' ')
if length:
context[objid] = 1
- indent = indent + self._indent_per_level
- self._format(object[0], stream, indent, allowance + 1,
- context, level)
- if length > 1:
- for ent in object[1:]:
- write(',\n' + ' '*indent)
- self._format(ent, stream, indent,
- allowance + 1, context, level)
- indent = indent - self._indent_per_level
+ self._format_items(object, stream,
+ indent + self._indent_per_level,
+ allowance + 1, context, level)
del context[objid]
if issubclass(typ, tuple) and length == 1:
write(',')
write(endchar)
return
+ if issubclass(typ, str) and len(object) > 0 and r is str.__repr__:
+ def _str_parts(s):
+ """
+ Return a list of string literals comprising the repr()
+ of the given string using literal concatenation.
+ """
+ lines = s.splitlines(True)
+ for i, line in enumerate(lines):
+ rep = repr(line)
+ if len(rep) <= max_width:
+ yield rep
+ else:
+ # A list of alternating (non-space, space) strings
+ parts = re.split(r'(\s+)', line) + ['']
+ current = ''
+ for i in range(0, len(parts), 2):
+ part = parts[i] + parts[i+1]
+ candidate = current + part
+ if len(repr(candidate)) > max_width:
+ if current:
+ yield repr(current)
+ current = part
+ else:
+ current = candidate
+ if current:
+ yield repr(current)
+ for i, rep in enumerate(_str_parts(object)):
+ if i > 0:
+ write('\n' + ' '*indent)
+ write(rep)
+ return
write(rep)
+ def _format_items(self, items, stream, indent, allowance, context, level):
+ write = stream.write
+ delimnl = ',\n' + ' ' * indent
+ delim = ''
+ width = max_width = self._width - indent - allowance + 2
+ for ent in items:
+ if self._compact:
+ rep = self._repr(ent, context, level)
+ w = len(rep) + 2
+ if width < w:
+ width = max_width
+ if delim:
+ delim = delimnl
+ if width >= w:
+ width -= w
+ write(delim)
+ delim = ', '
+ write(rep)
+ continue
+ write(delim)
+ delim = delimnl
+ self._format(ent, stream, indent, allowance, context, level)
+
def _repr(self, object, context, level):
repr, readable, recursive = self.format(object, context.copy(),
self._depth, level)
@@ -262,7 +310,7 @@ class PrettyPrinter:
# Return triple (repr_string, isreadable, isrecursive).
def _safe_repr(object, context, maxlevels, level):
- typ = _type(object)
+ typ = type(object)
if typ is str:
if 'locale' not in _sys.modules:
return repr(object), True, False
@@ -286,7 +334,7 @@ def _safe_repr(object, context, maxlevels, level):
if issubclass(typ, dict) and r is dict.__repr__:
if not object:
return "{}", True, False
- objid = _id(object)
+ objid = id(object)
if maxlevels and level >= maxlevels:
return "{...}", False, objid in context
if objid in context:
@@ -307,7 +355,7 @@ def _safe_repr(object, context, maxlevels, level):
if krecur or vrecur:
recursive = True
del context[objid]
- return "{%s}" % _commajoin(components), readable, recursive
+ return "{%s}" % ", ".join(components), readable, recursive
if (issubclass(typ, list) and r is list.__repr__) or \
(issubclass(typ, tuple) and r is tuple.__repr__):
@@ -315,13 +363,13 @@ def _safe_repr(object, context, maxlevels, level):
if not object:
return "[]", True, False
format = "[%s]"
- elif _len(object) == 1:
+ elif len(object) == 1:
format = "(%s,)"
else:
if not object:
return "()", True, False
format = "(%s)"
- objid = _id(object)
+ objid = id(object)
if maxlevels and level >= maxlevels:
return format % "...", False, objid in context
if objid in context:
@@ -340,7 +388,7 @@ def _safe_repr(object, context, maxlevels, level):
if orecur:
recursive = True
del context[objid]
- return format % _commajoin(components), readable, recursive
+ return format % ", ".join(components), readable, recursive
rep = repr(object)
return rep, (rep and not rep.startswith('<')), False
@@ -348,7 +396,7 @@ def _safe_repr(object, context, maxlevels, level):
def _recursion(object):
return ("<Recursion on %s with id=%s>"
- % (_type(object).__name__, _id(object)))
+ % (type(object).__name__, id(object)))
def _perfcheck(object=None):
diff --git a/Lib/profile.py b/Lib/profile.py
index 743e77d7bf..5d0e9685a4 100755
--- a/Lib/profile.py
+++ b/Lib/profile.py
@@ -40,6 +40,40 @@ __all__ = ["run", "runctx", "Profile"]
# return i_count
#itimes = integer_timer # replace with C coded timer returning integers
+class _Utils:
+ """Support class for utility functions which are shared by
+ profile.py and cProfile.py modules.
+ Not supposed to be used directly.
+ """
+
+ def __init__(self, profiler):
+ self.profiler = profiler
+
+ def run(self, statement, filename, sort):
+ prof = self.profiler()
+ try:
+ prof.run(statement)
+ except SystemExit:
+ pass
+ finally:
+ self._show(prof, filename, sort)
+
+ def runctx(self, statement, globals, locals, filename, sort):
+ prof = self.profiler()
+ try:
+ prof.runctx(statement, globals, locals)
+ except SystemExit:
+ pass
+ finally:
+ self._show(prof, filename, sort)
+
+ def _show(self, prof, filename, sort):
+ if filename is not None:
+ prof.dump_stats(filename)
+ else:
+ prof.print_stats(sort)
+
+
#**************************************************************************
# The following are the static member functions for the profiler class
# Note that an instance of Profile() is *not* needed to call them.
@@ -56,15 +90,7 @@ def run(statement, filename=None, sort=-1):
standard name string (file/line/function-name) that is presented in
each line.
"""
- prof = Profile()
- try:
- prof = prof.run(statement)
- except SystemExit:
- pass
- if filename is not None:
- prof.dump_stats(filename)
- else:
- return prof.print_stats(sort)
+ return _Utils(Profile).run(statement, filename, sort)
def runctx(statement, globals, locals, filename=None, sort=-1):
"""Run statement under profiler, supplying your own globals and locals,
@@ -72,16 +98,8 @@ def runctx(statement, globals, locals, filename=None, sort=-1):
statement and filename have the same semantics as profile.run
"""
- prof = Profile()
- try:
- prof = prof.runctx(statement, globals, locals)
- except SystemExit:
- pass
-
- if filename is not None:
- prof.dump_stats(filename)
- else:
- return prof.print_stats(sort)
+ return _Utils(Profile).runctx(statement, globals, locals, filename, sort)
+
class Profile:
"""Profiler class.
@@ -373,10 +391,9 @@ class Profile:
print_stats()
def dump_stats(self, file):
- f = open(file, 'wb')
- self.create_stats()
- marshal.dump(self.stats, f)
- f.close()
+ with open(file, 'wb') as f:
+ self.create_stats()
+ marshal.dump(self.stats, f)
def create_stats(self):
self.simulate_cmd_complete()
diff --git a/Lib/pstats.py b/Lib/pstats.py
index 6a77605e14..e1ec355740 100644
--- a/Lib/pstats.py
+++ b/Lib/pstats.py
@@ -93,9 +93,8 @@ class Stats:
self.stats = {}
return
elif isinstance(arg, str):
- f = open(arg, 'rb')
- self.stats = marshal.load(f)
- f.close()
+ with open(arg, 'rb') as f:
+ self.stats = marshal.load(f)
try:
file_stats = os.stat(arg)
arg = time.ctime(file_stats.st_mtime) + " " + arg
@@ -149,11 +148,8 @@ class Stats:
def dump_stats(self, filename):
"""Write the profile data to a file we know how to load back."""
- f = open(filename, 'wb')
- try:
+ with open(filename, 'wb') as f:
marshal.dump(self.stats, f)
- finally:
- f.close()
# list the tuple indices and directions for sorting,
# along with some printable description
@@ -612,7 +608,7 @@ if __name__ == '__main__':
if line:
try:
self.stats = Stats(line)
- except IOError as err:
+ except OSError as err:
print(err.args[1], file=self.stream)
return
except Exception as err:
diff --git a/Lib/pty.py b/Lib/pty.py
index 3ccf619896..e841f12f3e 100644
--- a/Lib/pty.py
+++ b/Lib/pty.py
@@ -47,27 +47,16 @@ def master_open():
return _open_terminal()
def _open_terminal():
- """Open pty master and return (master_fd, tty_name).
- SGI and generic BSD version, for when openpty() fails."""
- try:
- import sgi
- except ImportError:
- pass
- else:
- try:
- tty_name, master_fd = sgi._getpty(os.O_RDWR, 0o666, 0)
- except IOError as msg:
- raise os.error(msg)
- return master_fd, tty_name
+ """Open pty master and return (master_fd, tty_name)."""
for x in 'pqrstuvwxyzPQRST':
for y in '0123456789abcdef':
pty_name = '/dev/pty' + x + y
try:
fd = os.open(pty_name, os.O_RDWR)
- except os.error:
+ except OSError:
continue
return (fd, '/dev/tty' + x + y)
- raise os.error('out of pty devices')
+ raise OSError('out of pty devices')
def slave_open(tty_name):
"""slave_open(tty_name) -> slave_fd
@@ -83,7 +72,7 @@ def slave_open(tty_name):
try:
ioctl(result, I_PUSH, "ptem")
ioctl(result, I_PUSH, "ldterm")
- except IOError:
+ except OSError:
pass
return result
@@ -173,8 +162,9 @@ def spawn(argv, master_read=_read, stdin_read=_read):
restore = 0
try:
_copy(master_fd, master_read, stdin_read)
- except (IOError, OSError):
+ except OSError:
if restore:
tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
os.close(master_fd)
+ return os.waitpid(pid, 0)[1]
diff --git a/Lib/py_compile.py b/Lib/py_compile.py
index 62d69ad448..1277b93ea5 100644
--- a/Lib/py_compile.py
+++ b/Lib/py_compile.py
@@ -3,17 +3,14 @@
This module has intimate knowledge of the format of .pyc files.
"""
-import builtins
-import errno
-import imp
-import marshal
+import importlib._bootstrap
+import importlib.machinery
+import importlib.util
import os
+import os.path
import sys
-import tokenize
import traceback
-MAGIC = imp.get_magic()
-
__all__ = ["compile", "main", "PyCompileError"]
@@ -65,13 +62,6 @@ class PyCompileError(Exception):
return self.msg
-def wr_long(f, x):
- """Internal; write a 32-bit int to a file in little-endian order."""
- f.write(bytes([x & 0xff,
- (x >> 8) & 0xff,
- (x >> 16) & 0xff,
- (x >> 24) & 0xff]))
-
def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1):
"""Byte-compile one Python source file to Python bytecode.
@@ -107,18 +97,31 @@ def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1):
See compileall.py for a script/module that uses this module to
byte-compile all installed files (or all files in selected
directories).
+
+ Do note that FileExistsError is raised if cfile ends up pointing at a
+ non-regular file or symlink. Because the compilation uses a file renaming,
+ the resulting file would be regular and thus not the same type of file as
+ it was previously.
"""
- with tokenize.open(file) as f:
- try:
- st = os.fstat(f.fileno())
- except AttributeError:
- st = os.stat(file)
- timestamp = int(st.st_mtime)
- size = st.st_size & 0xFFFFFFFF
- codestring = f.read()
+ if cfile is None:
+ if optimize >= 0:
+ cfile = importlib.util.cache_from_source(file,
+ debug_override=not optimize)
+ else:
+ cfile = importlib.util.cache_from_source(file)
+ if os.path.islink(cfile):
+ msg = ('{} is a symlink and will be changed into a regular file if '
+ 'import writes a byte-compiled file to it')
+ raise FileExistsError(msg.format(cfile))
+ elif os.path.exists(cfile) and not os.path.isfile(cfile):
+ msg = ('{} is a non-regular file and will be changed into a regular '
+ 'one if import writes a byte-compiled file to it')
+ raise FileExistsError(msg.format(cfile))
+ loader = importlib.machinery.SourceFileLoader('<py_compile>', file)
+ source_bytes = loader.get_data(file)
try:
- codeobject = builtins.compile(codestring, dfile or file, 'exec',
- optimize=optimize)
+ code = loader.source_to_code(source_bytes, dfile or file,
+ _optimize=optimize)
except Exception as err:
py_exc = PyCompileError(err.__class__, err, dfile or file)
if doraise:
@@ -126,28 +129,20 @@ def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1):
else:
sys.stderr.write(py_exc.msg + '\n')
return
- if cfile is None:
- if optimize >= 0:
- cfile = imp.cache_from_source(file, debug_override=not optimize)
- else:
- cfile = imp.cache_from_source(file)
try:
dirname = os.path.dirname(cfile)
if dirname:
os.makedirs(dirname)
- except OSError as error:
- if error.errno != errno.EEXIST:
- raise
- with open(cfile, 'wb') as fc:
- fc.write(b'\0\0\0\0')
- wr_long(fc, timestamp)
- wr_long(fc, size)
- marshal.dump(codeobject, fc)
- fc.flush()
- fc.seek(0, 0)
- fc.write(MAGIC)
+ except FileExistsError:
+ pass
+ source_stats = loader.path_stats(file)
+ bytecode = importlib._bootstrap._code_to_bytecode(
+ code, source_stats['mtime'], source_stats['size'])
+ mode = importlib._bootstrap._calc_mode(file)
+ importlib._bootstrap._write_atomic(cfile, bytecode, mode)
return cfile
+
def main(args=None):
"""Compile several source files.
@@ -173,7 +168,7 @@ def main(args=None):
except PyCompileError as error:
rv = 1
sys.stderr.write("%s\n" % error.msg)
- except IOError as error:
+ except OSError as error:
rv = 1
sys.stderr.write("%s\n" % error)
else:
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 9dce07976d..2e632266eb 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -44,16 +44,16 @@ Richard Chamberlain, for the first implementation of textdoc.
"""
# Known bugs that can't be fixed here:
-# - imp.load_module() cannot be prevented from clobbering existing
-# loaded modules, so calling synopsis() on a binary module file
-# changes the contents of any existing module with the same name.
+# - synopsis() cannot be prevented from clobbering existing
+# loaded modules.
# - If the __file__ attribute on a module is a relative path and
# the current directory is changed with os.chdir(), an incorrect
# path will be displayed.
import builtins
-import imp
+import importlib._bootstrap
import importlib.machinery
+import importlib.util
import inspect
import io
import os
@@ -167,8 +167,9 @@ def _split_list(s, predicate):
def visiblename(name, all=None, obj=None):
"""Decide whether to show documentation on a variable."""
# Certain special names are redundant or internal.
+ # XXX Remove __initializing__?
if name in {'__author__', '__builtins__', '__cached__', '__credits__',
- '__date__', '__doc__', '__file__', '__initializing__',
+ '__date__', '__doc__', '__file__', '__spec__',
'__loader__', '__module__', '__name__', '__package__',
'__path__', '__qualname__', '__slots__', '__version__'}:
return 0
@@ -226,7 +227,7 @@ def synopsis(filename, cache={}):
if lastupdate is None or lastupdate < mtime:
try:
file = tokenize.open(filename)
- except IOError:
+ except OSError:
# module can't be opened, so skip it
return None
binary_suffixes = importlib.machinery.BYTECODE_SUFFIXES[:]
@@ -267,20 +268,19 @@ class ErrorDuringImport(Exception):
def importfile(path):
"""Import a Python source file or compiled file given its path."""
- magic = imp.get_magic()
+ magic = importlib.util.MAGIC_NUMBER
with open(path, 'rb') as file:
- if file.read(len(magic)) == magic:
- kind = imp.PY_COMPILED
- else:
- kind = imp.PY_SOURCE
- file.seek(0)
- filename = os.path.basename(path)
- name, ext = os.path.splitext(filename)
- try:
- module = imp.load_module(name, file, path, (ext, 'r', kind))
- except:
- raise ErrorDuringImport(path, sys.exc_info())
- return module
+ is_bytecode = magic == file.read(len(magic))
+ filename = os.path.basename(path)
+ name, ext = os.path.splitext(filename)
+ if is_bytecode:
+ loader = importlib._bootstrap.SourcelessFileLoader(name, path)
+ else:
+ loader = importlib._bootstrap.SourceFileLoader(name, path)
+ try:
+ return loader.load_module(name)
+ except:
+ raise ErrorDuringImport(path, sys.exc_info())
def safeimport(path, forceload=0, cache={}):
"""Import a module; handle errors; return None if the module isn't found.
@@ -916,20 +916,18 @@ class HTMLDoc(Doc):
reallink = realname
title = '<a name="%s"><strong>%s</strong></a> = %s' % (
anchor, name, reallink)
- if inspect.isfunction(object):
- args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann = \
- inspect.getfullargspec(object)
- argspec = inspect.formatargspec(
- args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann,
- formatvalue=self.formatvalue,
- formatannotation=inspect.formatannotationrelativeto(object))
- if realname == '<lambda>':
- title = '<strong>%s</strong> <em>lambda</em> ' % name
- # XXX lambda's won't usually have func_annotations['return']
- # since the syntax doesn't support but it is possible.
- # So removing parentheses isn't truly safe.
- argspec = argspec[1:-1] # remove parentheses
- else:
+ argspec = None
+ if inspect.isfunction(object) or inspect.isbuiltin(object):
+ signature = inspect.signature(object)
+ if signature:
+ argspec = str(signature)
+ if realname == '<lambda>':
+ title = '<strong>%s</strong> <em>lambda</em> ' % name
+ # XXX lambda's won't usually have func_annotations['return']
+ # since the syntax doesn't support but it is possible.
+ # So removing parentheses isn't truly safe.
+ argspec = argspec[1:-1] # remove parentheses
+ if not argspec:
argspec = '(...)'
decl = title + argspec + (note and self.grey(
@@ -1236,8 +1234,9 @@ location listed above.
doc = getdoc(value)
else:
doc = None
- push(self.docother(getattr(object, name),
- name, mod, maxlen=70, doc=doc) + '\n')
+ push(self.docother(
+ getattr(object, name, None) or homecls.__dict__[name],
+ name, mod, maxlen=70, doc=doc) + '\n')
return attrs
attrs = [(name, kind, cls, value)
@@ -1259,7 +1258,6 @@ location listed above.
else:
tag = "inherited from %s" % classname(thisclass,
object.__module__)
-
# Sort attrs by name.
attrs.sort()
@@ -1274,6 +1272,7 @@ location listed above.
lambda t: t[1] == 'data descriptor')
attrs = spilldata("Data and other attributes %s:\n" % tag, attrs,
lambda t: t[1] == 'data')
+
assert attrs == []
attrs = inherited
@@ -1312,20 +1311,18 @@ location listed above.
cl.__dict__[realname] is object):
skipdocs = 1
title = self.bold(name) + ' = ' + realname
- if inspect.isfunction(object):
- args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann = \
- inspect.getfullargspec(object)
- argspec = inspect.formatargspec(
- args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann,
- formatvalue=self.formatvalue,
- formatannotation=inspect.formatannotationrelativeto(object))
- if realname == '<lambda>':
- title = self.bold(name) + ' lambda '
- # XXX lambda's won't usually have func_annotations['return']
- # since the syntax doesn't support but it is possible.
- # So removing parentheses isn't truly safe.
- argspec = argspec[1:-1] # remove parentheses
- else:
+ argspec = None
+ if inspect.isfunction(object) or inspect.isbuiltin(object):
+ signature = inspect.signature(object)
+ if signature:
+ argspec = str(signature)
+ if realname == '<lambda>':
+ title = self.bold(name) + ' lambda '
+ # XXX lambda's won't usually have func_annotations['return']
+ # since the syntax doesn't support but it is possible.
+ # So removing parentheses isn't truly safe.
+ argspec = argspec[1:-1] # remove parentheses
+ if not argspec:
argspec = '(...)'
decl = title + argspec + note
@@ -1396,7 +1393,7 @@ def getpager():
return lambda text: pipepager(text, os.environ['PAGER'])
if os.environ.get('TERM') in ('dumb', 'emacs'):
return plainpager
- if sys.platform == 'win32' or sys.platform.startswith('os2'):
+ if sys.platform == 'win32':
return lambda text: tempfilepager(plain(text), 'more <')
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
return lambda text: pipepager(text, 'less')
@@ -1422,16 +1419,15 @@ def pipepager(text, cmd):
try:
pipe.write(text)
pipe.close()
- except IOError:
+ except OSError:
pass # Ignore broken pipes caused by quitting the pager program.
def tempfilepager(text, cmd):
"""Page through text by invoking a program on a temporary file."""
import tempfile
filename = tempfile.mktemp()
- file = open(filename, 'w')
- file.write(text)
- file.close()
+ with open(filename, 'w') as file:
+ file.write(text)
try:
os.system(cmd + ' "' + filename + '"')
finally:
@@ -1850,10 +1846,10 @@ Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules. To quit this help utility and
return to the interpreter, just type "quit".
-To get a list of available modules, keywords, or topics, type "modules",
-"keywords", or "topics". Each module also comes with a one-line summary
-of what it does; to list the modules whose summaries contain a given word
-such as "spam", type "modules spam".
+To get a list of available modules, keywords, symbols, or topics, type
+"modules", "keywords", "symbols", or "topics". Each module also comes
+with a one-line summary of what it does; to list the modules whose name
+or summary contain a given string such as "spam", type "modules spam".
''' % tuple([sys.version[:3]]*2))
def list(self, items, columns=4, width=80):
@@ -1917,11 +1913,10 @@ module "pydoc_data.topics" could not be found.
if more_xrefs:
xrefs = (xrefs or '') + ' ' + more_xrefs
if xrefs:
- import formatter
- buffer = io.StringIO()
- formatter.DumbWriter(buffer).send_flowing_data(
- 'Related help topics: ' + ', '.join(xrefs.split()) + '\n')
- self.output.write('\n%s\n' % buffer.getvalue())
+ import textwrap
+ text = 'Related help topics: ' + ', '.join(xrefs.split()) + '\n'
+ wrapped_text = textwrap.wrap(text, 72)
+ self.output.write('\n%s\n' % ''.join(wrapped_text))
def _gettopic(self, topic, more_xrefs=''):
"""Return unbuffered tuple of (topic, xrefs).
@@ -1958,9 +1953,10 @@ module "pydoc_data.topics" could not be found.
def listmodules(self, key=''):
if key:
self.output.write('''
-Here is a list of matching modules. Enter any module name to get more help.
+Here is a list of modules whose name or summary contains '{}'.
+If there are any, enter a module name to get more help.
-''')
+'''.format(key))
apropos(key)
else:
self.output.write('''
@@ -1979,35 +1975,11 @@ Please wait a moment while I gather a list of all available modules...
self.list(modules.keys())
self.output.write('''
Enter any module name to get more help. Or, type "modules spam" to search
-for modules whose descriptions contain the word "spam".
+for modules whose name or summary contain the string "spam".
''')
help = Helper()
-class Scanner:
- """A generic tree iterator."""
- def __init__(self, roots, children, descendp):
- self.roots = roots[:]
- self.state = []
- self.children = children
- self.descendp = descendp
-
- def next(self):
- if not self.state:
- if not self.roots:
- return None
- root = self.roots.pop(0)
- self.state = [(root, self.children(root))]
- node, children = self.state[-1]
- if not children:
- self.state.pop()
- return self.next()
- child = children.pop(0)
- if self.descendp(child):
- self.state.append((child, self.children(child)))
- return child
-
-
class ModuleScanner:
"""An interruptible scanner that searches module synopses."""
diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py
index ccfc4ec3f2..4dfa3238fe 100644
--- a/Lib/pydoc_data/topics.py
+++ b/Lib/pydoc_data/topics.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Autogenerated by Sphinx on Sun Oct 27 09:07:55 2013
+# Autogenerated by Sphinx on Sun Nov 24 06:50:34 2013
topics = {'assert': '\nThe ``assert`` statement\n************************\n\nAssert statements are a convenient way to insert debugging assertions\ninto a program:\n\n assert_stmt ::= "assert" expression ["," expression]\n\nThe simple form, ``assert expression``, is equivalent to\n\n if __debug__:\n if not expression: raise AssertionError\n\nThe extended form, ``assert expression1, expression2``, is equivalent\nto\n\n if __debug__:\n if not expression1: raise AssertionError(expression2)\n\nThese equivalences assume that ``__debug__`` and ``AssertionError``\nrefer to the built-in variables with those names. In the current\nimplementation, the built-in variable ``__debug__`` is ``True`` under\nnormal circumstances, ``False`` when optimization is requested\n(command line option -O). The current code generator emits no code\nfor an assert statement when optimization is requested at compile\ntime. Note that it is unnecessary to include the source code for the\nexpression that failed in the error message; it will be displayed as\npart of the stack trace.\n\nAssignments to ``__debug__`` are illegal. The value for the built-in\nvariable is determined when the interpreter starts.\n',
'assignment': '\nAssignment statements\n*********************\n\nAssignment statements are used to (re)bind names to values and to\nmodify attributes or items of mutable objects:\n\n assignment_stmt ::= (target_list "=")+ (expression_list | yield_expression)\n target_list ::= target ("," target)* [","]\n target ::= identifier\n | "(" target_list ")"\n | "[" target_list "]"\n | attributeref\n | subscription\n | slicing\n | "*" target\n\n(See section *Primaries* for the syntax definitions for the last three\nsymbols.)\n\nAn assignment statement evaluates the expression list (remember that\nthis can be a single expression or a comma-separated list, the latter\nyielding a tuple) and assigns the single resulting object to each of\nthe target lists, from left to right.\n\nAssignment is defined recursively depending on the form of the target\n(list). When a target is part of a mutable object (an attribute\nreference, subscription or slicing), the mutable object must\nultimately perform the assignment and decide about its validity, and\nmay raise an exception if the assignment is unacceptable. The rules\nobserved by various types and the exceptions raised are given with the\ndefinition of the object types (see section *The standard type\nhierarchy*).\n\nAssignment of an object to a target list, optionally enclosed in\nparentheses or square brackets, is recursively defined as follows.\n\n* If the target list is a single target: The object is assigned to\n that target.\n\n* If the target list is a comma-separated list of targets: The object\n must be an iterable with the same number of items as there are\n targets in the target list, and the items are assigned, from left to\n right, to the corresponding targets.\n\n * If the target list contains one target prefixed with an asterisk,\n called a "starred" target: The object must be a sequence with at\n least as many items as there are targets in the target list, minus\n one. The first items of the sequence are assigned, from left to\n right, to the targets before the starred target. The final items\n of the sequence are assigned to the targets after the starred\n target. A list of the remaining items in the sequence is then\n assigned to the starred target (the list can be empty).\n\n * Else: The object must be a sequence with the same number of items\n as there are targets in the target list, and the items are\n assigned, from left to right, to the corresponding targets.\n\nAssignment of an object to a single target is recursively defined as\nfollows.\n\n* If the target is an identifier (name):\n\n * If the name does not occur in a ``global`` or ``nonlocal``\n statement in the current code block: the name is bound to the\n object in the current local namespace.\n\n * Otherwise: the name is bound to the object in the global namespace\n or the outer namespace determined by ``nonlocal``, respectively.\n\n The name is rebound if it was already bound. This may cause the\n reference count for the object previously bound to the name to reach\n zero, causing the object to be deallocated and its destructor (if it\n has one) to be called.\n\n* If the target is a target list enclosed in parentheses or in square\n brackets: The object must be an iterable with the same number of\n items as there are targets in the target list, and its items are\n assigned, from left to right, to the corresponding targets.\n\n* If the target is an attribute reference: The primary expression in\n the reference is evaluated. It should yield an object with\n assignable attributes; if this is not the case, ``TypeError`` is\n raised. That object is then asked to assign the assigned object to\n the given attribute; if it cannot perform the assignment, it raises\n an exception (usually but not necessarily ``AttributeError``).\n\n Note: If the object is a class instance and the attribute reference\n occurs on both sides of the assignment operator, the RHS expression,\n ``a.x`` can access either an instance attribute or (if no instance\n attribute exists) a class attribute. The LHS target ``a.x`` is\n always set as an instance attribute, creating it if necessary.\n Thus, the two occurrences of ``a.x`` do not necessarily refer to the\n same attribute: if the RHS expression refers to a class attribute,\n the LHS creates a new instance attribute as the target of the\n assignment:\n\n class Cls:\n x = 3 # class variable\n inst = Cls()\n inst.x = inst.x + 1 # writes inst.x as 4 leaving Cls.x as 3\n\n This description does not necessarily apply to descriptor\n attributes, such as properties created with ``property()``.\n\n* If the target is a subscription: The primary expression in the\n reference is evaluated. It should yield either a mutable sequence\n object (such as a list) or a mapping object (such as a dictionary).\n Next, the subscript expression is evaluated.\n\n If the primary is a mutable sequence object (such as a list), the\n subscript must yield an integer. If it is negative, the sequence\'s\n length is added to it. The resulting value must be a nonnegative\n integer less than the sequence\'s length, and the sequence is asked\n to assign the assigned object to its item with that index. If the\n index is out of range, ``IndexError`` is raised (assignment to a\n subscripted sequence cannot add new items to a list).\n\n If the primary is a mapping object (such as a dictionary), the\n subscript must have a type compatible with the mapping\'s key type,\n and the mapping is then asked to create a key/datum pair which maps\n the subscript to the assigned object. This can either replace an\n existing key/value pair with the same key value, or insert a new\n key/value pair (if no key with the same value existed).\n\n For user-defined objects, the ``__setitem__()`` method is called\n with appropriate arguments.\n\n* If the target is a slicing: The primary expression in the reference\n is evaluated. It should yield a mutable sequence object (such as a\n list). The assigned object should be a sequence object of the same\n type. Next, the lower and upper bound expressions are evaluated,\n insofar they are present; defaults are zero and the sequence\'s\n length. The bounds should evaluate to integers. If either bound is\n negative, the sequence\'s length is added to it. The resulting\n bounds are clipped to lie between zero and the sequence\'s length,\n inclusive. Finally, the sequence object is asked to replace the\n slice with the items of the assigned sequence. The length of the\n slice may be different from the length of the assigned sequence,\n thus changing the length of the target sequence, if the object\n allows it.\n\n**CPython implementation detail:** In the current implementation, the\nsyntax for targets is taken to be the same as for expressions, and\ninvalid syntax is rejected during the code generation phase, causing\nless detailed error messages.\n\nWARNING: Although the definition of assignment implies that overlaps\nbetween the left-hand side and the right-hand side are \'safe\' (for\nexample ``a, b = b, a`` swaps two variables), overlaps *within* the\ncollection of assigned-to variables are not safe! For instance, the\nfollowing program prints ``[0, 2]``:\n\n x = [0, 1]\n i = 0\n i, x[i] = 1, 2\n print(x)\n\nSee also:\n\n **PEP 3132** - Extended Iterable Unpacking\n The specification for the ``*target`` feature.\n\n\nAugmented assignment statements\n===============================\n\nAugmented assignment is the combination, in a single statement, of a\nbinary operation and an assignment statement:\n\n augmented_assignment_stmt ::= augtarget augop (expression_list | yield_expression)\n augtarget ::= identifier | attributeref | subscription | slicing\n augop ::= "+=" | "-=" | "*=" | "/=" | "//=" | "%=" | "**="\n | ">>=" | "<<=" | "&=" | "^=" | "|="\n\n(See section *Primaries* for the syntax definitions for the last three\nsymbols.)\n\nAn augmented assignment evaluates the target (which, unlike normal\nassignment statements, cannot be an unpacking) and the expression\nlist, performs the binary operation specific to the type of assignment\non the two operands, and assigns the result to the original target.\nThe target is only evaluated once.\n\nAn augmented assignment expression like ``x += 1`` can be rewritten as\n``x = x + 1`` to achieve a similar, but not exactly equal effect. In\nthe augmented version, ``x`` is only evaluated once. Also, when\npossible, the actual operation is performed *in-place*, meaning that\nrather than creating a new object and assigning that to the target,\nthe old object is modified instead.\n\nWith the exception of assigning to tuples and multiple targets in a\nsingle statement, the assignment done by augmented assignment\nstatements is handled the same way as normal assignments. Similarly,\nwith the exception of the possible *in-place* behavior, the binary\noperation performed by augmented assignment is the same as the normal\nbinary operations.\n\nFor targets which are attribute references, the same *caveat about\nclass and instance attributes* applies as for regular assignments.\n',
'atom-identifiers': '\nIdentifiers (Names)\n*******************\n\nAn identifier occurring as an atom is a name. See section\n*Identifiers and keywords* for lexical definition and section *Naming\nand binding* for documentation of naming and binding.\n\nWhen the name is bound to an object, evaluation of the atom yields\nthat object. When a name is not bound, an attempt to evaluate it\nraises a ``NameError`` exception.\n\n**Private name mangling:** When an identifier that textually occurs in\na class definition begins with two or more underscore characters and\ndoes not end in two or more underscores, it is considered a *private\nname* of that class. Private names are transformed to a longer form\nbefore code is generated for them. The transformation inserts the\nclass name, with leading underscores removed and a single underscore\ninserted, in front of the name. For example, the identifier\n``__spam`` occurring in a class named ``Ham`` will be transformed to\n``_Ham__spam``. This transformation is independent of the syntactical\ncontext in which the identifier is used. If the transformed name is\nextremely long (longer than 255 characters), implementation defined\ntruncation may happen. If the class name consists only of underscores,\nno transformation is done.\n',
@@ -19,12 +19,12 @@ topics = {'assert': '\nThe ``assert`` statement\n************************\n\nAss
'calls': '\nCalls\n*****\n\nA call calls a callable object (e.g., a *function*) with a possibly\nempty series of *arguments*:\n\n call ::= primary "(" [argument_list [","] | comprehension] ")"\n argument_list ::= positional_arguments ["," keyword_arguments]\n ["," "*" expression] ["," keyword_arguments]\n ["," "**" expression]\n | keyword_arguments ["," "*" expression]\n ["," keyword_arguments] ["," "**" expression]\n | "*" expression ["," keyword_arguments] ["," "**" expression]\n | "**" expression\n positional_arguments ::= expression ("," expression)*\n keyword_arguments ::= keyword_item ("," keyword_item)*\n keyword_item ::= identifier "=" expression\n\nA trailing comma may be present after the positional and keyword\narguments but does not affect the semantics.\n\nThe primary must evaluate to a callable object (user-defined\nfunctions, built-in functions, methods of built-in objects, class\nobjects, methods of class instances, and all objects having a\n``__call__()`` method are callable). All argument expressions are\nevaluated before the call is attempted. Please refer to section\n*Function definitions* for the syntax of formal *parameter* lists.\n\nIf keyword arguments are present, they are first converted to\npositional arguments, as follows. First, a list of unfilled slots is\ncreated for the formal parameters. If there are N positional\narguments, they are placed in the first N slots. Next, for each\nkeyword argument, the identifier is used to determine the\ncorresponding slot (if the identifier is the same as the first formal\nparameter name, the first slot is used, and so on). If the slot is\nalready filled, a ``TypeError`` exception is raised. Otherwise, the\nvalue of the argument is placed in the slot, filling it (even if the\nexpression is ``None``, it fills the slot). When all arguments have\nbeen processed, the slots that are still unfilled are filled with the\ncorresponding default value from the function definition. (Default\nvalues are calculated, once, when the function is defined; thus, a\nmutable object such as a list or dictionary used as default value will\nbe shared by all calls that don\'t specify an argument value for the\ncorresponding slot; this should usually be avoided.) If there are any\nunfilled slots for which no default value is specified, a\n``TypeError`` exception is raised. Otherwise, the list of filled\nslots is used as the argument list for the call.\n\n**CPython implementation detail:** An implementation may provide\nbuilt-in functions whose positional parameters do not have names, even\nif they are \'named\' for the purpose of documentation, and which\ntherefore cannot be supplied by keyword. In CPython, this is the case\nfor functions implemented in C that use ``PyArg_ParseTuple()`` to\nparse their arguments.\n\nIf there are more positional arguments than there are formal parameter\nslots, a ``TypeError`` exception is raised, unless a formal parameter\nusing the syntax ``*identifier`` is present; in this case, that formal\nparameter receives a tuple containing the excess positional arguments\n(or an empty tuple if there were no excess positional arguments).\n\nIf any keyword argument does not correspond to a formal parameter\nname, a ``TypeError`` exception is raised, unless a formal parameter\nusing the syntax ``**identifier`` is present; in this case, that\nformal parameter receives a dictionary containing the excess keyword\narguments (using the keywords as keys and the argument values as\ncorresponding values), or a (new) empty dictionary if there were no\nexcess keyword arguments.\n\nIf the syntax ``*expression`` appears in the function call,\n``expression`` must evaluate to an iterable. Elements from this\niterable are treated as if they were additional positional arguments;\nif there are positional arguments *x1*, ..., *xN*, and ``expression``\nevaluates to a sequence *y1*, ..., *yM*, this is equivalent to a call\nwith M+N positional arguments *x1*, ..., *xN*, *y1*, ..., *yM*.\n\nA consequence of this is that although the ``*expression`` syntax may\nappear *after* some keyword arguments, it is processed *before* the\nkeyword arguments (and the ``**expression`` argument, if any -- see\nbelow). So:\n\n >>> def f(a, b):\n ... print(a, b)\n ...\n >>> f(b=1, *(2,))\n 2 1\n >>> f(a=1, *(2,))\n Traceback (most recent call last):\n File "<stdin>", line 1, in ?\n TypeError: f() got multiple values for keyword argument \'a\'\n >>> f(1, *(2,))\n 1 2\n\nIt is unusual for both keyword arguments and the ``*expression``\nsyntax to be used in the same call, so in practice this confusion does\nnot arise.\n\nIf the syntax ``**expression`` appears in the function call,\n``expression`` must evaluate to a mapping, the contents of which are\ntreated as additional keyword arguments. In the case of a keyword\nappearing in both ``expression`` and as an explicit keyword argument,\na ``TypeError`` exception is raised.\n\nFormal parameters using the syntax ``*identifier`` or ``**identifier``\ncannot be used as positional argument slots or as keyword argument\nnames.\n\nA call always returns some value, possibly ``None``, unless it raises\nan exception. How this value is computed depends on the type of the\ncallable object.\n\nIf it is---\n\na user-defined function:\n The code block for the function is executed, passing it the\n argument list. The first thing the code block will do is bind the\n formal parameters to the arguments; this is described in section\n *Function definitions*. When the code block executes a ``return``\n statement, this specifies the return value of the function call.\n\na built-in function or method:\n The result is up to the interpreter; see *Built-in Functions* for\n the descriptions of built-in functions and methods.\n\na class object:\n A new instance of that class is returned.\n\na class instance method:\n The corresponding user-defined function is called, with an argument\n list that is one longer than the argument list of the call: the\n instance becomes the first argument.\n\na class instance:\n The class must define a ``__call__()`` method; the effect is then\n the same as if that method was called.\n',
'class': '\nClass definitions\n*****************\n\nA class definition defines a class object (see section *The standard\ntype hierarchy*):\n\n classdef ::= [decorators] "class" classname [inheritance] ":" suite\n inheritance ::= "(" [parameter_list] ")"\n classname ::= identifier\n\nA class definition is an executable statement. The inheritance list\nusually gives a list of base classes (see *Customizing class creation*\nfor more advanced uses), so each item in the list should evaluate to a\nclass object which allows subclassing. Classes without an inheritance\nlist inherit, by default, from the base class ``object``; hence,\n\n class Foo:\n pass\n\nis equivalent to\n\n class Foo(object):\n pass\n\nThe class\'s suite is then executed in a new execution frame (see\n*Naming and binding*), using a newly created local namespace and the\noriginal global namespace. (Usually, the suite contains mostly\nfunction definitions.) When the class\'s suite finishes execution, its\nexecution frame is discarded but its local namespace is saved. [4] A\nclass object is then created using the inheritance list for the base\nclasses and the saved local namespace for the attribute dictionary.\nThe class name is bound to this class object in the original local\nnamespace.\n\nClass creation can be customized heavily using *metaclasses*.\n\nClasses can also be decorated: just like when decorating functions,\n\n @f1(arg)\n @f2\n class Foo: pass\n\nis equivalent to\n\n class Foo: pass\n Foo = f1(arg)(f2(Foo))\n\nThe evaluation rules for the decorator expressions are the same as for\nfunction decorators. The result must be a class object, which is then\nbound to the class name.\n\n**Programmer\'s note:** Variables defined in the class definition are\nclass attributes; they are shared by instances. Instance attributes\ncan be set in a method with ``self.name = value``. Both class and\ninstance attributes are accessible through the notation\n"``self.name``", and an instance attribute hides a class attribute\nwith the same name when accessed in this way. Class attributes can be\nused as defaults for instance attributes, but using mutable values\nthere can lead to unexpected results. *Descriptors* can be used to\ncreate instance variables with different implementation details.\n\nSee also:\n\n **PEP 3115** - Metaclasses in Python 3 **PEP 3129** - Class\n Decorators\n\n-[ Footnotes ]-\n\n[1] The exception is propagated to the invocation stack unless there\n is a ``finally`` clause which happens to raise another exception.\n That new exception causes the old one to be lost.\n\n[2] Currently, control "flows off the end" except in the case of an\n exception or the execution of a ``return``, ``continue``, or\n ``break`` statement.\n\n[3] A string literal appearing as the first statement in the function\n body is transformed into the function\'s ``__doc__`` attribute and\n therefore the function\'s *docstring*.\n\n[4] A string literal appearing as the first statement in the class\n body is transformed into the namespace\'s ``__doc__`` item and\n therefore the class\'s *docstring*.\n',
'comparisons': '\nComparisons\n***********\n\nUnlike C, all comparison operations in Python have the same priority,\nwhich is lower than that of any arithmetic, shifting or bitwise\noperation. Also unlike C, expressions like ``a < b < c`` have the\ninterpretation that is conventional in mathematics:\n\n comparison ::= or_expr ( comp_operator or_expr )*\n comp_operator ::= "<" | ">" | "==" | ">=" | "<=" | "!="\n | "is" ["not"] | ["not"] "in"\n\nComparisons yield boolean values: ``True`` or ``False``.\n\nComparisons can be chained arbitrarily, e.g., ``x < y <= z`` is\nequivalent to ``x < y and y <= z``, except that ``y`` is evaluated\nonly once (but in both cases ``z`` is not evaluated at all when ``x <\ny`` is found to be false).\n\nFormally, if *a*, *b*, *c*, ..., *y*, *z* are expressions and *op1*,\n*op2*, ..., *opN* are comparison operators, then ``a op1 b op2 c ... y\nopN z`` is equivalent to ``a op1 b and b op2 c and ... y opN z``,\nexcept that each expression is evaluated at most once.\n\nNote that ``a op1 b op2 c`` doesn\'t imply any kind of comparison\nbetween *a* and *c*, so that, e.g., ``x < y > z`` is perfectly legal\n(though perhaps not pretty).\n\nThe operators ``<``, ``>``, ``==``, ``>=``, ``<=``, and ``!=`` compare\nthe values of two objects. The objects need not have the same type.\nIf both are numbers, they are converted to a common type. Otherwise,\nthe ``==`` and ``!=`` operators *always* consider objects of different\ntypes to be unequal, while the ``<``, ``>``, ``>=`` and ``<=``\noperators raise a ``TypeError`` when comparing objects of different\ntypes that do not implement these operators for the given pair of\ntypes. You can control comparison behavior of objects of non-built-in\ntypes by defining rich comparison methods like ``__gt__()``, described\nin section *Basic customization*.\n\nComparison of objects of the same type depends on the type:\n\n* Numbers are compared arithmetically.\n\n* The values ``float(\'NaN\')`` and ``Decimal(\'NaN\')`` are special. The\n are identical to themselves, ``x is x`` but are not equal to\n themselves, ``x != x``. Additionally, comparing any value to a\n not-a-number value will return ``False``. For example, both ``3 <\n float(\'NaN\')`` and ``float(\'NaN\') < 3`` will return ``False``.\n\n* Bytes objects are compared lexicographically using the numeric\n values of their elements.\n\n* Strings are compared lexicographically using the numeric equivalents\n (the result of the built-in function ``ord()``) of their characters.\n [3] String and bytes object can\'t be compared!\n\n* Tuples and lists are compared lexicographically using comparison of\n corresponding elements. This means that to compare equal, each\n element must compare equal and the two sequences must be of the same\n type and have the same length.\n\n If not equal, the sequences are ordered the same as their first\n differing elements. For example, ``[1,2,x] <= [1,2,y]`` has the\n same value as ``x <= y``. If the corresponding element does not\n exist, the shorter sequence is ordered first (for example, ``[1,2] <\n [1,2,3]``).\n\n* Mappings (dictionaries) compare equal if and only if they have the\n same ``(key, value)`` pairs. Order comparisons ``(\'<\', \'<=\', \'>=\',\n \'>\')`` raise ``TypeError``.\n\n* Sets and frozensets define comparison operators to mean subset and\n superset tests. Those relations do not define total orderings (the\n two sets ``{1,2}`` and {2,3} are not equal, nor subsets of one\n another, nor supersets of one another). Accordingly, sets are not\n appropriate arguments for functions which depend on total ordering.\n For example, ``min()``, ``max()``, and ``sorted()`` produce\n undefined results given a list of sets as inputs.\n\n* Most other objects of built-in types compare unequal unless they are\n the same object; the choice whether one object is considered smaller\n or larger than another one is made arbitrarily but consistently\n within one execution of a program.\n\nComparison of objects of the differing types depends on whether either\nof the types provide explicit support for the comparison. Most\nnumeric types can be compared with one another. When cross-type\ncomparison is not supported, the comparison method returns\n``NotImplemented``.\n\nThe operators ``in`` and ``not in`` test for membership. ``x in s``\nevaluates to true if *x* is a member of *s*, and false otherwise. ``x\nnot in s`` returns the negation of ``x in s``. All built-in sequences\nand set types support this as well as dictionary, for which ``in``\ntests whether a the dictionary has a given key. For container types\nsuch as list, tuple, set, frozenset, dict, or collections.deque, the\nexpression ``x in y`` is equivalent to ``any(x is e or x == e for e in\ny)``.\n\nFor the string and bytes types, ``x in y`` is true if and only if *x*\nis a substring of *y*. An equivalent test is ``y.find(x) != -1``.\nEmpty strings are always considered to be a substring of any other\nstring, so ``"" in "abc"`` will return ``True``.\n\nFor user-defined classes which define the ``__contains__()`` method,\n``x in y`` is true if and only if ``y.__contains__(x)`` is true.\n\nFor user-defined classes which do not define ``__contains__()`` but do\ndefine ``__iter__()``, ``x in y`` is true if some value ``z`` with ``x\n== z`` is produced while iterating over ``y``. If an exception is\nraised during the iteration, it is as if ``in`` raised that exception.\n\nLastly, the old-style iteration protocol is tried: if a class defines\n``__getitem__()``, ``x in y`` is true if and only if there is a non-\nnegative integer index *i* such that ``x == y[i]``, and all lower\ninteger indices do not raise ``IndexError`` exception. (If any other\nexception is raised, it is as if ``in`` raised that exception).\n\nThe operator ``not in`` is defined to have the inverse true value of\n``in``.\n\nThe operators ``is`` and ``is not`` test for object identity: ``x is\ny`` is true if and only if *x* and *y* are the same object. ``x is\nnot y`` yields the inverse truth value. [4]\n',
- 'compound': '\nCompound statements\n*******************\n\nCompound statements contain (groups of) other statements; they affect\nor control the execution of those other statements in some way. In\ngeneral, compound statements span multiple lines, although in simple\nincarnations a whole compound statement may be contained in one line.\n\nThe ``if``, ``while`` and ``for`` statements implement traditional\ncontrol flow constructs. ``try`` specifies exception handlers and/or\ncleanup code for a group of statements, while the ``with`` statement\nallows the execution of initialization and finalization code around a\nblock of code. Function and class definitions are also syntactically\ncompound statements.\n\nCompound statements consist of one or more \'clauses.\' A clause\nconsists of a header and a \'suite.\' The clause headers of a\nparticular compound statement are all at the same indentation level.\nEach clause header begins with a uniquely identifying keyword and ends\nwith a colon. A suite is a group of statements controlled by a\nclause. A suite can be one or more semicolon-separated simple\nstatements on the same line as the header, following the header\'s\ncolon, or it can be one or more indented statements on subsequent\nlines. Only the latter form of suite can contain nested compound\nstatements; the following is illegal, mostly because it wouldn\'t be\nclear to which ``if`` clause a following ``else`` clause would belong:\n\n if test1: if test2: print(x)\n\nAlso note that the semicolon binds tighter than the colon in this\ncontext, so that in the following example, either all or none of the\n``print()`` calls are executed:\n\n if x < y < z: print(x); print(y); print(z)\n\nSummarizing:\n\n compound_stmt ::= if_stmt\n | while_stmt\n | for_stmt\n | try_stmt\n | with_stmt\n | funcdef\n | classdef\n suite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT\n statement ::= stmt_list NEWLINE | compound_stmt\n stmt_list ::= simple_stmt (";" simple_stmt)* [";"]\n\nNote that statements always end in a ``NEWLINE`` possibly followed by\na ``DEDENT``. Also note that optional continuation clauses always\nbegin with a keyword that cannot start a statement, thus there are no\nambiguities (the \'dangling ``else``\' problem is solved in Python by\nrequiring nested ``if`` statements to be indented).\n\nThe formatting of the grammar rules in the following sections places\neach clause on a separate line for clarity.\n\n\nThe ``if`` statement\n====================\n\nThe ``if`` statement is used for conditional execution:\n\n if_stmt ::= "if" expression ":" suite\n ( "elif" expression ":" suite )*\n ["else" ":" suite]\n\nIt selects exactly one of the suites by evaluating the expressions one\nby one until one is found to be true (see section *Boolean operations*\nfor the definition of true and false); then that suite is executed\n(and no other part of the ``if`` statement is executed or evaluated).\nIf all expressions are false, the suite of the ``else`` clause, if\npresent, is executed.\n\n\nThe ``while`` statement\n=======================\n\nThe ``while`` statement is used for repeated execution as long as an\nexpression is true:\n\n while_stmt ::= "while" expression ":" suite\n ["else" ":" suite]\n\nThis repeatedly tests the expression and, if it is true, executes the\nfirst suite; if the expression is false (which may be the first time\nit is tested) the suite of the ``else`` clause, if present, is\nexecuted and the loop terminates.\n\nA ``break`` statement executed in the first suite terminates the loop\nwithout executing the ``else`` clause\'s suite. A ``continue``\nstatement executed in the first suite skips the rest of the suite and\ngoes back to testing the expression.\n\n\nThe ``for`` statement\n=====================\n\nThe ``for`` statement is used to iterate over the elements of a\nsequence (such as a string, tuple or list) or other iterable object:\n\n for_stmt ::= "for" target_list "in" expression_list ":" suite\n ["else" ":" suite]\n\nThe expression list is evaluated once; it should yield an iterable\nobject. An iterator is created for the result of the\n``expression_list``. The suite is then executed once for each item\nprovided by the iterator, in the order of ascending indices. Each\nitem in turn is assigned to the target list using the standard rules\nfor assignments (see *Assignment statements*), and then the suite is\nexecuted. When the items are exhausted (which is immediately when the\nsequence is empty or an iterator raises a ``StopIteration``\nexception), the suite in the ``else`` clause, if present, is executed,\nand the loop terminates.\n\nA ``break`` statement executed in the first suite terminates the loop\nwithout executing the ``else`` clause\'s suite. A ``continue``\nstatement executed in the first suite skips the rest of the suite and\ncontinues with the next item, or with the ``else`` clause if there was\nno next item.\n\nThe suite may assign to the variable(s) in the target list; this does\nnot affect the next item assigned to it.\n\nNames in the target list are not deleted when the loop is finished,\nbut if the sequence is empty, it will not have been assigned to at all\nby the loop. Hint: the built-in function ``range()`` returns an\niterator of integers suitable to emulate the effect of Pascal\'s ``for\ni := a to b do``; e.g., ``list(range(3))`` returns the list ``[0, 1,\n2]``.\n\nNote: There is a subtlety when the sequence is being modified by the loop\n (this can only occur for mutable sequences, i.e. lists). An\n internal counter is used to keep track of which item is used next,\n and this is incremented on each iteration. When this counter has\n reached the length of the sequence the loop terminates. This means\n that if the suite deletes the current (or a previous) item from the\n sequence, the next item will be skipped (since it gets the index of\n the current item which has already been treated). Likewise, if the\n suite inserts an item in the sequence before the current item, the\n current item will be treated again the next time through the loop.\n This can lead to nasty bugs that can be avoided by making a\n temporary copy using a slice of the whole sequence, e.g.,\n\n for x in a[:]:\n if x < 0: a.remove(x)\n\n\nThe ``try`` statement\n=====================\n\nThe ``try`` statement specifies exception handlers and/or cleanup code\nfor a group of statements:\n\n try_stmt ::= try1_stmt | try2_stmt\n try1_stmt ::= "try" ":" suite\n ("except" [expression ["as" target]] ":" suite)+\n ["else" ":" suite]\n ["finally" ":" suite]\n try2_stmt ::= "try" ":" suite\n "finally" ":" suite\n\nThe ``except`` clause(s) specify one or more exception handlers. When\nno exception occurs in the ``try`` clause, no exception handler is\nexecuted. When an exception occurs in the ``try`` suite, a search for\nan exception handler is started. This search inspects the except\nclauses in turn until one is found that matches the exception. An\nexpression-less except clause, if present, must be last; it matches\nany exception. For an except clause with an expression, that\nexpression is evaluated, and the clause matches the exception if the\nresulting object is "compatible" with the exception. An object is\ncompatible with an exception if it is the class or a base class of the\nexception object or a tuple containing an item compatible with the\nexception.\n\nIf no except clause matches the exception, the search for an exception\nhandler continues in the surrounding code and on the invocation stack.\n[1]\n\nIf the evaluation of an expression in the header of an except clause\nraises an exception, the original search for a handler is canceled and\na search starts for the new exception in the surrounding code and on\nthe call stack (it is treated as if the entire ``try`` statement\nraised the exception).\n\nWhen a matching except clause is found, the exception is assigned to\nthe target specified after the ``as`` keyword in that except clause,\nif present, and the except clause\'s suite is executed. All except\nclauses must have an executable block. When the end of this block is\nreached, execution continues normally after the entire try statement.\n(This means that if two nested handlers exist for the same exception,\nand the exception occurs in the try clause of the inner handler, the\nouter handler will not handle the exception.)\n\nWhen an exception has been assigned using ``as target``, it is cleared\nat the end of the except clause. This is as if\n\n except E as N:\n foo\n\nwas translated to\n\n except E as N:\n try:\n foo\n finally:\n del N\n\nThis means the exception must be assigned to a different name to be\nable to refer to it after the except clause. Exceptions are cleared\nbecause with the traceback attached to them, they form a reference\ncycle with the stack frame, keeping all locals in that frame alive\nuntil the next garbage collection occurs.\n\nBefore an except clause\'s suite is executed, details about the\nexception are stored in the ``sys`` module and can be access via\n``sys.exc_info()``. ``sys.exc_info()`` returns a 3-tuple consisting of\nthe exception class, the exception instance and a traceback object\n(see section *The standard type hierarchy*) identifying the point in\nthe program where the exception occurred. ``sys.exc_info()`` values\nare restored to their previous values (before the call) when returning\nfrom a function that handled an exception.\n\nThe optional ``else`` clause is executed if and when control flows off\nthe end of the ``try`` clause. [2] Exceptions in the ``else`` clause\nare not handled by the preceding ``except`` clauses.\n\nIf ``finally`` is present, it specifies a \'cleanup\' handler. The\n``try`` clause is executed, including any ``except`` and ``else``\nclauses. If an exception occurs in any of the clauses and is not\nhandled, the exception is temporarily saved. The ``finally`` clause is\nexecuted. If there is a saved exception it is re-raised at the end of\nthe ``finally`` clause. If the ``finally`` clause raises another\nexception, the saved exception is set as the context of the new\nexception. If the ``finally`` clause executes a ``return`` or\n``break`` statement, the saved exception is discarded:\n\n def f():\n try:\n 1/0\n finally:\n return 42\n\n >>> f()\n 42\n\nThe exception information is not available to the program during\nexecution of the ``finally`` clause.\n\nWhen a ``return``, ``break`` or ``continue`` statement is executed in\nthe ``try`` suite of a ``try``...``finally`` statement, the\n``finally`` clause is also executed \'on the way out.\' A ``continue``\nstatement is illegal in the ``finally`` clause. (The reason is a\nproblem with the current implementation --- this restriction may be\nlifted in the future).\n\nAdditional information on exceptions can be found in section\n*Exceptions*, and information on using the ``raise`` statement to\ngenerate exceptions may be found in section *The raise statement*.\n\n\nThe ``with`` statement\n======================\n\nThe ``with`` statement is used to wrap the execution of a block with\nmethods defined by a context manager (see section *With Statement\nContext Managers*). This allows common\n``try``...``except``...``finally`` usage patterns to be encapsulated\nfor convenient reuse.\n\n with_stmt ::= "with" with_item ("," with_item)* ":" suite\n with_item ::= expression ["as" target]\n\nThe execution of the ``with`` statement with one "item" proceeds as\nfollows:\n\n1. The context expression (the expression given in the ``with_item``)\n is evaluated to obtain a context manager.\n\n2. The context manager\'s ``__exit__()`` is loaded for later use.\n\n3. The context manager\'s ``__enter__()`` method is invoked.\n\n4. If a target was included in the ``with`` statement, the return\n value from ``__enter__()`` is assigned to it.\n\n Note: The ``with`` statement guarantees that if the ``__enter__()``\n method returns without an error, then ``__exit__()`` will always\n be called. Thus, if an error occurs during the assignment to the\n target list, it will be treated the same as an error occurring\n within the suite would be. See step 6 below.\n\n5. The suite is executed.\n\n6. The context manager\'s ``__exit__()`` method is invoked. If an\n exception caused the suite to be exited, its type, value, and\n traceback are passed as arguments to ``__exit__()``. Otherwise,\n three ``None`` arguments are supplied.\n\n If the suite was exited due to an exception, and the return value\n from the ``__exit__()`` method was false, the exception is\n reraised. If the return value was true, the exception is\n suppressed, and execution continues with the statement following\n the ``with`` statement.\n\n If the suite was exited for any reason other than an exception, the\n return value from ``__exit__()`` is ignored, and execution proceeds\n at the normal location for the kind of exit that was taken.\n\nWith more than one item, the context managers are processed as if\nmultiple ``with`` statements were nested:\n\n with A() as a, B() as b:\n suite\n\nis equivalent to\n\n with A() as a:\n with B() as b:\n suite\n\nChanged in version 3.1: Support for multiple context expressions.\n\nSee also:\n\n **PEP 0343** - The "with" statement\n The specification, background, and examples for the Python\n ``with`` statement.\n\n\nFunction definitions\n====================\n\nA function definition defines a user-defined function object (see\nsection *The standard type hierarchy*):\n\n funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite\n decorators ::= decorator+\n decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE\n dotted_name ::= identifier ("." identifier)*\n parameter_list ::= (defparameter ",")*\n ( "*" [parameter] ("," defparameter)* ["," "**" parameter]\n | "**" parameter\n | defparameter [","] )\n parameter ::= identifier [":" expression]\n defparameter ::= parameter ["=" expression]\n funcname ::= identifier\n\nA function definition is an executable statement. Its execution binds\nthe function name in the current local namespace to a function object\n(a wrapper around the executable code for the function). This\nfunction object contains a reference to the current global namespace\nas the global namespace to be used when the function is called.\n\nThe function definition does not execute the function body; this gets\nexecuted only when the function is called. [3]\n\nA function definition may be wrapped by one or more *decorator*\nexpressions. Decorator expressions are evaluated when the function is\ndefined, in the scope that contains the function definition. The\nresult must be a callable, which is invoked with the function object\nas the only argument. The returned value is bound to the function name\ninstead of the function object. Multiple decorators are applied in\nnested fashion. For example, the following code\n\n @f1(arg)\n @f2\n def func(): pass\n\nis equivalent to\n\n def func(): pass\n func = f1(arg)(f2(func))\n\nWhen one or more *parameters* have the form *parameter* ``=``\n*expression*, the function is said to have "default parameter values."\nFor a parameter with a default value, the corresponding *argument* may\nbe omitted from a call, in which case the parameter\'s default value is\nsubstituted. If a parameter has a default value, all following\nparameters up until the "``*``" must also have a default value ---\nthis is a syntactic restriction that is not expressed by the grammar.\n\n**Default parameter values are evaluated when the function definition\nis executed.** This means that the expression is evaluated once, when\nthe function is defined, and that the same "pre-computed" value is\nused for each call. This is especially important to understand when a\ndefault parameter is a mutable object, such as a list or a dictionary:\nif the function modifies the object (e.g. by appending an item to a\nlist), the default value is in effect modified. This is generally not\nwhat was intended. A way around this is to use ``None`` as the\ndefault, and explicitly test for it in the body of the function, e.g.:\n\n def whats_on_the_telly(penguin=None):\n if penguin is None:\n penguin = []\n penguin.append("property of the zoo")\n return penguin\n\nFunction call semantics are described in more detail in section\n*Calls*. A function call always assigns values to all parameters\nmentioned in the parameter list, either from position arguments, from\nkeyword arguments, or from default values. If the form\n"``*identifier``" is present, it is initialized to a tuple receiving\nany excess positional parameters, defaulting to the empty tuple. If\nthe form "``**identifier``" is present, it is initialized to a new\ndictionary receiving any excess keyword arguments, defaulting to a new\nempty dictionary. Parameters after "``*``" or "``*identifier``" are\nkeyword-only parameters and may only be passed used keyword arguments.\n\nParameters may have annotations of the form "``: expression``"\nfollowing the parameter name. Any parameter may have an annotation\neven those of the form ``*identifier`` or ``**identifier``. Functions\nmay have "return" annotation of the form "``-> expression``" after the\nparameter list. These annotations can be any valid Python expression\nand are evaluated when the function definition is executed.\nAnnotations may be evaluated in a different order than they appear in\nthe source code. The presence of annotations does not change the\nsemantics of a function. The annotation values are available as\nvalues of a dictionary keyed by the parameters\' names in the\n``__annotations__`` attribute of the function object.\n\nIt is also possible to create anonymous functions (functions not bound\nto a name), for immediate use in expressions. This uses lambda\nexpressions, described in section *Lambdas*. Note that the lambda\nexpression is merely a shorthand for a simplified function definition;\na function defined in a "``def``" statement can be passed around or\nassigned to another name just like a function defined by a lambda\nexpression. The "``def``" form is actually more powerful since it\nallows the execution of multiple statements and annotations.\n\n**Programmer\'s note:** Functions are first-class objects. A "``def``"\nstatement executed inside a function definition defines a local\nfunction that can be returned or passed around. Free variables used\nin the nested function can access the local variables of the function\ncontaining the def. See section *Naming and binding* for details.\n\nSee also:\n\n **PEP 3107** - Function Annotations\n The original specification for function annotations.\n\n\nClass definitions\n=================\n\nA class definition defines a class object (see section *The standard\ntype hierarchy*):\n\n classdef ::= [decorators] "class" classname [inheritance] ":" suite\n inheritance ::= "(" [parameter_list] ")"\n classname ::= identifier\n\nA class definition is an executable statement. The inheritance list\nusually gives a list of base classes (see *Customizing class creation*\nfor more advanced uses), so each item in the list should evaluate to a\nclass object which allows subclassing. Classes without an inheritance\nlist inherit, by default, from the base class ``object``; hence,\n\n class Foo:\n pass\n\nis equivalent to\n\n class Foo(object):\n pass\n\nThe class\'s suite is then executed in a new execution frame (see\n*Naming and binding*), using a newly created local namespace and the\noriginal global namespace. (Usually, the suite contains mostly\nfunction definitions.) When the class\'s suite finishes execution, its\nexecution frame is discarded but its local namespace is saved. [4] A\nclass object is then created using the inheritance list for the base\nclasses and the saved local namespace for the attribute dictionary.\nThe class name is bound to this class object in the original local\nnamespace.\n\nClass creation can be customized heavily using *metaclasses*.\n\nClasses can also be decorated: just like when decorating functions,\n\n @f1(arg)\n @f2\n class Foo: pass\n\nis equivalent to\n\n class Foo: pass\n Foo = f1(arg)(f2(Foo))\n\nThe evaluation rules for the decorator expressions are the same as for\nfunction decorators. The result must be a class object, which is then\nbound to the class name.\n\n**Programmer\'s note:** Variables defined in the class definition are\nclass attributes; they are shared by instances. Instance attributes\ncan be set in a method with ``self.name = value``. Both class and\ninstance attributes are accessible through the notation\n"``self.name``", and an instance attribute hides a class attribute\nwith the same name when accessed in this way. Class attributes can be\nused as defaults for instance attributes, but using mutable values\nthere can lead to unexpected results. *Descriptors* can be used to\ncreate instance variables with different implementation details.\n\nSee also:\n\n **PEP 3115** - Metaclasses in Python 3 **PEP 3129** - Class\n Decorators\n\n-[ Footnotes ]-\n\n[1] The exception is propagated to the invocation stack unless there\n is a ``finally`` clause which happens to raise another exception.\n That new exception causes the old one to be lost.\n\n[2] Currently, control "flows off the end" except in the case of an\n exception or the execution of a ``return``, ``continue``, or\n ``break`` statement.\n\n[3] A string literal appearing as the first statement in the function\n body is transformed into the function\'s ``__doc__`` attribute and\n therefore the function\'s *docstring*.\n\n[4] A string literal appearing as the first statement in the class\n body is transformed into the namespace\'s ``__doc__`` item and\n therefore the class\'s *docstring*.\n',
+ 'compound': '\nCompound statements\n*******************\n\nCompound statements contain (groups of) other statements; they affect\nor control the execution of those other statements in some way. In\ngeneral, compound statements span multiple lines, although in simple\nincarnations a whole compound statement may be contained in one line.\n\nThe ``if``, ``while`` and ``for`` statements implement traditional\ncontrol flow constructs. ``try`` specifies exception handlers and/or\ncleanup code for a group of statements, while the ``with`` statement\nallows the execution of initialization and finalization code around a\nblock of code. Function and class definitions are also syntactically\ncompound statements.\n\nCompound statements consist of one or more \'clauses.\' A clause\nconsists of a header and a \'suite.\' The clause headers of a\nparticular compound statement are all at the same indentation level.\nEach clause header begins with a uniquely identifying keyword and ends\nwith a colon. A suite is a group of statements controlled by a\nclause. A suite can be one or more semicolon-separated simple\nstatements on the same line as the header, following the header\'s\ncolon, or it can be one or more indented statements on subsequent\nlines. Only the latter form of suite can contain nested compound\nstatements; the following is illegal, mostly because it wouldn\'t be\nclear to which ``if`` clause a following ``else`` clause would belong:\n\n if test1: if test2: print(x)\n\nAlso note that the semicolon binds tighter than the colon in this\ncontext, so that in the following example, either all or none of the\n``print()`` calls are executed:\n\n if x < y < z: print(x); print(y); print(z)\n\nSummarizing:\n\n compound_stmt ::= if_stmt\n | while_stmt\n | for_stmt\n | try_stmt\n | with_stmt\n | funcdef\n | classdef\n suite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT\n statement ::= stmt_list NEWLINE | compound_stmt\n stmt_list ::= simple_stmt (";" simple_stmt)* [";"]\n\nNote that statements always end in a ``NEWLINE`` possibly followed by\na ``DEDENT``. Also note that optional continuation clauses always\nbegin with a keyword that cannot start a statement, thus there are no\nambiguities (the \'dangling ``else``\' problem is solved in Python by\nrequiring nested ``if`` statements to be indented).\n\nThe formatting of the grammar rules in the following sections places\neach clause on a separate line for clarity.\n\n\nThe ``if`` statement\n====================\n\nThe ``if`` statement is used for conditional execution:\n\n if_stmt ::= "if" expression ":" suite\n ( "elif" expression ":" suite )*\n ["else" ":" suite]\n\nIt selects exactly one of the suites by evaluating the expressions one\nby one until one is found to be true (see section *Boolean operations*\nfor the definition of true and false); then that suite is executed\n(and no other part of the ``if`` statement is executed or evaluated).\nIf all expressions are false, the suite of the ``else`` clause, if\npresent, is executed.\n\n\nThe ``while`` statement\n=======================\n\nThe ``while`` statement is used for repeated execution as long as an\nexpression is true:\n\n while_stmt ::= "while" expression ":" suite\n ["else" ":" suite]\n\nThis repeatedly tests the expression and, if it is true, executes the\nfirst suite; if the expression is false (which may be the first time\nit is tested) the suite of the ``else`` clause, if present, is\nexecuted and the loop terminates.\n\nA ``break`` statement executed in the first suite terminates the loop\nwithout executing the ``else`` clause\'s suite. A ``continue``\nstatement executed in the first suite skips the rest of the suite and\ngoes back to testing the expression.\n\n\nThe ``for`` statement\n=====================\n\nThe ``for`` statement is used to iterate over the elements of a\nsequence (such as a string, tuple or list) or other iterable object:\n\n for_stmt ::= "for" target_list "in" expression_list ":" suite\n ["else" ":" suite]\n\nThe expression list is evaluated once; it should yield an iterable\nobject. An iterator is created for the result of the\n``expression_list``. The suite is then executed once for each item\nprovided by the iterator, in the order of ascending indices. Each\nitem in turn is assigned to the target list using the standard rules\nfor assignments (see *Assignment statements*), and then the suite is\nexecuted. When the items are exhausted (which is immediately when the\nsequence is empty or an iterator raises a ``StopIteration``\nexception), the suite in the ``else`` clause, if present, is executed,\nand the loop terminates.\n\nA ``break`` statement executed in the first suite terminates the loop\nwithout executing the ``else`` clause\'s suite. A ``continue``\nstatement executed in the first suite skips the rest of the suite and\ncontinues with the next item, or with the ``else`` clause if there was\nno next item.\n\nThe suite may assign to the variable(s) in the target list; this does\nnot affect the next item assigned to it.\n\nNames in the target list are not deleted when the loop is finished,\nbut if the sequence is empty, it will not have been assigned to at all\nby the loop. Hint: the built-in function ``range()`` returns an\niterator of integers suitable to emulate the effect of Pascal\'s ``for\ni := a to b do``; e.g., ``list(range(3))`` returns the list ``[0, 1,\n2]``.\n\nNote: There is a subtlety when the sequence is being modified by the loop\n (this can only occur for mutable sequences, i.e. lists). An\n internal counter is used to keep track of which item is used next,\n and this is incremented on each iteration. When this counter has\n reached the length of the sequence the loop terminates. This means\n that if the suite deletes the current (or a previous) item from the\n sequence, the next item will be skipped (since it gets the index of\n the current item which has already been treated). Likewise, if the\n suite inserts an item in the sequence before the current item, the\n current item will be treated again the next time through the loop.\n This can lead to nasty bugs that can be avoided by making a\n temporary copy using a slice of the whole sequence, e.g.,\n\n for x in a[:]:\n if x < 0: a.remove(x)\n\n\nThe ``try`` statement\n=====================\n\nThe ``try`` statement specifies exception handlers and/or cleanup code\nfor a group of statements:\n\n try_stmt ::= try1_stmt | try2_stmt\n try1_stmt ::= "try" ":" suite\n ("except" [expression ["as" target]] ":" suite)+\n ["else" ":" suite]\n ["finally" ":" suite]\n try2_stmt ::= "try" ":" suite\n "finally" ":" suite\n\nThe ``except`` clause(s) specify one or more exception handlers. When\nno exception occurs in the ``try`` clause, no exception handler is\nexecuted. When an exception occurs in the ``try`` suite, a search for\nan exception handler is started. This search inspects the except\nclauses in turn until one is found that matches the exception. An\nexpression-less except clause, if present, must be last; it matches\nany exception. For an except clause with an expression, that\nexpression is evaluated, and the clause matches the exception if the\nresulting object is "compatible" with the exception. An object is\ncompatible with an exception if it is the class or a base class of the\nexception object or a tuple containing an item compatible with the\nexception.\n\nIf no except clause matches the exception, the search for an exception\nhandler continues in the surrounding code and on the invocation stack.\n[1]\n\nIf the evaluation of an expression in the header of an except clause\nraises an exception, the original search for a handler is canceled and\na search starts for the new exception in the surrounding code and on\nthe call stack (it is treated as if the entire ``try`` statement\nraised the exception).\n\nWhen a matching except clause is found, the exception is assigned to\nthe target specified after the ``as`` keyword in that except clause,\nif present, and the except clause\'s suite is executed. All except\nclauses must have an executable block. When the end of this block is\nreached, execution continues normally after the entire try statement.\n(This means that if two nested handlers exist for the same exception,\nand the exception occurs in the try clause of the inner handler, the\nouter handler will not handle the exception.)\n\nWhen an exception has been assigned using ``as target``, it is cleared\nat the end of the except clause. This is as if\n\n except E as N:\n foo\n\nwas translated to\n\n except E as N:\n try:\n foo\n finally:\n del N\n\nThis means the exception must be assigned to a different name to be\nable to refer to it after the except clause. Exceptions are cleared\nbecause with the traceback attached to them, they form a reference\ncycle with the stack frame, keeping all locals in that frame alive\nuntil the next garbage collection occurs.\n\nBefore an except clause\'s suite is executed, details about the\nexception are stored in the ``sys`` module and can be access via\n``sys.exc_info()``. ``sys.exc_info()`` returns a 3-tuple consisting of\nthe exception class, the exception instance and a traceback object\n(see section *The standard type hierarchy*) identifying the point in\nthe program where the exception occurred. ``sys.exc_info()`` values\nare restored to their previous values (before the call) when returning\nfrom a function that handled an exception.\n\nThe optional ``else`` clause is executed if and when control flows off\nthe end of the ``try`` clause. [2] Exceptions in the ``else`` clause\nare not handled by the preceding ``except`` clauses.\n\nIf ``finally`` is present, it specifies a \'cleanup\' handler. The\n``try`` clause is executed, including any ``except`` and ``else``\nclauses. If an exception occurs in any of the clauses and is not\nhandled, the exception is temporarily saved. The ``finally`` clause is\nexecuted. If there is a saved exception it is re-raised at the end of\nthe ``finally`` clause. If the ``finally`` clause raises another\nexception, the saved exception is set as the context of the new\nexception. If the ``finally`` clause executes a ``return`` or\n``break`` statement, the saved exception is discarded:\n\n def f():\n try:\n 1/0\n finally:\n return 42\n\n >>> f()\n 42\n\nThe exception information is not available to the program during\nexecution of the ``finally`` clause.\n\nWhen a ``return``, ``break`` or ``continue`` statement is executed in\nthe ``try`` suite of a ``try``...``finally`` statement, the\n``finally`` clause is also executed \'on the way out.\' A ``continue``\nstatement is illegal in the ``finally`` clause. (The reason is a\nproblem with the current implementation --- this restriction may be\nlifted in the future).\n\nAdditional information on exceptions can be found in section\n*Exceptions*, and information on using the ``raise`` statement to\ngenerate exceptions may be found in section *The raise statement*.\n\n\nThe ``with`` statement\n======================\n\nThe ``with`` statement is used to wrap the execution of a block with\nmethods defined by a context manager (see section *With Statement\nContext Managers*). This allows common\n``try``...``except``...``finally`` usage patterns to be encapsulated\nfor convenient reuse.\n\n with_stmt ::= "with" with_item ("," with_item)* ":" suite\n with_item ::= expression ["as" target]\n\nThe execution of the ``with`` statement with one "item" proceeds as\nfollows:\n\n1. The context expression (the expression given in the ``with_item``)\n is evaluated to obtain a context manager.\n\n2. The context manager\'s ``__exit__()`` is loaded for later use.\n\n3. The context manager\'s ``__enter__()`` method is invoked.\n\n4. If a target was included in the ``with`` statement, the return\n value from ``__enter__()`` is assigned to it.\n\n Note: The ``with`` statement guarantees that if the ``__enter__()``\n method returns without an error, then ``__exit__()`` will always\n be called. Thus, if an error occurs during the assignment to the\n target list, it will be treated the same as an error occurring\n within the suite would be. See step 6 below.\n\n5. The suite is executed.\n\n6. The context manager\'s ``__exit__()`` method is invoked. If an\n exception caused the suite to be exited, its type, value, and\n traceback are passed as arguments to ``__exit__()``. Otherwise,\n three ``None`` arguments are supplied.\n\n If the suite was exited due to an exception, and the return value\n from the ``__exit__()`` method was false, the exception is\n reraised. If the return value was true, the exception is\n suppressed, and execution continues with the statement following\n the ``with`` statement.\n\n If the suite was exited for any reason other than an exception, the\n return value from ``__exit__()`` is ignored, and execution proceeds\n at the normal location for the kind of exit that was taken.\n\nWith more than one item, the context managers are processed as if\nmultiple ``with`` statements were nested:\n\n with A() as a, B() as b:\n suite\n\nis equivalent to\n\n with A() as a:\n with B() as b:\n suite\n\nChanged in version 3.1: Support for multiple context expressions.\n\nSee also:\n\n **PEP 0343** - The "with" statement\n The specification, background, and examples for the Python\n ``with`` statement.\n\n\nFunction definitions\n====================\n\nA function definition defines a user-defined function object (see\nsection *The standard type hierarchy*):\n\n funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite\n decorators ::= decorator+\n decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE\n dotted_name ::= identifier ("." identifier)*\n parameter_list ::= (defparameter ",")*\n ( "*" [parameter] ("," defparameter)* ["," "**" parameter]\n | "**" parameter\n | defparameter [","] )\n parameter ::= identifier [":" expression]\n defparameter ::= parameter ["=" expression]\n funcname ::= identifier\n\nA function definition is an executable statement. Its execution binds\nthe function name in the current local namespace to a function object\n(a wrapper around the executable code for the function). This\nfunction object contains a reference to the current global namespace\nas the global namespace to be used when the function is called.\n\nThe function definition does not execute the function body; this gets\nexecuted only when the function is called. [3]\n\nA function definition may be wrapped by one or more *decorator*\nexpressions. Decorator expressions are evaluated when the function is\ndefined, in the scope that contains the function definition. The\nresult must be a callable, which is invoked with the function object\nas the only argument. The returned value is bound to the function name\ninstead of the function object. Multiple decorators are applied in\nnested fashion. For example, the following code\n\n @f1(arg)\n @f2\n def func(): pass\n\nis equivalent to\n\n def func(): pass\n func = f1(arg)(f2(func))\n\nWhen one or more *parameters* have the form *parameter* ``=``\n*expression*, the function is said to have "default parameter values."\nFor a parameter with a default value, the corresponding *argument* may\nbe omitted from a call, in which case the parameter\'s default value is\nsubstituted. If a parameter has a default value, all following\nparameters up until the "``*``" must also have a default value ---\nthis is a syntactic restriction that is not expressed by the grammar.\n\n**Default parameter values are evaluated from left to right when the\nfunction definition is executed.** This means that the expression is\nevaluated once, when the function is defined, and that the same "pre-\ncomputed" value is used for each call. This is especially important\nto understand when a default parameter is a mutable object, such as a\nlist or a dictionary: if the function modifies the object (e.g. by\nappending an item to a list), the default value is in effect modified.\nThis is generally not what was intended. A way around this is to use\n``None`` as the default, and explicitly test for it in the body of the\nfunction, e.g.:\n\n def whats_on_the_telly(penguin=None):\n if penguin is None:\n penguin = []\n penguin.append("property of the zoo")\n return penguin\n\nFunction call semantics are described in more detail in section\n*Calls*. A function call always assigns values to all parameters\nmentioned in the parameter list, either from position arguments, from\nkeyword arguments, or from default values. If the form\n"``*identifier``" is present, it is initialized to a tuple receiving\nany excess positional parameters, defaulting to the empty tuple. If\nthe form "``**identifier``" is present, it is initialized to a new\ndictionary receiving any excess keyword arguments, defaulting to a new\nempty dictionary. Parameters after "``*``" or "``*identifier``" are\nkeyword-only parameters and may only be passed used keyword arguments.\n\nParameters may have annotations of the form "``: expression``"\nfollowing the parameter name. Any parameter may have an annotation\neven those of the form ``*identifier`` or ``**identifier``. Functions\nmay have "return" annotation of the form "``-> expression``" after the\nparameter list. These annotations can be any valid Python expression\nand are evaluated when the function definition is executed.\nAnnotations may be evaluated in a different order than they appear in\nthe source code. The presence of annotations does not change the\nsemantics of a function. The annotation values are available as\nvalues of a dictionary keyed by the parameters\' names in the\n``__annotations__`` attribute of the function object.\n\nIt is also possible to create anonymous functions (functions not bound\nto a name), for immediate use in expressions. This uses lambda\nexpressions, described in section *Lambdas*. Note that the lambda\nexpression is merely a shorthand for a simplified function definition;\na function defined in a "``def``" statement can be passed around or\nassigned to another name just like a function defined by a lambda\nexpression. The "``def``" form is actually more powerful since it\nallows the execution of multiple statements and annotations.\n\n**Programmer\'s note:** Functions are first-class objects. A "``def``"\nstatement executed inside a function definition defines a local\nfunction that can be returned or passed around. Free variables used\nin the nested function can access the local variables of the function\ncontaining the def. See section *Naming and binding* for details.\n\nSee also:\n\n **PEP 3107** - Function Annotations\n The original specification for function annotations.\n\n\nClass definitions\n=================\n\nA class definition defines a class object (see section *The standard\ntype hierarchy*):\n\n classdef ::= [decorators] "class" classname [inheritance] ":" suite\n inheritance ::= "(" [parameter_list] ")"\n classname ::= identifier\n\nA class definition is an executable statement. The inheritance list\nusually gives a list of base classes (see *Customizing class creation*\nfor more advanced uses), so each item in the list should evaluate to a\nclass object which allows subclassing. Classes without an inheritance\nlist inherit, by default, from the base class ``object``; hence,\n\n class Foo:\n pass\n\nis equivalent to\n\n class Foo(object):\n pass\n\nThe class\'s suite is then executed in a new execution frame (see\n*Naming and binding*), using a newly created local namespace and the\noriginal global namespace. (Usually, the suite contains mostly\nfunction definitions.) When the class\'s suite finishes execution, its\nexecution frame is discarded but its local namespace is saved. [4] A\nclass object is then created using the inheritance list for the base\nclasses and the saved local namespace for the attribute dictionary.\nThe class name is bound to this class object in the original local\nnamespace.\n\nClass creation can be customized heavily using *metaclasses*.\n\nClasses can also be decorated: just like when decorating functions,\n\n @f1(arg)\n @f2\n class Foo: pass\n\nis equivalent to\n\n class Foo: pass\n Foo = f1(arg)(f2(Foo))\n\nThe evaluation rules for the decorator expressions are the same as for\nfunction decorators. The result must be a class object, which is then\nbound to the class name.\n\n**Programmer\'s note:** Variables defined in the class definition are\nclass attributes; they are shared by instances. Instance attributes\ncan be set in a method with ``self.name = value``. Both class and\ninstance attributes are accessible through the notation\n"``self.name``", and an instance attribute hides a class attribute\nwith the same name when accessed in this way. Class attributes can be\nused as defaults for instance attributes, but using mutable values\nthere can lead to unexpected results. *Descriptors* can be used to\ncreate instance variables with different implementation details.\n\nSee also:\n\n **PEP 3115** - Metaclasses in Python 3 **PEP 3129** - Class\n Decorators\n\n-[ Footnotes ]-\n\n[1] The exception is propagated to the invocation stack unless there\n is a ``finally`` clause which happens to raise another exception.\n That new exception causes the old one to be lost.\n\n[2] Currently, control "flows off the end" except in the case of an\n exception or the execution of a ``return``, ``continue``, or\n ``break`` statement.\n\n[3] A string literal appearing as the first statement in the function\n body is transformed into the function\'s ``__doc__`` attribute and\n therefore the function\'s *docstring*.\n\n[4] A string literal appearing as the first statement in the class\n body is transformed into the namespace\'s ``__doc__`` item and\n therefore the class\'s *docstring*.\n',
'context-managers': '\nWith Statement Context Managers\n*******************************\n\nA *context manager* is an object that defines the runtime context to\nbe established when executing a ``with`` statement. The context\nmanager handles the entry into, and the exit from, the desired runtime\ncontext for the execution of the block of code. Context managers are\nnormally invoked using the ``with`` statement (described in section\n*The with statement*), but can also be used by directly invoking their\nmethods.\n\nTypical uses of context managers include saving and restoring various\nkinds of global state, locking and unlocking resources, closing opened\nfiles, etc.\n\nFor more information on context managers, see *Context Manager Types*.\n\nobject.__enter__(self)\n\n Enter the runtime context related to this object. The ``with``\n statement will bind this method\'s return value to the target(s)\n specified in the ``as`` clause of the statement, if any.\n\nobject.__exit__(self, exc_type, exc_value, traceback)\n\n Exit the runtime context related to this object. The parameters\n describe the exception that caused the context to be exited. If the\n context was exited without an exception, all three arguments will\n be ``None``.\n\n If an exception is supplied, and the method wishes to suppress the\n exception (i.e., prevent it from being propagated), it should\n return a true value. Otherwise, the exception will be processed\n normally upon exit from this method.\n\n Note that ``__exit__()`` methods should not reraise the passed-in\n exception; this is the caller\'s responsibility.\n\nSee also:\n\n **PEP 0343** - The "with" statement\n The specification, background, and examples for the Python\n ``with`` statement.\n',
'continue': '\nThe ``continue`` statement\n**************************\n\n continue_stmt ::= "continue"\n\n``continue`` may only occur syntactically nested in a ``for`` or\n``while`` loop, but not nested in a function or class definition or\n``finally`` clause within that loop. It continues with the next cycle\nof the nearest enclosing loop.\n\nWhen ``continue`` passes control out of a ``try`` statement with a\n``finally`` clause, that ``finally`` clause is executed before really\nstarting the next loop cycle.\n',
'conversions': '\nArithmetic conversions\n**********************\n\nWhen a description of an arithmetic operator below uses the phrase\n"the numeric arguments are converted to a common type," this means\nthat the operator implementation for built-in types works that way:\n\n* If either argument is a complex number, the other is converted to\n complex;\n\n* otherwise, if either argument is a floating point number, the other\n is converted to floating point;\n\n* otherwise, both must be integers and no conversion is necessary.\n\nSome additional rules apply for certain operators (e.g., a string left\nargument to the \'%\' operator). Extensions must define their own\nconversion behavior.\n',
- 'customization': '\nBasic customization\n*******************\n\nobject.__new__(cls[, ...])\n\n Called to create a new instance of class *cls*. ``__new__()`` is a\n static method (special-cased so you need not declare it as such)\n that takes the class of which an instance was requested as its\n first argument. The remaining arguments are those passed to the\n object constructor expression (the call to the class). The return\n value of ``__new__()`` should be the new object instance (usually\n an instance of *cls*).\n\n Typical implementations create a new instance of the class by\n invoking the superclass\'s ``__new__()`` method using\n ``super(currentclass, cls).__new__(cls[, ...])`` with appropriate\n arguments and then modifying the newly-created instance as\n necessary before returning it.\n\n If ``__new__()`` returns an instance of *cls*, then the new\n instance\'s ``__init__()`` method will be invoked like\n ``__init__(self[, ...])``, where *self* is the new instance and the\n remaining arguments are the same as were passed to ``__new__()``.\n\n If ``__new__()`` does not return an instance of *cls*, then the new\n instance\'s ``__init__()`` method will not be invoked.\n\n ``__new__()`` is intended mainly to allow subclasses of immutable\n types (like int, str, or tuple) to customize instance creation. It\n is also commonly overridden in custom metaclasses in order to\n customize class creation.\n\nobject.__init__(self[, ...])\n\n Called when the instance is created. The arguments are those\n passed to the class constructor expression. If a base class has an\n ``__init__()`` method, the derived class\'s ``__init__()`` method,\n if any, must explicitly call it to ensure proper initialization of\n the base class part of the instance; for example:\n ``BaseClass.__init__(self, [args...])``. As a special constraint\n on constructors, no value may be returned; doing so will cause a\n ``TypeError`` to be raised at runtime.\n\nobject.__del__(self)\n\n Called when the instance is about to be destroyed. This is also\n called a destructor. If a base class has a ``__del__()`` method,\n the derived class\'s ``__del__()`` method, if any, must explicitly\n call it to ensure proper deletion of the base class part of the\n instance. Note that it is possible (though not recommended!) for\n the ``__del__()`` method to postpone destruction of the instance by\n creating a new reference to it. It may then be called at a later\n time when this new reference is deleted. It is not guaranteed that\n ``__del__()`` methods are called for objects that still exist when\n the interpreter exits.\n\n Note: ``del x`` doesn\'t directly call ``x.__del__()`` --- the former\n decrements the reference count for ``x`` by one, and the latter\n is only called when ``x``\'s reference count reaches zero. Some\n common situations that may prevent the reference count of an\n object from going to zero include: circular references between\n objects (e.g., a doubly-linked list or a tree data structure with\n parent and child pointers); a reference to the object on the\n stack frame of a function that caught an exception (the traceback\n stored in ``sys.exc_info()[2]`` keeps the stack frame alive); or\n a reference to the object on the stack frame that raised an\n unhandled exception in interactive mode (the traceback stored in\n ``sys.last_traceback`` keeps the stack frame alive). The first\n situation can only be remedied by explicitly breaking the cycles;\n the latter two situations can be resolved by storing ``None`` in\n ``sys.last_traceback``. Circular references which are garbage are\n detected when the option cycle detector is enabled (it\'s on by\n default), but can only be cleaned up if there are no Python-\n level ``__del__()`` methods involved. Refer to the documentation\n for the ``gc`` module for more information about how\n ``__del__()`` methods are handled by the cycle detector,\n particularly the description of the ``garbage`` value.\n\n Warning: Due to the precarious circumstances under which ``__del__()``\n methods are invoked, exceptions that occur during their execution\n are ignored, and a warning is printed to ``sys.stderr`` instead.\n Also, when ``__del__()`` is invoked in response to a module being\n deleted (e.g., when execution of the program is done), other\n globals referenced by the ``__del__()`` method may already have\n been deleted or in the process of being torn down (e.g. the\n import machinery shutting down). For this reason, ``__del__()``\n methods should do the absolute minimum needed to maintain\n external invariants. Starting with version 1.5, Python\n guarantees that globals whose name begins with a single\n underscore are deleted from their module before other globals are\n deleted; if no other references to such globals exist, this may\n help in assuring that imported modules are still available at the\n time when the ``__del__()`` method is called.\n\nobject.__repr__(self)\n\n Called by the ``repr()`` built-in function to compute the\n "official" string representation of an object. If at all possible,\n this should look like a valid Python expression that could be used\n to recreate an object with the same value (given an appropriate\n environment). If this is not possible, a string of the form\n ``<...some useful description...>`` should be returned. The return\n value must be a string object. If a class defines ``__repr__()``\n but not ``__str__()``, then ``__repr__()`` is also used when an\n "informal" string representation of instances of that class is\n required.\n\n This is typically used for debugging, so it is important that the\n representation is information-rich and unambiguous.\n\nobject.__str__(self)\n\n Called by ``str(object)`` and the built-in functions ``format()``\n and ``print()`` to compute the "informal" or nicely printable\n string representation of an object. The return value must be a\n *string* object.\n\n This method differs from ``object.__repr__()`` in that there is no\n expectation that ``__str__()`` return a valid Python expression: a\n more convenient or concise representation can be used.\n\n The default implementation defined by the built-in type ``object``\n calls ``object.__repr__()``.\n\nobject.__bytes__(self)\n\n Called by ``bytes()`` to compute a byte-string representation of an\n object. This should return a ``bytes`` object.\n\nobject.__format__(self, format_spec)\n\n Called by the ``format()`` built-in function (and by extension, the\n ``str.format()`` method of class ``str``) to produce a "formatted"\n string representation of an object. The ``format_spec`` argument is\n a string that contains a description of the formatting options\n desired. The interpretation of the ``format_spec`` argument is up\n to the type implementing ``__format__()``, however most classes\n will either delegate formatting to one of the built-in types, or\n use a similar formatting option syntax.\n\n See *Format Specification Mini-Language* for a description of the\n standard formatting syntax.\n\n The return value must be a string object.\n\nobject.__lt__(self, other)\nobject.__le__(self, other)\nobject.__eq__(self, other)\nobject.__ne__(self, other)\nobject.__gt__(self, other)\nobject.__ge__(self, other)\n\n These are the so-called "rich comparison" methods. The\n correspondence between operator symbols and method names is as\n follows: ``x<y`` calls ``x.__lt__(y)``, ``x<=y`` calls\n ``x.__le__(y)``, ``x==y`` calls ``x.__eq__(y)``, ``x!=y`` calls\n ``x.__ne__(y)``, ``x>y`` calls ``x.__gt__(y)``, and ``x>=y`` calls\n ``x.__ge__(y)``.\n\n A rich comparison method may return the singleton\n ``NotImplemented`` if it does not implement the operation for a\n given pair of arguments. By convention, ``False`` and ``True`` are\n returned for a successful comparison. However, these methods can\n return any value, so if the comparison operator is used in a\n Boolean context (e.g., in the condition of an ``if`` statement),\n Python will call ``bool()`` on the value to determine if the result\n is true or false.\n\n There are no implied relationships among the comparison operators.\n The truth of ``x==y`` does not imply that ``x!=y`` is false.\n Accordingly, when defining ``__eq__()``, one should also define\n ``__ne__()`` so that the operators will behave as expected. See\n the paragraph on ``__hash__()`` for some important notes on\n creating *hashable* objects which support custom comparison\n operations and are usable as dictionary keys.\n\n There are no swapped-argument versions of these methods (to be used\n when the left argument does not support the operation but the right\n argument does); rather, ``__lt__()`` and ``__gt__()`` are each\n other\'s reflection, ``__le__()`` and ``__ge__()`` are each other\'s\n reflection, and ``__eq__()`` and ``__ne__()`` are their own\n reflection.\n\n Arguments to rich comparison methods are never coerced.\n\n To automatically generate ordering operations from a single root\n operation, see ``functools.total_ordering()``.\n\nobject.__hash__(self)\n\n Called by built-in function ``hash()`` and for operations on\n members of hashed collections including ``set``, ``frozenset``, and\n ``dict``. ``__hash__()`` should return an integer. The only\n required property is that objects which compare equal have the same\n hash value; it is advised to somehow mix together (e.g. using\n exclusive or) the hash values for the components of the object that\n also play a part in comparison of objects.\n\n Note: ``hash()`` truncates the value returned from an object\'s custom\n ``__hash__()`` method to the size of a ``Py_ssize_t``. This is\n typically 8 bytes on 64-bit builds and 4 bytes on 32-bit builds.\n If an object\'s ``__hash__()`` must interoperate on builds of\n different bit sizes, be sure to check the width on all supported\n builds. An easy way to do this is with ``python -c "import sys;\n print(sys.hash_info.width)"``\n\n If a class does not define an ``__eq__()`` method it should not\n define a ``__hash__()`` operation either; if it defines\n ``__eq__()`` but not ``__hash__()``, its instances will not be\n usable as items in hashable collections. If a class defines\n mutable objects and implements an ``__eq__()`` method, it should\n not implement ``__hash__()``, since the implementation of hashable\n collections requires that a key\'s hash value is immutable (if the\n object\'s hash value changes, it will be in the wrong hash bucket).\n\n User-defined classes have ``__eq__()`` and ``__hash__()`` methods\n by default; with them, all objects compare unequal (except with\n themselves) and ``x.__hash__()`` returns an appropriate value such\n that ``x == y`` implies both that ``x is y`` and ``hash(x) ==\n hash(y)``.\n\n A class that overrides ``__eq__()`` and does not define\n ``__hash__()`` will have its ``__hash__()`` implicitly set to\n ``None``. When the ``__hash__()`` method of a class is ``None``,\n instances of the class will raise an appropriate ``TypeError`` when\n a program attempts to retrieve their hash value, and will also be\n correctly identified as unhashable when checking ``isinstance(obj,\n collections.Hashable``).\n\n If a class that overrides ``__eq__()`` needs to retain the\n implementation of ``__hash__()`` from a parent class, the\n interpreter must be told this explicitly by setting ``__hash__ =\n <ParentClass>.__hash__``.\n\n If a class that does not override ``__eq__()`` wishes to suppress\n hash support, it should include ``__hash__ = None`` in the class\n definition. A class which defines its own ``__hash__()`` that\n explicitly raises a ``TypeError`` would be incorrectly identified\n as hashable by an ``isinstance(obj, collections.Hashable)`` call.\n\n Note: By default, the ``__hash__()`` values of str, bytes and datetime\n objects are "salted" with an unpredictable random value.\n Although they remain constant within an individual Python\n process, they are not predictable between repeated invocations of\n Python.This is intended to provide protection against a denial-\n of-service caused by carefully-chosen inputs that exploit the\n worst case performance of a dict insertion, O(n^2) complexity.\n See http://www.ocert.org/advisories/ocert-2011-003.html for\n details.Changing hash values affects the iteration order of\n dicts, sets and other mappings. Python has never made guarantees\n about this ordering (and it typically varies between 32-bit and\n 64-bit builds).See also ``PYTHONHASHSEED``.\n\n Changed in version 3.3: Hash randomization is enabled by default.\n\nobject.__bool__(self)\n\n Called to implement truth value testing and the built-in operation\n ``bool()``; should return ``False`` or ``True``. When this method\n is not defined, ``__len__()`` is called, if it is defined, and the\n object is considered true if its result is nonzero. If a class\n defines neither ``__len__()`` nor ``__bool__()``, all its instances\n are considered true.\n',
- 'debugger': '\n``pdb`` --- The Python Debugger\n*******************************\n\nThe module ``pdb`` defines an interactive source code debugger for\nPython programs. It supports setting (conditional) breakpoints and\nsingle stepping at the source line level, inspection of stack frames,\nsource code listing, and evaluation of arbitrary Python code in the\ncontext of any stack frame. It also supports post-mortem debugging\nand can be called under program control.\n\nThe debugger is extensible -- it is actually defined as the class\n``Pdb``. This is currently undocumented but easily understood by\nreading the source. The extension interface uses the modules ``bdb``\nand ``cmd``.\n\nThe debugger\'s prompt is ``(Pdb)``. Typical usage to run a program\nunder control of the debugger is:\n\n >>> import pdb\n >>> import mymodule\n >>> pdb.run(\'mymodule.test()\')\n > <string>(0)?()\n (Pdb) continue\n > <string>(1)?()\n (Pdb) continue\n NameError: \'spam\'\n > <string>(1)?()\n (Pdb)\n\nChanged in version 3.3: Tab-completion via the ``readline`` module is\navailable for commands and command arguments, e.g. the current global\nand local names are offered as arguments of the ``print`` command.\n\n``pdb.py`` can also be invoked as a script to debug other scripts.\nFor example:\n\n python3 -m pdb myscript.py\n\nWhen invoked as a script, pdb will automatically enter post-mortem\ndebugging if the program being debugged exits abnormally. After post-\nmortem debugging (or after normal exit of the program), pdb will\nrestart the program. Automatic restarting preserves pdb\'s state (such\nas breakpoints) and in most cases is more useful than quitting the\ndebugger upon program\'s exit.\n\nNew in version 3.2: ``pdb.py`` now accepts a ``-c`` option that\nexecutes commands as if given in a ``.pdbrc`` file, see *Debugger\nCommands*.\n\nThe typical usage to break into the debugger from a running program is\nto insert\n\n import pdb; pdb.set_trace()\n\nat the location you want to break into the debugger. You can then\nstep through the code following this statement, and continue running\nwithout the debugger using the ``continue`` command.\n\nThe typical usage to inspect a crashed program is:\n\n >>> import pdb\n >>> import mymodule\n >>> mymodule.test()\n Traceback (most recent call last):\n File "<stdin>", line 1, in ?\n File "./mymodule.py", line 4, in test\n test2()\n File "./mymodule.py", line 3, in test2\n print(spam)\n NameError: spam\n >>> pdb.pm()\n > ./mymodule.py(3)test2()\n -> print(spam)\n (Pdb)\n\nThe module defines the following functions; each enters the debugger\nin a slightly different way:\n\npdb.run(statement, globals=None, locals=None)\n\n Execute the *statement* (given as a string or a code object) under\n debugger control. The debugger prompt appears before any code is\n executed; you can set breakpoints and type ``continue``, or you can\n step through the statement using ``step`` or ``next`` (all these\n commands are explained below). The optional *globals* and *locals*\n arguments specify the environment in which the code is executed; by\n default the dictionary of the module ``__main__`` is used. (See\n the explanation of the built-in ``exec()`` or ``eval()``\n functions.)\n\npdb.runeval(expression, globals=None, locals=None)\n\n Evaluate the *expression* (given as a string or a code object)\n under debugger control. When ``runeval()`` returns, it returns the\n value of the expression. Otherwise this function is similar to\n ``run()``.\n\npdb.runcall(function, *args, **kwds)\n\n Call the *function* (a function or method object, not a string)\n with the given arguments. When ``runcall()`` returns, it returns\n whatever the function call returned. The debugger prompt appears\n as soon as the function is entered.\n\npdb.set_trace()\n\n Enter the debugger at the calling stack frame. This is useful to\n hard-code a breakpoint at a given point in a program, even if the\n code is not otherwise being debugged (e.g. when an assertion\n fails).\n\npdb.post_mortem(traceback=None)\n\n Enter post-mortem debugging of the given *traceback* object. If no\n *traceback* is given, it uses the one of the exception that is\n currently being handled (an exception must be being handled if the\n default is to be used).\n\npdb.pm()\n\n Enter post-mortem debugging of the traceback found in\n ``sys.last_traceback``.\n\nThe ``run*`` functions and ``set_trace()`` are aliases for\ninstantiating the ``Pdb`` class and calling the method of the same\nname. If you want to access further features, you have to do this\nyourself:\n\nclass class pdb.Pdb(completekey=\'tab\', stdin=None, stdout=None, skip=None, nosigint=False)\n\n ``Pdb`` is the debugger class.\n\n The *completekey*, *stdin* and *stdout* arguments are passed to the\n underlying ``cmd.Cmd`` class; see the description there.\n\n The *skip* argument, if given, must be an iterable of glob-style\n module name patterns. The debugger will not step into frames that\n originate in a module that matches one of these patterns. [1]\n\n By default, Pdb sets a handler for the SIGINT signal (which is sent\n when the user presses Ctrl-C on the console) when you give a\n ``continue`` command. This allows you to break into the debugger\n again by pressing Ctrl-C. If you want Pdb not to touch the SIGINT\n handler, set *nosigint* tot true.\n\n Example call to enable tracing with *skip*:\n\n import pdb; pdb.Pdb(skip=[\'django.*\']).set_trace()\n\n New in version 3.1: The *skip* argument.\n\n New in version 3.2: The *nosigint* argument. Previously, a SIGINT\n handler was never set by Pdb.\n\n run(statement, globals=None, locals=None)\n runeval(expression, globals=None, locals=None)\n runcall(function, *args, **kwds)\n set_trace()\n\n See the documentation for the functions explained above.\n\n\nDebugger Commands\n=================\n\nThe commands recognized by the debugger are listed below. Most\ncommands can be abbreviated to one or two letters as indicated; e.g.\n``h(elp)`` means that either ``h`` or ``help`` can be used to enter\nthe help command (but not ``he`` or ``hel``, nor ``H`` or ``Help`` or\n``HELP``). Arguments to commands must be separated by whitespace\n(spaces or tabs). Optional arguments are enclosed in square brackets\n(``[]``) in the command syntax; the square brackets must not be typed.\nAlternatives in the command syntax are separated by a vertical bar\n(``|``).\n\nEntering a blank line repeats the last command entered. Exception: if\nthe last command was a ``list`` command, the next 11 lines are listed.\n\nCommands that the debugger doesn\'t recognize are assumed to be Python\nstatements and are executed in the context of the program being\ndebugged. Python statements can also be prefixed with an exclamation\npoint (``!``). This is a powerful way to inspect the program being\ndebugged; it is even possible to change a variable or call a function.\nWhen an exception occurs in such a statement, the exception name is\nprinted but the debugger\'s state is not changed.\n\nThe debugger supports *aliases*. Aliases can have parameters which\nallows one a certain level of adaptability to the context under\nexamination.\n\nMultiple commands may be entered on a single line, separated by\n``;;``. (A single ``;`` is not used as it is the separator for\nmultiple commands in a line that is passed to the Python parser.) No\nintelligence is applied to separating the commands; the input is split\nat the first ``;;`` pair, even if it is in the middle of a quoted\nstring.\n\nIf a file ``.pdbrc`` exists in the user\'s home directory or in the\ncurrent directory, it is read in and executed as if it had been typed\nat the debugger prompt. This is particularly useful for aliases. If\nboth files exist, the one in the home directory is read first and\naliases defined there can be overridden by the local file.\n\nChanged in version 3.2: ``.pdbrc`` can now contain commands that\ncontinue debugging, such as ``continue`` or ``next``. Previously,\nthese commands had no effect.\n\nh(elp) [command]\n\n Without argument, print the list of available commands. With a\n *command* as argument, print help about that command. ``help pdb``\n displays the full documentation (the docstring of the ``pdb``\n module). Since the *command* argument must be an identifier,\n ``help exec`` must be entered to get help on the ``!`` command.\n\nw(here)\n\n Print a stack trace, with the most recent frame at the bottom. An\n arrow indicates the current frame, which determines the context of\n most commands.\n\nd(own) [count]\n\n Move the current frame *count* (default one) levels down in the\n stack trace (to a newer frame).\n\nu(p) [count]\n\n Move the current frame *count* (default one) levels up in the stack\n trace (to an older frame).\n\nb(reak) [([filename:]lineno | function) [, condition]]\n\n With a *lineno* argument, set a break there in the current file.\n With a *function* argument, set a break at the first executable\n statement within that function. The line number may be prefixed\n with a filename and a colon, to specify a breakpoint in another\n file (probably one that hasn\'t been loaded yet). The file is\n searched on ``sys.path``. Note that each breakpoint is assigned a\n number to which all the other breakpoint commands refer.\n\n If a second argument is present, it is an expression which must\n evaluate to true before the breakpoint is honored.\n\n Without argument, list all breaks, including for each breakpoint,\n the number of times that breakpoint has been hit, the current\n ignore count, and the associated condition if any.\n\ntbreak [([filename:]lineno | function) [, condition]]\n\n Temporary breakpoint, which is removed automatically when it is\n first hit. The arguments are the same as for ``break``.\n\ncl(ear) [filename:lineno | bpnumber [bpnumber ...]]\n\n With a *filename:lineno* argument, clear all the breakpoints at\n this line. With a space separated list of breakpoint numbers, clear\n those breakpoints. Without argument, clear all breaks (but first\n ask confirmation).\n\ndisable [bpnumber [bpnumber ...]]\n\n Disable the breakpoints given as a space separated list of\n breakpoint numbers. Disabling a breakpoint means it cannot cause\n the program to stop execution, but unlike clearing a breakpoint, it\n remains in the list of breakpoints and can be (re-)enabled.\n\nenable [bpnumber [bpnumber ...]]\n\n Enable the breakpoints specified.\n\nignore bpnumber [count]\n\n Set the ignore count for the given breakpoint number. If count is\n omitted, the ignore count is set to 0. A breakpoint becomes active\n when the ignore count is zero. When non-zero, the count is\n decremented each time the breakpoint is reached and the breakpoint\n is not disabled and any associated condition evaluates to true.\n\ncondition bpnumber [condition]\n\n Set a new *condition* for the breakpoint, an expression which must\n evaluate to true before the breakpoint is honored. If *condition*\n is absent, any existing condition is removed; i.e., the breakpoint\n is made unconditional.\n\ncommands [bpnumber]\n\n Specify a list of commands for breakpoint number *bpnumber*. The\n commands themselves appear on the following lines. Type a line\n containing just ``end`` to terminate the commands. An example:\n\n (Pdb) commands 1\n (com) print some_variable\n (com) end\n (Pdb)\n\n To remove all commands from a breakpoint, type commands and follow\n it immediately with ``end``; that is, give no commands.\n\n With no *bpnumber* argument, commands refers to the last breakpoint\n set.\n\n You can use breakpoint commands to start your program up again.\n Simply use the continue command, or step, or any other command that\n resumes execution.\n\n Specifying any command resuming execution (currently continue,\n step, next, return, jump, quit and their abbreviations) terminates\n the command list (as if that command was immediately followed by\n end). This is because any time you resume execution (even with a\n simple next or step), you may encounter another breakpoint--which\n could have its own command list, leading to ambiguities about which\n list to execute.\n\n If you use the \'silent\' command in the command list, the usual\n message about stopping at a breakpoint is not printed. This may be\n desirable for breakpoints that are to print a specific message and\n then continue. If none of the other commands print anything, you\n see no sign that the breakpoint was reached.\n\ns(tep)\n\n Execute the current line, stop at the first possible occasion\n (either in a function that is called or on the next line in the\n current function).\n\nn(ext)\n\n Continue execution until the next line in the current function is\n reached or it returns. (The difference between ``next`` and\n ``step`` is that ``step`` stops inside a called function, while\n ``next`` executes called functions at (nearly) full speed, only\n stopping at the next line in the current function.)\n\nunt(il) [lineno]\n\n Without argument, continue execution until the line with a number\n greater than the current one is reached.\n\n With a line number, continue execution until a line with a number\n greater or equal to that is reached. In both cases, also stop when\n the current frame returns.\n\n Changed in version 3.2: Allow giving an explicit line number.\n\nr(eturn)\n\n Continue execution until the current function returns.\n\nc(ont(inue))\n\n Continue execution, only stop when a breakpoint is encountered.\n\nj(ump) lineno\n\n Set the next line that will be executed. Only available in the\n bottom-most frame. This lets you jump back and execute code again,\n or jump forward to skip code that you don\'t want to run.\n\n It should be noted that not all jumps are allowed -- for instance\n it is not possible to jump into the middle of a ``for`` loop or out\n of a ``finally`` clause.\n\nl(ist) [first[, last]]\n\n List source code for the current file. Without arguments, list 11\n lines around the current line or continue the previous listing.\n With ``.`` as argument, list 11 lines around the current line.\n With one argument, list 11 lines around at that line. With two\n arguments, list the given range; if the second argument is less\n than the first, it is interpreted as a count.\n\n The current line in the current frame is indicated by ``->``. If\n an exception is being debugged, the line where the exception was\n originally raised or propagated is indicated by ``>>``, if it\n differs from the current line.\n\n New in version 3.2: The ``>>`` marker.\n\nll | longlist\n\n List all source code for the current function or frame.\n Interesting lines are marked as for ``list``.\n\n New in version 3.2.\n\na(rgs)\n\n Print the argument list of the current function.\n\np(rint) expression\n\n Evaluate the *expression* in the current context and print its\n value.\n\npp expression\n\n Like the ``print`` command, except the value of the expression is\n pretty-printed using the ``pprint`` module.\n\nwhatis expression\n\n Print the type of the *expression*.\n\nsource expression\n\n Try to get source code for the given object and display it.\n\n New in version 3.2.\n\ndisplay [expression]\n\n Display the value of the expression if it changed, each time\n execution stops in the current frame.\n\n Without expression, list all display expressions for the current\n frame.\n\n New in version 3.2.\n\nundisplay [expression]\n\n Do not display the expression any more in the current frame.\n Without expression, clear all display expressions for the current\n frame.\n\n New in version 3.2.\n\ninteract\n\n Start an interative interpreter (using the ``code`` module) whose\n global namespace contains all the (global and local) names found in\n the current scope.\n\n New in version 3.2.\n\nalias [name [command]]\n\n Create an alias called *name* that executes *command*. The command\n must *not* be enclosed in quotes. Replaceable parameters can be\n indicated by ``%1``, ``%2``, and so on, while ``%*`` is replaced by\n all the parameters. If no command is given, the current alias for\n *name* is shown. If no arguments are given, all aliases are listed.\n\n Aliases may be nested and can contain anything that can be legally\n typed at the pdb prompt. Note that internal pdb commands *can* be\n overridden by aliases. Such a command is then hidden until the\n alias is removed. Aliasing is recursively applied to the first\n word of the command line; all other words in the line are left\n alone.\n\n As an example, here are two useful aliases (especially when placed\n in the ``.pdbrc`` file):\n\n # Print instance variables (usage "pi classInst")\n alias pi for k in %1.__dict__.keys(): print("%1.",k,"=",%1.__dict__[k])\n # Print instance variables in self\n alias ps pi self\n\nunalias name\n\n Delete the specified alias.\n\n! statement\n\n Execute the (one-line) *statement* in the context of the current\n stack frame. The exclamation point can be omitted unless the first\n word of the statement resembles a debugger command. To set a\n global variable, you can prefix the assignment command with a\n ``global`` statement on the same line, e.g.:\n\n (Pdb) global list_options; list_options = [\'-l\']\n (Pdb)\n\nrun [args ...]\nrestart [args ...]\n\n Restart the debugged Python program. If an argument is supplied,\n it is split with ``shlex`` and the result is used as the new\n ``sys.argv``. History, breakpoints, actions and debugger options\n are preserved. ``restart`` is an alias for ``run``.\n\nq(uit)\n\n Quit from the debugger. The program being executed is aborted.\n\n-[ Footnotes ]-\n\n[1] Whether a frame is considered to originate in a certain module is\n determined by the ``__name__`` in the frame globals.\n',
+ 'customization': '\nBasic customization\n*******************\n\nobject.__new__(cls[, ...])\n\n Called to create a new instance of class *cls*. ``__new__()`` is a\n static method (special-cased so you need not declare it as such)\n that takes the class of which an instance was requested as its\n first argument. The remaining arguments are those passed to the\n object constructor expression (the call to the class). The return\n value of ``__new__()`` should be the new object instance (usually\n an instance of *cls*).\n\n Typical implementations create a new instance of the class by\n invoking the superclass\'s ``__new__()`` method using\n ``super(currentclass, cls).__new__(cls[, ...])`` with appropriate\n arguments and then modifying the newly-created instance as\n necessary before returning it.\n\n If ``__new__()`` returns an instance of *cls*, then the new\n instance\'s ``__init__()`` method will be invoked like\n ``__init__(self[, ...])``, where *self* is the new instance and the\n remaining arguments are the same as were passed to ``__new__()``.\n\n If ``__new__()`` does not return an instance of *cls*, then the new\n instance\'s ``__init__()`` method will not be invoked.\n\n ``__new__()`` is intended mainly to allow subclasses of immutable\n types (like int, str, or tuple) to customize instance creation. It\n is also commonly overridden in custom metaclasses in order to\n customize class creation.\n\nobject.__init__(self[, ...])\n\n Called when the instance is created. The arguments are those\n passed to the class constructor expression. If a base class has an\n ``__init__()`` method, the derived class\'s ``__init__()`` method,\n if any, must explicitly call it to ensure proper initialization of\n the base class part of the instance; for example:\n ``BaseClass.__init__(self, [args...])``. As a special constraint\n on constructors, no value may be returned; doing so will cause a\n ``TypeError`` to be raised at runtime.\n\nobject.__del__(self)\n\n Called when the instance is about to be destroyed. This is also\n called a destructor. If a base class has a ``__del__()`` method,\n the derived class\'s ``__del__()`` method, if any, must explicitly\n call it to ensure proper deletion of the base class part of the\n instance. Note that it is possible (though not recommended!) for\n the ``__del__()`` method to postpone destruction of the instance by\n creating a new reference to it. It may then be called at a later\n time when this new reference is deleted. It is not guaranteed that\n ``__del__()`` methods are called for objects that still exist when\n the interpreter exits.\n\n Note: ``del x`` doesn\'t directly call ``x.__del__()`` --- the former\n decrements the reference count for ``x`` by one, and the latter\n is only called when ``x``\'s reference count reaches zero. Some\n common situations that may prevent the reference count of an\n object from going to zero include: circular references between\n objects (e.g., a doubly-linked list or a tree data structure with\n parent and child pointers); a reference to the object on the\n stack frame of a function that caught an exception (the traceback\n stored in ``sys.exc_info()[2]`` keeps the stack frame alive); or\n a reference to the object on the stack frame that raised an\n unhandled exception in interactive mode (the traceback stored in\n ``sys.last_traceback`` keeps the stack frame alive). The first\n situation can only be remedied by explicitly breaking the cycles;\n the latter two situations can be resolved by storing ``None`` in\n ``sys.last_traceback``. Circular references which are garbage are\n detected and cleaned up when the cyclic garbage collector is\n enabled (it\'s on by default). Refer to the documentation for the\n ``gc`` module for more information about this topic.\n\n Warning: Due to the precarious circumstances under which ``__del__()``\n methods are invoked, exceptions that occur during their execution\n are ignored, and a warning is printed to ``sys.stderr`` instead.\n Also, when ``__del__()`` is invoked in response to a module being\n deleted (e.g., when execution of the program is done), other\n globals referenced by the ``__del__()`` method may already have\n been deleted or in the process of being torn down (e.g. the\n import machinery shutting down). For this reason, ``__del__()``\n methods should do the absolute minimum needed to maintain\n external invariants. Starting with version 1.5, Python\n guarantees that globals whose name begins with a single\n underscore are deleted from their module before other globals are\n deleted; if no other references to such globals exist, this may\n help in assuring that imported modules are still available at the\n time when the ``__del__()`` method is called.\n\nobject.__repr__(self)\n\n Called by the ``repr()`` built-in function to compute the\n "official" string representation of an object. If at all possible,\n this should look like a valid Python expression that could be used\n to recreate an object with the same value (given an appropriate\n environment). If this is not possible, a string of the form\n ``<...some useful description...>`` should be returned. The return\n value must be a string object. If a class defines ``__repr__()``\n but not ``__str__()``, then ``__repr__()`` is also used when an\n "informal" string representation of instances of that class is\n required.\n\n This is typically used for debugging, so it is important that the\n representation is information-rich and unambiguous.\n\nobject.__str__(self)\n\n Called by ``str(object)`` and the built-in functions ``format()``\n and ``print()`` to compute the "informal" or nicely printable\n string representation of an object. The return value must be a\n *string* object.\n\n This method differs from ``object.__repr__()`` in that there is no\n expectation that ``__str__()`` return a valid Python expression: a\n more convenient or concise representation can be used.\n\n The default implementation defined by the built-in type ``object``\n calls ``object.__repr__()``.\n\nobject.__bytes__(self)\n\n Called by ``bytes()`` to compute a byte-string representation of an\n object. This should return a ``bytes`` object.\n\nobject.__format__(self, format_spec)\n\n Called by the ``format()`` built-in function (and by extension, the\n ``str.format()`` method of class ``str``) to produce a "formatted"\n string representation of an object. The ``format_spec`` argument is\n a string that contains a description of the formatting options\n desired. The interpretation of the ``format_spec`` argument is up\n to the type implementing ``__format__()``, however most classes\n will either delegate formatting to one of the built-in types, or\n use a similar formatting option syntax.\n\n See *Format Specification Mini-Language* for a description of the\n standard formatting syntax.\n\n The return value must be a string object.\n\nobject.__lt__(self, other)\nobject.__le__(self, other)\nobject.__eq__(self, other)\nobject.__ne__(self, other)\nobject.__gt__(self, other)\nobject.__ge__(self, other)\n\n These are the so-called "rich comparison" methods. The\n correspondence between operator symbols and method names is as\n follows: ``x<y`` calls ``x.__lt__(y)``, ``x<=y`` calls\n ``x.__le__(y)``, ``x==y`` calls ``x.__eq__(y)``, ``x!=y`` calls\n ``x.__ne__(y)``, ``x>y`` calls ``x.__gt__(y)``, and ``x>=y`` calls\n ``x.__ge__(y)``.\n\n A rich comparison method may return the singleton\n ``NotImplemented`` if it does not implement the operation for a\n given pair of arguments. By convention, ``False`` and ``True`` are\n returned for a successful comparison. However, these methods can\n return any value, so if the comparison operator is used in a\n Boolean context (e.g., in the condition of an ``if`` statement),\n Python will call ``bool()`` on the value to determine if the result\n is true or false.\n\n There are no implied relationships among the comparison operators.\n The truth of ``x==y`` does not imply that ``x!=y`` is false.\n Accordingly, when defining ``__eq__()``, one should also define\n ``__ne__()`` so that the operators will behave as expected. See\n the paragraph on ``__hash__()`` for some important notes on\n creating *hashable* objects which support custom comparison\n operations and are usable as dictionary keys.\n\n There are no swapped-argument versions of these methods (to be used\n when the left argument does not support the operation but the right\n argument does); rather, ``__lt__()`` and ``__gt__()`` are each\n other\'s reflection, ``__le__()`` and ``__ge__()`` are each other\'s\n reflection, and ``__eq__()`` and ``__ne__()`` are their own\n reflection.\n\n Arguments to rich comparison methods are never coerced.\n\n To automatically generate ordering operations from a single root\n operation, see ``functools.total_ordering()``.\n\nobject.__hash__(self)\n\n Called by built-in function ``hash()`` and for operations on\n members of hashed collections including ``set``, ``frozenset``, and\n ``dict``. ``__hash__()`` should return an integer. The only\n required property is that objects which compare equal have the same\n hash value; it is advised to somehow mix together (e.g. using\n exclusive or) the hash values for the components of the object that\n also play a part in comparison of objects.\n\n Note: ``hash()`` truncates the value returned from an object\'s custom\n ``__hash__()`` method to the size of a ``Py_ssize_t``. This is\n typically 8 bytes on 64-bit builds and 4 bytes on 32-bit builds.\n If an object\'s ``__hash__()`` must interoperate on builds of\n different bit sizes, be sure to check the width on all supported\n builds. An easy way to do this is with ``python -c "import sys;\n print(sys.hash_info.width)"``\n\n If a class does not define an ``__eq__()`` method it should not\n define a ``__hash__()`` operation either; if it defines\n ``__eq__()`` but not ``__hash__()``, its instances will not be\n usable as items in hashable collections. If a class defines\n mutable objects and implements an ``__eq__()`` method, it should\n not implement ``__hash__()``, since the implementation of hashable\n collections requires that a key\'s hash value is immutable (if the\n object\'s hash value changes, it will be in the wrong hash bucket).\n\n User-defined classes have ``__eq__()`` and ``__hash__()`` methods\n by default; with them, all objects compare unequal (except with\n themselves) and ``x.__hash__()`` returns an appropriate value such\n that ``x == y`` implies both that ``x is y`` and ``hash(x) ==\n hash(y)``.\n\n A class that overrides ``__eq__()`` and does not define\n ``__hash__()`` will have its ``__hash__()`` implicitly set to\n ``None``. When the ``__hash__()`` method of a class is ``None``,\n instances of the class will raise an appropriate ``TypeError`` when\n a program attempts to retrieve their hash value, and will also be\n correctly identified as unhashable when checking ``isinstance(obj,\n collections.Hashable``).\n\n If a class that overrides ``__eq__()`` needs to retain the\n implementation of ``__hash__()`` from a parent class, the\n interpreter must be told this explicitly by setting ``__hash__ =\n <ParentClass>.__hash__``.\n\n If a class that does not override ``__eq__()`` wishes to suppress\n hash support, it should include ``__hash__ = None`` in the class\n definition. A class which defines its own ``__hash__()`` that\n explicitly raises a ``TypeError`` would be incorrectly identified\n as hashable by an ``isinstance(obj, collections.Hashable)`` call.\n\n Note: By default, the ``__hash__()`` values of str, bytes and datetime\n objects are "salted" with an unpredictable random value.\n Although they remain constant within an individual Python\n process, they are not predictable between repeated invocations of\n Python.This is intended to provide protection against a denial-\n of-service caused by carefully-chosen inputs that exploit the\n worst case performance of a dict insertion, O(n^2) complexity.\n See http://www.ocert.org/advisories/ocert-2011-003.html for\n details.Changing hash values affects the iteration order of\n dicts, sets and other mappings. Python has never made guarantees\n about this ordering (and it typically varies between 32-bit and\n 64-bit builds).See also ``PYTHONHASHSEED``.\n\n Changed in version 3.3: Hash randomization is enabled by default.\n\nobject.__bool__(self)\n\n Called to implement truth value testing and the built-in operation\n ``bool()``; should return ``False`` or ``True``. When this method\n is not defined, ``__len__()`` is called, if it is defined, and the\n object is considered true if its result is nonzero. If a class\n defines neither ``__len__()`` nor ``__bool__()``, all its instances\n are considered true.\n',
+ 'debugger': '\n``pdb`` --- The Python Debugger\n*******************************\n\nThe module ``pdb`` defines an interactive source code debugger for\nPython programs. It supports setting (conditional) breakpoints and\nsingle stepping at the source line level, inspection of stack frames,\nsource code listing, and evaluation of arbitrary Python code in the\ncontext of any stack frame. It also supports post-mortem debugging\nand can be called under program control.\n\nThe debugger is extensible -- it is actually defined as the class\n``Pdb``. This is currently undocumented but easily understood by\nreading the source. The extension interface uses the modules ``bdb``\nand ``cmd``.\n\nThe debugger\'s prompt is ``(Pdb)``. Typical usage to run a program\nunder control of the debugger is:\n\n >>> import pdb\n >>> import mymodule\n >>> pdb.run(\'mymodule.test()\')\n > <string>(0)?()\n (Pdb) continue\n > <string>(1)?()\n (Pdb) continue\n NameError: \'spam\'\n > <string>(1)?()\n (Pdb)\n\nChanged in version 3.3: Tab-completion via the ``readline`` module is\navailable for commands and command arguments, e.g. the current global\nand local names are offered as arguments of the ``p`` command.\n\n``pdb.py`` can also be invoked as a script to debug other scripts.\nFor example:\n\n python3 -m pdb myscript.py\n\nWhen invoked as a script, pdb will automatically enter post-mortem\ndebugging if the program being debugged exits abnormally. After post-\nmortem debugging (or after normal exit of the program), pdb will\nrestart the program. Automatic restarting preserves pdb\'s state (such\nas breakpoints) and in most cases is more useful than quitting the\ndebugger upon program\'s exit.\n\nNew in version 3.2: ``pdb.py`` now accepts a ``-c`` option that\nexecutes commands as if given in a ``.pdbrc`` file, see *Debugger\nCommands*.\n\nThe typical usage to break into the debugger from a running program is\nto insert\n\n import pdb; pdb.set_trace()\n\nat the location you want to break into the debugger. You can then\nstep through the code following this statement, and continue running\nwithout the debugger using the ``continue`` command.\n\nThe typical usage to inspect a crashed program is:\n\n >>> import pdb\n >>> import mymodule\n >>> mymodule.test()\n Traceback (most recent call last):\n File "<stdin>", line 1, in ?\n File "./mymodule.py", line 4, in test\n test2()\n File "./mymodule.py", line 3, in test2\n print(spam)\n NameError: spam\n >>> pdb.pm()\n > ./mymodule.py(3)test2()\n -> print(spam)\n (Pdb)\n\nThe module defines the following functions; each enters the debugger\nin a slightly different way:\n\npdb.run(statement, globals=None, locals=None)\n\n Execute the *statement* (given as a string or a code object) under\n debugger control. The debugger prompt appears before any code is\n executed; you can set breakpoints and type ``continue``, or you can\n step through the statement using ``step`` or ``next`` (all these\n commands are explained below). The optional *globals* and *locals*\n arguments specify the environment in which the code is executed; by\n default the dictionary of the module ``__main__`` is used. (See\n the explanation of the built-in ``exec()`` or ``eval()``\n functions.)\n\npdb.runeval(expression, globals=None, locals=None)\n\n Evaluate the *expression* (given as a string or a code object)\n under debugger control. When ``runeval()`` returns, it returns the\n value of the expression. Otherwise this function is similar to\n ``run()``.\n\npdb.runcall(function, *args, **kwds)\n\n Call the *function* (a function or method object, not a string)\n with the given arguments. When ``runcall()`` returns, it returns\n whatever the function call returned. The debugger prompt appears\n as soon as the function is entered.\n\npdb.set_trace()\n\n Enter the debugger at the calling stack frame. This is useful to\n hard-code a breakpoint at a given point in a program, even if the\n code is not otherwise being debugged (e.g. when an assertion\n fails).\n\npdb.post_mortem(traceback=None)\n\n Enter post-mortem debugging of the given *traceback* object. If no\n *traceback* is given, it uses the one of the exception that is\n currently being handled (an exception must be being handled if the\n default is to be used).\n\npdb.pm()\n\n Enter post-mortem debugging of the traceback found in\n ``sys.last_traceback``.\n\nThe ``run*`` functions and ``set_trace()`` are aliases for\ninstantiating the ``Pdb`` class and calling the method of the same\nname. If you want to access further features, you have to do this\nyourself:\n\nclass class pdb.Pdb(completekey=\'tab\', stdin=None, stdout=None, skip=None, nosigint=False)\n\n ``Pdb`` is the debugger class.\n\n The *completekey*, *stdin* and *stdout* arguments are passed to the\n underlying ``cmd.Cmd`` class; see the description there.\n\n The *skip* argument, if given, must be an iterable of glob-style\n module name patterns. The debugger will not step into frames that\n originate in a module that matches one of these patterns. [1]\n\n By default, Pdb sets a handler for the SIGINT signal (which is sent\n when the user presses Ctrl-C on the console) when you give a\n ``continue`` command. This allows you to break into the debugger\n again by pressing Ctrl-C. If you want Pdb not to touch the SIGINT\n handler, set *nosigint* tot true.\n\n Example call to enable tracing with *skip*:\n\n import pdb; pdb.Pdb(skip=[\'django.*\']).set_trace()\n\n New in version 3.1: The *skip* argument.\n\n New in version 3.2: The *nosigint* argument. Previously, a SIGINT\n handler was never set by Pdb.\n\n run(statement, globals=None, locals=None)\n runeval(expression, globals=None, locals=None)\n runcall(function, *args, **kwds)\n set_trace()\n\n See the documentation for the functions explained above.\n\n\nDebugger Commands\n=================\n\nThe commands recognized by the debugger are listed below. Most\ncommands can be abbreviated to one or two letters as indicated; e.g.\n``h(elp)`` means that either ``h`` or ``help`` can be used to enter\nthe help command (but not ``he`` or ``hel``, nor ``H`` or ``Help`` or\n``HELP``). Arguments to commands must be separated by whitespace\n(spaces or tabs). Optional arguments are enclosed in square brackets\n(``[]``) in the command syntax; the square brackets must not be typed.\nAlternatives in the command syntax are separated by a vertical bar\n(``|``).\n\nEntering a blank line repeats the last command entered. Exception: if\nthe last command was a ``list`` command, the next 11 lines are listed.\n\nCommands that the debugger doesn\'t recognize are assumed to be Python\nstatements and are executed in the context of the program being\ndebugged. Python statements can also be prefixed with an exclamation\npoint (``!``). This is a powerful way to inspect the program being\ndebugged; it is even possible to change a variable or call a function.\nWhen an exception occurs in such a statement, the exception name is\nprinted but the debugger\'s state is not changed.\n\nThe debugger supports *aliases*. Aliases can have parameters which\nallows one a certain level of adaptability to the context under\nexamination.\n\nMultiple commands may be entered on a single line, separated by\n``;;``. (A single ``;`` is not used as it is the separator for\nmultiple commands in a line that is passed to the Python parser.) No\nintelligence is applied to separating the commands; the input is split\nat the first ``;;`` pair, even if it is in the middle of a quoted\nstring.\n\nIf a file ``.pdbrc`` exists in the user\'s home directory or in the\ncurrent directory, it is read in and executed as if it had been typed\nat the debugger prompt. This is particularly useful for aliases. If\nboth files exist, the one in the home directory is read first and\naliases defined there can be overridden by the local file.\n\nChanged in version 3.2: ``.pdbrc`` can now contain commands that\ncontinue debugging, such as ``continue`` or ``next``. Previously,\nthese commands had no effect.\n\nh(elp) [command]\n\n Without argument, print the list of available commands. With a\n *command* as argument, print help about that command. ``help pdb``\n displays the full documentation (the docstring of the ``pdb``\n module). Since the *command* argument must be an identifier,\n ``help exec`` must be entered to get help on the ``!`` command.\n\nw(here)\n\n Print a stack trace, with the most recent frame at the bottom. An\n arrow indicates the current frame, which determines the context of\n most commands.\n\nd(own) [count]\n\n Move the current frame *count* (default one) levels down in the\n stack trace (to a newer frame).\n\nu(p) [count]\n\n Move the current frame *count* (default one) levels up in the stack\n trace (to an older frame).\n\nb(reak) [([filename:]lineno | function) [, condition]]\n\n With a *lineno* argument, set a break there in the current file.\n With a *function* argument, set a break at the first executable\n statement within that function. The line number may be prefixed\n with a filename and a colon, to specify a breakpoint in another\n file (probably one that hasn\'t been loaded yet). The file is\n searched on ``sys.path``. Note that each breakpoint is assigned a\n number to which all the other breakpoint commands refer.\n\n If a second argument is present, it is an expression which must\n evaluate to true before the breakpoint is honored.\n\n Without argument, list all breaks, including for each breakpoint,\n the number of times that breakpoint has been hit, the current\n ignore count, and the associated condition if any.\n\ntbreak [([filename:]lineno | function) [, condition]]\n\n Temporary breakpoint, which is removed automatically when it is\n first hit. The arguments are the same as for ``break``.\n\ncl(ear) [filename:lineno | bpnumber [bpnumber ...]]\n\n With a *filename:lineno* argument, clear all the breakpoints at\n this line. With a space separated list of breakpoint numbers, clear\n those breakpoints. Without argument, clear all breaks (but first\n ask confirmation).\n\ndisable [bpnumber [bpnumber ...]]\n\n Disable the breakpoints given as a space separated list of\n breakpoint numbers. Disabling a breakpoint means it cannot cause\n the program to stop execution, but unlike clearing a breakpoint, it\n remains in the list of breakpoints and can be (re-)enabled.\n\nenable [bpnumber [bpnumber ...]]\n\n Enable the breakpoints specified.\n\nignore bpnumber [count]\n\n Set the ignore count for the given breakpoint number. If count is\n omitted, the ignore count is set to 0. A breakpoint becomes active\n when the ignore count is zero. When non-zero, the count is\n decremented each time the breakpoint is reached and the breakpoint\n is not disabled and any associated condition evaluates to true.\n\ncondition bpnumber [condition]\n\n Set a new *condition* for the breakpoint, an expression which must\n evaluate to true before the breakpoint is honored. If *condition*\n is absent, any existing condition is removed; i.e., the breakpoint\n is made unconditional.\n\ncommands [bpnumber]\n\n Specify a list of commands for breakpoint number *bpnumber*. The\n commands themselves appear on the following lines. Type a line\n containing just ``end`` to terminate the commands. An example:\n\n (Pdb) commands 1\n (com) p some_variable\n (com) end\n (Pdb)\n\n To remove all commands from a breakpoint, type commands and follow\n it immediately with ``end``; that is, give no commands.\n\n With no *bpnumber* argument, commands refers to the last breakpoint\n set.\n\n You can use breakpoint commands to start your program up again.\n Simply use the continue command, or step, or any other command that\n resumes execution.\n\n Specifying any command resuming execution (currently continue,\n step, next, return, jump, quit and their abbreviations) terminates\n the command list (as if that command was immediately followed by\n end). This is because any time you resume execution (even with a\n simple next or step), you may encounter another breakpoint--which\n could have its own command list, leading to ambiguities about which\n list to execute.\n\n If you use the \'silent\' command in the command list, the usual\n message about stopping at a breakpoint is not printed. This may be\n desirable for breakpoints that are to print a specific message and\n then continue. If none of the other commands print anything, you\n see no sign that the breakpoint was reached.\n\ns(tep)\n\n Execute the current line, stop at the first possible occasion\n (either in a function that is called or on the next line in the\n current function).\n\nn(ext)\n\n Continue execution until the next line in the current function is\n reached or it returns. (The difference between ``next`` and\n ``step`` is that ``step`` stops inside a called function, while\n ``next`` executes called functions at (nearly) full speed, only\n stopping at the next line in the current function.)\n\nunt(il) [lineno]\n\n Without argument, continue execution until the line with a number\n greater than the current one is reached.\n\n With a line number, continue execution until a line with a number\n greater or equal to that is reached. In both cases, also stop when\n the current frame returns.\n\n Changed in version 3.2: Allow giving an explicit line number.\n\nr(eturn)\n\n Continue execution until the current function returns.\n\nc(ont(inue))\n\n Continue execution, only stop when a breakpoint is encountered.\n\nj(ump) lineno\n\n Set the next line that will be executed. Only available in the\n bottom-most frame. This lets you jump back and execute code again,\n or jump forward to skip code that you don\'t want to run.\n\n It should be noted that not all jumps are allowed -- for instance\n it is not possible to jump into the middle of a ``for`` loop or out\n of a ``finally`` clause.\n\nl(ist) [first[, last]]\n\n List source code for the current file. Without arguments, list 11\n lines around the current line or continue the previous listing.\n With ``.`` as argument, list 11 lines around the current line.\n With one argument, list 11 lines around at that line. With two\n arguments, list the given range; if the second argument is less\n than the first, it is interpreted as a count.\n\n The current line in the current frame is indicated by ``->``. If\n an exception is being debugged, the line where the exception was\n originally raised or propagated is indicated by ``>>``, if it\n differs from the current line.\n\n New in version 3.2: The ``>>`` marker.\n\nll | longlist\n\n List all source code for the current function or frame.\n Interesting lines are marked as for ``list``.\n\n New in version 3.2.\n\na(rgs)\n\n Print the argument list of the current function.\n\np expression\n\n Evaluate the *expression* in the current context and print its\n value.\n\n Note: ``print()`` can also be used, but is not a debugger command ---\n this executes the Python ``print()`` function.\n\npp expression\n\n Like the ``p`` command, except the value of the expression is\n pretty-printed using the ``pprint`` module.\n\nwhatis expression\n\n Print the type of the *expression*.\n\nsource expression\n\n Try to get source code for the given object and display it.\n\n New in version 3.2.\n\ndisplay [expression]\n\n Display the value of the expression if it changed, each time\n execution stops in the current frame.\n\n Without expression, list all display expressions for the current\n frame.\n\n New in version 3.2.\n\nundisplay [expression]\n\n Do not display the expression any more in the current frame.\n Without expression, clear all display expressions for the current\n frame.\n\n New in version 3.2.\n\ninteract\n\n Start an interative interpreter (using the ``code`` module) whose\n global namespace contains all the (global and local) names found in\n the current scope.\n\n New in version 3.2.\n\nalias [name [command]]\n\n Create an alias called *name* that executes *command*. The command\n must *not* be enclosed in quotes. Replaceable parameters can be\n indicated by ``%1``, ``%2``, and so on, while ``%*`` is replaced by\n all the parameters. If no command is given, the current alias for\n *name* is shown. If no arguments are given, all aliases are listed.\n\n Aliases may be nested and can contain anything that can be legally\n typed at the pdb prompt. Note that internal pdb commands *can* be\n overridden by aliases. Such a command is then hidden until the\n alias is removed. Aliasing is recursively applied to the first\n word of the command line; all other words in the line are left\n alone.\n\n As an example, here are two useful aliases (especially when placed\n in the ``.pdbrc`` file):\n\n # Print instance variables (usage "pi classInst")\n alias pi for k in %1.__dict__.keys(): print("%1.",k,"=",%1.__dict__[k])\n # Print instance variables in self\n alias ps pi self\n\nunalias name\n\n Delete the specified alias.\n\n! statement\n\n Execute the (one-line) *statement* in the context of the current\n stack frame. The exclamation point can be omitted unless the first\n word of the statement resembles a debugger command. To set a\n global variable, you can prefix the assignment command with a\n ``global`` statement on the same line, e.g.:\n\n (Pdb) global list_options; list_options = [\'-l\']\n (Pdb)\n\nrun [args ...]\nrestart [args ...]\n\n Restart the debugged Python program. If an argument is supplied,\n it is split with ``shlex`` and the result is used as the new\n ``sys.argv``. History, breakpoints, actions and debugger options\n are preserved. ``restart`` is an alias for ``run``.\n\nq(uit)\n\n Quit from the debugger. The program being executed is aborted.\n\n-[ Footnotes ]-\n\n[1] Whether a frame is considered to originate in a certain module is\n determined by the ``__name__`` in the frame globals.\n',
'del': '\nThe ``del`` statement\n*********************\n\n del_stmt ::= "del" target_list\n\nDeletion is recursively defined very similar to the way assignment is\ndefined. Rather than spelling it out in full details, here are some\nhints.\n\nDeletion of a target list recursively deletes each target, from left\nto right.\n\nDeletion of a name removes the binding of that name from the local or\nglobal namespace, depending on whether the name occurs in a ``global``\nstatement in the same code block. If the name is unbound, a\n``NameError`` exception will be raised.\n\nDeletion of attribute references, subscriptions and slicings is passed\nto the primary object involved; deletion of a slicing is in general\nequivalent to assignment of an empty slice of the right type (but even\nthis is determined by the sliced object).\n\nChanged in version 3.2: Previously it was illegal to delete a name\nfrom the local namespace if it occurs as a free variable in a nested\nblock.\n',
'dict': '\nDictionary displays\n*******************\n\nA dictionary display is a possibly empty series of key/datum pairs\nenclosed in curly braces:\n\n dict_display ::= "{" [key_datum_list | dict_comprehension] "}"\n key_datum_list ::= key_datum ("," key_datum)* [","]\n key_datum ::= expression ":" expression\n dict_comprehension ::= expression ":" expression comp_for\n\nA dictionary display yields a new dictionary object.\n\nIf a comma-separated sequence of key/datum pairs is given, they are\nevaluated from left to right to define the entries of the dictionary:\neach key object is used as a key into the dictionary to store the\ncorresponding datum. This means that you can specify the same key\nmultiple times in the key/datum list, and the final dictionary\'s value\nfor that key will be the last one given.\n\nA dict comprehension, in contrast to list and set comprehensions,\nneeds two expressions separated with a colon followed by the usual\n"for" and "if" clauses. When the comprehension is run, the resulting\nkey and value elements are inserted in the new dictionary in the order\nthey are produced.\n\nRestrictions on the types of the key values are listed earlier in\nsection *The standard type hierarchy*. (To summarize, the key type\nshould be *hashable*, which excludes all mutable objects.) Clashes\nbetween duplicate keys are not detected; the last datum (textually\nrightmost in the display) stored for a given key value prevails.\n',
'dynamic-features': '\nInteraction with dynamic features\n*********************************\n\nThere are several cases where Python statements are illegal when used\nin conjunction with nested scopes that contain free variables.\n\nIf a variable is referenced in an enclosing scope, it is illegal to\ndelete the name. An error will be reported at compile time.\n\nIf the wild card form of import --- ``import *`` --- is used in a\nfunction and the function contains or is a nested block with free\nvariables, the compiler will raise a ``SyntaxError``.\n\nThe ``eval()`` and ``exec()`` functions do not have access to the full\nenvironment for resolving names. Names may be resolved in the local\nand global namespaces of the caller. Free variables are not resolved\nin the nearest enclosing namespace, but in the global namespace. [1]\nThe ``exec()`` and ``eval()`` functions have optional arguments to\noverride the global and local namespace. If only one namespace is\nspecified, it is used for both.\n',
@@ -34,8 +34,8 @@ topics = {'assert': '\nThe ``assert`` statement\n************************\n\nAss
'exprlists': '\nExpression lists\n****************\n\n expression_list ::= expression ( "," expression )* [","]\n\nAn expression list containing at least one comma yields a tuple. The\nlength of the tuple is the number of expressions in the list. The\nexpressions are evaluated from left to right.\n\nThe trailing comma is required only to create a single tuple (a.k.a. a\n*singleton*); it is optional in all other cases. A single expression\nwithout a trailing comma doesn\'t create a tuple, but rather yields the\nvalue of that expression. (To create an empty tuple, use an empty pair\nof parentheses: ``()``.)\n',
'floating': '\nFloating point literals\n***********************\n\nFloating point literals are described by the following lexical\ndefinitions:\n\n floatnumber ::= pointfloat | exponentfloat\n pointfloat ::= [intpart] fraction | intpart "."\n exponentfloat ::= (intpart | pointfloat) exponent\n intpart ::= digit+\n fraction ::= "." digit+\n exponent ::= ("e" | "E") ["+" | "-"] digit+\n\nNote that the integer and exponent parts are always interpreted using\nradix 10. For example, ``077e010`` is legal, and denotes the same\nnumber as ``77e10``. The allowed range of floating point literals is\nimplementation-dependent. Some examples of floating point literals:\n\n 3.14 10. .001 1e100 3.14e-10 0e0\n\nNote that numeric literals do not include a sign; a phrase like ``-1``\nis actually an expression composed of the unary operator ``-`` and the\nliteral ``1``.\n',
'for': '\nThe ``for`` statement\n*********************\n\nThe ``for`` statement is used to iterate over the elements of a\nsequence (such as a string, tuple or list) or other iterable object:\n\n for_stmt ::= "for" target_list "in" expression_list ":" suite\n ["else" ":" suite]\n\nThe expression list is evaluated once; it should yield an iterable\nobject. An iterator is created for the result of the\n``expression_list``. The suite is then executed once for each item\nprovided by the iterator, in the order of ascending indices. Each\nitem in turn is assigned to the target list using the standard rules\nfor assignments (see *Assignment statements*), and then the suite is\nexecuted. When the items are exhausted (which is immediately when the\nsequence is empty or an iterator raises a ``StopIteration``\nexception), the suite in the ``else`` clause, if present, is executed,\nand the loop terminates.\n\nA ``break`` statement executed in the first suite terminates the loop\nwithout executing the ``else`` clause\'s suite. A ``continue``\nstatement executed in the first suite skips the rest of the suite and\ncontinues with the next item, or with the ``else`` clause if there was\nno next item.\n\nThe suite may assign to the variable(s) in the target list; this does\nnot affect the next item assigned to it.\n\nNames in the target list are not deleted when the loop is finished,\nbut if the sequence is empty, it will not have been assigned to at all\nby the loop. Hint: the built-in function ``range()`` returns an\niterator of integers suitable to emulate the effect of Pascal\'s ``for\ni := a to b do``; e.g., ``list(range(3))`` returns the list ``[0, 1,\n2]``.\n\nNote: There is a subtlety when the sequence is being modified by the loop\n (this can only occur for mutable sequences, i.e. lists). An\n internal counter is used to keep track of which item is used next,\n and this is incremented on each iteration. When this counter has\n reached the length of the sequence the loop terminates. This means\n that if the suite deletes the current (or a previous) item from the\n sequence, the next item will be skipped (since it gets the index of\n the current item which has already been treated). Likewise, if the\n suite inserts an item in the sequence before the current item, the\n current item will be treated again the next time through the loop.\n This can lead to nasty bugs that can be avoided by making a\n temporary copy using a slice of the whole sequence, e.g.,\n\n for x in a[:]:\n if x < 0: a.remove(x)\n',
- 'formatstrings': '\nFormat String Syntax\n********************\n\nThe ``str.format()`` method and the ``Formatter`` class share the same\nsyntax for format strings (although in the case of ``Formatter``,\nsubclasses can define their own format string syntax).\n\nFormat strings contain "replacement fields" surrounded by curly braces\n``{}``. Anything that is not contained in braces is considered literal\ntext, which is copied unchanged to the output. If you need to include\na brace character in the literal text, it can be escaped by doubling:\n``{{`` and ``}}``.\n\nThe grammar for a replacement field is as follows:\n\n replacement_field ::= "{" [field_name] ["!" conversion] [":" format_spec] "}"\n field_name ::= arg_name ("." attribute_name | "[" element_index "]")*\n arg_name ::= [identifier | integer]\n attribute_name ::= identifier\n element_index ::= integer | index_string\n index_string ::= <any source character except "]"> +\n conversion ::= "r" | "s" | "a"\n format_spec ::= <described in the next section>\n\nIn less formal terms, the replacement field can start with a\n*field_name* that specifies the object whose value is to be formatted\nand inserted into the output instead of the replacement field. The\n*field_name* is optionally followed by a *conversion* field, which is\npreceded by an exclamation point ``\'!\'``, and a *format_spec*, which\nis preceded by a colon ``\':\'``. These specify a non-default format\nfor the replacement value.\n\nSee also the *Format Specification Mini-Language* section.\n\nThe *field_name* itself begins with an *arg_name* that is either a\nnumber or a keyword. If it\'s a number, it refers to a positional\nargument, and if it\'s a keyword, it refers to a named keyword\nargument. If the numerical arg_names in a format string are 0, 1, 2,\n... in sequence, they can all be omitted (not just some) and the\nnumbers 0, 1, 2, ... will be automatically inserted in that order.\nBecause *arg_name* is not quote-delimited, it is not possible to\nspecify arbitrary dictionary keys (e.g., the strings ``\'10\'`` or\n``\':-]\'``) within a format string. The *arg_name* can be followed by\nany number of index or attribute expressions. An expression of the\nform ``\'.name\'`` selects the named attribute using ``getattr()``,\nwhile an expression of the form ``\'[index]\'`` does an index lookup\nusing ``__getitem__()``.\n\nChanged in version 3.1: The positional argument specifiers can be\nomitted, so ``\'{} {}\'`` is equivalent to ``\'{0} {1}\'``.\n\nSome simple format string examples:\n\n "First, thou shalt count to {0}" # References first positional argument\n "Bring me a {}" # Implicitly references the first positional argument\n "From {} to {}" # Same as "From {0} to {1}"\n "My quest is {name}" # References keyword argument \'name\'\n "Weight in tons {0.weight}" # \'weight\' attribute of first positional arg\n "Units destroyed: {players[0]}" # First element of keyword argument \'players\'.\n\nThe *conversion* field causes a type coercion before formatting.\nNormally, the job of formatting a value is done by the\n``__format__()`` method of the value itself. However, in some cases\nit is desirable to force a type to be formatted as a string,\noverriding its own definition of formatting. By converting the value\nto a string before calling ``__format__()``, the normal formatting\nlogic is bypassed.\n\nThree conversion flags are currently supported: ``\'!s\'`` which calls\n``str()`` on the value, ``\'!r\'`` which calls ``repr()`` and ``\'!a\'``\nwhich calls ``ascii()``.\n\nSome examples:\n\n "Harold\'s a clever {0!s}" # Calls str() on the argument first\n "Bring out the holy {name!r}" # Calls repr() on the argument first\n "More {!a}" # Calls ascii() on the argument first\n\nThe *format_spec* field contains a specification of how the value\nshould be presented, including such details as field width, alignment,\npadding, decimal precision and so on. Each value type can define its\nown "formatting mini-language" or interpretation of the *format_spec*.\n\nMost built-in types support a common formatting mini-language, which\nis described in the next section.\n\nA *format_spec* field can also include nested replacement fields\nwithin it. These nested replacement fields can contain only a field\nname; conversion flags and format specifications are not allowed. The\nreplacement fields within the format_spec are substituted before the\n*format_spec* string is interpreted. This allows the formatting of a\nvalue to be dynamically specified.\n\nSee the *Format examples* section for some examples.\n\n\nFormat Specification Mini-Language\n==================================\n\n"Format specifications" are used within replacement fields contained\nwithin a format string to define how individual values are presented\n(see *Format String Syntax*). They can also be passed directly to the\nbuilt-in ``format()`` function. Each formattable type may define how\nthe format specification is to be interpreted.\n\nMost built-in types implement the following options for format\nspecifications, although some of the formatting options are only\nsupported by the numeric types.\n\nA general convention is that an empty format string (``""``) produces\nthe same result as if you had called ``str()`` on the value. A non-\nempty format string typically modifies the result.\n\nThe general form of a *standard format specifier* is:\n\n format_spec ::= [[fill]align][sign][#][0][width][,][.precision][type]\n fill ::= <any character>\n align ::= "<" | ">" | "=" | "^"\n sign ::= "+" | "-" | " "\n width ::= integer\n precision ::= integer\n type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"\n\nIf a valid *align* value is specified, it can be preceeded by a *fill*\ncharacter that can be any character and defaults to a space if\nomitted. Note that it is not possible to use ``{`` and ``}`` as *fill*\nchar while using the ``str.format()`` method; this limitation however\ndoesn\'t affect the ``format()`` function.\n\nThe meaning of the various alignment options is as follows:\n\n +-----------+------------------------------------------------------------+\n | Option | Meaning |\n +===========+============================================================+\n | ``\'<\'`` | Forces the field to be left-aligned within the available |\n | | space (this is the default for most objects). |\n +-----------+------------------------------------------------------------+\n | ``\'>\'`` | Forces the field to be right-aligned within the available |\n | | space (this is the default for numbers). |\n +-----------+------------------------------------------------------------+\n | ``\'=\'`` | Forces the padding to be placed after the sign (if any) |\n | | but before the digits. This is used for printing fields |\n | | in the form \'+000000120\'. This alignment option is only |\n | | valid for numeric types. |\n +-----------+------------------------------------------------------------+\n | ``\'^\'`` | Forces the field to be centered within the available |\n | | space. |\n +-----------+------------------------------------------------------------+\n\nNote that unless a minimum field width is defined, the field width\nwill always be the same size as the data to fill it, so that the\nalignment option has no meaning in this case.\n\nThe *sign* option is only valid for number types, and can be one of\nthe following:\n\n +-----------+------------------------------------------------------------+\n | Option | Meaning |\n +===========+============================================================+\n | ``\'+\'`` | indicates that a sign should be used for both positive as |\n | | well as negative numbers. |\n +-----------+------------------------------------------------------------+\n | ``\'-\'`` | indicates that a sign should be used only for negative |\n | | numbers (this is the default behavior). |\n +-----------+------------------------------------------------------------+\n | space | indicates that a leading space should be used on positive |\n | | numbers, and a minus sign on negative numbers. |\n +-----------+------------------------------------------------------------+\n\nThe ``\'#\'`` option causes the "alternate form" to be used for the\nconversion. The alternate form is defined differently for different\ntypes. This option is only valid for integer, float, complex and\nDecimal types. For integers, when binary, octal, or hexadecimal output\nis used, this option adds the prefix respective ``\'0b\'``, ``\'0o\'``, or\n``\'0x\'`` to the output value. For floats, complex and Decimal the\nalternate form causes the result of the conversion to always contain a\ndecimal-point character, even if no digits follow it. Normally, a\ndecimal-point character appears in the result of these conversions\nonly if a digit follows it. In addition, for ``\'g\'`` and ``\'G\'``\nconversions, trailing zeros are not removed from the result.\n\nThe ``\',\'`` option signals the use of a comma for a thousands\nseparator. For a locale aware separator, use the ``\'n\'`` integer\npresentation type instead.\n\nChanged in version 3.1: Added the ``\',\'`` option (see also **PEP\n378**).\n\n*width* is a decimal integer defining the minimum field width. If not\nspecified, then the field width will be determined by the content.\n\nPreceding the *width* field by a zero (``\'0\'``) character enables\nsign-aware zero-padding for numeric types. This is equivalent to a\n*fill* character of ``\'0\'`` with an *alignment* type of ``\'=\'``.\n\nThe *precision* is a decimal number indicating how many digits should\nbe displayed after the decimal point for a floating point value\nformatted with ``\'f\'`` and ``\'F\'``, or before and after the decimal\npoint for a floating point value formatted with ``\'g\'`` or ``\'G\'``.\nFor non-number types the field indicates the maximum field size - in\nother words, how many characters will be used from the field content.\nThe *precision* is not allowed for integer values.\n\nFinally, the *type* determines how the data should be presented.\n\nThe available string presentation types are:\n\n +-----------+------------------------------------------------------------+\n | Type | Meaning |\n +===========+============================================================+\n | ``\'s\'`` | String format. This is the default type for strings and |\n | | may be omitted. |\n +-----------+------------------------------------------------------------+\n | None | The same as ``\'s\'``. |\n +-----------+------------------------------------------------------------+\n\nThe available integer presentation types are:\n\n +-----------+------------------------------------------------------------+\n | Type | Meaning |\n +===========+============================================================+\n | ``\'b\'`` | Binary format. Outputs the number in base 2. |\n +-----------+------------------------------------------------------------+\n | ``\'c\'`` | Character. Converts the integer to the corresponding |\n | | unicode character before printing. |\n +-----------+------------------------------------------------------------+\n | ``\'d\'`` | Decimal Integer. Outputs the number in base 10. |\n +-----------+------------------------------------------------------------+\n | ``\'o\'`` | Octal format. Outputs the number in base 8. |\n +-----------+------------------------------------------------------------+\n | ``\'x\'`` | Hex format. Outputs the number in base 16, using lower- |\n | | case letters for the digits above 9. |\n +-----------+------------------------------------------------------------+\n | ``\'X\'`` | Hex format. Outputs the number in base 16, using upper- |\n | | case letters for the digits above 9. |\n +-----------+------------------------------------------------------------+\n | ``\'n\'`` | Number. This is the same as ``\'d\'``, except that it uses |\n | | the current locale setting to insert the appropriate |\n | | number separator characters. |\n +-----------+------------------------------------------------------------+\n | None | The same as ``\'d\'``. |\n +-----------+------------------------------------------------------------+\n\nIn addition to the above presentation types, integers can be formatted\nwith the floating point presentation types listed below (except\n``\'n\'`` and None). When doing so, ``float()`` is used to convert the\ninteger to a floating point number before formatting.\n\nThe available presentation types for floating point and decimal values\nare:\n\n +-----------+------------------------------------------------------------+\n | Type | Meaning |\n +===========+============================================================+\n | ``\'e\'`` | Exponent notation. Prints the number in scientific |\n | | notation using the letter \'e\' to indicate the exponent. |\n | | The default precision is ``6``. |\n +-----------+------------------------------------------------------------+\n | ``\'E\'`` | Exponent notation. Same as ``\'e\'`` except it uses an upper |\n | | case \'E\' as the separator character. |\n +-----------+------------------------------------------------------------+\n | ``\'f\'`` | Fixed point. Displays the number as a fixed-point number. |\n | | The default precision is ``6``. |\n +-----------+------------------------------------------------------------+\n | ``\'F\'`` | Fixed point. Same as ``\'f\'``, but converts ``nan`` to |\n | | ``NAN`` and ``inf`` to ``INF``. |\n +-----------+------------------------------------------------------------+\n | ``\'g\'`` | General format. For a given precision ``p >= 1``, this |\n | | rounds the number to ``p`` significant digits and then |\n | | formats the result in either fixed-point format or in |\n | | scientific notation, depending on its magnitude. The |\n | | precise rules are as follows: suppose that the result |\n | | formatted with presentation type ``\'e\'`` and precision |\n | | ``p-1`` would have exponent ``exp``. Then if ``-4 <= exp |\n | | < p``, the number is formatted with presentation type |\n | | ``\'f\'`` and precision ``p-1-exp``. Otherwise, the number |\n | | is formatted with presentation type ``\'e\'`` and precision |\n | | ``p-1``. In both cases insignificant trailing zeros are |\n | | removed from the significand, and the decimal point is |\n | | also removed if there are no remaining digits following |\n | | it. Positive and negative infinity, positive and negative |\n | | zero, and nans, are formatted as ``inf``, ``-inf``, ``0``, |\n | | ``-0`` and ``nan`` respectively, regardless of the |\n | | precision. A precision of ``0`` is treated as equivalent |\n | | to a precision of ``1``. The default precision is ``6``. |\n +-----------+------------------------------------------------------------+\n | ``\'G\'`` | General format. Same as ``\'g\'`` except switches to ``\'E\'`` |\n | | if the number gets too large. The representations of |\n | | infinity and NaN are uppercased, too. |\n +-----------+------------------------------------------------------------+\n | ``\'n\'`` | Number. This is the same as ``\'g\'``, except that it uses |\n | | the current locale setting to insert the appropriate |\n | | number separator characters. |\n +-----------+------------------------------------------------------------+\n | ``\'%\'`` | Percentage. Multiplies the number by 100 and displays in |\n | | fixed (``\'f\'``) format, followed by a percent sign. |\n +-----------+------------------------------------------------------------+\n | None | Similar to ``\'g\'``, except with at least one digit past |\n | | the decimal point and a default precision of 12. This is |\n | | intended to match ``str()``, except you can add the other |\n | | format modifiers. |\n +-----------+------------------------------------------------------------+\n\n\nFormat examples\n===============\n\nThis section contains examples of the new format syntax and comparison\nwith the old ``%``-formatting.\n\nIn most of the cases the syntax is similar to the old\n``%``-formatting, with the addition of the ``{}`` and with ``:`` used\ninstead of ``%``. For example, ``\'%03.2f\'`` can be translated to\n``\'{:03.2f}\'``.\n\nThe new format syntax also supports new and different options, shown\nin the follow examples.\n\nAccessing arguments by position:\n\n >>> \'{0}, {1}, {2}\'.format(\'a\', \'b\', \'c\')\n \'a, b, c\'\n >>> \'{}, {}, {}\'.format(\'a\', \'b\', \'c\') # 3.1+ only\n \'a, b, c\'\n >>> \'{2}, {1}, {0}\'.format(\'a\', \'b\', \'c\')\n \'c, b, a\'\n >>> \'{2}, {1}, {0}\'.format(*\'abc\') # unpacking argument sequence\n \'c, b, a\'\n >>> \'{0}{1}{0}\'.format(\'abra\', \'cad\') # arguments\' indices can be repeated\n \'abracadabra\'\n\nAccessing arguments by name:\n\n >>> \'Coordinates: {latitude}, {longitude}\'.format(latitude=\'37.24N\', longitude=\'-115.81W\')\n \'Coordinates: 37.24N, -115.81W\'\n >>> coord = {\'latitude\': \'37.24N\', \'longitude\': \'-115.81W\'}\n >>> \'Coordinates: {latitude}, {longitude}\'.format(**coord)\n \'Coordinates: 37.24N, -115.81W\'\n\nAccessing arguments\' attributes:\n\n >>> c = 3-5j\n >>> (\'The complex number {0} is formed from the real part {0.real} \'\n ... \'and the imaginary part {0.imag}.\').format(c)\n \'The complex number (3-5j) is formed from the real part 3.0 and the imaginary part -5.0.\'\n >>> class Point:\n ... def __init__(self, x, y):\n ... self.x, self.y = x, y\n ... def __str__(self):\n ... return \'Point({self.x}, {self.y})\'.format(self=self)\n ...\n >>> str(Point(4, 2))\n \'Point(4, 2)\'\n\nAccessing arguments\' items:\n\n >>> coord = (3, 5)\n >>> \'X: {0[0]}; Y: {0[1]}\'.format(coord)\n \'X: 3; Y: 5\'\n\nReplacing ``%s`` and ``%r``:\n\n >>> "repr() shows quotes: {!r}; str() doesn\'t: {!s}".format(\'test1\', \'test2\')\n "repr() shows quotes: \'test1\'; str() doesn\'t: test2"\n\nAligning the text and specifying a width:\n\n >>> \'{:<30}\'.format(\'left aligned\')\n \'left aligned \'\n >>> \'{:>30}\'.format(\'right aligned\')\n \' right aligned\'\n >>> \'{:^30}\'.format(\'centered\')\n \' centered \'\n >>> \'{:*^30}\'.format(\'centered\') # use \'*\' as a fill char\n \'***********centered***********\'\n\nReplacing ``%+f``, ``%-f``, and ``% f`` and specifying a sign:\n\n >>> \'{:+f}; {:+f}\'.format(3.14, -3.14) # show it always\n \'+3.140000; -3.140000\'\n >>> \'{: f}; {: f}\'.format(3.14, -3.14) # show a space for positive numbers\n \' 3.140000; -3.140000\'\n >>> \'{:-f}; {:-f}\'.format(3.14, -3.14) # show only the minus -- same as \'{:f}; {:f}\'\n \'3.140000; -3.140000\'\n\nReplacing ``%x`` and ``%o`` and converting the value to different\nbases:\n\n >>> # format also supports binary numbers\n >>> "int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}".format(42)\n \'int: 42; hex: 2a; oct: 52; bin: 101010\'\n >>> # with 0x, 0o, or 0b as prefix:\n >>> "int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}".format(42)\n \'int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010\'\n\nUsing the comma as a thousands separator:\n\n >>> \'{:,}\'.format(1234567890)\n \'1,234,567,890\'\n\nExpressing a percentage:\n\n >>> points = 19\n >>> total = 22\n >>> \'Correct answers: {:.2%}\'.format(points/total)\n \'Correct answers: 86.36%\'\n\nUsing type-specific formatting:\n\n >>> import datetime\n >>> d = datetime.datetime(2010, 7, 4, 12, 15, 58)\n >>> \'{:%Y-%m-%d %H:%M:%S}\'.format(d)\n \'2010-07-04 12:15:58\'\n\nNesting arguments and more complex examples:\n\n >>> for align, text in zip(\'<^>\', [\'left\', \'center\', \'right\']):\n ... \'{0:{fill}{align}16}\'.format(text, fill=align, align=align)\n ...\n \'left<<<<<<<<<<<<\'\n \'^^^^^center^^^^^\'\n \'>>>>>>>>>>>right\'\n >>>\n >>> octets = [192, 168, 0, 1]\n >>> \'{:02X}{:02X}{:02X}{:02X}\'.format(*octets)\n \'C0A80001\'\n >>> int(_, 16)\n 3232235521\n >>>\n >>> width = 5\n >>> for num in range(5,12): #doctest: +NORMALIZE_WHITESPACE\n ... for base in \'dXob\':\n ... print(\'{0:{width}{base}}\'.format(num, base=base, width=width), end=\' \')\n ... print()\n ...\n 5 5 5 101\n 6 6 6 110\n 7 7 7 111\n 8 8 10 1000\n 9 9 11 1001\n 10 A 12 1010\n 11 B 13 1011\n',
- 'function': '\nFunction definitions\n********************\n\nA function definition defines a user-defined function object (see\nsection *The standard type hierarchy*):\n\n funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite\n decorators ::= decorator+\n decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE\n dotted_name ::= identifier ("." identifier)*\n parameter_list ::= (defparameter ",")*\n ( "*" [parameter] ("," defparameter)* ["," "**" parameter]\n | "**" parameter\n | defparameter [","] )\n parameter ::= identifier [":" expression]\n defparameter ::= parameter ["=" expression]\n funcname ::= identifier\n\nA function definition is an executable statement. Its execution binds\nthe function name in the current local namespace to a function object\n(a wrapper around the executable code for the function). This\nfunction object contains a reference to the current global namespace\nas the global namespace to be used when the function is called.\n\nThe function definition does not execute the function body; this gets\nexecuted only when the function is called. [3]\n\nA function definition may be wrapped by one or more *decorator*\nexpressions. Decorator expressions are evaluated when the function is\ndefined, in the scope that contains the function definition. The\nresult must be a callable, which is invoked with the function object\nas the only argument. The returned value is bound to the function name\ninstead of the function object. Multiple decorators are applied in\nnested fashion. For example, the following code\n\n @f1(arg)\n @f2\n def func(): pass\n\nis equivalent to\n\n def func(): pass\n func = f1(arg)(f2(func))\n\nWhen one or more *parameters* have the form *parameter* ``=``\n*expression*, the function is said to have "default parameter values."\nFor a parameter with a default value, the corresponding *argument* may\nbe omitted from a call, in which case the parameter\'s default value is\nsubstituted. If a parameter has a default value, all following\nparameters up until the "``*``" must also have a default value ---\nthis is a syntactic restriction that is not expressed by the grammar.\n\n**Default parameter values are evaluated when the function definition\nis executed.** This means that the expression is evaluated once, when\nthe function is defined, and that the same "pre-computed" value is\nused for each call. This is especially important to understand when a\ndefault parameter is a mutable object, such as a list or a dictionary:\nif the function modifies the object (e.g. by appending an item to a\nlist), the default value is in effect modified. This is generally not\nwhat was intended. A way around this is to use ``None`` as the\ndefault, and explicitly test for it in the body of the function, e.g.:\n\n def whats_on_the_telly(penguin=None):\n if penguin is None:\n penguin = []\n penguin.append("property of the zoo")\n return penguin\n\nFunction call semantics are described in more detail in section\n*Calls*. A function call always assigns values to all parameters\nmentioned in the parameter list, either from position arguments, from\nkeyword arguments, or from default values. If the form\n"``*identifier``" is present, it is initialized to a tuple receiving\nany excess positional parameters, defaulting to the empty tuple. If\nthe form "``**identifier``" is present, it is initialized to a new\ndictionary receiving any excess keyword arguments, defaulting to a new\nempty dictionary. Parameters after "``*``" or "``*identifier``" are\nkeyword-only parameters and may only be passed used keyword arguments.\n\nParameters may have annotations of the form "``: expression``"\nfollowing the parameter name. Any parameter may have an annotation\neven those of the form ``*identifier`` or ``**identifier``. Functions\nmay have "return" annotation of the form "``-> expression``" after the\nparameter list. These annotations can be any valid Python expression\nand are evaluated when the function definition is executed.\nAnnotations may be evaluated in a different order than they appear in\nthe source code. The presence of annotations does not change the\nsemantics of a function. The annotation values are available as\nvalues of a dictionary keyed by the parameters\' names in the\n``__annotations__`` attribute of the function object.\n\nIt is also possible to create anonymous functions (functions not bound\nto a name), for immediate use in expressions. This uses lambda\nexpressions, described in section *Lambdas*. Note that the lambda\nexpression is merely a shorthand for a simplified function definition;\na function defined in a "``def``" statement can be passed around or\nassigned to another name just like a function defined by a lambda\nexpression. The "``def``" form is actually more powerful since it\nallows the execution of multiple statements and annotations.\n\n**Programmer\'s note:** Functions are first-class objects. A "``def``"\nstatement executed inside a function definition defines a local\nfunction that can be returned or passed around. Free variables used\nin the nested function can access the local variables of the function\ncontaining the def. See section *Naming and binding* for details.\n\nSee also:\n\n **PEP 3107** - Function Annotations\n The original specification for function annotations.\n',
+ 'formatstrings': '\nFormat String Syntax\n********************\n\nThe ``str.format()`` method and the ``Formatter`` class share the same\nsyntax for format strings (although in the case of ``Formatter``,\nsubclasses can define their own format string syntax).\n\nFormat strings contain "replacement fields" surrounded by curly braces\n``{}``. Anything that is not contained in braces is considered literal\ntext, which is copied unchanged to the output. If you need to include\na brace character in the literal text, it can be escaped by doubling:\n``{{`` and ``}}``.\n\nThe grammar for a replacement field is as follows:\n\n replacement_field ::= "{" [field_name] ["!" conversion] [":" format_spec] "}"\n field_name ::= arg_name ("." attribute_name | "[" element_index "]")*\n arg_name ::= [identifier | integer]\n attribute_name ::= identifier\n element_index ::= integer | index_string\n index_string ::= <any source character except "]"> +\n conversion ::= "r" | "s" | "a"\n format_spec ::= <described in the next section>\n\nIn less formal terms, the replacement field can start with a\n*field_name* that specifies the object whose value is to be formatted\nand inserted into the output instead of the replacement field. The\n*field_name* is optionally followed by a *conversion* field, which is\npreceded by an exclamation point ``\'!\'``, and a *format_spec*, which\nis preceded by a colon ``\':\'``. These specify a non-default format\nfor the replacement value.\n\nSee also the *Format Specification Mini-Language* section.\n\nThe *field_name* itself begins with an *arg_name* that is either a\nnumber or a keyword. If it\'s a number, it refers to a positional\nargument, and if it\'s a keyword, it refers to a named keyword\nargument. If the numerical arg_names in a format string are 0, 1, 2,\n... in sequence, they can all be omitted (not just some) and the\nnumbers 0, 1, 2, ... will be automatically inserted in that order.\nBecause *arg_name* is not quote-delimited, it is not possible to\nspecify arbitrary dictionary keys (e.g., the strings ``\'10\'`` or\n``\':-]\'``) within a format string. The *arg_name* can be followed by\nany number of index or attribute expressions. An expression of the\nform ``\'.name\'`` selects the named attribute using ``getattr()``,\nwhile an expression of the form ``\'[index]\'`` does an index lookup\nusing ``__getitem__()``.\n\nChanged in version 3.1: The positional argument specifiers can be\nomitted, so ``\'{} {}\'`` is equivalent to ``\'{0} {1}\'``.\n\nSome simple format string examples:\n\n "First, thou shalt count to {0}" # References first positional argument\n "Bring me a {}" # Implicitly references the first positional argument\n "From {} to {}" # Same as "From {0} to {1}"\n "My quest is {name}" # References keyword argument \'name\'\n "Weight in tons {0.weight}" # \'weight\' attribute of first positional arg\n "Units destroyed: {players[0]}" # First element of keyword argument \'players\'.\n\nThe *conversion* field causes a type coercion before formatting.\nNormally, the job of formatting a value is done by the\n``__format__()`` method of the value itself. However, in some cases\nit is desirable to force a type to be formatted as a string,\noverriding its own definition of formatting. By converting the value\nto a string before calling ``__format__()``, the normal formatting\nlogic is bypassed.\n\nThree conversion flags are currently supported: ``\'!s\'`` which calls\n``str()`` on the value, ``\'!r\'`` which calls ``repr()`` and ``\'!a\'``\nwhich calls ``ascii()``.\n\nSome examples:\n\n "Harold\'s a clever {0!s}" # Calls str() on the argument first\n "Bring out the holy {name!r}" # Calls repr() on the argument first\n "More {!a}" # Calls ascii() on the argument first\n\nThe *format_spec* field contains a specification of how the value\nshould be presented, including such details as field width, alignment,\npadding, decimal precision and so on. Each value type can define its\nown "formatting mini-language" or interpretation of the *format_spec*.\n\nMost built-in types support a common formatting mini-language, which\nis described in the next section.\n\nA *format_spec* field can also include nested replacement fields\nwithin it. These nested replacement fields can contain only a field\nname; conversion flags and format specifications are not allowed. The\nreplacement fields within the format_spec are substituted before the\n*format_spec* string is interpreted. This allows the formatting of a\nvalue to be dynamically specified.\n\nSee the *Format examples* section for some examples.\n\n\nFormat Specification Mini-Language\n==================================\n\n"Format specifications" are used within replacement fields contained\nwithin a format string to define how individual values are presented\n(see *Format String Syntax*). They can also be passed directly to the\nbuilt-in ``format()`` function. Each formattable type may define how\nthe format specification is to be interpreted.\n\nMost built-in types implement the following options for format\nspecifications, although some of the formatting options are only\nsupported by the numeric types.\n\nA general convention is that an empty format string (``""``) produces\nthe same result as if you had called ``str()`` on the value. A non-\nempty format string typically modifies the result.\n\nThe general form of a *standard format specifier* is:\n\n format_spec ::= [[fill]align][sign][#][0][width][,][.precision][type]\n fill ::= <any character>\n align ::= "<" | ">" | "=" | "^"\n sign ::= "+" | "-" | " "\n width ::= integer\n precision ::= integer\n type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"\n\nIf a valid *align* value is specified, it can be preceded by a *fill*\ncharacter that can be any character and defaults to a space if\nomitted. Note that it is not possible to use ``{`` and ``}`` as *fill*\nchar while using the ``str.format()`` method; this limitation however\ndoesn\'t affect the ``format()`` function.\n\nThe meaning of the various alignment options is as follows:\n\n +-----------+------------------------------------------------------------+\n | Option | Meaning |\n +===========+============================================================+\n | ``\'<\'`` | Forces the field to be left-aligned within the available |\n | | space (this is the default for most objects). |\n +-----------+------------------------------------------------------------+\n | ``\'>\'`` | Forces the field to be right-aligned within the available |\n | | space (this is the default for numbers). |\n +-----------+------------------------------------------------------------+\n | ``\'=\'`` | Forces the padding to be placed after the sign (if any) |\n | | but before the digits. This is used for printing fields |\n | | in the form \'+000000120\'. This alignment option is only |\n | | valid for numeric types. |\n +-----------+------------------------------------------------------------+\n | ``\'^\'`` | Forces the field to be centered within the available |\n | | space. |\n +-----------+------------------------------------------------------------+\n\nNote that unless a minimum field width is defined, the field width\nwill always be the same size as the data to fill it, so that the\nalignment option has no meaning in this case.\n\nThe *sign* option is only valid for number types, and can be one of\nthe following:\n\n +-----------+------------------------------------------------------------+\n | Option | Meaning |\n +===========+============================================================+\n | ``\'+\'`` | indicates that a sign should be used for both positive as |\n | | well as negative numbers. |\n +-----------+------------------------------------------------------------+\n | ``\'-\'`` | indicates that a sign should be used only for negative |\n | | numbers (this is the default behavior). |\n +-----------+------------------------------------------------------------+\n | space | indicates that a leading space should be used on positive |\n | | numbers, and a minus sign on negative numbers. |\n +-----------+------------------------------------------------------------+\n\nThe ``\'#\'`` option causes the "alternate form" to be used for the\nconversion. The alternate form is defined differently for different\ntypes. This option is only valid for integer, float, complex and\nDecimal types. For integers, when binary, octal, or hexadecimal output\nis used, this option adds the prefix respective ``\'0b\'``, ``\'0o\'``, or\n``\'0x\'`` to the output value. For floats, complex and Decimal the\nalternate form causes the result of the conversion to always contain a\ndecimal-point character, even if no digits follow it. Normally, a\ndecimal-point character appears in the result of these conversions\nonly if a digit follows it. In addition, for ``\'g\'`` and ``\'G\'``\nconversions, trailing zeros are not removed from the result.\n\nThe ``\',\'`` option signals the use of a comma for a thousands\nseparator. For a locale aware separator, use the ``\'n\'`` integer\npresentation type instead.\n\nChanged in version 3.1: Added the ``\',\'`` option (see also **PEP\n378**).\n\n*width* is a decimal integer defining the minimum field width. If not\nspecified, then the field width will be determined by the content.\n\nPreceding the *width* field by a zero (``\'0\'``) character enables\nsign-aware zero-padding for numeric types. This is equivalent to a\n*fill* character of ``\'0\'`` with an *alignment* type of ``\'=\'``.\n\nThe *precision* is a decimal number indicating how many digits should\nbe displayed after the decimal point for a floating point value\nformatted with ``\'f\'`` and ``\'F\'``, or before and after the decimal\npoint for a floating point value formatted with ``\'g\'`` or ``\'G\'``.\nFor non-number types the field indicates the maximum field size - in\nother words, how many characters will be used from the field content.\nThe *precision* is not allowed for integer values.\n\nFinally, the *type* determines how the data should be presented.\n\nThe available string presentation types are:\n\n +-----------+------------------------------------------------------------+\n | Type | Meaning |\n +===========+============================================================+\n | ``\'s\'`` | String format. This is the default type for strings and |\n | | may be omitted. |\n +-----------+------------------------------------------------------------+\n | None | The same as ``\'s\'``. |\n +-----------+------------------------------------------------------------+\n\nThe available integer presentation types are:\n\n +-----------+------------------------------------------------------------+\n | Type | Meaning |\n +===========+============================================================+\n | ``\'b\'`` | Binary format. Outputs the number in base 2. |\n +-----------+------------------------------------------------------------+\n | ``\'c\'`` | Character. Converts the integer to the corresponding |\n | | unicode character before printing. |\n +-----------+------------------------------------------------------------+\n | ``\'d\'`` | Decimal Integer. Outputs the number in base 10. |\n +-----------+------------------------------------------------------------+\n | ``\'o\'`` | Octal format. Outputs the number in base 8. |\n +-----------+------------------------------------------------------------+\n | ``\'x\'`` | Hex format. Outputs the number in base 16, using lower- |\n | | case letters for the digits above 9. |\n +-----------+------------------------------------------------------------+\n | ``\'X\'`` | Hex format. Outputs the number in base 16, using upper- |\n | | case letters for the digits above 9. |\n +-----------+------------------------------------------------------------+\n | ``\'n\'`` | Number. This is the same as ``\'d\'``, except that it uses |\n | | the current locale setting to insert the appropriate |\n | | number separator characters. |\n +-----------+------------------------------------------------------------+\n | None | The same as ``\'d\'``. |\n +-----------+------------------------------------------------------------+\n\nIn addition to the above presentation types, integers can be formatted\nwith the floating point presentation types listed below (except\n``\'n\'`` and None). When doing so, ``float()`` is used to convert the\ninteger to a floating point number before formatting.\n\nThe available presentation types for floating point and decimal values\nare:\n\n +-----------+------------------------------------------------------------+\n | Type | Meaning |\n +===========+============================================================+\n | ``\'e\'`` | Exponent notation. Prints the number in scientific |\n | | notation using the letter \'e\' to indicate the exponent. |\n | | The default precision is ``6``. |\n +-----------+------------------------------------------------------------+\n | ``\'E\'`` | Exponent notation. Same as ``\'e\'`` except it uses an upper |\n | | case \'E\' as the separator character. |\n +-----------+------------------------------------------------------------+\n | ``\'f\'`` | Fixed point. Displays the number as a fixed-point number. |\n | | The default precision is ``6``. |\n +-----------+------------------------------------------------------------+\n | ``\'F\'`` | Fixed point. Same as ``\'f\'``, but converts ``nan`` to |\n | | ``NAN`` and ``inf`` to ``INF``. |\n +-----------+------------------------------------------------------------+\n | ``\'g\'`` | General format. For a given precision ``p >= 1``, this |\n | | rounds the number to ``p`` significant digits and then |\n | | formats the result in either fixed-point format or in |\n | | scientific notation, depending on its magnitude. The |\n | | precise rules are as follows: suppose that the result |\n | | formatted with presentation type ``\'e\'`` and precision |\n | | ``p-1`` would have exponent ``exp``. Then if ``-4 <= exp |\n | | < p``, the number is formatted with presentation type |\n | | ``\'f\'`` and precision ``p-1-exp``. Otherwise, the number |\n | | is formatted with presentation type ``\'e\'`` and precision |\n | | ``p-1``. In both cases insignificant trailing zeros are |\n | | removed from the significand, and the decimal point is |\n | | also removed if there are no remaining digits following |\n | | it. Positive and negative infinity, positive and negative |\n | | zero, and nans, are formatted as ``inf``, ``-inf``, ``0``, |\n | | ``-0`` and ``nan`` respectively, regardless of the |\n | | precision. A precision of ``0`` is treated as equivalent |\n | | to a precision of ``1``. The default precision is ``6``. |\n +-----------+------------------------------------------------------------+\n | ``\'G\'`` | General format. Same as ``\'g\'`` except switches to ``\'E\'`` |\n | | if the number gets too large. The representations of |\n | | infinity and NaN are uppercased, too. |\n +-----------+------------------------------------------------------------+\n | ``\'n\'`` | Number. This is the same as ``\'g\'``, except that it uses |\n | | the current locale setting to insert the appropriate |\n | | number separator characters. |\n +-----------+------------------------------------------------------------+\n | ``\'%\'`` | Percentage. Multiplies the number by 100 and displays in |\n | | fixed (``\'f\'``) format, followed by a percent sign. |\n +-----------+------------------------------------------------------------+\n | None | Similar to ``\'g\'``, except with at least one digit past |\n | | the decimal point and a default precision of 12. This is |\n | | intended to match ``str()``, except you can add the other |\n | | format modifiers. |\n +-----------+------------------------------------------------------------+\n\n\nFormat examples\n===============\n\nThis section contains examples of the new format syntax and comparison\nwith the old ``%``-formatting.\n\nIn most of the cases the syntax is similar to the old\n``%``-formatting, with the addition of the ``{}`` and with ``:`` used\ninstead of ``%``. For example, ``\'%03.2f\'`` can be translated to\n``\'{:03.2f}\'``.\n\nThe new format syntax also supports new and different options, shown\nin the follow examples.\n\nAccessing arguments by position:\n\n >>> \'{0}, {1}, {2}\'.format(\'a\', \'b\', \'c\')\n \'a, b, c\'\n >>> \'{}, {}, {}\'.format(\'a\', \'b\', \'c\') # 3.1+ only\n \'a, b, c\'\n >>> \'{2}, {1}, {0}\'.format(\'a\', \'b\', \'c\')\n \'c, b, a\'\n >>> \'{2}, {1}, {0}\'.format(*\'abc\') # unpacking argument sequence\n \'c, b, a\'\n >>> \'{0}{1}{0}\'.format(\'abra\', \'cad\') # arguments\' indices can be repeated\n \'abracadabra\'\n\nAccessing arguments by name:\n\n >>> \'Coordinates: {latitude}, {longitude}\'.format(latitude=\'37.24N\', longitude=\'-115.81W\')\n \'Coordinates: 37.24N, -115.81W\'\n >>> coord = {\'latitude\': \'37.24N\', \'longitude\': \'-115.81W\'}\n >>> \'Coordinates: {latitude}, {longitude}\'.format(**coord)\n \'Coordinates: 37.24N, -115.81W\'\n\nAccessing arguments\' attributes:\n\n >>> c = 3-5j\n >>> (\'The complex number {0} is formed from the real part {0.real} \'\n ... \'and the imaginary part {0.imag}.\').format(c)\n \'The complex number (3-5j) is formed from the real part 3.0 and the imaginary part -5.0.\'\n >>> class Point:\n ... def __init__(self, x, y):\n ... self.x, self.y = x, y\n ... def __str__(self):\n ... return \'Point({self.x}, {self.y})\'.format(self=self)\n ...\n >>> str(Point(4, 2))\n \'Point(4, 2)\'\n\nAccessing arguments\' items:\n\n >>> coord = (3, 5)\n >>> \'X: {0[0]}; Y: {0[1]}\'.format(coord)\n \'X: 3; Y: 5\'\n\nReplacing ``%s`` and ``%r``:\n\n >>> "repr() shows quotes: {!r}; str() doesn\'t: {!s}".format(\'test1\', \'test2\')\n "repr() shows quotes: \'test1\'; str() doesn\'t: test2"\n\nAligning the text and specifying a width:\n\n >>> \'{:<30}\'.format(\'left aligned\')\n \'left aligned \'\n >>> \'{:>30}\'.format(\'right aligned\')\n \' right aligned\'\n >>> \'{:^30}\'.format(\'centered\')\n \' centered \'\n >>> \'{:*^30}\'.format(\'centered\') # use \'*\' as a fill char\n \'***********centered***********\'\n\nReplacing ``%+f``, ``%-f``, and ``% f`` and specifying a sign:\n\n >>> \'{:+f}; {:+f}\'.format(3.14, -3.14) # show it always\n \'+3.140000; -3.140000\'\n >>> \'{: f}; {: f}\'.format(3.14, -3.14) # show a space for positive numbers\n \' 3.140000; -3.140000\'\n >>> \'{:-f}; {:-f}\'.format(3.14, -3.14) # show only the minus -- same as \'{:f}; {:f}\'\n \'3.140000; -3.140000\'\n\nReplacing ``%x`` and ``%o`` and converting the value to different\nbases:\n\n >>> # format also supports binary numbers\n >>> "int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}".format(42)\n \'int: 42; hex: 2a; oct: 52; bin: 101010\'\n >>> # with 0x, 0o, or 0b as prefix:\n >>> "int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}".format(42)\n \'int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010\'\n\nUsing the comma as a thousands separator:\n\n >>> \'{:,}\'.format(1234567890)\n \'1,234,567,890\'\n\nExpressing a percentage:\n\n >>> points = 19\n >>> total = 22\n >>> \'Correct answers: {:.2%}\'.format(points/total)\n \'Correct answers: 86.36%\'\n\nUsing type-specific formatting:\n\n >>> import datetime\n >>> d = datetime.datetime(2010, 7, 4, 12, 15, 58)\n >>> \'{:%Y-%m-%d %H:%M:%S}\'.format(d)\n \'2010-07-04 12:15:58\'\n\nNesting arguments and more complex examples:\n\n >>> for align, text in zip(\'<^>\', [\'left\', \'center\', \'right\']):\n ... \'{0:{fill}{align}16}\'.format(text, fill=align, align=align)\n ...\n \'left<<<<<<<<<<<<\'\n \'^^^^^center^^^^^\'\n \'>>>>>>>>>>>right\'\n >>>\n >>> octets = [192, 168, 0, 1]\n >>> \'{:02X}{:02X}{:02X}{:02X}\'.format(*octets)\n \'C0A80001\'\n >>> int(_, 16)\n 3232235521\n >>>\n >>> width = 5\n >>> for num in range(5,12): #doctest: +NORMALIZE_WHITESPACE\n ... for base in \'dXob\':\n ... print(\'{0:{width}{base}}\'.format(num, base=base, width=width), end=\' \')\n ... print()\n ...\n 5 5 5 101\n 6 6 6 110\n 7 7 7 111\n 8 8 10 1000\n 9 9 11 1001\n 10 A 12 1010\n 11 B 13 1011\n',
+ 'function': '\nFunction definitions\n********************\n\nA function definition defines a user-defined function object (see\nsection *The standard type hierarchy*):\n\n funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite\n decorators ::= decorator+\n decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE\n dotted_name ::= identifier ("." identifier)*\n parameter_list ::= (defparameter ",")*\n ( "*" [parameter] ("," defparameter)* ["," "**" parameter]\n | "**" parameter\n | defparameter [","] )\n parameter ::= identifier [":" expression]\n defparameter ::= parameter ["=" expression]\n funcname ::= identifier\n\nA function definition is an executable statement. Its execution binds\nthe function name in the current local namespace to a function object\n(a wrapper around the executable code for the function). This\nfunction object contains a reference to the current global namespace\nas the global namespace to be used when the function is called.\n\nThe function definition does not execute the function body; this gets\nexecuted only when the function is called. [3]\n\nA function definition may be wrapped by one or more *decorator*\nexpressions. Decorator expressions are evaluated when the function is\ndefined, in the scope that contains the function definition. The\nresult must be a callable, which is invoked with the function object\nas the only argument. The returned value is bound to the function name\ninstead of the function object. Multiple decorators are applied in\nnested fashion. For example, the following code\n\n @f1(arg)\n @f2\n def func(): pass\n\nis equivalent to\n\n def func(): pass\n func = f1(arg)(f2(func))\n\nWhen one or more *parameters* have the form *parameter* ``=``\n*expression*, the function is said to have "default parameter values."\nFor a parameter with a default value, the corresponding *argument* may\nbe omitted from a call, in which case the parameter\'s default value is\nsubstituted. If a parameter has a default value, all following\nparameters up until the "``*``" must also have a default value ---\nthis is a syntactic restriction that is not expressed by the grammar.\n\n**Default parameter values are evaluated from left to right when the\nfunction definition is executed.** This means that the expression is\nevaluated once, when the function is defined, and that the same "pre-\ncomputed" value is used for each call. This is especially important\nto understand when a default parameter is a mutable object, such as a\nlist or a dictionary: if the function modifies the object (e.g. by\nappending an item to a list), the default value is in effect modified.\nThis is generally not what was intended. A way around this is to use\n``None`` as the default, and explicitly test for it in the body of the\nfunction, e.g.:\n\n def whats_on_the_telly(penguin=None):\n if penguin is None:\n penguin = []\n penguin.append("property of the zoo")\n return penguin\n\nFunction call semantics are described in more detail in section\n*Calls*. A function call always assigns values to all parameters\nmentioned in the parameter list, either from position arguments, from\nkeyword arguments, or from default values. If the form\n"``*identifier``" is present, it is initialized to a tuple receiving\nany excess positional parameters, defaulting to the empty tuple. If\nthe form "``**identifier``" is present, it is initialized to a new\ndictionary receiving any excess keyword arguments, defaulting to a new\nempty dictionary. Parameters after "``*``" or "``*identifier``" are\nkeyword-only parameters and may only be passed used keyword arguments.\n\nParameters may have annotations of the form "``: expression``"\nfollowing the parameter name. Any parameter may have an annotation\neven those of the form ``*identifier`` or ``**identifier``. Functions\nmay have "return" annotation of the form "``-> expression``" after the\nparameter list. These annotations can be any valid Python expression\nand are evaluated when the function definition is executed.\nAnnotations may be evaluated in a different order than they appear in\nthe source code. The presence of annotations does not change the\nsemantics of a function. The annotation values are available as\nvalues of a dictionary keyed by the parameters\' names in the\n``__annotations__`` attribute of the function object.\n\nIt is also possible to create anonymous functions (functions not bound\nto a name), for immediate use in expressions. This uses lambda\nexpressions, described in section *Lambdas*. Note that the lambda\nexpression is merely a shorthand for a simplified function definition;\na function defined in a "``def``" statement can be passed around or\nassigned to another name just like a function defined by a lambda\nexpression. The "``def``" form is actually more powerful since it\nallows the execution of multiple statements and annotations.\n\n**Programmer\'s note:** Functions are first-class objects. A "``def``"\nstatement executed inside a function definition defines a local\nfunction that can be returned or passed around. Free variables used\nin the nested function can access the local variables of the function\ncontaining the def. See section *Naming and binding* for details.\n\nSee also:\n\n **PEP 3107** - Function Annotations\n The original specification for function annotations.\n',
'global': '\nThe ``global`` statement\n************************\n\n global_stmt ::= "global" identifier ("," identifier)*\n\nThe ``global`` statement is a declaration which holds for the entire\ncurrent code block. It means that the listed identifiers are to be\ninterpreted as globals. It would be impossible to assign to a global\nvariable without ``global``, although free variables may refer to\nglobals without being declared global.\n\nNames listed in a ``global`` statement must not be used in the same\ncode block textually preceding that ``global`` statement.\n\nNames listed in a ``global`` statement must not be defined as formal\nparameters or in a ``for`` loop control target, ``class`` definition,\nfunction definition, or ``import`` statement.\n\n**CPython implementation detail:** The current implementation does not\nenforce the latter two restrictions, but programs should not abuse\nthis freedom, as future implementations may enforce them or silently\nchange the meaning of the program.\n\n**Programmer\'s note:** the ``global`` is a directive to the parser.\nIt applies only to code parsed at the same time as the ``global``\nstatement. In particular, a ``global`` statement contained in a string\nor code object supplied to the built-in ``exec()`` function does not\naffect the code block *containing* the function call, and code\ncontained in such a string is unaffected by ``global`` statements in\nthe code containing the function call. The same applies to the\n``eval()`` and ``compile()`` functions.\n',
'id-classes': '\nReserved classes of identifiers\n*******************************\n\nCertain classes of identifiers (besides keywords) have special\nmeanings. These classes are identified by the patterns of leading and\ntrailing underscore characters:\n\n``_*``\n Not imported by ``from module import *``. The special identifier\n ``_`` is used in the interactive interpreter to store the result of\n the last evaluation; it is stored in the ``builtins`` module. When\n not in interactive mode, ``_`` has no special meaning and is not\n defined. See section *The import statement*.\n\n Note: The name ``_`` is often used in conjunction with\n internationalization; refer to the documentation for the\n ``gettext`` module for more information on this convention.\n\n``__*__``\n System-defined names. These names are defined by the interpreter\n and its implementation (including the standard library). Current\n system names are discussed in the *Special method names* section\n and elsewhere. More will likely be defined in future versions of\n Python. *Any* use of ``__*__`` names, in any context, that does\n not follow explicitly documented use, is subject to breakage\n without warning.\n\n``__*``\n Class-private names. Names in this category, when used within the\n context of a class definition, are re-written to use a mangled form\n to help avoid name clashes between "private" attributes of base and\n derived classes. See section *Identifiers (Names)*.\n',
'identifiers': '\nIdentifiers and keywords\n************************\n\nIdentifiers (also referred to as *names*) are described by the\nfollowing lexical definitions.\n\nThe syntax of identifiers in Python is based on the Unicode standard\nannex UAX-31, with elaboration and changes as defined below; see also\n**PEP 3131** for further details.\n\nWithin the ASCII range (U+0001..U+007F), the valid characters for\nidentifiers are the same as in Python 2.x: the uppercase and lowercase\nletters ``A`` through ``Z``, the underscore ``_`` and, except for the\nfirst character, the digits ``0`` through ``9``.\n\nPython 3.0 introduces additional characters from outside the ASCII\nrange (see **PEP 3131**). For these characters, the classification\nuses the version of the Unicode Character Database as included in the\n``unicodedata`` module.\n\nIdentifiers are unlimited in length. Case is significant.\n\n identifier ::= xid_start xid_continue*\n id_start ::= <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>\n id_continue ::= <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>\n xid_start ::= <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">\n xid_continue ::= <all characters in id_continue whose NFKC normalization is in "id_continue*">\n\nThe Unicode category codes mentioned above stand for:\n\n* *Lu* - uppercase letters\n\n* *Ll* - lowercase letters\n\n* *Lt* - titlecase letters\n\n* *Lm* - modifier letters\n\n* *Lo* - other letters\n\n* *Nl* - letter numbers\n\n* *Mn* - nonspacing marks\n\n* *Mc* - spacing combining marks\n\n* *Nd* - decimal numbers\n\n* *Pc* - connector punctuations\n\n* *Other_ID_Start* - explicit list of characters in PropList.txt to\n support backwards compatibility\n\n* *Other_ID_Continue* - likewise\n\nAll identifiers are converted into the normal form NFKC while parsing;\ncomparison of identifiers is based on NFKC.\n\nA non-normative HTML file listing all valid identifier characters for\nUnicode 4.1 can be found at http://www.dcl.hpi.uni-\npotsdam.de/home/loewis/table-3131.html.\n\n\nKeywords\n========\n\nThe following identifiers are used as reserved words, or *keywords* of\nthe language, and cannot be used as ordinary identifiers. They must\nbe spelled exactly as written here:\n\n False class finally is return\n None continue for lambda try\n True def from nonlocal while\n and del global not with\n as elif if or yield\n assert else import pass\n break except in raise\n\n\nReserved classes of identifiers\n===============================\n\nCertain classes of identifiers (besides keywords) have special\nmeanings. These classes are identified by the patterns of leading and\ntrailing underscore characters:\n\n``_*``\n Not imported by ``from module import *``. The special identifier\n ``_`` is used in the interactive interpreter to store the result of\n the last evaluation; it is stored in the ``builtins`` module. When\n not in interactive mode, ``_`` has no special meaning and is not\n defined. See section *The import statement*.\n\n Note: The name ``_`` is often used in conjunction with\n internationalization; refer to the documentation for the\n ``gettext`` module for more information on this convention.\n\n``__*__``\n System-defined names. These names are defined by the interpreter\n and its implementation (including the standard library). Current\n system names are discussed in the *Special method names* section\n and elsewhere. More will likely be defined in future versions of\n Python. *Any* use of ``__*__`` names, in any context, that does\n not follow explicitly documented use, is subject to breakage\n without warning.\n\n``__*``\n Class-private names. Names in this category, when used within the\n context of a class definition, are re-written to use a mangled form\n to help avoid name clashes between "private" attributes of base and\n derived classes. See section *Identifiers (Names)*.\n',
@@ -56,22 +56,22 @@ topics = {'assert': '\nThe ``assert`` statement\n************************\n\nAss
'power': '\nThe power operator\n******************\n\nThe power operator binds more tightly than unary operators on its\nleft; it binds less tightly than unary operators on its right. The\nsyntax is:\n\n power ::= primary ["**" u_expr]\n\nThus, in an unparenthesized sequence of power and unary operators, the\noperators are evaluated from right to left (this does not constrain\nthe evaluation order for the operands): ``-1**2`` results in ``-1``.\n\nThe power operator has the same semantics as the built-in ``pow()``\nfunction, when called with two arguments: it yields its left argument\nraised to the power of its right argument. The numeric arguments are\nfirst converted to a common type, and the result is of that type.\n\nFor int operands, the result has the same type as the operands unless\nthe second argument is negative; in that case, all arguments are\nconverted to float and a float result is delivered. For example,\n``10**2`` returns ``100``, but ``10**-2`` returns ``0.01``.\n\nRaising ``0.0`` to a negative power results in a\n``ZeroDivisionError``. Raising a negative number to a fractional power\nresults in a ``complex`` number. (In earlier versions it raised a\n``ValueError``.)\n',
'raise': '\nThe ``raise`` statement\n***********************\n\n raise_stmt ::= "raise" [expression ["from" expression]]\n\nIf no expressions are present, ``raise`` re-raises the last exception\nthat was active in the current scope. If no exception is active in\nthe current scope, a ``RuntimeError`` exception is raised indicating\nthat this is an error.\n\nOtherwise, ``raise`` evaluates the first expression as the exception\nobject. It must be either a subclass or an instance of\n``BaseException``. If it is a class, the exception instance will be\nobtained when needed by instantiating the class with no arguments.\n\nThe *type* of the exception is the exception instance\'s class, the\n*value* is the instance itself.\n\nA traceback object is normally created automatically when an exception\nis raised and attached to it as the ``__traceback__`` attribute, which\nis writable. You can create an exception and set your own traceback in\none step using the ``with_traceback()`` exception method (which\nreturns the same exception instance, with its traceback set to its\nargument), like so:\n\n raise Exception("foo occurred").with_traceback(tracebackobj)\n\nThe ``from`` clause is used for exception chaining: if given, the\nsecond *expression* must be another exception class or instance, which\nwill then be attached to the raised exception as the ``__cause__``\nattribute (which is writable). If the raised exception is not\nhandled, both exceptions will be printed:\n\n >>> try:\n ... print(1 / 0)\n ... except Exception as exc:\n ... raise RuntimeError("Something bad happened") from exc\n ...\n Traceback (most recent call last):\n File "<stdin>", line 2, in <module>\n ZeroDivisionError: int division or modulo by zero\n\n The above exception was the direct cause of the following exception:\n\n Traceback (most recent call last):\n File "<stdin>", line 4, in <module>\n RuntimeError: Something bad happened\n\nA similar mechanism works implicitly if an exception is raised inside\nan exception handler: the previous exception is then attached as the\nnew exception\'s ``__context__`` attribute:\n\n >>> try:\n ... print(1 / 0)\n ... except:\n ... raise RuntimeError("Something bad happened")\n ...\n Traceback (most recent call last):\n File "<stdin>", line 2, in <module>\n ZeroDivisionError: int division or modulo by zero\n\n During handling of the above exception, another exception occurred:\n\n Traceback (most recent call last):\n File "<stdin>", line 4, in <module>\n RuntimeError: Something bad happened\n\nAdditional information on exceptions can be found in section\n*Exceptions*, and information about handling exceptions is in section\n*The try statement*.\n',
'return': '\nThe ``return`` statement\n************************\n\n return_stmt ::= "return" [expression_list]\n\n``return`` may only occur syntactically nested in a function\ndefinition, not within a nested class definition.\n\nIf an expression list is present, it is evaluated, else ``None`` is\nsubstituted.\n\n``return`` leaves the current function call with the expression list\n(or ``None``) as return value.\n\nWhen ``return`` passes control out of a ``try`` statement with a\n``finally`` clause, that ``finally`` clause is executed before really\nleaving the function.\n\nIn a generator function, the ``return`` statement indicates that the\ngenerator is done and will cause ``StopIteration`` to be raised. The\nreturned value (if any) is used as an argument to construct\n``StopIteration`` and becomes the ``StopIteration.value`` attribute.\n',
- 'sequence-types': "\nEmulating container types\n*************************\n\nThe following methods can be defined to implement container objects.\nContainers usually are sequences (such as lists or tuples) or mappings\n(like dictionaries), but can represent other containers as well. The\nfirst set of methods is used either to emulate a sequence or to\nemulate a mapping; the difference is that for a sequence, the\nallowable keys should be the integers *k* for which ``0 <= k < N``\nwhere *N* is the length of the sequence, or slice objects, which\ndefine a range of items. It is also recommended that mappings provide\nthe methods ``keys()``, ``values()``, ``items()``, ``get()``,\n``clear()``, ``setdefault()``, ``pop()``, ``popitem()``, ``copy()``,\nand ``update()`` behaving similar to those for Python's standard\ndictionary objects. The ``collections`` module provides a\n``MutableMapping`` abstract base class to help create those methods\nfrom a base set of ``__getitem__()``, ``__setitem__()``,\n``__delitem__()``, and ``keys()``. Mutable sequences should provide\nmethods ``append()``, ``count()``, ``index()``, ``extend()``,\n``insert()``, ``pop()``, ``remove()``, ``reverse()`` and ``sort()``,\nlike Python standard list objects. Finally, sequence types should\nimplement addition (meaning concatenation) and multiplication (meaning\nrepetition) by defining the methods ``__add__()``, ``__radd__()``,\n``__iadd__()``, ``__mul__()``, ``__rmul__()`` and ``__imul__()``\ndescribed below; they should not define other numerical operators. It\nis recommended that both mappings and sequences implement the\n``__contains__()`` method to allow efficient use of the ``in``\noperator; for mappings, ``in`` should search the mapping's keys; for\nsequences, it should search through the values. It is further\nrecommended that both mappings and sequences implement the\n``__iter__()`` method to allow efficient iteration through the\ncontainer; for mappings, ``__iter__()`` should be the same as\n``keys()``; for sequences, it should iterate through the values.\n\nobject.__len__(self)\n\n Called to implement the built-in function ``len()``. Should return\n the length of the object, an integer ``>=`` 0. Also, an object\n that doesn't define a ``__bool__()`` method and whose ``__len__()``\n method returns zero is considered to be false in a Boolean context.\n\nNote: Slicing is done exclusively with the following three methods. A\n call like\n\n a[1:2] = b\n\n is translated to\n\n a[slice(1, 2, None)] = b\n\n and so forth. Missing slice items are always filled in with\n ``None``.\n\nobject.__getitem__(self, key)\n\n Called to implement evaluation of ``self[key]``. For sequence\n types, the accepted keys should be integers and slice objects.\n Note that the special interpretation of negative indexes (if the\n class wishes to emulate a sequence type) is up to the\n ``__getitem__()`` method. If *key* is of an inappropriate type,\n ``TypeError`` may be raised; if of a value outside the set of\n indexes for the sequence (after any special interpretation of\n negative values), ``IndexError`` should be raised. For mapping\n types, if *key* is missing (not in the container), ``KeyError``\n should be raised.\n\n Note: ``for`` loops expect that an ``IndexError`` will be raised for\n illegal indexes to allow proper detection of the end of the\n sequence.\n\nobject.__setitem__(self, key, value)\n\n Called to implement assignment to ``self[key]``. Same note as for\n ``__getitem__()``. This should only be implemented for mappings if\n the objects support changes to the values for keys, or if new keys\n can be added, or for sequences if elements can be replaced. The\n same exceptions should be raised for improper *key* values as for\n the ``__getitem__()`` method.\n\nobject.__delitem__(self, key)\n\n Called to implement deletion of ``self[key]``. Same note as for\n ``__getitem__()``. This should only be implemented for mappings if\n the objects support removal of keys, or for sequences if elements\n can be removed from the sequence. The same exceptions should be\n raised for improper *key* values as for the ``__getitem__()``\n method.\n\nobject.__iter__(self)\n\n This method is called when an iterator is required for a container.\n This method should return a new iterator object that can iterate\n over all the objects in the container. For mappings, it should\n iterate over the keys of the container, and should also be made\n available as the method ``keys()``.\n\n Iterator objects also need to implement this method; they are\n required to return themselves. For more information on iterator\n objects, see *Iterator Types*.\n\nobject.__reversed__(self)\n\n Called (if present) by the ``reversed()`` built-in to implement\n reverse iteration. It should return a new iterator object that\n iterates over all the objects in the container in reverse order.\n\n If the ``__reversed__()`` method is not provided, the\n ``reversed()`` built-in will fall back to using the sequence\n protocol (``__len__()`` and ``__getitem__()``). Objects that\n support the sequence protocol should only provide\n ``__reversed__()`` if they can provide an implementation that is\n more efficient than the one provided by ``reversed()``.\n\nThe membership test operators (``in`` and ``not in``) are normally\nimplemented as an iteration through a sequence. However, container\nobjects can supply the following special method with a more efficient\nimplementation, which also does not require the object be a sequence.\n\nobject.__contains__(self, item)\n\n Called to implement membership test operators. Should return true\n if *item* is in *self*, false otherwise. For mapping objects, this\n should consider the keys of the mapping rather than the values or\n the key-item pairs.\n\n For objects that don't define ``__contains__()``, the membership\n test first tries iteration via ``__iter__()``, then the old\n sequence iteration protocol via ``__getitem__()``, see *this\n section in the language reference*.\n",
+ 'sequence-types': "\nEmulating container types\n*************************\n\nThe following methods can be defined to implement container objects.\nContainers usually are sequences (such as lists or tuples) or mappings\n(like dictionaries), but can represent other containers as well. The\nfirst set of methods is used either to emulate a sequence or to\nemulate a mapping; the difference is that for a sequence, the\nallowable keys should be the integers *k* for which ``0 <= k < N``\nwhere *N* is the length of the sequence, or slice objects, which\ndefine a range of items. It is also recommended that mappings provide\nthe methods ``keys()``, ``values()``, ``items()``, ``get()``,\n``clear()``, ``setdefault()``, ``pop()``, ``popitem()``, ``copy()``,\nand ``update()`` behaving similar to those for Python's standard\ndictionary objects. The ``collections`` module provides a\n``MutableMapping`` abstract base class to help create those methods\nfrom a base set of ``__getitem__()``, ``__setitem__()``,\n``__delitem__()``, and ``keys()``. Mutable sequences should provide\nmethods ``append()``, ``count()``, ``index()``, ``extend()``,\n``insert()``, ``pop()``, ``remove()``, ``reverse()`` and ``sort()``,\nlike Python standard list objects. Finally, sequence types should\nimplement addition (meaning concatenation) and multiplication (meaning\nrepetition) by defining the methods ``__add__()``, ``__radd__()``,\n``__iadd__()``, ``__mul__()``, ``__rmul__()`` and ``__imul__()``\ndescribed below; they should not define other numerical operators. It\nis recommended that both mappings and sequences implement the\n``__contains__()`` method to allow efficient use of the ``in``\noperator; for mappings, ``in`` should search the mapping's keys; for\nsequences, it should search through the values. It is further\nrecommended that both mappings and sequences implement the\n``__iter__()`` method to allow efficient iteration through the\ncontainer; for mappings, ``__iter__()`` should be the same as\n``keys()``; for sequences, it should iterate through the values.\n\nobject.__len__(self)\n\n Called to implement the built-in function ``len()``. Should return\n the length of the object, an integer ``>=`` 0. Also, an object\n that doesn't define a ``__bool__()`` method and whose ``__len__()``\n method returns zero is considered to be false in a Boolean context.\n\nobject.__length_hint__(self)\n\n Called to implement ``operator.length_hint()``. Should return an\n estimated length for the object (which may be greater or less than\n the actual length). The length must be an integer ``>=`` 0. This\n method is purely an optimization and is never required for\n correctness.\n\n New in version 3.4.\n\nNote: Slicing is done exclusively with the following three methods. A\n call like\n\n a[1:2] = b\n\n is translated to\n\n a[slice(1, 2, None)] = b\n\n and so forth. Missing slice items are always filled in with\n ``None``.\n\nobject.__getitem__(self, key)\n\n Called to implement evaluation of ``self[key]``. For sequence\n types, the accepted keys should be integers and slice objects.\n Note that the special interpretation of negative indexes (if the\n class wishes to emulate a sequence type) is up to the\n ``__getitem__()`` method. If *key* is of an inappropriate type,\n ``TypeError`` may be raised; if of a value outside the set of\n indexes for the sequence (after any special interpretation of\n negative values), ``IndexError`` should be raised. For mapping\n types, if *key* is missing (not in the container), ``KeyError``\n should be raised.\n\n Note: ``for`` loops expect that an ``IndexError`` will be raised for\n illegal indexes to allow proper detection of the end of the\n sequence.\n\nobject.__setitem__(self, key, value)\n\n Called to implement assignment to ``self[key]``. Same note as for\n ``__getitem__()``. This should only be implemented for mappings if\n the objects support changes to the values for keys, or if new keys\n can be added, or for sequences if elements can be replaced. The\n same exceptions should be raised for improper *key* values as for\n the ``__getitem__()`` method.\n\nobject.__delitem__(self, key)\n\n Called to implement deletion of ``self[key]``. Same note as for\n ``__getitem__()``. This should only be implemented for mappings if\n the objects support removal of keys, or for sequences if elements\n can be removed from the sequence. The same exceptions should be\n raised for improper *key* values as for the ``__getitem__()``\n method.\n\nobject.__iter__(self)\n\n This method is called when an iterator is required for a container.\n This method should return a new iterator object that can iterate\n over all the objects in the container. For mappings, it should\n iterate over the keys of the container, and should also be made\n available as the method ``keys()``.\n\n Iterator objects also need to implement this method; they are\n required to return themselves. For more information on iterator\n objects, see *Iterator Types*.\n\nobject.__reversed__(self)\n\n Called (if present) by the ``reversed()`` built-in to implement\n reverse iteration. It should return a new iterator object that\n iterates over all the objects in the container in reverse order.\n\n If the ``__reversed__()`` method is not provided, the\n ``reversed()`` built-in will fall back to using the sequence\n protocol (``__len__()`` and ``__getitem__()``). Objects that\n support the sequence protocol should only provide\n ``__reversed__()`` if they can provide an implementation that is\n more efficient than the one provided by ``reversed()``.\n\nThe membership test operators (``in`` and ``not in``) are normally\nimplemented as an iteration through a sequence. However, container\nobjects can supply the following special method with a more efficient\nimplementation, which also does not require the object be a sequence.\n\nobject.__contains__(self, item)\n\n Called to implement membership test operators. Should return true\n if *item* is in *self*, false otherwise. For mapping objects, this\n should consider the keys of the mapping rather than the values or\n the key-item pairs.\n\n For objects that don't define ``__contains__()``, the membership\n test first tries iteration via ``__iter__()``, then the old\n sequence iteration protocol via ``__getitem__()``, see *this\n section in the language reference*.\n",
'shifting': '\nShifting operations\n*******************\n\nThe shifting operations have lower priority than the arithmetic\noperations:\n\n shift_expr ::= a_expr | shift_expr ( "<<" | ">>" ) a_expr\n\nThese operators accept integers as arguments. They shift the first\nargument to the left or right by the number of bits given by the\nsecond argument.\n\nA right shift by *n* bits is defined as floor division by\n``pow(2,n)``. A left shift by *n* bits is defined as multiplication\nwith ``pow(2,n)``.\n\nNote: In the current implementation, the right-hand operand is required to\n be at most ``sys.maxsize``. If the right-hand operand is larger\n than ``sys.maxsize`` an ``OverflowError`` exception is raised.\n',
'slicings': '\nSlicings\n********\n\nA slicing selects a range of items in a sequence object (e.g., a\nstring, tuple or list). Slicings may be used as expressions or as\ntargets in assignment or ``del`` statements. The syntax for a\nslicing:\n\n slicing ::= primary "[" slice_list "]"\n slice_list ::= slice_item ("," slice_item)* [","]\n slice_item ::= expression | proper_slice\n proper_slice ::= [lower_bound] ":" [upper_bound] [ ":" [stride] ]\n lower_bound ::= expression\n upper_bound ::= expression\n stride ::= expression\n\nThere is ambiguity in the formal syntax here: anything that looks like\nan expression list also looks like a slice list, so any subscription\ncan be interpreted as a slicing. Rather than further complicating the\nsyntax, this is disambiguated by defining that in this case the\ninterpretation as a subscription takes priority over the\ninterpretation as a slicing (this is the case if the slice list\ncontains no proper slice).\n\nThe semantics for a slicing are as follows. The primary must evaluate\nto a mapping object, and it is indexed (using the same\n``__getitem__()`` method as normal subscription) with a key that is\nconstructed from the slice list, as follows. If the slice list\ncontains at least one comma, the key is a tuple containing the\nconversion of the slice items; otherwise, the conversion of the lone\nslice item is the key. The conversion of a slice item that is an\nexpression is that expression. The conversion of a proper slice is a\nslice object (see section *The standard type hierarchy*) whose\n``start``, ``stop`` and ``step`` attributes are the values of the\nexpressions given as lower bound, upper bound and stride,\nrespectively, substituting ``None`` for missing expressions.\n',
'specialattrs': '\nSpecial Attributes\n******************\n\nThe implementation adds a few special read-only attributes to several\nobject types, where they are relevant. Some of these are not reported\nby the ``dir()`` built-in function.\n\nobject.__dict__\n\n A dictionary or other mapping object used to store an object\'s\n (writable) attributes.\n\ninstance.__class__\n\n The class to which a class instance belongs.\n\nclass.__bases__\n\n The tuple of base classes of a class object.\n\nclass.__name__\n\n The name of the class or type.\n\nclass.__qualname__\n\n The *qualified name* of the class or type.\n\n New in version 3.3.\n\nclass.__mro__\n\n This attribute is a tuple of classes that are considered when\n looking for base classes during method resolution.\n\nclass.mro()\n\n This method can be overridden by a metaclass to customize the\n method resolution order for its instances. It is called at class\n instantiation, and its result is stored in ``__mro__``.\n\nclass.__subclasses__()\n\n Each class keeps a list of weak references to its immediate\n subclasses. This method returns a list of all those references\n still alive. Example:\n\n >>> int.__subclasses__()\n [<class \'bool\'>]\n\n-[ Footnotes ]-\n\n[1] Additional information on these special methods may be found in\n the Python Reference Manual (*Basic customization*).\n\n[2] As a consequence, the list ``[1, 2]`` is considered equal to\n ``[1.0, 2.0]``, and similarly for tuples.\n\n[3] They must have since the parser can\'t tell the type of the\n operands.\n\n[4] Cased characters are those with general category property being\n one of "Lu" (Letter, uppercase), "Ll" (Letter, lowercase), or "Lt"\n (Letter, titlecase).\n\n[5] To format only a tuple you should therefore provide a singleton\n tuple whose only element is the tuple to be formatted.\n',
- 'specialnames': '\nSpecial method names\n********************\n\nA class can implement certain operations that are invoked by special\nsyntax (such as arithmetic operations or subscripting and slicing) by\ndefining methods with special names. This is Python\'s approach to\n*operator overloading*, allowing classes to define their own behavior\nwith respect to language operators. For instance, if a class defines\na method named ``__getitem__()``, and ``x`` is an instance of this\nclass, then ``x[i]`` is roughly equivalent to ``type(x).__getitem__(x,\ni)``. Except where mentioned, attempts to execute an operation raise\nan exception when no appropriate method is defined (typically\n``AttributeError`` or ``TypeError``).\n\nWhen implementing a class that emulates any built-in type, it is\nimportant that the emulation only be implemented to the degree that it\nmakes sense for the object being modelled. For example, some\nsequences may work well with retrieval of individual elements, but\nextracting a slice may not make sense. (One example of this is the\n``NodeList`` interface in the W3C\'s Document Object Model.)\n\n\nBasic customization\n===================\n\nobject.__new__(cls[, ...])\n\n Called to create a new instance of class *cls*. ``__new__()`` is a\n static method (special-cased so you need not declare it as such)\n that takes the class of which an instance was requested as its\n first argument. The remaining arguments are those passed to the\n object constructor expression (the call to the class). The return\n value of ``__new__()`` should be the new object instance (usually\n an instance of *cls*).\n\n Typical implementations create a new instance of the class by\n invoking the superclass\'s ``__new__()`` method using\n ``super(currentclass, cls).__new__(cls[, ...])`` with appropriate\n arguments and then modifying the newly-created instance as\n necessary before returning it.\n\n If ``__new__()`` returns an instance of *cls*, then the new\n instance\'s ``__init__()`` method will be invoked like\n ``__init__(self[, ...])``, where *self* is the new instance and the\n remaining arguments are the same as were passed to ``__new__()``.\n\n If ``__new__()`` does not return an instance of *cls*, then the new\n instance\'s ``__init__()`` method will not be invoked.\n\n ``__new__()`` is intended mainly to allow subclasses of immutable\n types (like int, str, or tuple) to customize instance creation. It\n is also commonly overridden in custom metaclasses in order to\n customize class creation.\n\nobject.__init__(self[, ...])\n\n Called when the instance is created. The arguments are those\n passed to the class constructor expression. If a base class has an\n ``__init__()`` method, the derived class\'s ``__init__()`` method,\n if any, must explicitly call it to ensure proper initialization of\n the base class part of the instance; for example:\n ``BaseClass.__init__(self, [args...])``. As a special constraint\n on constructors, no value may be returned; doing so will cause a\n ``TypeError`` to be raised at runtime.\n\nobject.__del__(self)\n\n Called when the instance is about to be destroyed. This is also\n called a destructor. If a base class has a ``__del__()`` method,\n the derived class\'s ``__del__()`` method, if any, must explicitly\n call it to ensure proper deletion of the base class part of the\n instance. Note that it is possible (though not recommended!) for\n the ``__del__()`` method to postpone destruction of the instance by\n creating a new reference to it. It may then be called at a later\n time when this new reference is deleted. It is not guaranteed that\n ``__del__()`` methods are called for objects that still exist when\n the interpreter exits.\n\n Note: ``del x`` doesn\'t directly call ``x.__del__()`` --- the former\n decrements the reference count for ``x`` by one, and the latter\n is only called when ``x``\'s reference count reaches zero. Some\n common situations that may prevent the reference count of an\n object from going to zero include: circular references between\n objects (e.g., a doubly-linked list or a tree data structure with\n parent and child pointers); a reference to the object on the\n stack frame of a function that caught an exception (the traceback\n stored in ``sys.exc_info()[2]`` keeps the stack frame alive); or\n a reference to the object on the stack frame that raised an\n unhandled exception in interactive mode (the traceback stored in\n ``sys.last_traceback`` keeps the stack frame alive). The first\n situation can only be remedied by explicitly breaking the cycles;\n the latter two situations can be resolved by storing ``None`` in\n ``sys.last_traceback``. Circular references which are garbage are\n detected when the option cycle detector is enabled (it\'s on by\n default), but can only be cleaned up if there are no Python-\n level ``__del__()`` methods involved. Refer to the documentation\n for the ``gc`` module for more information about how\n ``__del__()`` methods are handled by the cycle detector,\n particularly the description of the ``garbage`` value.\n\n Warning: Due to the precarious circumstances under which ``__del__()``\n methods are invoked, exceptions that occur during their execution\n are ignored, and a warning is printed to ``sys.stderr`` instead.\n Also, when ``__del__()`` is invoked in response to a module being\n deleted (e.g., when execution of the program is done), other\n globals referenced by the ``__del__()`` method may already have\n been deleted or in the process of being torn down (e.g. the\n import machinery shutting down). For this reason, ``__del__()``\n methods should do the absolute minimum needed to maintain\n external invariants. Starting with version 1.5, Python\n guarantees that globals whose name begins with a single\n underscore are deleted from their module before other globals are\n deleted; if no other references to such globals exist, this may\n help in assuring that imported modules are still available at the\n time when the ``__del__()`` method is called.\n\nobject.__repr__(self)\n\n Called by the ``repr()`` built-in function to compute the\n "official" string representation of an object. If at all possible,\n this should look like a valid Python expression that could be used\n to recreate an object with the same value (given an appropriate\n environment). If this is not possible, a string of the form\n ``<...some useful description...>`` should be returned. The return\n value must be a string object. If a class defines ``__repr__()``\n but not ``__str__()``, then ``__repr__()`` is also used when an\n "informal" string representation of instances of that class is\n required.\n\n This is typically used for debugging, so it is important that the\n representation is information-rich and unambiguous.\n\nobject.__str__(self)\n\n Called by ``str(object)`` and the built-in functions ``format()``\n and ``print()`` to compute the "informal" or nicely printable\n string representation of an object. The return value must be a\n *string* object.\n\n This method differs from ``object.__repr__()`` in that there is no\n expectation that ``__str__()`` return a valid Python expression: a\n more convenient or concise representation can be used.\n\n The default implementation defined by the built-in type ``object``\n calls ``object.__repr__()``.\n\nobject.__bytes__(self)\n\n Called by ``bytes()`` to compute a byte-string representation of an\n object. This should return a ``bytes`` object.\n\nobject.__format__(self, format_spec)\n\n Called by the ``format()`` built-in function (and by extension, the\n ``str.format()`` method of class ``str``) to produce a "formatted"\n string representation of an object. The ``format_spec`` argument is\n a string that contains a description of the formatting options\n desired. The interpretation of the ``format_spec`` argument is up\n to the type implementing ``__format__()``, however most classes\n will either delegate formatting to one of the built-in types, or\n use a similar formatting option syntax.\n\n See *Format Specification Mini-Language* for a description of the\n standard formatting syntax.\n\n The return value must be a string object.\n\nobject.__lt__(self, other)\nobject.__le__(self, other)\nobject.__eq__(self, other)\nobject.__ne__(self, other)\nobject.__gt__(self, other)\nobject.__ge__(self, other)\n\n These are the so-called "rich comparison" methods. The\n correspondence between operator symbols and method names is as\n follows: ``x<y`` calls ``x.__lt__(y)``, ``x<=y`` calls\n ``x.__le__(y)``, ``x==y`` calls ``x.__eq__(y)``, ``x!=y`` calls\n ``x.__ne__(y)``, ``x>y`` calls ``x.__gt__(y)``, and ``x>=y`` calls\n ``x.__ge__(y)``.\n\n A rich comparison method may return the singleton\n ``NotImplemented`` if it does not implement the operation for a\n given pair of arguments. By convention, ``False`` and ``True`` are\n returned for a successful comparison. However, these methods can\n return any value, so if the comparison operator is used in a\n Boolean context (e.g., in the condition of an ``if`` statement),\n Python will call ``bool()`` on the value to determine if the result\n is true or false.\n\n There are no implied relationships among the comparison operators.\n The truth of ``x==y`` does not imply that ``x!=y`` is false.\n Accordingly, when defining ``__eq__()``, one should also define\n ``__ne__()`` so that the operators will behave as expected. See\n the paragraph on ``__hash__()`` for some important notes on\n creating *hashable* objects which support custom comparison\n operations and are usable as dictionary keys.\n\n There are no swapped-argument versions of these methods (to be used\n when the left argument does not support the operation but the right\n argument does); rather, ``__lt__()`` and ``__gt__()`` are each\n other\'s reflection, ``__le__()`` and ``__ge__()`` are each other\'s\n reflection, and ``__eq__()`` and ``__ne__()`` are their own\n reflection.\n\n Arguments to rich comparison methods are never coerced.\n\n To automatically generate ordering operations from a single root\n operation, see ``functools.total_ordering()``.\n\nobject.__hash__(self)\n\n Called by built-in function ``hash()`` and for operations on\n members of hashed collections including ``set``, ``frozenset``, and\n ``dict``. ``__hash__()`` should return an integer. The only\n required property is that objects which compare equal have the same\n hash value; it is advised to somehow mix together (e.g. using\n exclusive or) the hash values for the components of the object that\n also play a part in comparison of objects.\n\n Note: ``hash()`` truncates the value returned from an object\'s custom\n ``__hash__()`` method to the size of a ``Py_ssize_t``. This is\n typically 8 bytes on 64-bit builds and 4 bytes on 32-bit builds.\n If an object\'s ``__hash__()`` must interoperate on builds of\n different bit sizes, be sure to check the width on all supported\n builds. An easy way to do this is with ``python -c "import sys;\n print(sys.hash_info.width)"``\n\n If a class does not define an ``__eq__()`` method it should not\n define a ``__hash__()`` operation either; if it defines\n ``__eq__()`` but not ``__hash__()``, its instances will not be\n usable as items in hashable collections. If a class defines\n mutable objects and implements an ``__eq__()`` method, it should\n not implement ``__hash__()``, since the implementation of hashable\n collections requires that a key\'s hash value is immutable (if the\n object\'s hash value changes, it will be in the wrong hash bucket).\n\n User-defined classes have ``__eq__()`` and ``__hash__()`` methods\n by default; with them, all objects compare unequal (except with\n themselves) and ``x.__hash__()`` returns an appropriate value such\n that ``x == y`` implies both that ``x is y`` and ``hash(x) ==\n hash(y)``.\n\n A class that overrides ``__eq__()`` and does not define\n ``__hash__()`` will have its ``__hash__()`` implicitly set to\n ``None``. When the ``__hash__()`` method of a class is ``None``,\n instances of the class will raise an appropriate ``TypeError`` when\n a program attempts to retrieve their hash value, and will also be\n correctly identified as unhashable when checking ``isinstance(obj,\n collections.Hashable``).\n\n If a class that overrides ``__eq__()`` needs to retain the\n implementation of ``__hash__()`` from a parent class, the\n interpreter must be told this explicitly by setting ``__hash__ =\n <ParentClass>.__hash__``.\n\n If a class that does not override ``__eq__()`` wishes to suppress\n hash support, it should include ``__hash__ = None`` in the class\n definition. A class which defines its own ``__hash__()`` that\n explicitly raises a ``TypeError`` would be incorrectly identified\n as hashable by an ``isinstance(obj, collections.Hashable)`` call.\n\n Note: By default, the ``__hash__()`` values of str, bytes and datetime\n objects are "salted" with an unpredictable random value.\n Although they remain constant within an individual Python\n process, they are not predictable between repeated invocations of\n Python.This is intended to provide protection against a denial-\n of-service caused by carefully-chosen inputs that exploit the\n worst case performance of a dict insertion, O(n^2) complexity.\n See http://www.ocert.org/advisories/ocert-2011-003.html for\n details.Changing hash values affects the iteration order of\n dicts, sets and other mappings. Python has never made guarantees\n about this ordering (and it typically varies between 32-bit and\n 64-bit builds).See also ``PYTHONHASHSEED``.\n\n Changed in version 3.3: Hash randomization is enabled by default.\n\nobject.__bool__(self)\n\n Called to implement truth value testing and the built-in operation\n ``bool()``; should return ``False`` or ``True``. When this method\n is not defined, ``__len__()`` is called, if it is defined, and the\n object is considered true if its result is nonzero. If a class\n defines neither ``__len__()`` nor ``__bool__()``, all its instances\n are considered true.\n\n\nCustomizing attribute access\n============================\n\nThe following methods can be defined to customize the meaning of\nattribute access (use of, assignment to, or deletion of ``x.name``)\nfor class instances.\n\nobject.__getattr__(self, name)\n\n Called when an attribute lookup has not found the attribute in the\n usual places (i.e. it is not an instance attribute nor is it found\n in the class tree for ``self``). ``name`` is the attribute name.\n This method should return the (computed) attribute value or raise\n an ``AttributeError`` exception.\n\n Note that if the attribute is found through the normal mechanism,\n ``__getattr__()`` is not called. (This is an intentional asymmetry\n between ``__getattr__()`` and ``__setattr__()``.) This is done both\n for efficiency reasons and because otherwise ``__getattr__()``\n would have no way to access other attributes of the instance. Note\n that at least for instance variables, you can fake total control by\n not inserting any values in the instance attribute dictionary (but\n instead inserting them in another object). See the\n ``__getattribute__()`` method below for a way to actually get total\n control over attribute access.\n\nobject.__getattribute__(self, name)\n\n Called unconditionally to implement attribute accesses for\n instances of the class. If the class also defines\n ``__getattr__()``, the latter will not be called unless\n ``__getattribute__()`` either calls it explicitly or raises an\n ``AttributeError``. This method should return the (computed)\n attribute value or raise an ``AttributeError`` exception. In order\n to avoid infinite recursion in this method, its implementation\n should always call the base class method with the same name to\n access any attributes it needs, for example,\n ``object.__getattribute__(self, name)``.\n\n Note: This method may still be bypassed when looking up special methods\n as the result of implicit invocation via language syntax or\n built-in functions. See *Special method lookup*.\n\nobject.__setattr__(self, name, value)\n\n Called when an attribute assignment is attempted. This is called\n instead of the normal mechanism (i.e. store the value in the\n instance dictionary). *name* is the attribute name, *value* is the\n value to be assigned to it.\n\n If ``__setattr__()`` wants to assign to an instance attribute, it\n should call the base class method with the same name, for example,\n ``object.__setattr__(self, name, value)``.\n\nobject.__delattr__(self, name)\n\n Like ``__setattr__()`` but for attribute deletion instead of\n assignment. This should only be implemented if ``del obj.name`` is\n meaningful for the object.\n\nobject.__dir__(self)\n\n Called when ``dir()`` is called on the object. A sequence must be\n returned. ``dir()`` converts the returned sequence to a list and\n sorts it.\n\n\nImplementing Descriptors\n------------------------\n\nThe following methods only apply when an instance of the class\ncontaining the method (a so-called *descriptor* class) appears in an\n*owner* class (the descriptor must be in either the owner\'s class\ndictionary or in the class dictionary for one of its parents). In the\nexamples below, "the attribute" refers to the attribute whose name is\nthe key of the property in the owner class\' ``__dict__``.\n\nobject.__get__(self, instance, owner)\n\n Called to get the attribute of the owner class (class attribute\n access) or of an instance of that class (instance attribute\n access). *owner* is always the owner class, while *instance* is the\n instance that the attribute was accessed through, or ``None`` when\n the attribute is accessed through the *owner*. This method should\n return the (computed) attribute value or raise an\n ``AttributeError`` exception.\n\nobject.__set__(self, instance, value)\n\n Called to set the attribute on an instance *instance* of the owner\n class to a new value, *value*.\n\nobject.__delete__(self, instance)\n\n Called to delete the attribute on an instance *instance* of the\n owner class.\n\n\nInvoking Descriptors\n--------------------\n\nIn general, a descriptor is an object attribute with "binding\nbehavior", one whose attribute access has been overridden by methods\nin the descriptor protocol: ``__get__()``, ``__set__()``, and\n``__delete__()``. If any of those methods are defined for an object,\nit is said to be a descriptor.\n\nThe default behavior for attribute access is to get, set, or delete\nthe attribute from an object\'s dictionary. For instance, ``a.x`` has a\nlookup chain starting with ``a.__dict__[\'x\']``, then\n``type(a).__dict__[\'x\']``, and continuing through the base classes of\n``type(a)`` excluding metaclasses.\n\nHowever, if the looked-up value is an object defining one of the\ndescriptor methods, then Python may override the default behavior and\ninvoke the descriptor method instead. Where this occurs in the\nprecedence chain depends on which descriptor methods were defined and\nhow they were called.\n\nThe starting point for descriptor invocation is a binding, ``a.x``.\nHow the arguments are assembled depends on ``a``:\n\nDirect Call\n The simplest and least common call is when user code directly\n invokes a descriptor method: ``x.__get__(a)``.\n\nInstance Binding\n If binding to an object instance, ``a.x`` is transformed into the\n call: ``type(a).__dict__[\'x\'].__get__(a, type(a))``.\n\nClass Binding\n If binding to a class, ``A.x`` is transformed into the call:\n ``A.__dict__[\'x\'].__get__(None, A)``.\n\nSuper Binding\n If ``a`` is an instance of ``super``, then the binding ``super(B,\n obj).m()`` searches ``obj.__class__.__mro__`` for the base class\n ``A`` immediately preceding ``B`` and then invokes the descriptor\n with the call: ``A.__dict__[\'m\'].__get__(obj, obj.__class__)``.\n\nFor instance bindings, the precedence of descriptor invocation depends\non the which descriptor methods are defined. A descriptor can define\nany combination of ``__get__()``, ``__set__()`` and ``__delete__()``.\nIf it does not define ``__get__()``, then accessing the attribute will\nreturn the descriptor object itself unless there is a value in the\nobject\'s instance dictionary. If the descriptor defines ``__set__()``\nand/or ``__delete__()``, it is a data descriptor; if it defines\nneither, it is a non-data descriptor. Normally, data descriptors\ndefine both ``__get__()`` and ``__set__()``, while non-data\ndescriptors have just the ``__get__()`` method. Data descriptors with\n``__set__()`` and ``__get__()`` defined always override a redefinition\nin an instance dictionary. In contrast, non-data descriptors can be\noverridden by instances.\n\nPython methods (including ``staticmethod()`` and ``classmethod()``)\nare implemented as non-data descriptors. Accordingly, instances can\nredefine and override methods. This allows individual instances to\nacquire behaviors that differ from other instances of the same class.\n\nThe ``property()`` function is implemented as a data descriptor.\nAccordingly, instances cannot override the behavior of a property.\n\n\n__slots__\n---------\n\nBy default, instances of classes have a dictionary for attribute\nstorage. This wastes space for objects having very few instance\nvariables. The space consumption can become acute when creating large\nnumbers of instances.\n\nThe default can be overridden by defining *__slots__* in a class\ndefinition. The *__slots__* declaration takes a sequence of instance\nvariables and reserves just enough space in each instance to hold a\nvalue for each variable. Space is saved because *__dict__* is not\ncreated for each instance.\n\nobject.__slots__\n\n This class variable can be assigned a string, iterable, or sequence\n of strings with variable names used by instances. If defined in a\n class, *__slots__* reserves space for the declared variables and\n prevents the automatic creation of *__dict__* and *__weakref__* for\n each instance.\n\n\nNotes on using *__slots__*\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* When inheriting from a class without *__slots__*, the *__dict__*\n attribute of that class will always be accessible, so a *__slots__*\n definition in the subclass is meaningless.\n\n* Without a *__dict__* variable, instances cannot be assigned new\n variables not listed in the *__slots__* definition. Attempts to\n assign to an unlisted variable name raises ``AttributeError``. If\n dynamic assignment of new variables is desired, then add\n ``\'__dict__\'`` to the sequence of strings in the *__slots__*\n declaration.\n\n* Without a *__weakref__* variable for each instance, classes defining\n *__slots__* do not support weak references to its instances. If weak\n reference support is needed, then add ``\'__weakref__\'`` to the\n sequence of strings in the *__slots__* declaration.\n\n* *__slots__* are implemented at the class level by creating\n descriptors (*Implementing Descriptors*) for each variable name. As\n a result, class attributes cannot be used to set default values for\n instance variables defined by *__slots__*; otherwise, the class\n attribute would overwrite the descriptor assignment.\n\n* The action of a *__slots__* declaration is limited to the class\n where it is defined. As a result, subclasses will have a *__dict__*\n unless they also define *__slots__* (which must only contain names\n of any *additional* slots).\n\n* If a class defines a slot also defined in a base class, the instance\n variable defined by the base class slot is inaccessible (except by\n retrieving its descriptor directly from the base class). This\n renders the meaning of the program undefined. In the future, a\n check may be added to prevent this.\n\n* Nonempty *__slots__* does not work for classes derived from\n "variable-length" built-in types such as ``int``, ``str`` and\n ``tuple``.\n\n* Any non-string iterable may be assigned to *__slots__*. Mappings may\n also be used; however, in the future, special meaning may be\n assigned to the values corresponding to each key.\n\n* *__class__* assignment works only if both classes have the same\n *__slots__*.\n\n\nCustomizing class creation\n==========================\n\nBy default, classes are constructed using ``type()``. The class body\nis executed in a new namespace and the class name is bound locally to\nthe result of ``type(name, bases, namespace)``.\n\nThe class creation process can be customised by passing the\n``metaclass`` keyword argument in the class definition line, or by\ninheriting from an existing class that included such an argument. In\nthe following example, both ``MyClass`` and ``MySubclass`` are\ninstances of ``Meta``:\n\n class Meta(type):\n pass\n\n class MyClass(metaclass=Meta):\n pass\n\n class MySubclass(MyClass):\n pass\n\nAny other keyword arguments that are specified in the class definition\nare passed through to all metaclass operations described below.\n\nWhen a class definition is executed, the following steps occur:\n\n* the appropriate metaclass is determined\n\n* the class namespace is prepared\n\n* the class body is executed\n\n* the class object is created\n\n\nDetermining the appropriate metaclass\n-------------------------------------\n\nThe appropriate metaclass for a class definition is determined as\nfollows:\n\n* if no bases and no explicit metaclass are given, then ``type()`` is\n used\n\n* if an explicit metaclass is given and it is *not* an instance of\n ``type()``, then it is used directly as the metaclass\n\n* if an instance of ``type()`` is given as the explicit metaclass, or\n bases are defined, then the most derived metaclass is used\n\nThe most derived metaclass is selected from the explicitly specified\nmetaclass (if any) and the metaclasses (i.e. ``type(cls)``) of all\nspecified base classes. The most derived metaclass is one which is a\nsubtype of *all* of these candidate metaclasses. If none of the\ncandidate metaclasses meets that criterion, then the class definition\nwill fail with ``TypeError``.\n\n\nPreparing the class namespace\n-----------------------------\n\nOnce the appropriate metaclass has been identified, then the class\nnamespace is prepared. If the metaclass has a ``__prepare__``\nattribute, it is called as ``namespace = metaclass.__prepare__(name,\nbases, **kwds)`` (where the additional keyword arguments, if any, come\nfrom the class definition).\n\nIf the metaclass has no ``__prepare__`` attribute, then the class\nnamespace is initialised as an empty ``dict()`` instance.\n\nSee also:\n\n **PEP 3115** - Metaclasses in Python 3000\n Introduced the ``__prepare__`` namespace hook\n\n\nExecuting the class body\n------------------------\n\nThe class body is executed (approximately) as ``exec(body, globals(),\nnamespace)``. The key difference from a normal call to ``exec()`` is\nthat lexical scoping allows the class body (including any methods) to\nreference names from the current and outer scopes when the class\ndefinition occurs inside a function.\n\nHowever, even when the class definition occurs inside the function,\nmethods defined inside the class still cannot see names defined at the\nclass scope. Class variables must be accessed through the first\nparameter of instance or class methods, and cannot be accessed at all\nfrom static methods.\n\n\nCreating the class object\n-------------------------\n\nOnce the class namespace has been populated by executing the class\nbody, the class object is created by calling ``metaclass(name, bases,\nnamespace, **kwds)`` (the additional keywords passed here are the same\nas those passed to ``__prepare__``).\n\nThis class object is the one that will be referenced by the zero-\nargument form of ``super()``. ``__class__`` is an implicit closure\nreference created by the compiler if any methods in a class body refer\nto either ``__class__`` or ``super``. This allows the zero argument\nform of ``super()`` to correctly identify the class being defined\nbased on lexical scoping, while the class or instance that was used to\nmake the current call is identified based on the first argument passed\nto the method.\n\nAfter the class object is created, it is passed to the class\ndecorators included in the class definition (if any) and the resulting\nobject is bound in the local namespace as the defined class.\n\nSee also:\n\n **PEP 3135** - New super\n Describes the implicit ``__class__`` closure reference\n\n\nMetaclass example\n-----------------\n\nThe potential uses for metaclasses are boundless. Some ideas that have\nbeen explored include logging, interface checking, automatic\ndelegation, automatic property creation, proxies, frameworks, and\nautomatic resource locking/synchronization.\n\nHere is an example of a metaclass that uses an\n``collections.OrderedDict`` to remember the order that class members\nwere defined:\n\n class OrderedClass(type):\n\n @classmethod\n def __prepare__(metacls, name, bases, **kwds):\n return collections.OrderedDict()\n\n def __new__(cls, name, bases, namespace, **kwds):\n result = type.__new__(cls, name, bases, dict(namespace))\n result.members = tuple(namespace)\n return result\n\n class A(metaclass=OrderedClass):\n def one(self): pass\n def two(self): pass\n def three(self): pass\n def four(self): pass\n\n >>> A.members\n (\'__module__\', \'one\', \'two\', \'three\', \'four\')\n\nWhen the class definition for *A* gets executed, the process begins\nwith calling the metaclass\'s ``__prepare__()`` method which returns an\nempty ``collections.OrderedDict``. That mapping records the methods\nand attributes of *A* as they are defined within the body of the class\nstatement. Once those definitions are executed, the ordered dictionary\nis fully populated and the metaclass\'s ``__new__()`` method gets\ninvoked. That method builds the new type and it saves the ordered\ndictionary keys in an attribute called ``members``.\n\n\nCustomizing instance and subclass checks\n========================================\n\nThe following methods are used to override the default behavior of the\n``isinstance()`` and ``issubclass()`` built-in functions.\n\nIn particular, the metaclass ``abc.ABCMeta`` implements these methods\nin order to allow the addition of Abstract Base Classes (ABCs) as\n"virtual base classes" to any class or type (including built-in\ntypes), including other ABCs.\n\nclass.__instancecheck__(self, instance)\n\n Return true if *instance* should be considered a (direct or\n indirect) instance of *class*. If defined, called to implement\n ``isinstance(instance, class)``.\n\nclass.__subclasscheck__(self, subclass)\n\n Return true if *subclass* should be considered a (direct or\n indirect) subclass of *class*. If defined, called to implement\n ``issubclass(subclass, class)``.\n\nNote that these methods are looked up on the type (metaclass) of a\nclass. They cannot be defined as class methods in the actual class.\nThis is consistent with the lookup of special methods that are called\non instances, only in this case the instance is itself a class.\n\nSee also:\n\n **PEP 3119** - Introducing Abstract Base Classes\n Includes the specification for customizing ``isinstance()`` and\n ``issubclass()`` behavior through ``__instancecheck__()`` and\n ``__subclasscheck__()``, with motivation for this functionality\n in the context of adding Abstract Base Classes (see the ``abc``\n module) to the language.\n\n\nEmulating callable objects\n==========================\n\nobject.__call__(self[, args...])\n\n Called when the instance is "called" as a function; if this method\n is defined, ``x(arg1, arg2, ...)`` is a shorthand for\n ``x.__call__(arg1, arg2, ...)``.\n\n\nEmulating container types\n=========================\n\nThe following methods can be defined to implement container objects.\nContainers usually are sequences (such as lists or tuples) or mappings\n(like dictionaries), but can represent other containers as well. The\nfirst set of methods is used either to emulate a sequence or to\nemulate a mapping; the difference is that for a sequence, the\nallowable keys should be the integers *k* for which ``0 <= k < N``\nwhere *N* is the length of the sequence, or slice objects, which\ndefine a range of items. It is also recommended that mappings provide\nthe methods ``keys()``, ``values()``, ``items()``, ``get()``,\n``clear()``, ``setdefault()``, ``pop()``, ``popitem()``, ``copy()``,\nand ``update()`` behaving similar to those for Python\'s standard\ndictionary objects. The ``collections`` module provides a\n``MutableMapping`` abstract base class to help create those methods\nfrom a base set of ``__getitem__()``, ``__setitem__()``,\n``__delitem__()``, and ``keys()``. Mutable sequences should provide\nmethods ``append()``, ``count()``, ``index()``, ``extend()``,\n``insert()``, ``pop()``, ``remove()``, ``reverse()`` and ``sort()``,\nlike Python standard list objects. Finally, sequence types should\nimplement addition (meaning concatenation) and multiplication (meaning\nrepetition) by defining the methods ``__add__()``, ``__radd__()``,\n``__iadd__()``, ``__mul__()``, ``__rmul__()`` and ``__imul__()``\ndescribed below; they should not define other numerical operators. It\nis recommended that both mappings and sequences implement the\n``__contains__()`` method to allow efficient use of the ``in``\noperator; for mappings, ``in`` should search the mapping\'s keys; for\nsequences, it should search through the values. It is further\nrecommended that both mappings and sequences implement the\n``__iter__()`` method to allow efficient iteration through the\ncontainer; for mappings, ``__iter__()`` should be the same as\n``keys()``; for sequences, it should iterate through the values.\n\nobject.__len__(self)\n\n Called to implement the built-in function ``len()``. Should return\n the length of the object, an integer ``>=`` 0. Also, an object\n that doesn\'t define a ``__bool__()`` method and whose ``__len__()``\n method returns zero is considered to be false in a Boolean context.\n\nNote: Slicing is done exclusively with the following three methods. A\n call like\n\n a[1:2] = b\n\n is translated to\n\n a[slice(1, 2, None)] = b\n\n and so forth. Missing slice items are always filled in with\n ``None``.\n\nobject.__getitem__(self, key)\n\n Called to implement evaluation of ``self[key]``. For sequence\n types, the accepted keys should be integers and slice objects.\n Note that the special interpretation of negative indexes (if the\n class wishes to emulate a sequence type) is up to the\n ``__getitem__()`` method. If *key* is of an inappropriate type,\n ``TypeError`` may be raised; if of a value outside the set of\n indexes for the sequence (after any special interpretation of\n negative values), ``IndexError`` should be raised. For mapping\n types, if *key* is missing (not in the container), ``KeyError``\n should be raised.\n\n Note: ``for`` loops expect that an ``IndexError`` will be raised for\n illegal indexes to allow proper detection of the end of the\n sequence.\n\nobject.__setitem__(self, key, value)\n\n Called to implement assignment to ``self[key]``. Same note as for\n ``__getitem__()``. This should only be implemented for mappings if\n the objects support changes to the values for keys, or if new keys\n can be added, or for sequences if elements can be replaced. The\n same exceptions should be raised for improper *key* values as for\n the ``__getitem__()`` method.\n\nobject.__delitem__(self, key)\n\n Called to implement deletion of ``self[key]``. Same note as for\n ``__getitem__()``. This should only be implemented for mappings if\n the objects support removal of keys, or for sequences if elements\n can be removed from the sequence. The same exceptions should be\n raised for improper *key* values as for the ``__getitem__()``\n method.\n\nobject.__iter__(self)\n\n This method is called when an iterator is required for a container.\n This method should return a new iterator object that can iterate\n over all the objects in the container. For mappings, it should\n iterate over the keys of the container, and should also be made\n available as the method ``keys()``.\n\n Iterator objects also need to implement this method; they are\n required to return themselves. For more information on iterator\n objects, see *Iterator Types*.\n\nobject.__reversed__(self)\n\n Called (if present) by the ``reversed()`` built-in to implement\n reverse iteration. It should return a new iterator object that\n iterates over all the objects in the container in reverse order.\n\n If the ``__reversed__()`` method is not provided, the\n ``reversed()`` built-in will fall back to using the sequence\n protocol (``__len__()`` and ``__getitem__()``). Objects that\n support the sequence protocol should only provide\n ``__reversed__()`` if they can provide an implementation that is\n more efficient than the one provided by ``reversed()``.\n\nThe membership test operators (``in`` and ``not in``) are normally\nimplemented as an iteration through a sequence. However, container\nobjects can supply the following special method with a more efficient\nimplementation, which also does not require the object be a sequence.\n\nobject.__contains__(self, item)\n\n Called to implement membership test operators. Should return true\n if *item* is in *self*, false otherwise. For mapping objects, this\n should consider the keys of the mapping rather than the values or\n the key-item pairs.\n\n For objects that don\'t define ``__contains__()``, the membership\n test first tries iteration via ``__iter__()``, then the old\n sequence iteration protocol via ``__getitem__()``, see *this\n section in the language reference*.\n\n\nEmulating numeric types\n=======================\n\nThe following methods can be defined to emulate numeric objects.\nMethods corresponding to operations that are not supported by the\nparticular kind of number implemented (e.g., bitwise operations for\nnon-integral numbers) should be left undefined.\n\nobject.__add__(self, other)\nobject.__sub__(self, other)\nobject.__mul__(self, other)\nobject.__truediv__(self, other)\nobject.__floordiv__(self, other)\nobject.__mod__(self, other)\nobject.__divmod__(self, other)\nobject.__pow__(self, other[, modulo])\nobject.__lshift__(self, other)\nobject.__rshift__(self, other)\nobject.__and__(self, other)\nobject.__xor__(self, other)\nobject.__or__(self, other)\n\n These methods are called to implement the binary arithmetic\n operations (``+``, ``-``, ``*``, ``/``, ``//``, ``%``,\n ``divmod()``, ``pow()``, ``**``, ``<<``, ``>>``, ``&``, ``^``,\n ``|``). For instance, to evaluate the expression ``x + y``, where\n *x* is an instance of a class that has an ``__add__()`` method,\n ``x.__add__(y)`` is called. The ``__divmod__()`` method should be\n the equivalent to using ``__floordiv__()`` and ``__mod__()``; it\n should not be related to ``__truediv__()``. Note that\n ``__pow__()`` should be defined to accept an optional third\n argument if the ternary version of the built-in ``pow()`` function\n is to be supported.\n\n If one of those methods does not support the operation with the\n supplied arguments, it should return ``NotImplemented``.\n\nobject.__radd__(self, other)\nobject.__rsub__(self, other)\nobject.__rmul__(self, other)\nobject.__rtruediv__(self, other)\nobject.__rfloordiv__(self, other)\nobject.__rmod__(self, other)\nobject.__rdivmod__(self, other)\nobject.__rpow__(self, other)\nobject.__rlshift__(self, other)\nobject.__rrshift__(self, other)\nobject.__rand__(self, other)\nobject.__rxor__(self, other)\nobject.__ror__(self, other)\n\n These methods are called to implement the binary arithmetic\n operations (``+``, ``-``, ``*``, ``/``, ``//``, ``%``,\n ``divmod()``, ``pow()``, ``**``, ``<<``, ``>>``, ``&``, ``^``,\n ``|``) with reflected (swapped) operands. These functions are only\n called if the left operand does not support the corresponding\n operation and the operands are of different types. [2] For\n instance, to evaluate the expression ``x - y``, where *y* is an\n instance of a class that has an ``__rsub__()`` method,\n ``y.__rsub__(x)`` is called if ``x.__sub__(y)`` returns\n *NotImplemented*.\n\n Note that ternary ``pow()`` will not try calling ``__rpow__()``\n (the coercion rules would become too complicated).\n\n Note: If the right operand\'s type is a subclass of the left operand\'s\n type and that subclass provides the reflected method for the\n operation, this method will be called before the left operand\'s\n non-reflected method. This behavior allows subclasses to\n override their ancestors\' operations.\n\nobject.__iadd__(self, other)\nobject.__isub__(self, other)\nobject.__imul__(self, other)\nobject.__itruediv__(self, other)\nobject.__ifloordiv__(self, other)\nobject.__imod__(self, other)\nobject.__ipow__(self, other[, modulo])\nobject.__ilshift__(self, other)\nobject.__irshift__(self, other)\nobject.__iand__(self, other)\nobject.__ixor__(self, other)\nobject.__ior__(self, other)\n\n These methods are called to implement the augmented arithmetic\n assignments (``+=``, ``-=``, ``*=``, ``/=``, ``//=``, ``%=``,\n ``**=``, ``<<=``, ``>>=``, ``&=``, ``^=``, ``|=``). These methods\n should attempt to do the operation in-place (modifying *self*) and\n return the result (which could be, but does not have to be,\n *self*). If a specific method is not defined, the augmented\n assignment falls back to the normal methods. For instance, to\n execute the statement ``x += y``, where *x* is an instance of a\n class that has an ``__iadd__()`` method, ``x.__iadd__(y)`` is\n called. If *x* is an instance of a class that does not define a\n ``__iadd__()`` method, ``x.__add__(y)`` and ``y.__radd__(x)`` are\n considered, as with the evaluation of ``x + y``.\n\nobject.__neg__(self)\nobject.__pos__(self)\nobject.__abs__(self)\nobject.__invert__(self)\n\n Called to implement the unary arithmetic operations (``-``, ``+``,\n ``abs()`` and ``~``).\n\nobject.__complex__(self)\nobject.__int__(self)\nobject.__float__(self)\nobject.__round__(self[, n])\n\n Called to implement the built-in functions ``complex()``,\n ``int()``, ``float()`` and ``round()``. Should return a value of\n the appropriate type.\n\nobject.__index__(self)\n\n Called to implement ``operator.index()``. Also called whenever\n Python needs an integer object (such as in slicing, or in the\n built-in ``bin()``, ``hex()`` and ``oct()`` functions). Must return\n an integer.\n\n\nWith Statement Context Managers\n===============================\n\nA *context manager* is an object that defines the runtime context to\nbe established when executing a ``with`` statement. The context\nmanager handles the entry into, and the exit from, the desired runtime\ncontext for the execution of the block of code. Context managers are\nnormally invoked using the ``with`` statement (described in section\n*The with statement*), but can also be used by directly invoking their\nmethods.\n\nTypical uses of context managers include saving and restoring various\nkinds of global state, locking and unlocking resources, closing opened\nfiles, etc.\n\nFor more information on context managers, see *Context Manager Types*.\n\nobject.__enter__(self)\n\n Enter the runtime context related to this object. The ``with``\n statement will bind this method\'s return value to the target(s)\n specified in the ``as`` clause of the statement, if any.\n\nobject.__exit__(self, exc_type, exc_value, traceback)\n\n Exit the runtime context related to this object. The parameters\n describe the exception that caused the context to be exited. If the\n context was exited without an exception, all three arguments will\n be ``None``.\n\n If an exception is supplied, and the method wishes to suppress the\n exception (i.e., prevent it from being propagated), it should\n return a true value. Otherwise, the exception will be processed\n normally upon exit from this method.\n\n Note that ``__exit__()`` methods should not reraise the passed-in\n exception; this is the caller\'s responsibility.\n\nSee also:\n\n **PEP 0343** - The "with" statement\n The specification, background, and examples for the Python\n ``with`` statement.\n\n\nSpecial method lookup\n=====================\n\nFor custom classes, implicit invocations of special methods are only\nguaranteed to work correctly if defined on an object\'s type, not in\nthe object\'s instance dictionary. That behaviour is the reason why\nthe following code raises an exception:\n\n >>> class C:\n ... pass\n ...\n >>> c = C()\n >>> c.__len__ = lambda: 5\n >>> len(c)\n Traceback (most recent call last):\n File "<stdin>", line 1, in <module>\n TypeError: object of type \'C\' has no len()\n\nThe rationale behind this behaviour lies with a number of special\nmethods such as ``__hash__()`` and ``__repr__()`` that are implemented\nby all objects, including type objects. If the implicit lookup of\nthese methods used the conventional lookup process, they would fail\nwhen invoked on the type object itself:\n\n >>> 1 .__hash__() == hash(1)\n True\n >>> int.__hash__() == hash(int)\n Traceback (most recent call last):\n File "<stdin>", line 1, in <module>\n TypeError: descriptor \'__hash__\' of \'int\' object needs an argument\n\nIncorrectly attempting to invoke an unbound method of a class in this\nway is sometimes referred to as \'metaclass confusion\', and is avoided\nby bypassing the instance when looking up special methods:\n\n >>> type(1).__hash__(1) == hash(1)\n True\n >>> type(int).__hash__(int) == hash(int)\n True\n\nIn addition to bypassing any instance attributes in the interest of\ncorrectness, implicit special method lookup generally also bypasses\nthe ``__getattribute__()`` method even of the object\'s metaclass:\n\n >>> class Meta(type):\n ... def __getattribute__(*args):\n ... print("Metaclass getattribute invoked")\n ... return type.__getattribute__(*args)\n ...\n >>> class C(object, metaclass=Meta):\n ... def __len__(self):\n ... return 10\n ... def __getattribute__(*args):\n ... print("Class getattribute invoked")\n ... return object.__getattribute__(*args)\n ...\n >>> c = C()\n >>> c.__len__() # Explicit lookup via instance\n Class getattribute invoked\n 10\n >>> type(c).__len__(c) # Explicit lookup via type\n Metaclass getattribute invoked\n 10\n >>> len(c) # Implicit lookup\n 10\n\nBypassing the ``__getattribute__()`` machinery in this fashion\nprovides significant scope for speed optimisations within the\ninterpreter, at the cost of some flexibility in the handling of\nspecial methods (the special method *must* be set on the class object\nitself in order to be consistently invoked by the interpreter).\n\n-[ Footnotes ]-\n\n[1] It *is* possible in some cases to change an object\'s type, under\n certain controlled conditions. It generally isn\'t a good idea\n though, since it can lead to some very strange behaviour if it is\n handled incorrectly.\n\n[2] For operands of the same type, it is assumed that if the non-\n reflected method (such as ``__add__()``) fails the operation is\n not supported, which is why the reflected method is not called.\n',
- 'string-methods': '\nString Methods\n**************\n\nStrings implement all of the *common* sequence operations, along with\nthe additional methods described below.\n\nStrings also support two styles of string formatting, one providing a\nlarge degree of flexibility and customization (see ``str.format()``,\n*Format String Syntax* and *String Formatting*) and the other based on\nC ``printf`` style formatting that handles a narrower range of types\nand is slightly harder to use correctly, but is often faster for the\ncases it can handle (*printf-style String Formatting*).\n\nThe *Text Processing Services* section of the standard library covers\na number of other modules that provide various text related utilities\n(including regular expression support in the ``re`` module).\n\nstr.capitalize()\n\n Return a copy of the string with its first character capitalized\n and the rest lowercased.\n\nstr.casefold()\n\n Return a casefolded copy of the string. Casefolded strings may be\n used for caseless matching.\n\n Casefolding is similar to lowercasing but more aggressive because\n it is intended to remove all case distinctions in a string. For\n example, the German lowercase letter ``\'\xc3\x9f\'`` is equivalent to\n ``"ss"``. Since it is already lowercase, ``lower()`` would do\n nothing to ``\'\xc3\x9f\'``; ``casefold()`` converts it to ``"ss"``.\n\n The casefolding algorithm is described in section 3.13 of the\n Unicode Standard.\n\n New in version 3.3.\n\nstr.center(width[, fillchar])\n\n Return centered in a string of length *width*. Padding is done\n using the specified *fillchar* (default is a space).\n\nstr.count(sub[, start[, end]])\n\n Return the number of non-overlapping occurrences of substring *sub*\n in the range [*start*, *end*]. Optional arguments *start* and\n *end* are interpreted as in slice notation.\n\nstr.encode(encoding="utf-8", errors="strict")\n\n Return an encoded version of the string as a bytes object. Default\n encoding is ``\'utf-8\'``. *errors* may be given to set a different\n error handling scheme. The default for *errors* is ``\'strict\'``,\n meaning that encoding errors raise a ``UnicodeError``. Other\n possible values are ``\'ignore\'``, ``\'replace\'``,\n ``\'xmlcharrefreplace\'``, ``\'backslashreplace\'`` and any other name\n registered via ``codecs.register_error()``, see section *Codec Base\n Classes*. For a list of possible encodings, see section *Standard\n Encodings*.\n\n Changed in version 3.1: Support for keyword arguments added.\n\nstr.endswith(suffix[, start[, end]])\n\n Return ``True`` if the string ends with the specified *suffix*,\n otherwise return ``False``. *suffix* can also be a tuple of\n suffixes to look for. With optional *start*, test beginning at\n that position. With optional *end*, stop comparing at that\n position.\n\nstr.expandtabs([tabsize])\n\n Return a copy of the string where all tab characters are replaced\n by one or more spaces, depending on the current column and the\n given tab size. Tab positions occur every *tabsize* characters\n (default is 8, giving tab positions at columns 0, 8, 16 and so on).\n To expand the string, the current column is set to zero and the\n string is examined character by character. If the character is a\n tab (``\\t``), one or more space characters are inserted in the\n result until the current column is equal to the next tab position.\n (The tab character itself is not copied.) If the character is a\n newline (``\\n``) or return (``\\r``), it is copied and the current\n column is reset to zero. Any other character is copied unchanged\n and the current column is incremented by one regardless of how the\n character is represented when printed.\n\n >>> \'01\\t012\\t0123\\t01234\'.expandtabs()\n \'01 012 0123 01234\'\n >>> \'01\\t012\\t0123\\t01234\'.expandtabs(4)\n \'01 012 0123 01234\'\n\nstr.find(sub[, start[, end]])\n\n Return the lowest index in the string where substring *sub* is\n found, such that *sub* is contained in the slice ``s[start:end]``.\n Optional arguments *start* and *end* are interpreted as in slice\n notation. Return ``-1`` if *sub* is not found.\n\n Note: The ``find()`` method should be used only if you need to know the\n position of *sub*. To check if *sub* is a substring or not, use\n the ``in`` operator:\n\n >>> \'Py\' in \'Python\'\n True\n\nstr.format(*args, **kwargs)\n\n Perform a string formatting operation. The string on which this\n method is called can contain literal text or replacement fields\n delimited by braces ``{}``. Each replacement field contains either\n the numeric index of a positional argument, or the name of a\n keyword argument. Returns a copy of the string where each\n replacement field is replaced with the string value of the\n corresponding argument.\n\n >>> "The sum of 1 + 2 is {0}".format(1+2)\n \'The sum of 1 + 2 is 3\'\n\n See *Format String Syntax* for a description of the various\n formatting options that can be specified in format strings.\n\nstr.format_map(mapping)\n\n Similar to ``str.format(**mapping)``, except that ``mapping`` is\n used directly and not copied to a ``dict`` . This is useful if for\n example ``mapping`` is a dict subclass:\n\n >>> class Default(dict):\n ... def __missing__(self, key):\n ... return key\n ...\n >>> \'{name} was born in {country}\'.format_map(Default(name=\'Guido\'))\n \'Guido was born in country\'\n\n New in version 3.2.\n\nstr.index(sub[, start[, end]])\n\n Like ``find()``, but raise ``ValueError`` when the substring is not\n found.\n\nstr.isalnum()\n\n Return true if all characters in the string are alphanumeric and\n there is at least one character, false otherwise. A character\n ``c`` is alphanumeric if one of the following returns ``True``:\n ``c.isalpha()``, ``c.isdecimal()``, ``c.isdigit()``, or\n ``c.isnumeric()``.\n\nstr.isalpha()\n\n Return true if all characters in the string are alphabetic and\n there is at least one character, false otherwise. Alphabetic\n characters are those characters defined in the Unicode character\n database as "Letter", i.e., those with general category property\n being one of "Lm", "Lt", "Lu", "Ll", or "Lo". Note that this is\n different from the "Alphabetic" property defined in the Unicode\n Standard.\n\nstr.isdecimal()\n\n Return true if all characters in the string are decimal characters\n and there is at least one character, false otherwise. Decimal\n characters are those from general category "Nd". This category\n includes digit characters, and all characters that can be used to\n form decimal-radix numbers, e.g. U+0660, ARABIC-INDIC DIGIT ZERO.\n\nstr.isdigit()\n\n Return true if all characters in the string are digits and there is\n at least one character, false otherwise. Digits include decimal\n characters and digits that need special handling, such as the\n compatibility superscript digits. Formally, a digit is a character\n that has the property value Numeric_Type=Digit or\n Numeric_Type=Decimal.\n\nstr.isidentifier()\n\n Return true if the string is a valid identifier according to the\n language definition, section *Identifiers and keywords*.\n\n Use ``keyword.iskeyword()`` to test for reserved identifiers such\n as ``def`` and ``class``.\n\nstr.islower()\n\n Return true if all cased characters [4] in the string are lowercase\n and there is at least one cased character, false otherwise.\n\nstr.isnumeric()\n\n Return true if all characters in the string are numeric characters,\n and there is at least one character, false otherwise. Numeric\n characters include digit characters, and all characters that have\n the Unicode numeric value property, e.g. U+2155, VULGAR FRACTION\n ONE FIFTH. Formally, numeric characters are those with the\n property value Numeric_Type=Digit, Numeric_Type=Decimal or\n Numeric_Type=Numeric.\n\nstr.isprintable()\n\n Return true if all characters in the string are printable or the\n string is empty, false otherwise. Nonprintable characters are\n those characters defined in the Unicode character database as\n "Other" or "Separator", excepting the ASCII space (0x20) which is\n considered printable. (Note that printable characters in this\n context are those which should not be escaped when ``repr()`` is\n invoked on a string. It has no bearing on the handling of strings\n written to ``sys.stdout`` or ``sys.stderr``.)\n\nstr.isspace()\n\n Return true if there are only whitespace characters in the string\n and there is at least one character, false otherwise. Whitespace\n characters are those characters defined in the Unicode character\n database as "Other" or "Separator" and those with bidirectional\n property being one of "WS", "B", or "S".\n\nstr.istitle()\n\n Return true if the string is a titlecased string and there is at\n least one character, for example uppercase characters may only\n follow uncased characters and lowercase characters only cased ones.\n Return false otherwise.\n\nstr.isupper()\n\n Return true if all cased characters [4] in the string are uppercase\n and there is at least one cased character, false otherwise.\n\nstr.join(iterable)\n\n Return a string which is the concatenation of the strings in the\n *iterable* *iterable*. A ``TypeError`` will be raised if there are\n any non-string values in *iterable*, including ``bytes`` objects.\n The separator between elements is the string providing this method.\n\nstr.ljust(width[, fillchar])\n\n Return the string left justified in a string of length *width*.\n Padding is done using the specified *fillchar* (default is a\n space). The original string is returned if *width* is less than or\n equal to ``len(s)``.\n\nstr.lower()\n\n Return a copy of the string with all the cased characters [4]\n converted to lowercase.\n\n The lowercasing algorithm used is described in section 3.13 of the\n Unicode Standard.\n\nstr.lstrip([chars])\n\n Return a copy of the string with leading characters removed. The\n *chars* argument is a string specifying the set of characters to be\n removed. If omitted or ``None``, the *chars* argument defaults to\n removing whitespace. The *chars* argument is not a prefix; rather,\n all combinations of its values are stripped:\n\n >>> \' spacious \'.lstrip()\n \'spacious \'\n >>> \'www.example.com\'.lstrip(\'cmowz.\')\n \'example.com\'\n\nstatic str.maketrans(x[, y[, z]])\n\n This static method returns a translation table usable for\n ``str.translate()``.\n\n If there is only one argument, it must be a dictionary mapping\n Unicode ordinals (integers) or characters (strings of length 1) to\n Unicode ordinals, strings (of arbitrary lengths) or None.\n Character keys will then be converted to ordinals.\n\n If there are two arguments, they must be strings of equal length,\n and in the resulting dictionary, each character in x will be mapped\n to the character at the same position in y. If there is a third\n argument, it must be a string, whose characters will be mapped to\n None in the result.\n\nstr.partition(sep)\n\n Split the string at the first occurrence of *sep*, and return a\n 3-tuple containing the part before the separator, the separator\n itself, and the part after the separator. If the separator is not\n found, return a 3-tuple containing the string itself, followed by\n two empty strings.\n\nstr.replace(old, new[, count])\n\n Return a copy of the string with all occurrences of substring *old*\n replaced by *new*. If the optional argument *count* is given, only\n the first *count* occurrences are replaced.\n\nstr.rfind(sub[, start[, end]])\n\n Return the highest index in the string where substring *sub* is\n found, such that *sub* is contained within ``s[start:end]``.\n Optional arguments *start* and *end* are interpreted as in slice\n notation. Return ``-1`` on failure.\n\nstr.rindex(sub[, start[, end]])\n\n Like ``rfind()`` but raises ``ValueError`` when the substring *sub*\n is not found.\n\nstr.rjust(width[, fillchar])\n\n Return the string right justified in a string of length *width*.\n Padding is done using the specified *fillchar* (default is a\n space). The original string is returned if *width* is less than or\n equal to ``len(s)``.\n\nstr.rpartition(sep)\n\n Split the string at the last occurrence of *sep*, and return a\n 3-tuple containing the part before the separator, the separator\n itself, and the part after the separator. If the separator is not\n found, return a 3-tuple containing two empty strings, followed by\n the string itself.\n\nstr.rsplit(sep=None, maxsplit=-1)\n\n Return a list of the words in the string, using *sep* as the\n delimiter string. If *maxsplit* is given, at most *maxsplit* splits\n are done, the *rightmost* ones. If *sep* is not specified or\n ``None``, any whitespace string is a separator. Except for\n splitting from the right, ``rsplit()`` behaves like ``split()``\n which is described in detail below.\n\nstr.rstrip([chars])\n\n Return a copy of the string with trailing characters removed. The\n *chars* argument is a string specifying the set of characters to be\n removed. If omitted or ``None``, the *chars* argument defaults to\n removing whitespace. The *chars* argument is not a suffix; rather,\n all combinations of its values are stripped:\n\n >>> \' spacious \'.rstrip()\n \' spacious\'\n >>> \'mississippi\'.rstrip(\'ipz\')\n \'mississ\'\n\nstr.split(sep=None, maxsplit=-1)\n\n Return a list of the words in the string, using *sep* as the\n delimiter string. If *maxsplit* is given, at most *maxsplit*\n splits are done (thus, the list will have at most ``maxsplit+1``\n elements). If *maxsplit* is not specified or ``-1``, then there is\n no limit on the number of splits (all possible splits are made).\n\n If *sep* is given, consecutive delimiters are not grouped together\n and are deemed to delimit empty strings (for example,\n ``\'1,,2\'.split(\',\')`` returns ``[\'1\', \'\', \'2\']``). The *sep*\n argument may consist of multiple characters (for example,\n ``\'1<>2<>3\'.split(\'<>\')`` returns ``[\'1\', \'2\', \'3\']``). Splitting\n an empty string with a specified separator returns ``[\'\']``.\n\n If *sep* is not specified or is ``None``, a different splitting\n algorithm is applied: runs of consecutive whitespace are regarded\n as a single separator, and the result will contain no empty strings\n at the start or end if the string has leading or trailing\n whitespace. Consequently, splitting an empty string or a string\n consisting of just whitespace with a ``None`` separator returns\n ``[]``.\n\n For example, ``\' 1 2 3 \'.split()`` returns ``[\'1\', \'2\', \'3\']``,\n and ``\' 1 2 3 \'.split(None, 1)`` returns ``[\'1\', \'2 3 \']``.\n\nstr.splitlines([keepends])\n\n Return a list of the lines in the string, breaking at line\n boundaries. This method uses the *universal newlines* approach to\n splitting lines. Line breaks are not included in the resulting list\n unless *keepends* is given and true.\n\n For example, ``\'ab c\\n\\nde fg\\rkl\\r\\n\'.splitlines()`` returns\n ``[\'ab c\', \'\', \'de fg\', \'kl\']``, while the same call with\n ``splitlines(True)`` returns ``[\'ab c\\n\', \'\\n\', \'de fg\\r\',\n \'kl\\r\\n\']``.\n\n Unlike ``split()`` when a delimiter string *sep* is given, this\n method returns an empty list for the empty string, and a terminal\n line break does not result in an extra line.\n\nstr.startswith(prefix[, start[, end]])\n\n Return ``True`` if string starts with the *prefix*, otherwise\n return ``False``. *prefix* can also be a tuple of prefixes to look\n for. With optional *start*, test string beginning at that\n position. With optional *end*, stop comparing string at that\n position.\n\nstr.strip([chars])\n\n Return a copy of the string with the leading and trailing\n characters removed. The *chars* argument is a string specifying the\n set of characters to be removed. If omitted or ``None``, the\n *chars* argument defaults to removing whitespace. The *chars*\n argument is not a prefix or suffix; rather, all combinations of its\n values are stripped:\n\n >>> \' spacious \'.strip()\n \'spacious\'\n >>> \'www.example.com\'.strip(\'cmowz.\')\n \'example\'\n\nstr.swapcase()\n\n Return a copy of the string with uppercase characters converted to\n lowercase and vice versa. Note that it is not necessarily true that\n ``s.swapcase().swapcase() == s``.\n\nstr.title()\n\n Return a titlecased version of the string where words start with an\n uppercase character and the remaining characters are lowercase.\n\n The algorithm uses a simple language-independent definition of a\n word as groups of consecutive letters. The definition works in\n many contexts but it means that apostrophes in contractions and\n possessives form word boundaries, which may not be the desired\n result:\n\n >>> "they\'re bill\'s friends from the UK".title()\n "They\'Re Bill\'S Friends From The Uk"\n\n A workaround for apostrophes can be constructed using regular\n expressions:\n\n >>> import re\n >>> def titlecase(s):\n ... return re.sub(r"[A-Za-z]+(\'[A-Za-z]+)?",\n ... lambda mo: mo.group(0)[0].upper() +\n ... mo.group(0)[1:].lower(),\n ... s)\n ...\n >>> titlecase("they\'re bill\'s friends.")\n "They\'re Bill\'s Friends."\n\nstr.translate(map)\n\n Return a copy of the *s* where all characters have been mapped\n through the *map* which must be a dictionary of Unicode ordinals\n (integers) to Unicode ordinals, strings or ``None``. Unmapped\n characters are left untouched. Characters mapped to ``None`` are\n deleted.\n\n You can use ``str.maketrans()`` to create a translation map from\n character-to-character mappings in different formats.\n\n Note: An even more flexible approach is to create a custom character\n mapping codec using the ``codecs`` module (see\n ``encodings.cp1251`` for an example).\n\nstr.upper()\n\n Return a copy of the string with all the cased characters [4]\n converted to uppercase. Note that ``str.upper().isupper()`` might\n be ``False`` if ``s`` contains uncased characters or if the Unicode\n category of the resulting character(s) is not "Lu" (Letter,\n uppercase), but e.g. "Lt" (Letter, titlecase).\n\n The uppercasing algorithm used is described in section 3.13 of the\n Unicode Standard.\n\nstr.zfill(width)\n\n Return the numeric string left filled with zeros in a string of\n length *width*. A sign prefix is handled correctly. The original\n string is returned if *width* is less than or equal to ``len(s)``.\n',
+ 'specialnames': '\nSpecial method names\n********************\n\nA class can implement certain operations that are invoked by special\nsyntax (such as arithmetic operations or subscripting and slicing) by\ndefining methods with special names. This is Python\'s approach to\n*operator overloading*, allowing classes to define their own behavior\nwith respect to language operators. For instance, if a class defines\na method named ``__getitem__()``, and ``x`` is an instance of this\nclass, then ``x[i]`` is roughly equivalent to ``type(x).__getitem__(x,\ni)``. Except where mentioned, attempts to execute an operation raise\nan exception when no appropriate method is defined (typically\n``AttributeError`` or ``TypeError``).\n\nWhen implementing a class that emulates any built-in type, it is\nimportant that the emulation only be implemented to the degree that it\nmakes sense for the object being modelled. For example, some\nsequences may work well with retrieval of individual elements, but\nextracting a slice may not make sense. (One example of this is the\n``NodeList`` interface in the W3C\'s Document Object Model.)\n\n\nBasic customization\n===================\n\nobject.__new__(cls[, ...])\n\n Called to create a new instance of class *cls*. ``__new__()`` is a\n static method (special-cased so you need not declare it as such)\n that takes the class of which an instance was requested as its\n first argument. The remaining arguments are those passed to the\n object constructor expression (the call to the class). The return\n value of ``__new__()`` should be the new object instance (usually\n an instance of *cls*).\n\n Typical implementations create a new instance of the class by\n invoking the superclass\'s ``__new__()`` method using\n ``super(currentclass, cls).__new__(cls[, ...])`` with appropriate\n arguments and then modifying the newly-created instance as\n necessary before returning it.\n\n If ``__new__()`` returns an instance of *cls*, then the new\n instance\'s ``__init__()`` method will be invoked like\n ``__init__(self[, ...])``, where *self* is the new instance and the\n remaining arguments are the same as were passed to ``__new__()``.\n\n If ``__new__()`` does not return an instance of *cls*, then the new\n instance\'s ``__init__()`` method will not be invoked.\n\n ``__new__()`` is intended mainly to allow subclasses of immutable\n types (like int, str, or tuple) to customize instance creation. It\n is also commonly overridden in custom metaclasses in order to\n customize class creation.\n\nobject.__init__(self[, ...])\n\n Called when the instance is created. The arguments are those\n passed to the class constructor expression. If a base class has an\n ``__init__()`` method, the derived class\'s ``__init__()`` method,\n if any, must explicitly call it to ensure proper initialization of\n the base class part of the instance; for example:\n ``BaseClass.__init__(self, [args...])``. As a special constraint\n on constructors, no value may be returned; doing so will cause a\n ``TypeError`` to be raised at runtime.\n\nobject.__del__(self)\n\n Called when the instance is about to be destroyed. This is also\n called a destructor. If a base class has a ``__del__()`` method,\n the derived class\'s ``__del__()`` method, if any, must explicitly\n call it to ensure proper deletion of the base class part of the\n instance. Note that it is possible (though not recommended!) for\n the ``__del__()`` method to postpone destruction of the instance by\n creating a new reference to it. It may then be called at a later\n time when this new reference is deleted. It is not guaranteed that\n ``__del__()`` methods are called for objects that still exist when\n the interpreter exits.\n\n Note: ``del x`` doesn\'t directly call ``x.__del__()`` --- the former\n decrements the reference count for ``x`` by one, and the latter\n is only called when ``x``\'s reference count reaches zero. Some\n common situations that may prevent the reference count of an\n object from going to zero include: circular references between\n objects (e.g., a doubly-linked list or a tree data structure with\n parent and child pointers); a reference to the object on the\n stack frame of a function that caught an exception (the traceback\n stored in ``sys.exc_info()[2]`` keeps the stack frame alive); or\n a reference to the object on the stack frame that raised an\n unhandled exception in interactive mode (the traceback stored in\n ``sys.last_traceback`` keeps the stack frame alive). The first\n situation can only be remedied by explicitly breaking the cycles;\n the latter two situations can be resolved by storing ``None`` in\n ``sys.last_traceback``. Circular references which are garbage are\n detected and cleaned up when the cyclic garbage collector is\n enabled (it\'s on by default). Refer to the documentation for the\n ``gc`` module for more information about this topic.\n\n Warning: Due to the precarious circumstances under which ``__del__()``\n methods are invoked, exceptions that occur during their execution\n are ignored, and a warning is printed to ``sys.stderr`` instead.\n Also, when ``__del__()`` is invoked in response to a module being\n deleted (e.g., when execution of the program is done), other\n globals referenced by the ``__del__()`` method may already have\n been deleted or in the process of being torn down (e.g. the\n import machinery shutting down). For this reason, ``__del__()``\n methods should do the absolute minimum needed to maintain\n external invariants. Starting with version 1.5, Python\n guarantees that globals whose name begins with a single\n underscore are deleted from their module before other globals are\n deleted; if no other references to such globals exist, this may\n help in assuring that imported modules are still available at the\n time when the ``__del__()`` method is called.\n\nobject.__repr__(self)\n\n Called by the ``repr()`` built-in function to compute the\n "official" string representation of an object. If at all possible,\n this should look like a valid Python expression that could be used\n to recreate an object with the same value (given an appropriate\n environment). If this is not possible, a string of the form\n ``<...some useful description...>`` should be returned. The return\n value must be a string object. If a class defines ``__repr__()``\n but not ``__str__()``, then ``__repr__()`` is also used when an\n "informal" string representation of instances of that class is\n required.\n\n This is typically used for debugging, so it is important that the\n representation is information-rich and unambiguous.\n\nobject.__str__(self)\n\n Called by ``str(object)`` and the built-in functions ``format()``\n and ``print()`` to compute the "informal" or nicely printable\n string representation of an object. The return value must be a\n *string* object.\n\n This method differs from ``object.__repr__()`` in that there is no\n expectation that ``__str__()`` return a valid Python expression: a\n more convenient or concise representation can be used.\n\n The default implementation defined by the built-in type ``object``\n calls ``object.__repr__()``.\n\nobject.__bytes__(self)\n\n Called by ``bytes()`` to compute a byte-string representation of an\n object. This should return a ``bytes`` object.\n\nobject.__format__(self, format_spec)\n\n Called by the ``format()`` built-in function (and by extension, the\n ``str.format()`` method of class ``str``) to produce a "formatted"\n string representation of an object. The ``format_spec`` argument is\n a string that contains a description of the formatting options\n desired. The interpretation of the ``format_spec`` argument is up\n to the type implementing ``__format__()``, however most classes\n will either delegate formatting to one of the built-in types, or\n use a similar formatting option syntax.\n\n See *Format Specification Mini-Language* for a description of the\n standard formatting syntax.\n\n The return value must be a string object.\n\nobject.__lt__(self, other)\nobject.__le__(self, other)\nobject.__eq__(self, other)\nobject.__ne__(self, other)\nobject.__gt__(self, other)\nobject.__ge__(self, other)\n\n These are the so-called "rich comparison" methods. The\n correspondence between operator symbols and method names is as\n follows: ``x<y`` calls ``x.__lt__(y)``, ``x<=y`` calls\n ``x.__le__(y)``, ``x==y`` calls ``x.__eq__(y)``, ``x!=y`` calls\n ``x.__ne__(y)``, ``x>y`` calls ``x.__gt__(y)``, and ``x>=y`` calls\n ``x.__ge__(y)``.\n\n A rich comparison method may return the singleton\n ``NotImplemented`` if it does not implement the operation for a\n given pair of arguments. By convention, ``False`` and ``True`` are\n returned for a successful comparison. However, these methods can\n return any value, so if the comparison operator is used in a\n Boolean context (e.g., in the condition of an ``if`` statement),\n Python will call ``bool()`` on the value to determine if the result\n is true or false.\n\n There are no implied relationships among the comparison operators.\n The truth of ``x==y`` does not imply that ``x!=y`` is false.\n Accordingly, when defining ``__eq__()``, one should also define\n ``__ne__()`` so that the operators will behave as expected. See\n the paragraph on ``__hash__()`` for some important notes on\n creating *hashable* objects which support custom comparison\n operations and are usable as dictionary keys.\n\n There are no swapped-argument versions of these methods (to be used\n when the left argument does not support the operation but the right\n argument does); rather, ``__lt__()`` and ``__gt__()`` are each\n other\'s reflection, ``__le__()`` and ``__ge__()`` are each other\'s\n reflection, and ``__eq__()`` and ``__ne__()`` are their own\n reflection.\n\n Arguments to rich comparison methods are never coerced.\n\n To automatically generate ordering operations from a single root\n operation, see ``functools.total_ordering()``.\n\nobject.__hash__(self)\n\n Called by built-in function ``hash()`` and for operations on\n members of hashed collections including ``set``, ``frozenset``, and\n ``dict``. ``__hash__()`` should return an integer. The only\n required property is that objects which compare equal have the same\n hash value; it is advised to somehow mix together (e.g. using\n exclusive or) the hash values for the components of the object that\n also play a part in comparison of objects.\n\n Note: ``hash()`` truncates the value returned from an object\'s custom\n ``__hash__()`` method to the size of a ``Py_ssize_t``. This is\n typically 8 bytes on 64-bit builds and 4 bytes on 32-bit builds.\n If an object\'s ``__hash__()`` must interoperate on builds of\n different bit sizes, be sure to check the width on all supported\n builds. An easy way to do this is with ``python -c "import sys;\n print(sys.hash_info.width)"``\n\n If a class does not define an ``__eq__()`` method it should not\n define a ``__hash__()`` operation either; if it defines\n ``__eq__()`` but not ``__hash__()``, its instances will not be\n usable as items in hashable collections. If a class defines\n mutable objects and implements an ``__eq__()`` method, it should\n not implement ``__hash__()``, since the implementation of hashable\n collections requires that a key\'s hash value is immutable (if the\n object\'s hash value changes, it will be in the wrong hash bucket).\n\n User-defined classes have ``__eq__()`` and ``__hash__()`` methods\n by default; with them, all objects compare unequal (except with\n themselves) and ``x.__hash__()`` returns an appropriate value such\n that ``x == y`` implies both that ``x is y`` and ``hash(x) ==\n hash(y)``.\n\n A class that overrides ``__eq__()`` and does not define\n ``__hash__()`` will have its ``__hash__()`` implicitly set to\n ``None``. When the ``__hash__()`` method of a class is ``None``,\n instances of the class will raise an appropriate ``TypeError`` when\n a program attempts to retrieve their hash value, and will also be\n correctly identified as unhashable when checking ``isinstance(obj,\n collections.Hashable``).\n\n If a class that overrides ``__eq__()`` needs to retain the\n implementation of ``__hash__()`` from a parent class, the\n interpreter must be told this explicitly by setting ``__hash__ =\n <ParentClass>.__hash__``.\n\n If a class that does not override ``__eq__()`` wishes to suppress\n hash support, it should include ``__hash__ = None`` in the class\n definition. A class which defines its own ``__hash__()`` that\n explicitly raises a ``TypeError`` would be incorrectly identified\n as hashable by an ``isinstance(obj, collections.Hashable)`` call.\n\n Note: By default, the ``__hash__()`` values of str, bytes and datetime\n objects are "salted" with an unpredictable random value.\n Although they remain constant within an individual Python\n process, they are not predictable between repeated invocations of\n Python.This is intended to provide protection against a denial-\n of-service caused by carefully-chosen inputs that exploit the\n worst case performance of a dict insertion, O(n^2) complexity.\n See http://www.ocert.org/advisories/ocert-2011-003.html for\n details.Changing hash values affects the iteration order of\n dicts, sets and other mappings. Python has never made guarantees\n about this ordering (and it typically varies between 32-bit and\n 64-bit builds).See also ``PYTHONHASHSEED``.\n\n Changed in version 3.3: Hash randomization is enabled by default.\n\nobject.__bool__(self)\n\n Called to implement truth value testing and the built-in operation\n ``bool()``; should return ``False`` or ``True``. When this method\n is not defined, ``__len__()`` is called, if it is defined, and the\n object is considered true if its result is nonzero. If a class\n defines neither ``__len__()`` nor ``__bool__()``, all its instances\n are considered true.\n\n\nCustomizing attribute access\n============================\n\nThe following methods can be defined to customize the meaning of\nattribute access (use of, assignment to, or deletion of ``x.name``)\nfor class instances.\n\nobject.__getattr__(self, name)\n\n Called when an attribute lookup has not found the attribute in the\n usual places (i.e. it is not an instance attribute nor is it found\n in the class tree for ``self``). ``name`` is the attribute name.\n This method should return the (computed) attribute value or raise\n an ``AttributeError`` exception.\n\n Note that if the attribute is found through the normal mechanism,\n ``__getattr__()`` is not called. (This is an intentional asymmetry\n between ``__getattr__()`` and ``__setattr__()``.) This is done both\n for efficiency reasons and because otherwise ``__getattr__()``\n would have no way to access other attributes of the instance. Note\n that at least for instance variables, you can fake total control by\n not inserting any values in the instance attribute dictionary (but\n instead inserting them in another object). See the\n ``__getattribute__()`` method below for a way to actually get total\n control over attribute access.\n\nobject.__getattribute__(self, name)\n\n Called unconditionally to implement attribute accesses for\n instances of the class. If the class also defines\n ``__getattr__()``, the latter will not be called unless\n ``__getattribute__()`` either calls it explicitly or raises an\n ``AttributeError``. This method should return the (computed)\n attribute value or raise an ``AttributeError`` exception. In order\n to avoid infinite recursion in this method, its implementation\n should always call the base class method with the same name to\n access any attributes it needs, for example,\n ``object.__getattribute__(self, name)``.\n\n Note: This method may still be bypassed when looking up special methods\n as the result of implicit invocation via language syntax or\n built-in functions. See *Special method lookup*.\n\nobject.__setattr__(self, name, value)\n\n Called when an attribute assignment is attempted. This is called\n instead of the normal mechanism (i.e. store the value in the\n instance dictionary). *name* is the attribute name, *value* is the\n value to be assigned to it.\n\n If ``__setattr__()`` wants to assign to an instance attribute, it\n should call the base class method with the same name, for example,\n ``object.__setattr__(self, name, value)``.\n\nobject.__delattr__(self, name)\n\n Like ``__setattr__()`` but for attribute deletion instead of\n assignment. This should only be implemented if ``del obj.name`` is\n meaningful for the object.\n\nobject.__dir__(self)\n\n Called when ``dir()`` is called on the object. A sequence must be\n returned. ``dir()`` converts the returned sequence to a list and\n sorts it.\n\n\nImplementing Descriptors\n------------------------\n\nThe following methods only apply when an instance of the class\ncontaining the method (a so-called *descriptor* class) appears in an\n*owner* class (the descriptor must be in either the owner\'s class\ndictionary or in the class dictionary for one of its parents). In the\nexamples below, "the attribute" refers to the attribute whose name is\nthe key of the property in the owner class\' ``__dict__``.\n\nobject.__get__(self, instance, owner)\n\n Called to get the attribute of the owner class (class attribute\n access) or of an instance of that class (instance attribute\n access). *owner* is always the owner class, while *instance* is the\n instance that the attribute was accessed through, or ``None`` when\n the attribute is accessed through the *owner*. This method should\n return the (computed) attribute value or raise an\n ``AttributeError`` exception.\n\nobject.__set__(self, instance, value)\n\n Called to set the attribute on an instance *instance* of the owner\n class to a new value, *value*.\n\nobject.__delete__(self, instance)\n\n Called to delete the attribute on an instance *instance* of the\n owner class.\n\n\nInvoking Descriptors\n--------------------\n\nIn general, a descriptor is an object attribute with "binding\nbehavior", one whose attribute access has been overridden by methods\nin the descriptor protocol: ``__get__()``, ``__set__()``, and\n``__delete__()``. If any of those methods are defined for an object,\nit is said to be a descriptor.\n\nThe default behavior for attribute access is to get, set, or delete\nthe attribute from an object\'s dictionary. For instance, ``a.x`` has a\nlookup chain starting with ``a.__dict__[\'x\']``, then\n``type(a).__dict__[\'x\']``, and continuing through the base classes of\n``type(a)`` excluding metaclasses.\n\nHowever, if the looked-up value is an object defining one of the\ndescriptor methods, then Python may override the default behavior and\ninvoke the descriptor method instead. Where this occurs in the\nprecedence chain depends on which descriptor methods were defined and\nhow they were called.\n\nThe starting point for descriptor invocation is a binding, ``a.x``.\nHow the arguments are assembled depends on ``a``:\n\nDirect Call\n The simplest and least common call is when user code directly\n invokes a descriptor method: ``x.__get__(a)``.\n\nInstance Binding\n If binding to an object instance, ``a.x`` is transformed into the\n call: ``type(a).__dict__[\'x\'].__get__(a, type(a))``.\n\nClass Binding\n If binding to a class, ``A.x`` is transformed into the call:\n ``A.__dict__[\'x\'].__get__(None, A)``.\n\nSuper Binding\n If ``a`` is an instance of ``super``, then the binding ``super(B,\n obj).m()`` searches ``obj.__class__.__mro__`` for the base class\n ``A`` immediately preceding ``B`` and then invokes the descriptor\n with the call: ``A.__dict__[\'m\'].__get__(obj, obj.__class__)``.\n\nFor instance bindings, the precedence of descriptor invocation depends\non the which descriptor methods are defined. A descriptor can define\nany combination of ``__get__()``, ``__set__()`` and ``__delete__()``.\nIf it does not define ``__get__()``, then accessing the attribute will\nreturn the descriptor object itself unless there is a value in the\nobject\'s instance dictionary. If the descriptor defines ``__set__()``\nand/or ``__delete__()``, it is a data descriptor; if it defines\nneither, it is a non-data descriptor. Normally, data descriptors\ndefine both ``__get__()`` and ``__set__()``, while non-data\ndescriptors have just the ``__get__()`` method. Data descriptors with\n``__set__()`` and ``__get__()`` defined always override a redefinition\nin an instance dictionary. In contrast, non-data descriptors can be\noverridden by instances.\n\nPython methods (including ``staticmethod()`` and ``classmethod()``)\nare implemented as non-data descriptors. Accordingly, instances can\nredefine and override methods. This allows individual instances to\nacquire behaviors that differ from other instances of the same class.\n\nThe ``property()`` function is implemented as a data descriptor.\nAccordingly, instances cannot override the behavior of a property.\n\n\n__slots__\n---------\n\nBy default, instances of classes have a dictionary for attribute\nstorage. This wastes space for objects having very few instance\nvariables. The space consumption can become acute when creating large\nnumbers of instances.\n\nThe default can be overridden by defining *__slots__* in a class\ndefinition. The *__slots__* declaration takes a sequence of instance\nvariables and reserves just enough space in each instance to hold a\nvalue for each variable. Space is saved because *__dict__* is not\ncreated for each instance.\n\nobject.__slots__\n\n This class variable can be assigned a string, iterable, or sequence\n of strings with variable names used by instances. If defined in a\n class, *__slots__* reserves space for the declared variables and\n prevents the automatic creation of *__dict__* and *__weakref__* for\n each instance.\n\n\nNotes on using *__slots__*\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* When inheriting from a class without *__slots__*, the *__dict__*\n attribute of that class will always be accessible, so a *__slots__*\n definition in the subclass is meaningless.\n\n* Without a *__dict__* variable, instances cannot be assigned new\n variables not listed in the *__slots__* definition. Attempts to\n assign to an unlisted variable name raises ``AttributeError``. If\n dynamic assignment of new variables is desired, then add\n ``\'__dict__\'`` to the sequence of strings in the *__slots__*\n declaration.\n\n* Without a *__weakref__* variable for each instance, classes defining\n *__slots__* do not support weak references to its instances. If weak\n reference support is needed, then add ``\'__weakref__\'`` to the\n sequence of strings in the *__slots__* declaration.\n\n* *__slots__* are implemented at the class level by creating\n descriptors (*Implementing Descriptors*) for each variable name. As\n a result, class attributes cannot be used to set default values for\n instance variables defined by *__slots__*; otherwise, the class\n attribute would overwrite the descriptor assignment.\n\n* The action of a *__slots__* declaration is limited to the class\n where it is defined. As a result, subclasses will have a *__dict__*\n unless they also define *__slots__* (which must only contain names\n of any *additional* slots).\n\n* If a class defines a slot also defined in a base class, the instance\n variable defined by the base class slot is inaccessible (except by\n retrieving its descriptor directly from the base class). This\n renders the meaning of the program undefined. In the future, a\n check may be added to prevent this.\n\n* Nonempty *__slots__* does not work for classes derived from\n "variable-length" built-in types such as ``int``, ``str`` and\n ``tuple``.\n\n* Any non-string iterable may be assigned to *__slots__*. Mappings may\n also be used; however, in the future, special meaning may be\n assigned to the values corresponding to each key.\n\n* *__class__* assignment works only if both classes have the same\n *__slots__*.\n\n\nCustomizing class creation\n==========================\n\nBy default, classes are constructed using ``type()``. The class body\nis executed in a new namespace and the class name is bound locally to\nthe result of ``type(name, bases, namespace)``.\n\nThe class creation process can be customised by passing the\n``metaclass`` keyword argument in the class definition line, or by\ninheriting from an existing class that included such an argument. In\nthe following example, both ``MyClass`` and ``MySubclass`` are\ninstances of ``Meta``:\n\n class Meta(type):\n pass\n\n class MyClass(metaclass=Meta):\n pass\n\n class MySubclass(MyClass):\n pass\n\nAny other keyword arguments that are specified in the class definition\nare passed through to all metaclass operations described below.\n\nWhen a class definition is executed, the following steps occur:\n\n* the appropriate metaclass is determined\n\n* the class namespace is prepared\n\n* the class body is executed\n\n* the class object is created\n\n\nDetermining the appropriate metaclass\n-------------------------------------\n\nThe appropriate metaclass for a class definition is determined as\nfollows:\n\n* if no bases and no explicit metaclass are given, then ``type()`` is\n used\n\n* if an explicit metaclass is given and it is *not* an instance of\n ``type()``, then it is used directly as the metaclass\n\n* if an instance of ``type()`` is given as the explicit metaclass, or\n bases are defined, then the most derived metaclass is used\n\nThe most derived metaclass is selected from the explicitly specified\nmetaclass (if any) and the metaclasses (i.e. ``type(cls)``) of all\nspecified base classes. The most derived metaclass is one which is a\nsubtype of *all* of these candidate metaclasses. If none of the\ncandidate metaclasses meets that criterion, then the class definition\nwill fail with ``TypeError``.\n\n\nPreparing the class namespace\n-----------------------------\n\nOnce the appropriate metaclass has been identified, then the class\nnamespace is prepared. If the metaclass has a ``__prepare__``\nattribute, it is called as ``namespace = metaclass.__prepare__(name,\nbases, **kwds)`` (where the additional keyword arguments, if any, come\nfrom the class definition).\n\nIf the metaclass has no ``__prepare__`` attribute, then the class\nnamespace is initialised as an empty ``dict()`` instance.\n\nSee also:\n\n **PEP 3115** - Metaclasses in Python 3000\n Introduced the ``__prepare__`` namespace hook\n\n\nExecuting the class body\n------------------------\n\nThe class body is executed (approximately) as ``exec(body, globals(),\nnamespace)``. The key difference from a normal call to ``exec()`` is\nthat lexical scoping allows the class body (including any methods) to\nreference names from the current and outer scopes when the class\ndefinition occurs inside a function.\n\nHowever, even when the class definition occurs inside the function,\nmethods defined inside the class still cannot see names defined at the\nclass scope. Class variables must be accessed through the first\nparameter of instance or class methods, and cannot be accessed at all\nfrom static methods.\n\n\nCreating the class object\n-------------------------\n\nOnce the class namespace has been populated by executing the class\nbody, the class object is created by calling ``metaclass(name, bases,\nnamespace, **kwds)`` (the additional keywords passed here are the same\nas those passed to ``__prepare__``).\n\nThis class object is the one that will be referenced by the zero-\nargument form of ``super()``. ``__class__`` is an implicit closure\nreference created by the compiler if any methods in a class body refer\nto either ``__class__`` or ``super``. This allows the zero argument\nform of ``super()`` to correctly identify the class being defined\nbased on lexical scoping, while the class or instance that was used to\nmake the current call is identified based on the first argument passed\nto the method.\n\nAfter the class object is created, it is passed to the class\ndecorators included in the class definition (if any) and the resulting\nobject is bound in the local namespace as the defined class.\n\nSee also:\n\n **PEP 3135** - New super\n Describes the implicit ``__class__`` closure reference\n\n\nMetaclass example\n-----------------\n\nThe potential uses for metaclasses are boundless. Some ideas that have\nbeen explored include logging, interface checking, automatic\ndelegation, automatic property creation, proxies, frameworks, and\nautomatic resource locking/synchronization.\n\nHere is an example of a metaclass that uses an\n``collections.OrderedDict`` to remember the order that class members\nwere defined:\n\n class OrderedClass(type):\n\n @classmethod\n def __prepare__(metacls, name, bases, **kwds):\n return collections.OrderedDict()\n\n def __new__(cls, name, bases, namespace, **kwds):\n result = type.__new__(cls, name, bases, dict(namespace))\n result.members = tuple(namespace)\n return result\n\n class A(metaclass=OrderedClass):\n def one(self): pass\n def two(self): pass\n def three(self): pass\n def four(self): pass\n\n >>> A.members\n (\'__module__\', \'one\', \'two\', \'three\', \'four\')\n\nWhen the class definition for *A* gets executed, the process begins\nwith calling the metaclass\'s ``__prepare__()`` method which returns an\nempty ``collections.OrderedDict``. That mapping records the methods\nand attributes of *A* as they are defined within the body of the class\nstatement. Once those definitions are executed, the ordered dictionary\nis fully populated and the metaclass\'s ``__new__()`` method gets\ninvoked. That method builds the new type and it saves the ordered\ndictionary keys in an attribute called ``members``.\n\n\nCustomizing instance and subclass checks\n========================================\n\nThe following methods are used to override the default behavior of the\n``isinstance()`` and ``issubclass()`` built-in functions.\n\nIn particular, the metaclass ``abc.ABCMeta`` implements these methods\nin order to allow the addition of Abstract Base Classes (ABCs) as\n"virtual base classes" to any class or type (including built-in\ntypes), including other ABCs.\n\nclass.__instancecheck__(self, instance)\n\n Return true if *instance* should be considered a (direct or\n indirect) instance of *class*. If defined, called to implement\n ``isinstance(instance, class)``.\n\nclass.__subclasscheck__(self, subclass)\n\n Return true if *subclass* should be considered a (direct or\n indirect) subclass of *class*. If defined, called to implement\n ``issubclass(subclass, class)``.\n\nNote that these methods are looked up on the type (metaclass) of a\nclass. They cannot be defined as class methods in the actual class.\nThis is consistent with the lookup of special methods that are called\non instances, only in this case the instance is itself a class.\n\nSee also:\n\n **PEP 3119** - Introducing Abstract Base Classes\n Includes the specification for customizing ``isinstance()`` and\n ``issubclass()`` behavior through ``__instancecheck__()`` and\n ``__subclasscheck__()``, with motivation for this functionality\n in the context of adding Abstract Base Classes (see the ``abc``\n module) to the language.\n\n\nEmulating callable objects\n==========================\n\nobject.__call__(self[, args...])\n\n Called when the instance is "called" as a function; if this method\n is defined, ``x(arg1, arg2, ...)`` is a shorthand for\n ``x.__call__(arg1, arg2, ...)``.\n\n\nEmulating container types\n=========================\n\nThe following methods can be defined to implement container objects.\nContainers usually are sequences (such as lists or tuples) or mappings\n(like dictionaries), but can represent other containers as well. The\nfirst set of methods is used either to emulate a sequence or to\nemulate a mapping; the difference is that for a sequence, the\nallowable keys should be the integers *k* for which ``0 <= k < N``\nwhere *N* is the length of the sequence, or slice objects, which\ndefine a range of items. It is also recommended that mappings provide\nthe methods ``keys()``, ``values()``, ``items()``, ``get()``,\n``clear()``, ``setdefault()``, ``pop()``, ``popitem()``, ``copy()``,\nand ``update()`` behaving similar to those for Python\'s standard\ndictionary objects. The ``collections`` module provides a\n``MutableMapping`` abstract base class to help create those methods\nfrom a base set of ``__getitem__()``, ``__setitem__()``,\n``__delitem__()``, and ``keys()``. Mutable sequences should provide\nmethods ``append()``, ``count()``, ``index()``, ``extend()``,\n``insert()``, ``pop()``, ``remove()``, ``reverse()`` and ``sort()``,\nlike Python standard list objects. Finally, sequence types should\nimplement addition (meaning concatenation) and multiplication (meaning\nrepetition) by defining the methods ``__add__()``, ``__radd__()``,\n``__iadd__()``, ``__mul__()``, ``__rmul__()`` and ``__imul__()``\ndescribed below; they should not define other numerical operators. It\nis recommended that both mappings and sequences implement the\n``__contains__()`` method to allow efficient use of the ``in``\noperator; for mappings, ``in`` should search the mapping\'s keys; for\nsequences, it should search through the values. It is further\nrecommended that both mappings and sequences implement the\n``__iter__()`` method to allow efficient iteration through the\ncontainer; for mappings, ``__iter__()`` should be the same as\n``keys()``; for sequences, it should iterate through the values.\n\nobject.__len__(self)\n\n Called to implement the built-in function ``len()``. Should return\n the length of the object, an integer ``>=`` 0. Also, an object\n that doesn\'t define a ``__bool__()`` method and whose ``__len__()``\n method returns zero is considered to be false in a Boolean context.\n\nobject.__length_hint__(self)\n\n Called to implement ``operator.length_hint()``. Should return an\n estimated length for the object (which may be greater or less than\n the actual length). The length must be an integer ``>=`` 0. This\n method is purely an optimization and is never required for\n correctness.\n\n New in version 3.4.\n\nNote: Slicing is done exclusively with the following three methods. A\n call like\n\n a[1:2] = b\n\n is translated to\n\n a[slice(1, 2, None)] = b\n\n and so forth. Missing slice items are always filled in with\n ``None``.\n\nobject.__getitem__(self, key)\n\n Called to implement evaluation of ``self[key]``. For sequence\n types, the accepted keys should be integers and slice objects.\n Note that the special interpretation of negative indexes (if the\n class wishes to emulate a sequence type) is up to the\n ``__getitem__()`` method. If *key* is of an inappropriate type,\n ``TypeError`` may be raised; if of a value outside the set of\n indexes for the sequence (after any special interpretation of\n negative values), ``IndexError`` should be raised. For mapping\n types, if *key* is missing (not in the container), ``KeyError``\n should be raised.\n\n Note: ``for`` loops expect that an ``IndexError`` will be raised for\n illegal indexes to allow proper detection of the end of the\n sequence.\n\nobject.__setitem__(self, key, value)\n\n Called to implement assignment to ``self[key]``. Same note as for\n ``__getitem__()``. This should only be implemented for mappings if\n the objects support changes to the values for keys, or if new keys\n can be added, or for sequences if elements can be replaced. The\n same exceptions should be raised for improper *key* values as for\n the ``__getitem__()`` method.\n\nobject.__delitem__(self, key)\n\n Called to implement deletion of ``self[key]``. Same note as for\n ``__getitem__()``. This should only be implemented for mappings if\n the objects support removal of keys, or for sequences if elements\n can be removed from the sequence. The same exceptions should be\n raised for improper *key* values as for the ``__getitem__()``\n method.\n\nobject.__iter__(self)\n\n This method is called when an iterator is required for a container.\n This method should return a new iterator object that can iterate\n over all the objects in the container. For mappings, it should\n iterate over the keys of the container, and should also be made\n available as the method ``keys()``.\n\n Iterator objects also need to implement this method; they are\n required to return themselves. For more information on iterator\n objects, see *Iterator Types*.\n\nobject.__reversed__(self)\n\n Called (if present) by the ``reversed()`` built-in to implement\n reverse iteration. It should return a new iterator object that\n iterates over all the objects in the container in reverse order.\n\n If the ``__reversed__()`` method is not provided, the\n ``reversed()`` built-in will fall back to using the sequence\n protocol (``__len__()`` and ``__getitem__()``). Objects that\n support the sequence protocol should only provide\n ``__reversed__()`` if they can provide an implementation that is\n more efficient than the one provided by ``reversed()``.\n\nThe membership test operators (``in`` and ``not in``) are normally\nimplemented as an iteration through a sequence. However, container\nobjects can supply the following special method with a more efficient\nimplementation, which also does not require the object be a sequence.\n\nobject.__contains__(self, item)\n\n Called to implement membership test operators. Should return true\n if *item* is in *self*, false otherwise. For mapping objects, this\n should consider the keys of the mapping rather than the values or\n the key-item pairs.\n\n For objects that don\'t define ``__contains__()``, the membership\n test first tries iteration via ``__iter__()``, then the old\n sequence iteration protocol via ``__getitem__()``, see *this\n section in the language reference*.\n\n\nEmulating numeric types\n=======================\n\nThe following methods can be defined to emulate numeric objects.\nMethods corresponding to operations that are not supported by the\nparticular kind of number implemented (e.g., bitwise operations for\nnon-integral numbers) should be left undefined.\n\nobject.__add__(self, other)\nobject.__sub__(self, other)\nobject.__mul__(self, other)\nobject.__truediv__(self, other)\nobject.__floordiv__(self, other)\nobject.__mod__(self, other)\nobject.__divmod__(self, other)\nobject.__pow__(self, other[, modulo])\nobject.__lshift__(self, other)\nobject.__rshift__(self, other)\nobject.__and__(self, other)\nobject.__xor__(self, other)\nobject.__or__(self, other)\n\n These methods are called to implement the binary arithmetic\n operations (``+``, ``-``, ``*``, ``/``, ``//``, ``%``,\n ``divmod()``, ``pow()``, ``**``, ``<<``, ``>>``, ``&``, ``^``,\n ``|``). For instance, to evaluate the expression ``x + y``, where\n *x* is an instance of a class that has an ``__add__()`` method,\n ``x.__add__(y)`` is called. The ``__divmod__()`` method should be\n the equivalent to using ``__floordiv__()`` and ``__mod__()``; it\n should not be related to ``__truediv__()``. Note that\n ``__pow__()`` should be defined to accept an optional third\n argument if the ternary version of the built-in ``pow()`` function\n is to be supported.\n\n If one of those methods does not support the operation with the\n supplied arguments, it should return ``NotImplemented``.\n\nobject.__radd__(self, other)\nobject.__rsub__(self, other)\nobject.__rmul__(self, other)\nobject.__rtruediv__(self, other)\nobject.__rfloordiv__(self, other)\nobject.__rmod__(self, other)\nobject.__rdivmod__(self, other)\nobject.__rpow__(self, other)\nobject.__rlshift__(self, other)\nobject.__rrshift__(self, other)\nobject.__rand__(self, other)\nobject.__rxor__(self, other)\nobject.__ror__(self, other)\n\n These methods are called to implement the binary arithmetic\n operations (``+``, ``-``, ``*``, ``/``, ``//``, ``%``,\n ``divmod()``, ``pow()``, ``**``, ``<<``, ``>>``, ``&``, ``^``,\n ``|``) with reflected (swapped) operands. These functions are only\n called if the left operand does not support the corresponding\n operation and the operands are of different types. [2] For\n instance, to evaluate the expression ``x - y``, where *y* is an\n instance of a class that has an ``__rsub__()`` method,\n ``y.__rsub__(x)`` is called if ``x.__sub__(y)`` returns\n *NotImplemented*.\n\n Note that ternary ``pow()`` will not try calling ``__rpow__()``\n (the coercion rules would become too complicated).\n\n Note: If the right operand\'s type is a subclass of the left operand\'s\n type and that subclass provides the reflected method for the\n operation, this method will be called before the left operand\'s\n non-reflected method. This behavior allows subclasses to\n override their ancestors\' operations.\n\nobject.__iadd__(self, other)\nobject.__isub__(self, other)\nobject.__imul__(self, other)\nobject.__itruediv__(self, other)\nobject.__ifloordiv__(self, other)\nobject.__imod__(self, other)\nobject.__ipow__(self, other[, modulo])\nobject.__ilshift__(self, other)\nobject.__irshift__(self, other)\nobject.__iand__(self, other)\nobject.__ixor__(self, other)\nobject.__ior__(self, other)\n\n These methods are called to implement the augmented arithmetic\n assignments (``+=``, ``-=``, ``*=``, ``/=``, ``//=``, ``%=``,\n ``**=``, ``<<=``, ``>>=``, ``&=``, ``^=``, ``|=``). These methods\n should attempt to do the operation in-place (modifying *self*) and\n return the result (which could be, but does not have to be,\n *self*). If a specific method is not defined, the augmented\n assignment falls back to the normal methods. For instance, to\n execute the statement ``x += y``, where *x* is an instance of a\n class that has an ``__iadd__()`` method, ``x.__iadd__(y)`` is\n called. If *x* is an instance of a class that does not define a\n ``__iadd__()`` method, ``x.__add__(y)`` and ``y.__radd__(x)`` are\n considered, as with the evaluation of ``x + y``.\n\nobject.__neg__(self)\nobject.__pos__(self)\nobject.__abs__(self)\nobject.__invert__(self)\n\n Called to implement the unary arithmetic operations (``-``, ``+``,\n ``abs()`` and ``~``).\n\nobject.__complex__(self)\nobject.__int__(self)\nobject.__float__(self)\nobject.__round__(self[, n])\n\n Called to implement the built-in functions ``complex()``,\n ``int()``, ``float()`` and ``round()``. Should return a value of\n the appropriate type.\n\nobject.__index__(self)\n\n Called to implement ``operator.index()``. Also called whenever\n Python needs an integer object (such as in slicing, or in the\n built-in ``bin()``, ``hex()`` and ``oct()`` functions). Must return\n an integer.\n\n\nWith Statement Context Managers\n===============================\n\nA *context manager* is an object that defines the runtime context to\nbe established when executing a ``with`` statement. The context\nmanager handles the entry into, and the exit from, the desired runtime\ncontext for the execution of the block of code. Context managers are\nnormally invoked using the ``with`` statement (described in section\n*The with statement*), but can also be used by directly invoking their\nmethods.\n\nTypical uses of context managers include saving and restoring various\nkinds of global state, locking and unlocking resources, closing opened\nfiles, etc.\n\nFor more information on context managers, see *Context Manager Types*.\n\nobject.__enter__(self)\n\n Enter the runtime context related to this object. The ``with``\n statement will bind this method\'s return value to the target(s)\n specified in the ``as`` clause of the statement, if any.\n\nobject.__exit__(self, exc_type, exc_value, traceback)\n\n Exit the runtime context related to this object. The parameters\n describe the exception that caused the context to be exited. If the\n context was exited without an exception, all three arguments will\n be ``None``.\n\n If an exception is supplied, and the method wishes to suppress the\n exception (i.e., prevent it from being propagated), it should\n return a true value. Otherwise, the exception will be processed\n normally upon exit from this method.\n\n Note that ``__exit__()`` methods should not reraise the passed-in\n exception; this is the caller\'s responsibility.\n\nSee also:\n\n **PEP 0343** - The "with" statement\n The specification, background, and examples for the Python\n ``with`` statement.\n\n\nSpecial method lookup\n=====================\n\nFor custom classes, implicit invocations of special methods are only\nguaranteed to work correctly if defined on an object\'s type, not in\nthe object\'s instance dictionary. That behaviour is the reason why\nthe following code raises an exception:\n\n >>> class C:\n ... pass\n ...\n >>> c = C()\n >>> c.__len__ = lambda: 5\n >>> len(c)\n Traceback (most recent call last):\n File "<stdin>", line 1, in <module>\n TypeError: object of type \'C\' has no len()\n\nThe rationale behind this behaviour lies with a number of special\nmethods such as ``__hash__()`` and ``__repr__()`` that are implemented\nby all objects, including type objects. If the implicit lookup of\nthese methods used the conventional lookup process, they would fail\nwhen invoked on the type object itself:\n\n >>> 1 .__hash__() == hash(1)\n True\n >>> int.__hash__() == hash(int)\n Traceback (most recent call last):\n File "<stdin>", line 1, in <module>\n TypeError: descriptor \'__hash__\' of \'int\' object needs an argument\n\nIncorrectly attempting to invoke an unbound method of a class in this\nway is sometimes referred to as \'metaclass confusion\', and is avoided\nby bypassing the instance when looking up special methods:\n\n >>> type(1).__hash__(1) == hash(1)\n True\n >>> type(int).__hash__(int) == hash(int)\n True\n\nIn addition to bypassing any instance attributes in the interest of\ncorrectness, implicit special method lookup generally also bypasses\nthe ``__getattribute__()`` method even of the object\'s metaclass:\n\n >>> class Meta(type):\n ... def __getattribute__(*args):\n ... print("Metaclass getattribute invoked")\n ... return type.__getattribute__(*args)\n ...\n >>> class C(object, metaclass=Meta):\n ... def __len__(self):\n ... return 10\n ... def __getattribute__(*args):\n ... print("Class getattribute invoked")\n ... return object.__getattribute__(*args)\n ...\n >>> c = C()\n >>> c.__len__() # Explicit lookup via instance\n Class getattribute invoked\n 10\n >>> type(c).__len__(c) # Explicit lookup via type\n Metaclass getattribute invoked\n 10\n >>> len(c) # Implicit lookup\n 10\n\nBypassing the ``__getattribute__()`` machinery in this fashion\nprovides significant scope for speed optimisations within the\ninterpreter, at the cost of some flexibility in the handling of\nspecial methods (the special method *must* be set on the class object\nitself in order to be consistently invoked by the interpreter).\n\n-[ Footnotes ]-\n\n[1] It *is* possible in some cases to change an object\'s type, under\n certain controlled conditions. It generally isn\'t a good idea\n though, since it can lead to some very strange behaviour if it is\n handled incorrectly.\n\n[2] For operands of the same type, it is assumed that if the non-\n reflected method (such as ``__add__()``) fails the operation is\n not supported, which is why the reflected method is not called.\n',
+ 'string-methods': '\nString Methods\n**************\n\nStrings implement all of the *common* sequence operations, along with\nthe additional methods described below.\n\nStrings also support two styles of string formatting, one providing a\nlarge degree of flexibility and customization (see ``str.format()``,\n*Format String Syntax* and *String Formatting*) and the other based on\nC ``printf`` style formatting that handles a narrower range of types\nand is slightly harder to use correctly, but is often faster for the\ncases it can handle (*printf-style String Formatting*).\n\nThe *Text Processing Services* section of the standard library covers\na number of other modules that provide various text related utilities\n(including regular expression support in the ``re`` module).\n\nstr.capitalize()\n\n Return a copy of the string with its first character capitalized\n and the rest lowercased.\n\nstr.casefold()\n\n Return a casefolded copy of the string. Casefolded strings may be\n used for caseless matching.\n\n Casefolding is similar to lowercasing but more aggressive because\n it is intended to remove all case distinctions in a string. For\n example, the German lowercase letter ``\'\xc3\x9f\'`` is equivalent to\n ``"ss"``. Since it is already lowercase, ``lower()`` would do\n nothing to ``\'\xc3\x9f\'``; ``casefold()`` converts it to ``"ss"``.\n\n The casefolding algorithm is described in section 3.13 of the\n Unicode Standard.\n\n New in version 3.3.\n\nstr.center(width[, fillchar])\n\n Return centered in a string of length *width*. Padding is done\n using the specified *fillchar* (default is a space).\n\nstr.count(sub[, start[, end]])\n\n Return the number of non-overlapping occurrences of substring *sub*\n in the range [*start*, *end*]. Optional arguments *start* and\n *end* are interpreted as in slice notation.\n\nstr.encode(encoding="utf-8", errors="strict")\n\n Return an encoded version of the string as a bytes object. Default\n encoding is ``\'utf-8\'``. *errors* may be given to set a different\n error handling scheme. The default for *errors* is ``\'strict\'``,\n meaning that encoding errors raise a ``UnicodeError``. Other\n possible values are ``\'ignore\'``, ``\'replace\'``,\n ``\'xmlcharrefreplace\'``, ``\'backslashreplace\'`` and any other name\n registered via ``codecs.register_error()``, see section *Codec Base\n Classes*. For a list of possible encodings, see section *Standard\n Encodings*.\n\n Changed in version 3.1: Support for keyword arguments added.\n\nstr.endswith(suffix[, start[, end]])\n\n Return ``True`` if the string ends with the specified *suffix*,\n otherwise return ``False``. *suffix* can also be a tuple of\n suffixes to look for. With optional *start*, test beginning at\n that position. With optional *end*, stop comparing at that\n position.\n\nstr.expandtabs(tabsize=8)\n\n Return a copy of the string where all tab characters are replaced\n by one or more spaces, depending on the current column and the\n given tab size. Tab positions occur every *tabsize* characters\n (default is 8, giving tab positions at columns 0, 8, 16 and so on).\n To expand the string, the current column is set to zero and the\n string is examined character by character. If the character is a\n tab (``\\t``), one or more space characters are inserted in the\n result until the current column is equal to the next tab position.\n (The tab character itself is not copied.) If the character is a\n newline (``\\n``) or return (``\\r``), it is copied and the current\n column is reset to zero. Any other character is copied unchanged\n and the current column is incremented by one regardless of how the\n character is represented when printed.\n\n >>> \'01\\t012\\t0123\\t01234\'.expandtabs()\n \'01 012 0123 01234\'\n >>> \'01\\t012\\t0123\\t01234\'.expandtabs(4)\n \'01 012 0123 01234\'\n\nstr.find(sub[, start[, end]])\n\n Return the lowest index in the string where substring *sub* is\n found, such that *sub* is contained in the slice ``s[start:end]``.\n Optional arguments *start* and *end* are interpreted as in slice\n notation. Return ``-1`` if *sub* is not found.\n\n Note: The ``find()`` method should be used only if you need to know the\n position of *sub*. To check if *sub* is a substring or not, use\n the ``in`` operator:\n\n >>> \'Py\' in \'Python\'\n True\n\nstr.format(*args, **kwargs)\n\n Perform a string formatting operation. The string on which this\n method is called can contain literal text or replacement fields\n delimited by braces ``{}``. Each replacement field contains either\n the numeric index of a positional argument, or the name of a\n keyword argument. Returns a copy of the string where each\n replacement field is replaced with the string value of the\n corresponding argument.\n\n >>> "The sum of 1 + 2 is {0}".format(1+2)\n \'The sum of 1 + 2 is 3\'\n\n See *Format String Syntax* for a description of the various\n formatting options that can be specified in format strings.\n\nstr.format_map(mapping)\n\n Similar to ``str.format(**mapping)``, except that ``mapping`` is\n used directly and not copied to a ``dict`` . This is useful if for\n example ``mapping`` is a dict subclass:\n\n >>> class Default(dict):\n ... def __missing__(self, key):\n ... return key\n ...\n >>> \'{name} was born in {country}\'.format_map(Default(name=\'Guido\'))\n \'Guido was born in country\'\n\n New in version 3.2.\n\nstr.index(sub[, start[, end]])\n\n Like ``find()``, but raise ``ValueError`` when the substring is not\n found.\n\nstr.isalnum()\n\n Return true if all characters in the string are alphanumeric and\n there is at least one character, false otherwise. A character\n ``c`` is alphanumeric if one of the following returns ``True``:\n ``c.isalpha()``, ``c.isdecimal()``, ``c.isdigit()``, or\n ``c.isnumeric()``.\n\nstr.isalpha()\n\n Return true if all characters in the string are alphabetic and\n there is at least one character, false otherwise. Alphabetic\n characters are those characters defined in the Unicode character\n database as "Letter", i.e., those with general category property\n being one of "Lm", "Lt", "Lu", "Ll", or "Lo". Note that this is\n different from the "Alphabetic" property defined in the Unicode\n Standard.\n\nstr.isdecimal()\n\n Return true if all characters in the string are decimal characters\n and there is at least one character, false otherwise. Decimal\n characters are those from general category "Nd". This category\n includes digit characters, and all characters that can be used to\n form decimal-radix numbers, e.g. U+0660, ARABIC-INDIC DIGIT ZERO.\n\nstr.isdigit()\n\n Return true if all characters in the string are digits and there is\n at least one character, false otherwise. Digits include decimal\n characters and digits that need special handling, such as the\n compatibility superscript digits. Formally, a digit is a character\n that has the property value Numeric_Type=Digit or\n Numeric_Type=Decimal.\n\nstr.isidentifier()\n\n Return true if the string is a valid identifier according to the\n language definition, section *Identifiers and keywords*.\n\n Use ``keyword.iskeyword()`` to test for reserved identifiers such\n as ``def`` and ``class``.\n\nstr.islower()\n\n Return true if all cased characters [4] in the string are lowercase\n and there is at least one cased character, false otherwise.\n\nstr.isnumeric()\n\n Return true if all characters in the string are numeric characters,\n and there is at least one character, false otherwise. Numeric\n characters include digit characters, and all characters that have\n the Unicode numeric value property, e.g. U+2155, VULGAR FRACTION\n ONE FIFTH. Formally, numeric characters are those with the\n property value Numeric_Type=Digit, Numeric_Type=Decimal or\n Numeric_Type=Numeric.\n\nstr.isprintable()\n\n Return true if all characters in the string are printable or the\n string is empty, false otherwise. Nonprintable characters are\n those characters defined in the Unicode character database as\n "Other" or "Separator", excepting the ASCII space (0x20) which is\n considered printable. (Note that printable characters in this\n context are those which should not be escaped when ``repr()`` is\n invoked on a string. It has no bearing on the handling of strings\n written to ``sys.stdout`` or ``sys.stderr``.)\n\nstr.isspace()\n\n Return true if there are only whitespace characters in the string\n and there is at least one character, false otherwise. Whitespace\n characters are those characters defined in the Unicode character\n database as "Other" or "Separator" and those with bidirectional\n property being one of "WS", "B", or "S".\n\nstr.istitle()\n\n Return true if the string is a titlecased string and there is at\n least one character, for example uppercase characters may only\n follow uncased characters and lowercase characters only cased ones.\n Return false otherwise.\n\nstr.isupper()\n\n Return true if all cased characters [4] in the string are uppercase\n and there is at least one cased character, false otherwise.\n\nstr.join(iterable)\n\n Return a string which is the concatenation of the strings in the\n *iterable* *iterable*. A ``TypeError`` will be raised if there are\n any non-string values in *iterable*, including ``bytes`` objects.\n The separator between elements is the string providing this method.\n\nstr.ljust(width[, fillchar])\n\n Return the string left justified in a string of length *width*.\n Padding is done using the specified *fillchar* (default is a\n space). The original string is returned if *width* is less than or\n equal to ``len(s)``.\n\nstr.lower()\n\n Return a copy of the string with all the cased characters [4]\n converted to lowercase.\n\n The lowercasing algorithm used is described in section 3.13 of the\n Unicode Standard.\n\nstr.lstrip([chars])\n\n Return a copy of the string with leading characters removed. The\n *chars* argument is a string specifying the set of characters to be\n removed. If omitted or ``None``, the *chars* argument defaults to\n removing whitespace. The *chars* argument is not a prefix; rather,\n all combinations of its values are stripped:\n\n >>> \' spacious \'.lstrip()\n \'spacious \'\n >>> \'www.example.com\'.lstrip(\'cmowz.\')\n \'example.com\'\n\nstatic str.maketrans(x[, y[, z]])\n\n This static method returns a translation table usable for\n ``str.translate()``.\n\n If there is only one argument, it must be a dictionary mapping\n Unicode ordinals (integers) or characters (strings of length 1) to\n Unicode ordinals, strings (of arbitrary lengths) or None.\n Character keys will then be converted to ordinals.\n\n If there are two arguments, they must be strings of equal length,\n and in the resulting dictionary, each character in x will be mapped\n to the character at the same position in y. If there is a third\n argument, it must be a string, whose characters will be mapped to\n None in the result.\n\nstr.partition(sep)\n\n Split the string at the first occurrence of *sep*, and return a\n 3-tuple containing the part before the separator, the separator\n itself, and the part after the separator. If the separator is not\n found, return a 3-tuple containing the string itself, followed by\n two empty strings.\n\nstr.replace(old, new[, count])\n\n Return a copy of the string with all occurrences of substring *old*\n replaced by *new*. If the optional argument *count* is given, only\n the first *count* occurrences are replaced.\n\nstr.rfind(sub[, start[, end]])\n\n Return the highest index in the string where substring *sub* is\n found, such that *sub* is contained within ``s[start:end]``.\n Optional arguments *start* and *end* are interpreted as in slice\n notation. Return ``-1`` on failure.\n\nstr.rindex(sub[, start[, end]])\n\n Like ``rfind()`` but raises ``ValueError`` when the substring *sub*\n is not found.\n\nstr.rjust(width[, fillchar])\n\n Return the string right justified in a string of length *width*.\n Padding is done using the specified *fillchar* (default is a\n space). The original string is returned if *width* is less than or\n equal to ``len(s)``.\n\nstr.rpartition(sep)\n\n Split the string at the last occurrence of *sep*, and return a\n 3-tuple containing the part before the separator, the separator\n itself, and the part after the separator. If the separator is not\n found, return a 3-tuple containing two empty strings, followed by\n the string itself.\n\nstr.rsplit(sep=None, maxsplit=-1)\n\n Return a list of the words in the string, using *sep* as the\n delimiter string. If *maxsplit* is given, at most *maxsplit* splits\n are done, the *rightmost* ones. If *sep* is not specified or\n ``None``, any whitespace string is a separator. Except for\n splitting from the right, ``rsplit()`` behaves like ``split()``\n which is described in detail below.\n\nstr.rstrip([chars])\n\n Return a copy of the string with trailing characters removed. The\n *chars* argument is a string specifying the set of characters to be\n removed. If omitted or ``None``, the *chars* argument defaults to\n removing whitespace. The *chars* argument is not a suffix; rather,\n all combinations of its values are stripped:\n\n >>> \' spacious \'.rstrip()\n \' spacious\'\n >>> \'mississippi\'.rstrip(\'ipz\')\n \'mississ\'\n\nstr.split(sep=None, maxsplit=-1)\n\n Return a list of the words in the string, using *sep* as the\n delimiter string. If *maxsplit* is given, at most *maxsplit*\n splits are done (thus, the list will have at most ``maxsplit+1``\n elements). If *maxsplit* is not specified or ``-1``, then there is\n no limit on the number of splits (all possible splits are made).\n\n If *sep* is given, consecutive delimiters are not grouped together\n and are deemed to delimit empty strings (for example,\n ``\'1,,2\'.split(\',\')`` returns ``[\'1\', \'\', \'2\']``). The *sep*\n argument may consist of multiple characters (for example,\n ``\'1<>2<>3\'.split(\'<>\')`` returns ``[\'1\', \'2\', \'3\']``). Splitting\n an empty string with a specified separator returns ``[\'\']``.\n\n If *sep* is not specified or is ``None``, a different splitting\n algorithm is applied: runs of consecutive whitespace are regarded\n as a single separator, and the result will contain no empty strings\n at the start or end if the string has leading or trailing\n whitespace. Consequently, splitting an empty string or a string\n consisting of just whitespace with a ``None`` separator returns\n ``[]``.\n\n For example, ``\' 1 2 3 \'.split()`` returns ``[\'1\', \'2\', \'3\']``,\n and ``\' 1 2 3 \'.split(None, 1)`` returns ``[\'1\', \'2 3 \']``.\n\nstr.splitlines([keepends])\n\n Return a list of the lines in the string, breaking at line\n boundaries. This method uses the *universal newlines* approach to\n splitting lines. Line breaks are not included in the resulting list\n unless *keepends* is given and true.\n\n For example, ``\'ab c\\n\\nde fg\\rkl\\r\\n\'.splitlines()`` returns\n ``[\'ab c\', \'\', \'de fg\', \'kl\']``, while the same call with\n ``splitlines(True)`` returns ``[\'ab c\\n\', \'\\n\', \'de fg\\r\',\n \'kl\\r\\n\']``.\n\n Unlike ``split()`` when a delimiter string *sep* is given, this\n method returns an empty list for the empty string, and a terminal\n line break does not result in an extra line.\n\nstr.startswith(prefix[, start[, end]])\n\n Return ``True`` if string starts with the *prefix*, otherwise\n return ``False``. *prefix* can also be a tuple of prefixes to look\n for. With optional *start*, test string beginning at that\n position. With optional *end*, stop comparing string at that\n position.\n\nstr.strip([chars])\n\n Return a copy of the string with the leading and trailing\n characters removed. The *chars* argument is a string specifying the\n set of characters to be removed. If omitted or ``None``, the\n *chars* argument defaults to removing whitespace. The *chars*\n argument is not a prefix or suffix; rather, all combinations of its\n values are stripped:\n\n >>> \' spacious \'.strip()\n \'spacious\'\n >>> \'www.example.com\'.strip(\'cmowz.\')\n \'example\'\n\nstr.swapcase()\n\n Return a copy of the string with uppercase characters converted to\n lowercase and vice versa. Note that it is not necessarily true that\n ``s.swapcase().swapcase() == s``.\n\nstr.title()\n\n Return a titlecased version of the string where words start with an\n uppercase character and the remaining characters are lowercase.\n\n The algorithm uses a simple language-independent definition of a\n word as groups of consecutive letters. The definition works in\n many contexts but it means that apostrophes in contractions and\n possessives form word boundaries, which may not be the desired\n result:\n\n >>> "they\'re bill\'s friends from the UK".title()\n "They\'Re Bill\'S Friends From The Uk"\n\n A workaround for apostrophes can be constructed using regular\n expressions:\n\n >>> import re\n >>> def titlecase(s):\n ... return re.sub(r"[A-Za-z]+(\'[A-Za-z]+)?",\n ... lambda mo: mo.group(0)[0].upper() +\n ... mo.group(0)[1:].lower(),\n ... s)\n ...\n >>> titlecase("they\'re bill\'s friends.")\n "They\'re Bill\'s Friends."\n\nstr.translate(map)\n\n Return a copy of the *s* where all characters have been mapped\n through the *map* which must be a dictionary of Unicode ordinals\n (integers) to Unicode ordinals, strings or ``None``. Unmapped\n characters are left untouched. Characters mapped to ``None`` are\n deleted.\n\n You can use ``str.maketrans()`` to create a translation map from\n character-to-character mappings in different formats.\n\n Note: An even more flexible approach is to create a custom character\n mapping codec using the ``codecs`` module (see\n ``encodings.cp1251`` for an example).\n\nstr.upper()\n\n Return a copy of the string with all the cased characters [4]\n converted to uppercase. Note that ``str.upper().isupper()`` might\n be ``False`` if ``s`` contains uncased characters or if the Unicode\n category of the resulting character(s) is not "Lu" (Letter,\n uppercase), but e.g. "Lt" (Letter, titlecase).\n\n The uppercasing algorithm used is described in section 3.13 of the\n Unicode Standard.\n\nstr.zfill(width)\n\n Return the numeric string left filled with zeros in a string of\n length *width*. A sign prefix is handled correctly. The original\n string is returned if *width* is less than or equal to ``len(s)``.\n',
'strings': '\nString and Bytes literals\n*************************\n\nString literals are described by the following lexical definitions:\n\n stringliteral ::= [stringprefix](shortstring | longstring)\n stringprefix ::= "r" | "u" | "R" | "U"\n shortstring ::= "\'" shortstringitem* "\'" | \'"\' shortstringitem* \'"\'\n longstring ::= "\'\'\'" longstringitem* "\'\'\'" | \'"""\' longstringitem* \'"""\'\n shortstringitem ::= shortstringchar | stringescapeseq\n longstringitem ::= longstringchar | stringescapeseq\n shortstringchar ::= <any source character except "\\" or newline or the quote>\n longstringchar ::= <any source character except "\\">\n stringescapeseq ::= "\\" <any source character>\n\n bytesliteral ::= bytesprefix(shortbytes | longbytes)\n bytesprefix ::= "b" | "B" | "br" | "Br" | "bR" | "BR" | "rb" | "rB" | "Rb" | "RB"\n shortbytes ::= "\'" shortbytesitem* "\'" | \'"\' shortbytesitem* \'"\'\n longbytes ::= "\'\'\'" longbytesitem* "\'\'\'" | \'"""\' longbytesitem* \'"""\'\n shortbytesitem ::= shortbyteschar | bytesescapeseq\n longbytesitem ::= longbyteschar | bytesescapeseq\n shortbyteschar ::= <any ASCII character except "\\" or newline or the quote>\n longbyteschar ::= <any ASCII character except "\\">\n bytesescapeseq ::= "\\" <any ASCII character>\n\nOne syntactic restriction not indicated by these productions is that\nwhitespace is not allowed between the ``stringprefix`` or\n``bytesprefix`` and the rest of the literal. The source character set\nis defined by the encoding declaration; it is UTF-8 if no encoding\ndeclaration is given in the source file; see section *Encoding\ndeclarations*.\n\nIn plain English: Both types of literals can be enclosed in matching\nsingle quotes (``\'``) or double quotes (``"``). They can also be\nenclosed in matching groups of three single or double quotes (these\nare generally referred to as *triple-quoted strings*). The backslash\n(``\\``) character is used to escape characters that otherwise have a\nspecial meaning, such as newline, backslash itself, or the quote\ncharacter.\n\nBytes literals are always prefixed with ``\'b\'`` or ``\'B\'``; they\nproduce an instance of the ``bytes`` type instead of the ``str`` type.\nThey may only contain ASCII characters; bytes with a numeric value of\n128 or greater must be expressed with escapes.\n\nAs of Python 3.3 it is possible again to prefix unicode strings with a\n``u`` prefix to simplify maintenance of dual 2.x and 3.x codebases.\n\nBoth string and bytes literals may optionally be prefixed with a\nletter ``\'r\'`` or ``\'R\'``; such strings are called *raw strings* and\ntreat backslashes as literal characters. As a result, in string\nliterals, ``\'\\U\'`` and ``\'\\u\'`` escapes in raw strings are not treated\nspecially. Given that Python 2.x\'s raw unicode literals behave\ndifferently than Python 3.x\'s the ``\'ur\'`` syntax is not supported.\n\n New in version 3.3: The ``\'rb\'`` prefix of raw bytes literals has\n been added as a synonym of ``\'br\'``.\n\n New in version 3.3: Support for the unicode legacy literal\n (``u\'value\'``) was reintroduced to simplify the maintenance of dual\n Python 2.x and 3.x codebases. See **PEP 414** for more information.\n\nIn triple-quoted strings, unescaped newlines and quotes are allowed\n(and are retained), except that three unescaped quotes in a row\nterminate the string. (A "quote" is the character used to open the\nstring, i.e. either ``\'`` or ``"``.)\n\nUnless an ``\'r\'`` or ``\'R\'`` prefix is present, escape sequences in\nstrings are interpreted according to rules similar to those used by\nStandard C. The recognized escape sequences are:\n\n+-------------------+-----------------------------------+---------+\n| Escape Sequence | Meaning | Notes |\n+===================+===================================+=========+\n| ``\\newline`` | Backslash and newline ignored | |\n+-------------------+-----------------------------------+---------+\n| ``\\\\`` | Backslash (``\\``) | |\n+-------------------+-----------------------------------+---------+\n| ``\\\'`` | Single quote (``\'``) | |\n+-------------------+-----------------------------------+---------+\n| ``\\"`` | Double quote (``"``) | |\n+-------------------+-----------------------------------+---------+\n| ``\\a`` | ASCII Bell (BEL) | |\n+-------------------+-----------------------------------+---------+\n| ``\\b`` | ASCII Backspace (BS) | |\n+-------------------+-----------------------------------+---------+\n| ``\\f`` | ASCII Formfeed (FF) | |\n+-------------------+-----------------------------------+---------+\n| ``\\n`` | ASCII Linefeed (LF) | |\n+-------------------+-----------------------------------+---------+\n| ``\\r`` | ASCII Carriage Return (CR) | |\n+-------------------+-----------------------------------+---------+\n| ``\\t`` | ASCII Horizontal Tab (TAB) | |\n+-------------------+-----------------------------------+---------+\n| ``\\v`` | ASCII Vertical Tab (VT) | |\n+-------------------+-----------------------------------+---------+\n| ``\\ooo`` | Character with octal value *ooo* | (1,3) |\n+-------------------+-----------------------------------+---------+\n| ``\\xhh`` | Character with hex value *hh* | (2,3) |\n+-------------------+-----------------------------------+---------+\n\nEscape sequences only recognized in string literals are:\n\n+-------------------+-----------------------------------+---------+\n| Escape Sequence | Meaning | Notes |\n+===================+===================================+=========+\n| ``\\N{name}`` | Character named *name* in the | (4) |\n| | Unicode database | |\n+-------------------+-----------------------------------+---------+\n| ``\\uxxxx`` | Character with 16-bit hex value | (5) |\n| | *xxxx* | |\n+-------------------+-----------------------------------+---------+\n| ``\\Uxxxxxxxx`` | Character with 32-bit hex value | (6) |\n| | *xxxxxxxx* | |\n+-------------------+-----------------------------------+---------+\n\nNotes:\n\n1. As in Standard C, up to three octal digits are accepted.\n\n2. Unlike in Standard C, exactly two hex digits are required.\n\n3. In a bytes literal, hexadecimal and octal escapes denote the byte\n with the given value. In a string literal, these escapes denote a\n Unicode character with the given value.\n\n4. Changed in version 3.3: Support for name aliases [1] has been\n added.\n\n5. Individual code units which form parts of a surrogate pair can be\n encoded using this escape sequence. Exactly four hex digits are\n required.\n\n6. Any Unicode character can be encoded this way. Exactly eight hex\n digits are required.\n\nUnlike Standard C, all unrecognized escape sequences are left in the\nstring unchanged, i.e., *the backslash is left in the string*. (This\nbehavior is useful when debugging: if an escape sequence is mistyped,\nthe resulting output is more easily recognized as broken.) It is also\nimportant to note that the escape sequences only recognized in string\nliterals fall into the category of unrecognized escapes for bytes\nliterals.\n\nEven in a raw string, string quotes can be escaped with a backslash,\nbut the backslash remains in the string; for example, ``r"\\""`` is a\nvalid string literal consisting of two characters: a backslash and a\ndouble quote; ``r"\\"`` is not a valid string literal (even a raw\nstring cannot end in an odd number of backslashes). Specifically, *a\nraw string cannot end in a single backslash* (since the backslash\nwould escape the following quote character). Note also that a single\nbackslash followed by a newline is interpreted as those two characters\nas part of the string, *not* as a line continuation.\n',
'subscriptions': '\nSubscriptions\n*************\n\nA subscription selects an item of a sequence (string, tuple or list)\nor mapping (dictionary) object:\n\n subscription ::= primary "[" expression_list "]"\n\nThe primary must evaluate to an object that supports subscription,\ne.g. a list or dictionary. User-defined objects can support\nsubscription by defining a ``__getitem__()`` method.\n\nFor built-in objects, there are two types of objects that support\nsubscription:\n\nIf the primary is a mapping, the expression list must evaluate to an\nobject whose value is one of the keys of the mapping, and the\nsubscription selects the value in the mapping that corresponds to that\nkey. (The expression list is a tuple except if it has exactly one\nitem.)\n\nIf the primary is a sequence, the expression (list) must evaluate to\nan integer or a slice (as discussed in the following section).\n\nThe formal syntax makes no special provision for negative indices in\nsequences; however, built-in sequences all provide a ``__getitem__()``\nmethod that interprets negative indices by adding the length of the\nsequence to the index (so that ``x[-1]`` selects the last item of\n``x``). The resulting value must be a nonnegative integer less than\nthe number of items in the sequence, and the subscription selects the\nitem whose index is that value (counting from zero). Since the support\nfor negative indices and slicing occurs in the object\'s\n``__getitem__()`` method, subclasses overriding this method will need\nto explicitly add that support.\n\nA string\'s items are characters. A character is not a separate data\ntype but a string of exactly one character.\n',
'truth': "\nTruth Value Testing\n*******************\n\nAny object can be tested for truth value, for use in an ``if`` or\n``while`` condition or as operand of the Boolean operations below. The\nfollowing values are considered false:\n\n* ``None``\n\n* ``False``\n\n* zero of any numeric type, for example, ``0``, ``0.0``, ``0j``.\n\n* any empty sequence, for example, ``''``, ``()``, ``[]``.\n\n* any empty mapping, for example, ``{}``.\n\n* instances of user-defined classes, if the class defines a\n ``__bool__()`` or ``__len__()`` method, when that method returns the\n integer zero or ``bool`` value ``False``. [1]\n\nAll other values are considered true --- so objects of many types are\nalways true.\n\nOperations and built-in functions that have a Boolean result always\nreturn ``0`` or ``False`` for false and ``1`` or ``True`` for true,\nunless otherwise stated. (Important exception: the Boolean operations\n``or`` and ``and`` always return one of their operands.)\n",
'try': '\nThe ``try`` statement\n*********************\n\nThe ``try`` statement specifies exception handlers and/or cleanup code\nfor a group of statements:\n\n try_stmt ::= try1_stmt | try2_stmt\n try1_stmt ::= "try" ":" suite\n ("except" [expression ["as" target]] ":" suite)+\n ["else" ":" suite]\n ["finally" ":" suite]\n try2_stmt ::= "try" ":" suite\n "finally" ":" suite\n\nThe ``except`` clause(s) specify one or more exception handlers. When\nno exception occurs in the ``try`` clause, no exception handler is\nexecuted. When an exception occurs in the ``try`` suite, a search for\nan exception handler is started. This search inspects the except\nclauses in turn until one is found that matches the exception. An\nexpression-less except clause, if present, must be last; it matches\nany exception. For an except clause with an expression, that\nexpression is evaluated, and the clause matches the exception if the\nresulting object is "compatible" with the exception. An object is\ncompatible with an exception if it is the class or a base class of the\nexception object or a tuple containing an item compatible with the\nexception.\n\nIf no except clause matches the exception, the search for an exception\nhandler continues in the surrounding code and on the invocation stack.\n[1]\n\nIf the evaluation of an expression in the header of an except clause\nraises an exception, the original search for a handler is canceled and\na search starts for the new exception in the surrounding code and on\nthe call stack (it is treated as if the entire ``try`` statement\nraised the exception).\n\nWhen a matching except clause is found, the exception is assigned to\nthe target specified after the ``as`` keyword in that except clause,\nif present, and the except clause\'s suite is executed. All except\nclauses must have an executable block. When the end of this block is\nreached, execution continues normally after the entire try statement.\n(This means that if two nested handlers exist for the same exception,\nand the exception occurs in the try clause of the inner handler, the\nouter handler will not handle the exception.)\n\nWhen an exception has been assigned using ``as target``, it is cleared\nat the end of the except clause. This is as if\n\n except E as N:\n foo\n\nwas translated to\n\n except E as N:\n try:\n foo\n finally:\n del N\n\nThis means the exception must be assigned to a different name to be\nable to refer to it after the except clause. Exceptions are cleared\nbecause with the traceback attached to them, they form a reference\ncycle with the stack frame, keeping all locals in that frame alive\nuntil the next garbage collection occurs.\n\nBefore an except clause\'s suite is executed, details about the\nexception are stored in the ``sys`` module and can be access via\n``sys.exc_info()``. ``sys.exc_info()`` returns a 3-tuple consisting of\nthe exception class, the exception instance and a traceback object\n(see section *The standard type hierarchy*) identifying the point in\nthe program where the exception occurred. ``sys.exc_info()`` values\nare restored to their previous values (before the call) when returning\nfrom a function that handled an exception.\n\nThe optional ``else`` clause is executed if and when control flows off\nthe end of the ``try`` clause. [2] Exceptions in the ``else`` clause\nare not handled by the preceding ``except`` clauses.\n\nIf ``finally`` is present, it specifies a \'cleanup\' handler. The\n``try`` clause is executed, including any ``except`` and ``else``\nclauses. If an exception occurs in any of the clauses and is not\nhandled, the exception is temporarily saved. The ``finally`` clause is\nexecuted. If there is a saved exception it is re-raised at the end of\nthe ``finally`` clause. If the ``finally`` clause raises another\nexception, the saved exception is set as the context of the new\nexception. If the ``finally`` clause executes a ``return`` or\n``break`` statement, the saved exception is discarded:\n\n def f():\n try:\n 1/0\n finally:\n return 42\n\n >>> f()\n 42\n\nThe exception information is not available to the program during\nexecution of the ``finally`` clause.\n\nWhen a ``return``, ``break`` or ``continue`` statement is executed in\nthe ``try`` suite of a ``try``...``finally`` statement, the\n``finally`` clause is also executed \'on the way out.\' A ``continue``\nstatement is illegal in the ``finally`` clause. (The reason is a\nproblem with the current implementation --- this restriction may be\nlifted in the future).\n\nAdditional information on exceptions can be found in section\n*Exceptions*, and information on using the ``raise`` statement to\ngenerate exceptions may be found in section *The raise statement*.\n',
- 'types': '\nThe standard type hierarchy\n***************************\n\nBelow is a list of the types that are built into Python. Extension\nmodules (written in C, Java, or other languages, depending on the\nimplementation) can define additional types. Future versions of\nPython may add types to the type hierarchy (e.g., rational numbers,\nefficiently stored arrays of integers, etc.), although such additions\nwill often be provided via the standard library instead.\n\nSome of the type descriptions below contain a paragraph listing\n\'special attributes.\' These are attributes that provide access to the\nimplementation and are not intended for general use. Their definition\nmay change in the future.\n\nNone\n This type has a single value. There is a single object with this\n value. This object is accessed through the built-in name ``None``.\n It is used to signify the absence of a value in many situations,\n e.g., it is returned from functions that don\'t explicitly return\n anything. Its truth value is false.\n\nNotImplemented\n This type has a single value. There is a single object with this\n value. This object is accessed through the built-in name\n ``NotImplemented``. Numeric methods and rich comparison methods may\n return this value if they do not implement the operation for the\n operands provided. (The interpreter will then try the reflected\n operation, or some other fallback, depending on the operator.) Its\n truth value is true.\n\nEllipsis\n This type has a single value. There is a single object with this\n value. This object is accessed through the literal ``...`` or the\n built-in name ``Ellipsis``. Its truth value is true.\n\n``numbers.Number``\n These are created by numeric literals and returned as results by\n arithmetic operators and arithmetic built-in functions. Numeric\n objects are immutable; once created their value never changes.\n Python numbers are of course strongly related to mathematical\n numbers, but subject to the limitations of numerical representation\n in computers.\n\n Python distinguishes between integers, floating point numbers, and\n complex numbers:\n\n ``numbers.Integral``\n These represent elements from the mathematical set of integers\n (positive and negative).\n\n There are two types of integers:\n\n Integers (``int``)\n\n These represent numbers in an unlimited range, subject to\n available (virtual) memory only. For the purpose of shift\n and mask operations, a binary representation is assumed, and\n negative numbers are represented in a variant of 2\'s\n complement which gives the illusion of an infinite string of\n sign bits extending to the left.\n\n Booleans (``bool``)\n These represent the truth values False and True. The two\n objects representing the values False and True are the only\n Boolean objects. The Boolean type is a subtype of the integer\n type, and Boolean values behave like the values 0 and 1,\n respectively, in almost all contexts, the exception being\n that when converted to a string, the strings ``"False"`` or\n ``"True"`` are returned, respectively.\n\n The rules for integer representation are intended to give the\n most meaningful interpretation of shift and mask operations\n involving negative integers.\n\n ``numbers.Real`` (``float``)\n These represent machine-level double precision floating point\n numbers. You are at the mercy of the underlying machine\n architecture (and C or Java implementation) for the accepted\n range and handling of overflow. Python does not support single-\n precision floating point numbers; the savings in processor and\n memory usage that are usually the reason for using these is\n dwarfed by the overhead of using objects in Python, so there is\n no reason to complicate the language with two kinds of floating\n point numbers.\n\n ``numbers.Complex`` (``complex``)\n These represent complex numbers as a pair of machine-level\n double precision floating point numbers. The same caveats apply\n as for floating point numbers. The real and imaginary parts of a\n complex number ``z`` can be retrieved through the read-only\n attributes ``z.real`` and ``z.imag``.\n\nSequences\n These represent finite ordered sets indexed by non-negative\n numbers. The built-in function ``len()`` returns the number of\n items of a sequence. When the length of a sequence is *n*, the\n index set contains the numbers 0, 1, ..., *n*-1. Item *i* of\n sequence *a* is selected by ``a[i]``.\n\n Sequences also support slicing: ``a[i:j]`` selects all items with\n index *k* such that *i* ``<=`` *k* ``<`` *j*. When used as an\n expression, a slice is a sequence of the same type. This implies\n that the index set is renumbered so that it starts at 0.\n\n Some sequences also support "extended slicing" with a third "step"\n parameter: ``a[i:j:k]`` selects all items of *a* with index *x*\n where ``x = i + n*k``, *n* ``>=`` ``0`` and *i* ``<=`` *x* ``<``\n *j*.\n\n Sequences are distinguished according to their mutability:\n\n Immutable sequences\n An object of an immutable sequence type cannot change once it is\n created. (If the object contains references to other objects,\n these other objects may be mutable and may be changed; however,\n the collection of objects directly referenced by an immutable\n object cannot change.)\n\n The following types are immutable sequences:\n\n Strings\n A string is a sequence of values that represent Unicode\n codepoints. All the codepoints in range ``U+0000 - U+10FFFF``\n can be represented in a string. Python doesn\'t have a\n ``chr`` type, and every character in the string is\n represented as a string object with length ``1``. The built-\n in function ``ord()`` converts a character to its codepoint\n (as an integer); ``chr()`` converts an integer in range ``0 -\n 10FFFF`` to the corresponding character. ``str.encode()`` can\n be used to convert a ``str`` to ``bytes`` using the given\n encoding, and ``bytes.decode()`` can be used to achieve the\n opposite.\n\n Tuples\n The items of a tuple are arbitrary Python objects. Tuples of\n two or more items are formed by comma-separated lists of\n expressions. A tuple of one item (a \'singleton\') can be\n formed by affixing a comma to an expression (an expression by\n itself does not create a tuple, since parentheses must be\n usable for grouping of expressions). An empty tuple can be\n formed by an empty pair of parentheses.\n\n Bytes\n A bytes object is an immutable array. The items are 8-bit\n bytes, represented by integers in the range 0 <= x < 256.\n Bytes literals (like ``b\'abc\'``) and the built-in function\n ``bytes()`` can be used to construct bytes objects. Also,\n bytes objects can be decoded to strings via the ``decode()``\n method.\n\n Mutable sequences\n Mutable sequences can be changed after they are created. The\n subscription and slicing notations can be used as the target of\n assignment and ``del`` (delete) statements.\n\n There are currently two intrinsic mutable sequence types:\n\n Lists\n The items of a list are arbitrary Python objects. Lists are\n formed by placing a comma-separated list of expressions in\n square brackets. (Note that there are no special cases needed\n to form lists of length 0 or 1.)\n\n Byte Arrays\n A bytearray object is a mutable array. They are created by\n the built-in ``bytearray()`` constructor. Aside from being\n mutable (and hence unhashable), byte arrays otherwise provide\n the same interface and functionality as immutable bytes\n objects.\n\n The extension module ``array`` provides an additional example of\n a mutable sequence type, as does the ``collections`` module.\n\nSet types\n These represent unordered, finite sets of unique, immutable\n objects. As such, they cannot be indexed by any subscript. However,\n they can be iterated over, and the built-in function ``len()``\n returns the number of items in a set. Common uses for sets are fast\n membership testing, removing duplicates from a sequence, and\n computing mathematical operations such as intersection, union,\n difference, and symmetric difference.\n\n For set elements, the same immutability rules apply as for\n dictionary keys. Note that numeric types obey the normal rules for\n numeric comparison: if two numbers compare equal (e.g., ``1`` and\n ``1.0``), only one of them can be contained in a set.\n\n There are currently two intrinsic set types:\n\n Sets\n These represent a mutable set. They are created by the built-in\n ``set()`` constructor and can be modified afterwards by several\n methods, such as ``add()``.\n\n Frozen sets\n These represent an immutable set. They are created by the\n built-in ``frozenset()`` constructor. As a frozenset is\n immutable and *hashable*, it can be used again as an element of\n another set, or as a dictionary key.\n\nMappings\n These represent finite sets of objects indexed by arbitrary index\n sets. The subscript notation ``a[k]`` selects the item indexed by\n ``k`` from the mapping ``a``; this can be used in expressions and\n as the target of assignments or ``del`` statements. The built-in\n function ``len()`` returns the number of items in a mapping.\n\n There is currently a single intrinsic mapping type:\n\n Dictionaries\n These represent finite sets of objects indexed by nearly\n arbitrary values. The only types of values not acceptable as\n keys are values containing lists or dictionaries or other\n mutable types that are compared by value rather than by object\n identity, the reason being that the efficient implementation of\n dictionaries requires a key\'s hash value to remain constant.\n Numeric types used for keys obey the normal rules for numeric\n comparison: if two numbers compare equal (e.g., ``1`` and\n ``1.0``) then they can be used interchangeably to index the same\n dictionary entry.\n\n Dictionaries are mutable; they can be created by the ``{...}``\n notation (see section *Dictionary displays*).\n\n The extension modules ``dbm.ndbm`` and ``dbm.gnu`` provide\n additional examples of mapping types, as does the\n ``collections`` module.\n\nCallable types\n These are the types to which the function call operation (see\n section *Calls*) can be applied:\n\n User-defined functions\n A user-defined function object is created by a function\n definition (see section *Function definitions*). It should be\n called with an argument list containing the same number of items\n as the function\'s formal parameter list.\n\n Special attributes:\n\n +---------------------------+---------------------------------+-------------+\n | Attribute | Meaning | |\n +===========================+=================================+=============+\n | ``__doc__`` | The function\'s documentation | Writable |\n | | string, or ``None`` if | |\n | | unavailable | |\n +---------------------------+---------------------------------+-------------+\n | ``__name__`` | The function\'s name | Writable |\n +---------------------------+---------------------------------+-------------+\n | ``__qualname__`` | The function\'s *qualified name* | Writable |\n | | New in version 3.3. | |\n +---------------------------+---------------------------------+-------------+\n | ``__module__`` | The name of the module the | Writable |\n | | function was defined in, or | |\n | | ``None`` if unavailable. | |\n +---------------------------+---------------------------------+-------------+\n | ``__defaults__`` | A tuple containing default | Writable |\n | | argument values for those | |\n | | arguments that have defaults, | |\n | | or ``None`` if no arguments | |\n | | have a default value | |\n +---------------------------+---------------------------------+-------------+\n | ``__code__`` | The code object representing | Writable |\n | | the compiled function body. | |\n +---------------------------+---------------------------------+-------------+\n | ``__globals__`` | A reference to the dictionary | Read-only |\n | | that holds the function\'s | |\n | | global variables --- the global | |\n | | namespace of the module in | |\n | | which the function was defined. | |\n +---------------------------+---------------------------------+-------------+\n | ``__dict__`` | The namespace supporting | Writable |\n | | arbitrary function attributes. | |\n +---------------------------+---------------------------------+-------------+\n | ``__closure__`` | ``None`` or a tuple of cells | Read-only |\n | | that contain bindings for the | |\n | | function\'s free variables. | |\n +---------------------------+---------------------------------+-------------+\n | ``__annotations__`` | A dict containing annotations | Writable |\n | | of parameters. The keys of the | |\n | | dict are the parameter names, | |\n | | or ``\'return\'`` for the return | |\n | | annotation, if provided. | |\n +---------------------------+---------------------------------+-------------+\n | ``__kwdefaults__`` | A dict containing defaults for | Writable |\n | | keyword-only parameters. | |\n +---------------------------+---------------------------------+-------------+\n\n Most of the attributes labelled "Writable" check the type of the\n assigned value.\n\n Function objects also support getting and setting arbitrary\n attributes, which can be used, for example, to attach metadata\n to functions. Regular attribute dot-notation is used to get and\n set such attributes. *Note that the current implementation only\n supports function attributes on user-defined functions. Function\n attributes on built-in functions may be supported in the\n future.*\n\n Additional information about a function\'s definition can be\n retrieved from its code object; see the description of internal\n types below.\n\n Instance methods\n An instance method object combines a class, a class instance and\n any callable object (normally a user-defined function).\n\n Special read-only attributes: ``__self__`` is the class instance\n object, ``__func__`` is the function object; ``__doc__`` is the\n method\'s documentation (same as ``__func__.__doc__``);\n ``__name__`` is the method name (same as ``__func__.__name__``);\n ``__module__`` is the name of the module the method was defined\n in, or ``None`` if unavailable.\n\n Methods also support accessing (but not setting) the arbitrary\n function attributes on the underlying function object.\n\n User-defined method objects may be created when getting an\n attribute of a class (perhaps via an instance of that class), if\n that attribute is a user-defined function object or a class\n method object.\n\n When an instance method object is created by retrieving a user-\n defined function object from a class via one of its instances,\n its ``__self__`` attribute is the instance, and the method\n object is said to be bound. The new method\'s ``__func__``\n attribute is the original function object.\n\n When a user-defined method object is created by retrieving\n another method object from a class or instance, the behaviour is\n the same as for a function object, except that the ``__func__``\n attribute of the new instance is not the original method object\n but its ``__func__`` attribute.\n\n When an instance method object is created by retrieving a class\n method object from a class or instance, its ``__self__``\n attribute is the class itself, and its ``__func__`` attribute is\n the function object underlying the class method.\n\n When an instance method object is called, the underlying\n function (``__func__``) is called, inserting the class instance\n (``__self__``) in front of the argument list. For instance,\n when ``C`` is a class which contains a definition for a function\n ``f()``, and ``x`` is an instance of ``C``, calling ``x.f(1)``\n is equivalent to calling ``C.f(x, 1)``.\n\n When an instance method object is derived from a class method\n object, the "class instance" stored in ``__self__`` will\n actually be the class itself, so that calling either ``x.f(1)``\n or ``C.f(1)`` is equivalent to calling ``f(C,1)`` where ``f`` is\n the underlying function.\n\n Note that the transformation from function object to instance\n method object happens each time the attribute is retrieved from\n the instance. In some cases, a fruitful optimization is to\n assign the attribute to a local variable and call that local\n variable. Also notice that this transformation only happens for\n user-defined functions; other callable objects (and all non-\n callable objects) are retrieved without transformation. It is\n also important to note that user-defined functions which are\n attributes of a class instance are not converted to bound\n methods; this *only* happens when the function is an attribute\n of the class.\n\n Generator functions\n A function or method which uses the ``yield`` statement (see\n section *The yield statement*) is called a *generator function*.\n Such a function, when called, always returns an iterator object\n which can be used to execute the body of the function: calling\n the iterator\'s ``iterator.__next__()`` method will cause the\n function to execute until it provides a value using the\n ``yield`` statement. When the function executes a ``return``\n statement or falls off the end, a ``StopIteration`` exception is\n raised and the iterator will have reached the end of the set of\n values to be returned.\n\n Built-in functions\n A built-in function object is a wrapper around a C function.\n Examples of built-in functions are ``len()`` and ``math.sin()``\n (``math`` is a standard built-in module). The number and type of\n the arguments are determined by the C function. Special read-\n only attributes: ``__doc__`` is the function\'s documentation\n string, or ``None`` if unavailable; ``__name__`` is the\n function\'s name; ``__self__`` is set to ``None`` (but see the\n next item); ``__module__`` is the name of the module the\n function was defined in or ``None`` if unavailable.\n\n Built-in methods\n This is really a different disguise of a built-in function, this\n time containing an object passed to the C function as an\n implicit extra argument. An example of a built-in method is\n ``alist.append()``, assuming *alist* is a list object. In this\n case, the special read-only attribute ``__self__`` is set to the\n object denoted by *alist*.\n\n Classes\n Classes are callable. These objects normally act as factories\n for new instances of themselves, but variations are possible for\n class types that override ``__new__()``. The arguments of the\n call are passed to ``__new__()`` and, in the typical case, to\n ``__init__()`` to initialize the new instance.\n\n Class Instances\n Instances of arbitrary classes can be made callable by defining\n a ``__call__()`` method in their class.\n\nModules\n Modules are a basic organizational unit of Python code, and are\n created by the *import system* as invoked either by the ``import``\n statement (see ``import``), or by calling functions such as\n ``importlib.import_module()`` and built-in ``__import__()``. A\n module object has a namespace implemented by a dictionary object\n (this is the dictionary referenced by the ``__globals__`` attribute\n of functions defined in the module). Attribute references are\n translated to lookups in this dictionary, e.g., ``m.x`` is\n equivalent to ``m.__dict__["x"]``. A module object does not contain\n the code object used to initialize the module (since it isn\'t\n needed once the initialization is done).\n\n Attribute assignment updates the module\'s namespace dictionary,\n e.g., ``m.x = 1`` is equivalent to ``m.__dict__["x"] = 1``.\n\n Special read-only attribute: ``__dict__`` is the module\'s namespace\n as a dictionary object.\n\n **CPython implementation detail:** Because of the way CPython\n clears module dictionaries, the module dictionary will be cleared\n when the module falls out of scope even if the dictionary still has\n live references. To avoid this, copy the dictionary or keep the\n module around while using its dictionary directly.\n\n Predefined (writable) attributes: ``__name__`` is the module\'s\n name; ``__doc__`` is the module\'s documentation string, or ``None``\n if unavailable; ``__file__`` is the pathname of the file from which\n the module was loaded, if it was loaded from a file. The\n ``__file__`` attribute may be missing for certain types of modules,\n such as C modules that are statically linked into the interpreter;\n for extension modules loaded dynamically from a shared library, it\n is the pathname of the shared library file.\n\nCustom classes\n Custom class types are typically created by class definitions (see\n section *Class definitions*). A class has a namespace implemented\n by a dictionary object. Class attribute references are translated\n to lookups in this dictionary, e.g., ``C.x`` is translated to\n ``C.__dict__["x"]`` (although there are a number of hooks which\n allow for other means of locating attributes). When the attribute\n name is not found there, the attribute search continues in the base\n classes. This search of the base classes uses the C3 method\n resolution order which behaves correctly even in the presence of\n \'diamond\' inheritance structures where there are multiple\n inheritance paths leading back to a common ancestor. Additional\n details on the C3 MRO used by Python can be found in the\n documentation accompanying the 2.3 release at\n http://www.python.org/download/releases/2.3/mro/.\n\n When a class attribute reference (for class ``C``, say) would yield\n a class method object, it is transformed into an instance method\n object whose ``__self__`` attributes is ``C``. When it would yield\n a static method object, it is transformed into the object wrapped\n by the static method object. See section *Implementing Descriptors*\n for another way in which attributes retrieved from a class may\n differ from those actually contained in its ``__dict__``.\n\n Class attribute assignments update the class\'s dictionary, never\n the dictionary of a base class.\n\n A class object can be called (see above) to yield a class instance\n (see below).\n\n Special attributes: ``__name__`` is the class name; ``__module__``\n is the module name in which the class was defined; ``__dict__`` is\n the dictionary containing the class\'s namespace; ``__bases__`` is a\n tuple (possibly empty or a singleton) containing the base classes,\n in the order of their occurrence in the base class list;\n ``__doc__`` is the class\'s documentation string, or None if\n undefined.\n\nClass instances\n A class instance is created by calling a class object (see above).\n A class instance has a namespace implemented as a dictionary which\n is the first place in which attribute references are searched.\n When an attribute is not found there, and the instance\'s class has\n an attribute by that name, the search continues with the class\n attributes. If a class attribute is found that is a user-defined\n function object, it is transformed into an instance method object\n whose ``__self__`` attribute is the instance. Static method and\n class method objects are also transformed; see above under\n "Classes". See section *Implementing Descriptors* for another way\n in which attributes of a class retrieved via its instances may\n differ from the objects actually stored in the class\'s\n ``__dict__``. If no class attribute is found, and the object\'s\n class has a ``__getattr__()`` method, that is called to satisfy the\n lookup.\n\n Attribute assignments and deletions update the instance\'s\n dictionary, never a class\'s dictionary. If the class has a\n ``__setattr__()`` or ``__delattr__()`` method, this is called\n instead of updating the instance dictionary directly.\n\n Class instances can pretend to be numbers, sequences, or mappings\n if they have methods with certain special names. See section\n *Special method names*.\n\n Special attributes: ``__dict__`` is the attribute dictionary;\n ``__class__`` is the instance\'s class.\n\nI/O objects (also known as file objects)\n A *file object* represents an open file. Various shortcuts are\n available to create file objects: the ``open()`` built-in function,\n and also ``os.popen()``, ``os.fdopen()``, and the ``makefile()``\n method of socket objects (and perhaps by other functions or methods\n provided by extension modules).\n\n The objects ``sys.stdin``, ``sys.stdout`` and ``sys.stderr`` are\n initialized to file objects corresponding to the interpreter\'s\n standard input, output and error streams; they are all open in text\n mode and therefore follow the interface defined by the\n ``io.TextIOBase`` abstract class.\n\nInternal types\n A few types used internally by the interpreter are exposed to the\n user. Their definitions may change with future versions of the\n interpreter, but they are mentioned here for completeness.\n\n Code objects\n Code objects represent *byte-compiled* executable Python code,\n or *bytecode*. The difference between a code object and a\n function object is that the function object contains an explicit\n reference to the function\'s globals (the module in which it was\n defined), while a code object contains no context; also the\n default argument values are stored in the function object, not\n in the code object (because they represent values calculated at\n run-time). Unlike function objects, code objects are immutable\n and contain no references (directly or indirectly) to mutable\n objects.\n\n Special read-only attributes: ``co_name`` gives the function\n name; ``co_argcount`` is the number of positional arguments\n (including arguments with default values); ``co_nlocals`` is the\n number of local variables used by the function (including\n arguments); ``co_varnames`` is a tuple containing the names of\n the local variables (starting with the argument names);\n ``co_cellvars`` is a tuple containing the names of local\n variables that are referenced by nested functions;\n ``co_freevars`` is a tuple containing the names of free\n variables; ``co_code`` is a string representing the sequence of\n bytecode instructions; ``co_consts`` is a tuple containing the\n literals used by the bytecode; ``co_names`` is a tuple\n containing the names used by the bytecode; ``co_filename`` is\n the filename from which the code was compiled;\n ``co_firstlineno`` is the first line number of the function;\n ``co_lnotab`` is a string encoding the mapping from bytecode\n offsets to line numbers (for details see the source code of the\n interpreter); ``co_stacksize`` is the required stack size\n (including local variables); ``co_flags`` is an integer encoding\n a number of flags for the interpreter.\n\n The following flag bits are defined for ``co_flags``: bit\n ``0x04`` is set if the function uses the ``*arguments`` syntax\n to accept an arbitrary number of positional arguments; bit\n ``0x08`` is set if the function uses the ``**keywords`` syntax\n to accept arbitrary keyword arguments; bit ``0x20`` is set if\n the function is a generator.\n\n Future feature declarations (``from __future__ import\n division``) also use bits in ``co_flags`` to indicate whether a\n code object was compiled with a particular feature enabled: bit\n ``0x2000`` is set if the function was compiled with future\n division enabled; bits ``0x10`` and ``0x1000`` were used in\n earlier versions of Python.\n\n Other bits in ``co_flags`` are reserved for internal use.\n\n If a code object represents a function, the first item in\n ``co_consts`` is the documentation string of the function, or\n ``None`` if undefined.\n\n Frame objects\n Frame objects represent execution frames. They may occur in\n traceback objects (see below).\n\n Special read-only attributes: ``f_back`` is to the previous\n stack frame (towards the caller), or ``None`` if this is the\n bottom stack frame; ``f_code`` is the code object being executed\n in this frame; ``f_locals`` is the dictionary used to look up\n local variables; ``f_globals`` is used for global variables;\n ``f_builtins`` is used for built-in (intrinsic) names;\n ``f_lasti`` gives the precise instruction (this is an index into\n the bytecode string of the code object).\n\n Special writable attributes: ``f_trace``, if not ``None``, is a\n function called at the start of each source code line (this is\n used by the debugger); ``f_lineno`` is the current line number\n of the frame --- writing to this from within a trace function\n jumps to the given line (only for the bottom-most frame). A\n debugger can implement a Jump command (aka Set Next Statement)\n by writing to f_lineno.\n\n Traceback objects\n Traceback objects represent a stack trace of an exception. A\n traceback object is created when an exception occurs. When the\n search for an exception handler unwinds the execution stack, at\n each unwound level a traceback object is inserted in front of\n the current traceback. When an exception handler is entered,\n the stack trace is made available to the program. (See section\n *The try statement*.) It is accessible as the third item of the\n tuple returned by ``sys.exc_info()``. When the program contains\n no suitable handler, the stack trace is written (nicely\n formatted) to the standard error stream; if the interpreter is\n interactive, it is also made available to the user as\n ``sys.last_traceback``.\n\n Special read-only attributes: ``tb_next`` is the next level in\n the stack trace (towards the frame where the exception\n occurred), or ``None`` if there is no next level; ``tb_frame``\n points to the execution frame of the current level;\n ``tb_lineno`` gives the line number where the exception\n occurred; ``tb_lasti`` indicates the precise instruction. The\n line number and last instruction in the traceback may differ\n from the line number of its frame object if the exception\n occurred in a ``try`` statement with no matching except clause\n or with a finally clause.\n\n Slice objects\n Slice objects are used to represent slices for ``__getitem__()``\n methods. They are also created by the built-in ``slice()``\n function.\n\n Special read-only attributes: ``start`` is the lower bound;\n ``stop`` is the upper bound; ``step`` is the step value; each is\n ``None`` if omitted. These attributes can have any type.\n\n Slice objects support one method:\n\n slice.indices(self, length)\n\n This method takes a single integer argument *length* and\n computes information about the slice that the slice object\n would describe if applied to a sequence of *length* items.\n It returns a tuple of three integers; respectively these are\n the *start* and *stop* indices and the *step* or stride\n length of the slice. Missing or out-of-bounds indices are\n handled in a manner consistent with regular slices.\n\n Static method objects\n Static method objects provide a way of defeating the\n transformation of function objects to method objects described\n above. A static method object is a wrapper around any other\n object, usually a user-defined method object. When a static\n method object is retrieved from a class or a class instance, the\n object actually returned is the wrapped object, which is not\n subject to any further transformation. Static method objects are\n not themselves callable, although the objects they wrap usually\n are. Static method objects are created by the built-in\n ``staticmethod()`` constructor.\n\n Class method objects\n A class method object, like a static method object, is a wrapper\n around another object that alters the way in which that object\n is retrieved from classes and class instances. The behaviour of\n class method objects upon such retrieval is described above,\n under "User-defined methods". Class method objects are created\n by the built-in ``classmethod()`` constructor.\n',
+ 'types': '\nThe standard type hierarchy\n***************************\n\nBelow is a list of the types that are built into Python. Extension\nmodules (written in C, Java, or other languages, depending on the\nimplementation) can define additional types. Future versions of\nPython may add types to the type hierarchy (e.g., rational numbers,\nefficiently stored arrays of integers, etc.), although such additions\nwill often be provided via the standard library instead.\n\nSome of the type descriptions below contain a paragraph listing\n\'special attributes.\' These are attributes that provide access to the\nimplementation and are not intended for general use. Their definition\nmay change in the future.\n\nNone\n This type has a single value. There is a single object with this\n value. This object is accessed through the built-in name ``None``.\n It is used to signify the absence of a value in many situations,\n e.g., it is returned from functions that don\'t explicitly return\n anything. Its truth value is false.\n\nNotImplemented\n This type has a single value. There is a single object with this\n value. This object is accessed through the built-in name\n ``NotImplemented``. Numeric methods and rich comparison methods may\n return this value if they do not implement the operation for the\n operands provided. (The interpreter will then try the reflected\n operation, or some other fallback, depending on the operator.) Its\n truth value is true.\n\nEllipsis\n This type has a single value. There is a single object with this\n value. This object is accessed through the literal ``...`` or the\n built-in name ``Ellipsis``. Its truth value is true.\n\n``numbers.Number``\n These are created by numeric literals and returned as results by\n arithmetic operators and arithmetic built-in functions. Numeric\n objects are immutable; once created their value never changes.\n Python numbers are of course strongly related to mathematical\n numbers, but subject to the limitations of numerical representation\n in computers.\n\n Python distinguishes between integers, floating point numbers, and\n complex numbers:\n\n ``numbers.Integral``\n These represent elements from the mathematical set of integers\n (positive and negative).\n\n There are two types of integers:\n\n Integers (``int``)\n\n These represent numbers in an unlimited range, subject to\n available (virtual) memory only. For the purpose of shift\n and mask operations, a binary representation is assumed, and\n negative numbers are represented in a variant of 2\'s\n complement which gives the illusion of an infinite string of\n sign bits extending to the left.\n\n Booleans (``bool``)\n These represent the truth values False and True. The two\n objects representing the values False and True are the only\n Boolean objects. The Boolean type is a subtype of the integer\n type, and Boolean values behave like the values 0 and 1,\n respectively, in almost all contexts, the exception being\n that when converted to a string, the strings ``"False"`` or\n ``"True"`` are returned, respectively.\n\n The rules for integer representation are intended to give the\n most meaningful interpretation of shift and mask operations\n involving negative integers.\n\n ``numbers.Real`` (``float``)\n These represent machine-level double precision floating point\n numbers. You are at the mercy of the underlying machine\n architecture (and C or Java implementation) for the accepted\n range and handling of overflow. Python does not support single-\n precision floating point numbers; the savings in processor and\n memory usage that are usually the reason for using these is\n dwarfed by the overhead of using objects in Python, so there is\n no reason to complicate the language with two kinds of floating\n point numbers.\n\n ``numbers.Complex`` (``complex``)\n These represent complex numbers as a pair of machine-level\n double precision floating point numbers. The same caveats apply\n as for floating point numbers. The real and imaginary parts of a\n complex number ``z`` can be retrieved through the read-only\n attributes ``z.real`` and ``z.imag``.\n\nSequences\n These represent finite ordered sets indexed by non-negative\n numbers. The built-in function ``len()`` returns the number of\n items of a sequence. When the length of a sequence is *n*, the\n index set contains the numbers 0, 1, ..., *n*-1. Item *i* of\n sequence *a* is selected by ``a[i]``.\n\n Sequences also support slicing: ``a[i:j]`` selects all items with\n index *k* such that *i* ``<=`` *k* ``<`` *j*. When used as an\n expression, a slice is a sequence of the same type. This implies\n that the index set is renumbered so that it starts at 0.\n\n Some sequences also support "extended slicing" with a third "step"\n parameter: ``a[i:j:k]`` selects all items of *a* with index *x*\n where ``x = i + n*k``, *n* ``>=`` ``0`` and *i* ``<=`` *x* ``<``\n *j*.\n\n Sequences are distinguished according to their mutability:\n\n Immutable sequences\n An object of an immutable sequence type cannot change once it is\n created. (If the object contains references to other objects,\n these other objects may be mutable and may be changed; however,\n the collection of objects directly referenced by an immutable\n object cannot change.)\n\n The following types are immutable sequences:\n\n Strings\n A string is a sequence of values that represent Unicode\n codepoints. All the codepoints in range ``U+0000 - U+10FFFF``\n can be represented in a string. Python doesn\'t have a\n ``chr`` type, and every character in the string is\n represented as a string object with length ``1``. The built-\n in function ``ord()`` converts a character to its codepoint\n (as an integer); ``chr()`` converts an integer in range ``0 -\n 10FFFF`` to the corresponding character. ``str.encode()`` can\n be used to convert a ``str`` to ``bytes`` using the given\n encoding, and ``bytes.decode()`` can be used to achieve the\n opposite.\n\n Tuples\n The items of a tuple are arbitrary Python objects. Tuples of\n two or more items are formed by comma-separated lists of\n expressions. A tuple of one item (a \'singleton\') can be\n formed by affixing a comma to an expression (an expression by\n itself does not create a tuple, since parentheses must be\n usable for grouping of expressions). An empty tuple can be\n formed by an empty pair of parentheses.\n\n Bytes\n A bytes object is an immutable array. The items are 8-bit\n bytes, represented by integers in the range 0 <= x < 256.\n Bytes literals (like ``b\'abc\'``) and the built-in function\n ``bytes()`` can be used to construct bytes objects. Also,\n bytes objects can be decoded to strings via the ``decode()``\n method.\n\n Mutable sequences\n Mutable sequences can be changed after they are created. The\n subscription and slicing notations can be used as the target of\n assignment and ``del`` (delete) statements.\n\n There are currently two intrinsic mutable sequence types:\n\n Lists\n The items of a list are arbitrary Python objects. Lists are\n formed by placing a comma-separated list of expressions in\n square brackets. (Note that there are no special cases needed\n to form lists of length 0 or 1.)\n\n Byte Arrays\n A bytearray object is a mutable array. They are created by\n the built-in ``bytearray()`` constructor. Aside from being\n mutable (and hence unhashable), byte arrays otherwise provide\n the same interface and functionality as immutable bytes\n objects.\n\n The extension module ``array`` provides an additional example of\n a mutable sequence type, as does the ``collections`` module.\n\nSet types\n These represent unordered, finite sets of unique, immutable\n objects. As such, they cannot be indexed by any subscript. However,\n they can be iterated over, and the built-in function ``len()``\n returns the number of items in a set. Common uses for sets are fast\n membership testing, removing duplicates from a sequence, and\n computing mathematical operations such as intersection, union,\n difference, and symmetric difference.\n\n For set elements, the same immutability rules apply as for\n dictionary keys. Note that numeric types obey the normal rules for\n numeric comparison: if two numbers compare equal (e.g., ``1`` and\n ``1.0``), only one of them can be contained in a set.\n\n There are currently two intrinsic set types:\n\n Sets\n These represent a mutable set. They are created by the built-in\n ``set()`` constructor and can be modified afterwards by several\n methods, such as ``add()``.\n\n Frozen sets\n These represent an immutable set. They are created by the\n built-in ``frozenset()`` constructor. As a frozenset is\n immutable and *hashable*, it can be used again as an element of\n another set, or as a dictionary key.\n\nMappings\n These represent finite sets of objects indexed by arbitrary index\n sets. The subscript notation ``a[k]`` selects the item indexed by\n ``k`` from the mapping ``a``; this can be used in expressions and\n as the target of assignments or ``del`` statements. The built-in\n function ``len()`` returns the number of items in a mapping.\n\n There is currently a single intrinsic mapping type:\n\n Dictionaries\n These represent finite sets of objects indexed by nearly\n arbitrary values. The only types of values not acceptable as\n keys are values containing lists or dictionaries or other\n mutable types that are compared by value rather than by object\n identity, the reason being that the efficient implementation of\n dictionaries requires a key\'s hash value to remain constant.\n Numeric types used for keys obey the normal rules for numeric\n comparison: if two numbers compare equal (e.g., ``1`` and\n ``1.0``) then they can be used interchangeably to index the same\n dictionary entry.\n\n Dictionaries are mutable; they can be created by the ``{...}``\n notation (see section *Dictionary displays*).\n\n The extension modules ``dbm.ndbm`` and ``dbm.gnu`` provide\n additional examples of mapping types, as does the\n ``collections`` module.\n\nCallable types\n These are the types to which the function call operation (see\n section *Calls*) can be applied:\n\n User-defined functions\n A user-defined function object is created by a function\n definition (see section *Function definitions*). It should be\n called with an argument list containing the same number of items\n as the function\'s formal parameter list.\n\n Special attributes:\n\n +---------------------------+---------------------------------+-------------+\n | Attribute | Meaning | |\n +===========================+=================================+=============+\n | ``__doc__`` | The function\'s documentation | Writable |\n | | string, or ``None`` if | |\n | | unavailable | |\n +---------------------------+---------------------------------+-------------+\n | ``__name__`` | The function\'s name | Writable |\n +---------------------------+---------------------------------+-------------+\n | ``__qualname__`` | The function\'s *qualified name* | Writable |\n | | New in version 3.3. | |\n +---------------------------+---------------------------------+-------------+\n | ``__module__`` | The name of the module the | Writable |\n | | function was defined in, or | |\n | | ``None`` if unavailable. | |\n +---------------------------+---------------------------------+-------------+\n | ``__defaults__`` | A tuple containing default | Writable |\n | | argument values for those | |\n | | arguments that have defaults, | |\n | | or ``None`` if no arguments | |\n | | have a default value | |\n +---------------------------+---------------------------------+-------------+\n | ``__code__`` | The code object representing | Writable |\n | | the compiled function body. | |\n +---------------------------+---------------------------------+-------------+\n | ``__globals__`` | A reference to the dictionary | Read-only |\n | | that holds the function\'s | |\n | | global variables --- the global | |\n | | namespace of the module in | |\n | | which the function was defined. | |\n +---------------------------+---------------------------------+-------------+\n | ``__dict__`` | The namespace supporting | Writable |\n | | arbitrary function attributes. | |\n +---------------------------+---------------------------------+-------------+\n | ``__closure__`` | ``None`` or a tuple of cells | Read-only |\n | | that contain bindings for the | |\n | | function\'s free variables. | |\n +---------------------------+---------------------------------+-------------+\n | ``__annotations__`` | A dict containing annotations | Writable |\n | | of parameters. The keys of the | |\n | | dict are the parameter names, | |\n | | or ``\'return\'`` for the return | |\n | | annotation, if provided. | |\n +---------------------------+---------------------------------+-------------+\n | ``__kwdefaults__`` | A dict containing defaults for | Writable |\n | | keyword-only parameters. | |\n +---------------------------+---------------------------------+-------------+\n\n Most of the attributes labelled "Writable" check the type of the\n assigned value.\n\n Function objects also support getting and setting arbitrary\n attributes, which can be used, for example, to attach metadata\n to functions. Regular attribute dot-notation is used to get and\n set such attributes. *Note that the current implementation only\n supports function attributes on user-defined functions. Function\n attributes on built-in functions may be supported in the\n future.*\n\n Additional information about a function\'s definition can be\n retrieved from its code object; see the description of internal\n types below.\n\n Instance methods\n An instance method object combines a class, a class instance and\n any callable object (normally a user-defined function).\n\n Special read-only attributes: ``__self__`` is the class instance\n object, ``__func__`` is the function object; ``__doc__`` is the\n method\'s documentation (same as ``__func__.__doc__``);\n ``__name__`` is the method name (same as ``__func__.__name__``);\n ``__module__`` is the name of the module the method was defined\n in, or ``None`` if unavailable.\n\n Methods also support accessing (but not setting) the arbitrary\n function attributes on the underlying function object.\n\n User-defined method objects may be created when getting an\n attribute of a class (perhaps via an instance of that class), if\n that attribute is a user-defined function object or a class\n method object.\n\n When an instance method object is created by retrieving a user-\n defined function object from a class via one of its instances,\n its ``__self__`` attribute is the instance, and the method\n object is said to be bound. The new method\'s ``__func__``\n attribute is the original function object.\n\n When a user-defined method object is created by retrieving\n another method object from a class or instance, the behaviour is\n the same as for a function object, except that the ``__func__``\n attribute of the new instance is not the original method object\n but its ``__func__`` attribute.\n\n When an instance method object is created by retrieving a class\n method object from a class or instance, its ``__self__``\n attribute is the class itself, and its ``__func__`` attribute is\n the function object underlying the class method.\n\n When an instance method object is called, the underlying\n function (``__func__``) is called, inserting the class instance\n (``__self__``) in front of the argument list. For instance,\n when ``C`` is a class which contains a definition for a function\n ``f()``, and ``x`` is an instance of ``C``, calling ``x.f(1)``\n is equivalent to calling ``C.f(x, 1)``.\n\n When an instance method object is derived from a class method\n object, the "class instance" stored in ``__self__`` will\n actually be the class itself, so that calling either ``x.f(1)``\n or ``C.f(1)`` is equivalent to calling ``f(C,1)`` where ``f`` is\n the underlying function.\n\n Note that the transformation from function object to instance\n method object happens each time the attribute is retrieved from\n the instance. In some cases, a fruitful optimization is to\n assign the attribute to a local variable and call that local\n variable. Also notice that this transformation only happens for\n user-defined functions; other callable objects (and all non-\n callable objects) are retrieved without transformation. It is\n also important to note that user-defined functions which are\n attributes of a class instance are not converted to bound\n methods; this *only* happens when the function is an attribute\n of the class.\n\n Generator functions\n A function or method which uses the ``yield`` statement (see\n section *The yield statement*) is called a *generator function*.\n Such a function, when called, always returns an iterator object\n which can be used to execute the body of the function: calling\n the iterator\'s ``iterator.__next__()`` method will cause the\n function to execute until it provides a value using the\n ``yield`` statement. When the function executes a ``return``\n statement or falls off the end, a ``StopIteration`` exception is\n raised and the iterator will have reached the end of the set of\n values to be returned.\n\n Built-in functions\n A built-in function object is a wrapper around a C function.\n Examples of built-in functions are ``len()`` and ``math.sin()``\n (``math`` is a standard built-in module). The number and type of\n the arguments are determined by the C function. Special read-\n only attributes: ``__doc__`` is the function\'s documentation\n string, or ``None`` if unavailable; ``__name__`` is the\n function\'s name; ``__self__`` is set to ``None`` (but see the\n next item); ``__module__`` is the name of the module the\n function was defined in or ``None`` if unavailable.\n\n Built-in methods\n This is really a different disguise of a built-in function, this\n time containing an object passed to the C function as an\n implicit extra argument. An example of a built-in method is\n ``alist.append()``, assuming *alist* is a list object. In this\n case, the special read-only attribute ``__self__`` is set to the\n object denoted by *alist*.\n\n Classes\n Classes are callable. These objects normally act as factories\n for new instances of themselves, but variations are possible for\n class types that override ``__new__()``. The arguments of the\n call are passed to ``__new__()`` and, in the typical case, to\n ``__init__()`` to initialize the new instance.\n\n Class Instances\n Instances of arbitrary classes can be made callable by defining\n a ``__call__()`` method in their class.\n\nModules\n Modules are a basic organizational unit of Python code, and are\n created by the *import system* as invoked either by the ``import``\n statement (see ``import``), or by calling functions such as\n ``importlib.import_module()`` and built-in ``__import__()``. A\n module object has a namespace implemented by a dictionary object\n (this is the dictionary referenced by the ``__globals__`` attribute\n of functions defined in the module). Attribute references are\n translated to lookups in this dictionary, e.g., ``m.x`` is\n equivalent to ``m.__dict__["x"]``. A module object does not contain\n the code object used to initialize the module (since it isn\'t\n needed once the initialization is done).\n\n Attribute assignment updates the module\'s namespace dictionary,\n e.g., ``m.x = 1`` is equivalent to ``m.__dict__["x"] = 1``.\n\n Special read-only attribute: ``__dict__`` is the module\'s namespace\n as a dictionary object.\n\n **CPython implementation detail:** Because of the way CPython\n clears module dictionaries, the module dictionary will be cleared\n when the module falls out of scope even if the dictionary still has\n live references. To avoid this, copy the dictionary or keep the\n module around while using its dictionary directly.\n\n Predefined (writable) attributes: ``__name__`` is the module\'s\n name; ``__doc__`` is the module\'s documentation string, or ``None``\n if unavailable; ``__file__`` is the pathname of the file from which\n the module was loaded, if it was loaded from a file. The\n ``__file__`` attribute may be missing for certain types of modules,\n such as C modules that are statically linked into the interpreter;\n for extension modules loaded dynamically from a shared library, it\n is the pathname of the shared library file.\n\nCustom classes\n Custom class types are typically created by class definitions (see\n section *Class definitions*). A class has a namespace implemented\n by a dictionary object. Class attribute references are translated\n to lookups in this dictionary, e.g., ``C.x`` is translated to\n ``C.__dict__["x"]`` (although there are a number of hooks which\n allow for other means of locating attributes). When the attribute\n name is not found there, the attribute search continues in the base\n classes. This search of the base classes uses the C3 method\n resolution order which behaves correctly even in the presence of\n \'diamond\' inheritance structures where there are multiple\n inheritance paths leading back to a common ancestor. Additional\n details on the C3 MRO used by Python can be found in the\n documentation accompanying the 2.3 release at\n http://www.python.org/download/releases/2.3/mro/.\n\n When a class attribute reference (for class ``C``, say) would yield\n a class method object, it is transformed into an instance method\n object whose ``__self__`` attributes is ``C``. When it would yield\n a static method object, it is transformed into the object wrapped\n by the static method object. See section *Implementing Descriptors*\n for another way in which attributes retrieved from a class may\n differ from those actually contained in its ``__dict__``.\n\n Class attribute assignments update the class\'s dictionary, never\n the dictionary of a base class.\n\n A class object can be called (see above) to yield a class instance\n (see below).\n\n Special attributes: ``__name__`` is the class name; ``__module__``\n is the module name in which the class was defined; ``__dict__`` is\n the dictionary containing the class\'s namespace; ``__bases__`` is a\n tuple (possibly empty or a singleton) containing the base classes,\n in the order of their occurrence in the base class list;\n ``__doc__`` is the class\'s documentation string, or None if\n undefined.\n\nClass instances\n A class instance is created by calling a class object (see above).\n A class instance has a namespace implemented as a dictionary which\n is the first place in which attribute references are searched.\n When an attribute is not found there, and the instance\'s class has\n an attribute by that name, the search continues with the class\n attributes. If a class attribute is found that is a user-defined\n function object, it is transformed into an instance method object\n whose ``__self__`` attribute is the instance. Static method and\n class method objects are also transformed; see above under\n "Classes". See section *Implementing Descriptors* for another way\n in which attributes of a class retrieved via its instances may\n differ from the objects actually stored in the class\'s\n ``__dict__``. If no class attribute is found, and the object\'s\n class has a ``__getattr__()`` method, that is called to satisfy the\n lookup.\n\n Attribute assignments and deletions update the instance\'s\n dictionary, never a class\'s dictionary. If the class has a\n ``__setattr__()`` or ``__delattr__()`` method, this is called\n instead of updating the instance dictionary directly.\n\n Class instances can pretend to be numbers, sequences, or mappings\n if they have methods with certain special names. See section\n *Special method names*.\n\n Special attributes: ``__dict__`` is the attribute dictionary;\n ``__class__`` is the instance\'s class.\n\nI/O objects (also known as file objects)\n A *file object* represents an open file. Various shortcuts are\n available to create file objects: the ``open()`` built-in function,\n and also ``os.popen()``, ``os.fdopen()``, and the ``makefile()``\n method of socket objects (and perhaps by other functions or methods\n provided by extension modules).\n\n The objects ``sys.stdin``, ``sys.stdout`` and ``sys.stderr`` are\n initialized to file objects corresponding to the interpreter\'s\n standard input, output and error streams; they are all open in text\n mode and therefore follow the interface defined by the\n ``io.TextIOBase`` abstract class.\n\nInternal types\n A few types used internally by the interpreter are exposed to the\n user. Their definitions may change with future versions of the\n interpreter, but they are mentioned here for completeness.\n\n Code objects\n Code objects represent *byte-compiled* executable Python code,\n or *bytecode*. The difference between a code object and a\n function object is that the function object contains an explicit\n reference to the function\'s globals (the module in which it was\n defined), while a code object contains no context; also the\n default argument values are stored in the function object, not\n in the code object (because they represent values calculated at\n run-time). Unlike function objects, code objects are immutable\n and contain no references (directly or indirectly) to mutable\n objects.\n\n Special read-only attributes: ``co_name`` gives the function\n name; ``co_argcount`` is the number of positional arguments\n (including arguments with default values); ``co_nlocals`` is the\n number of local variables used by the function (including\n arguments); ``co_varnames`` is a tuple containing the names of\n the local variables (starting with the argument names);\n ``co_cellvars`` is a tuple containing the names of local\n variables that are referenced by nested functions;\n ``co_freevars`` is a tuple containing the names of free\n variables; ``co_code`` is a string representing the sequence of\n bytecode instructions; ``co_consts`` is a tuple containing the\n literals used by the bytecode; ``co_names`` is a tuple\n containing the names used by the bytecode; ``co_filename`` is\n the filename from which the code was compiled;\n ``co_firstlineno`` is the first line number of the function;\n ``co_lnotab`` is a string encoding the mapping from bytecode\n offsets to line numbers (for details see the source code of the\n interpreter); ``co_stacksize`` is the required stack size\n (including local variables); ``co_flags`` is an integer encoding\n a number of flags for the interpreter.\n\n The following flag bits are defined for ``co_flags``: bit\n ``0x04`` is set if the function uses the ``*arguments`` syntax\n to accept an arbitrary number of positional arguments; bit\n ``0x08`` is set if the function uses the ``**keywords`` syntax\n to accept arbitrary keyword arguments; bit ``0x20`` is set if\n the function is a generator.\n\n Future feature declarations (``from __future__ import\n division``) also use bits in ``co_flags`` to indicate whether a\n code object was compiled with a particular feature enabled: bit\n ``0x2000`` is set if the function was compiled with future\n division enabled; bits ``0x10`` and ``0x1000`` were used in\n earlier versions of Python.\n\n Other bits in ``co_flags`` are reserved for internal use.\n\n If a code object represents a function, the first item in\n ``co_consts`` is the documentation string of the function, or\n ``None`` if undefined.\n\n Frame objects\n Frame objects represent execution frames. They may occur in\n traceback objects (see below).\n\n Special read-only attributes: ``f_back`` is to the previous\n stack frame (towards the caller), or ``None`` if this is the\n bottom stack frame; ``f_code`` is the code object being executed\n in this frame; ``f_locals`` is the dictionary used to look up\n local variables; ``f_globals`` is used for global variables;\n ``f_builtins`` is used for built-in (intrinsic) names;\n ``f_lasti`` gives the precise instruction (this is an index into\n the bytecode string of the code object).\n\n Special writable attributes: ``f_trace``, if not ``None``, is a\n function called at the start of each source code line (this is\n used by the debugger); ``f_lineno`` is the current line number\n of the frame --- writing to this from within a trace function\n jumps to the given line (only for the bottom-most frame). A\n debugger can implement a Jump command (aka Set Next Statement)\n by writing to f_lineno.\n\n Frame objects support one method:\n\n frame.clear()\n\n This method clears all references to local variables held by\n the frame. Also, if the frame belonged to a generator, the\n generator is finalized. This helps break reference cycles\n involving frame objects (for example when catching an\n exception and storing its traceback for later use).\n\n ``RuntimeError`` is raised if the frame is currently\n executing.\n\n New in version 3.4.\n\n Traceback objects\n Traceback objects represent a stack trace of an exception. A\n traceback object is created when an exception occurs. When the\n search for an exception handler unwinds the execution stack, at\n each unwound level a traceback object is inserted in front of\n the current traceback. When an exception handler is entered,\n the stack trace is made available to the program. (See section\n *The try statement*.) It is accessible as the third item of the\n tuple returned by ``sys.exc_info()``. When the program contains\n no suitable handler, the stack trace is written (nicely\n formatted) to the standard error stream; if the interpreter is\n interactive, it is also made available to the user as\n ``sys.last_traceback``.\n\n Special read-only attributes: ``tb_next`` is the next level in\n the stack trace (towards the frame where the exception\n occurred), or ``None`` if there is no next level; ``tb_frame``\n points to the execution frame of the current level;\n ``tb_lineno`` gives the line number where the exception\n occurred; ``tb_lasti`` indicates the precise instruction. The\n line number and last instruction in the traceback may differ\n from the line number of its frame object if the exception\n occurred in a ``try`` statement with no matching except clause\n or with a finally clause.\n\n Slice objects\n Slice objects are used to represent slices for ``__getitem__()``\n methods. They are also created by the built-in ``slice()``\n function.\n\n Special read-only attributes: ``start`` is the lower bound;\n ``stop`` is the upper bound; ``step`` is the step value; each is\n ``None`` if omitted. These attributes can have any type.\n\n Slice objects support one method:\n\n slice.indices(self, length)\n\n This method takes a single integer argument *length* and\n computes information about the slice that the slice object\n would describe if applied to a sequence of *length* items.\n It returns a tuple of three integers; respectively these are\n the *start* and *stop* indices and the *step* or stride\n length of the slice. Missing or out-of-bounds indices are\n handled in a manner consistent with regular slices.\n\n Static method objects\n Static method objects provide a way of defeating the\n transformation of function objects to method objects described\n above. A static method object is a wrapper around any other\n object, usually a user-defined method object. When a static\n method object is retrieved from a class or a class instance, the\n object actually returned is the wrapped object, which is not\n subject to any further transformation. Static method objects are\n not themselves callable, although the objects they wrap usually\n are. Static method objects are created by the built-in\n ``staticmethod()`` constructor.\n\n Class method objects\n A class method object, like a static method object, is a wrapper\n around another object that alters the way in which that object\n is retrieved from classes and class instances. The behaviour of\n class method objects upon such retrieval is described above,\n under "User-defined methods". Class method objects are created\n by the built-in ``classmethod()`` constructor.\n',
'typesfunctions': '\nFunctions\n*********\n\nFunction objects are created by function definitions. The only\noperation on a function object is to call it: ``func(argument-list)``.\n\nThere are really two flavors of function objects: built-in functions\nand user-defined functions. Both support the same operation (to call\nthe function), but the implementation is different, hence the\ndifferent object types.\n\nSee *Function definitions* for more information.\n',
'typesmapping': '\nMapping Types --- ``dict``\n**************************\n\nA *mapping* object maps *hashable* values to arbitrary objects.\nMappings are mutable objects. There is currently only one standard\nmapping type, the *dictionary*. (For other containers see the built-\nin ``list``, ``set``, and ``tuple`` classes, and the ``collections``\nmodule.)\n\nA dictionary\'s keys are *almost* arbitrary values. Values that are\nnot *hashable*, that is, values containing lists, dictionaries or\nother mutable types (that are compared by value rather than by object\nidentity) may not be used as keys. Numeric types used for keys obey\nthe normal rules for numeric comparison: if two numbers compare equal\n(such as ``1`` and ``1.0``) then they can be used interchangeably to\nindex the same dictionary entry. (Note however, that since computers\nstore floating-point numbers as approximations it is usually unwise to\nuse them as dictionary keys.)\n\nDictionaries can be created by placing a comma-separated list of\n``key: value`` pairs within braces, for example: ``{\'jack\': 4098,\n\'sjoerd\': 4127}`` or ``{4098: \'jack\', 4127: \'sjoerd\'}``, or by the\n``dict`` constructor.\n\nclass class dict(**kwarg)\nclass class dict(mapping, **kwarg)\nclass class dict(iterable, **kwarg)\n\n Return a new dictionary initialized from an optional positional\n argument and a possibly empty set of keyword arguments.\n\n If no positional argument is given, an empty dictionary is created.\n If a positional argument is given and it is a mapping object, a\n dictionary is created with the same key-value pairs as the mapping\n object. Otherwise, the positional argument must be an *iterator*\n object. Each item in the iterable must itself be an iterator with\n exactly two objects. The first object of each item becomes a key\n in the new dictionary, and the second object the corresponding\n value. If a key occurs more than once, the last value for that key\n becomes the corresponding value in the new dictionary.\n\n If keyword arguments are given, the keyword arguments and their\n values are added to the dictionary created from the positional\n argument. If a key being added is already present, the value from\n the keyword argument replaces the value from the positional\n argument.\n\n To illustrate, the following examples all return a dictionary equal\n to ``{"one": 1, "two": 2, "three": 3}``:\n\n >>> a = dict(one=1, two=2, three=3)\n >>> b = {\'one\': 1, \'two\': 2, \'three\': 3}\n >>> c = dict(zip([\'one\', \'two\', \'three\'], [1, 2, 3]))\n >>> d = dict([(\'two\', 2), (\'one\', 1), (\'three\', 3)])\n >>> e = dict({\'three\': 3, \'one\': 1, \'two\': 2})\n >>> a == b == c == d == e\n True\n\n Providing keyword arguments as in the first example only works for\n keys that are valid Python identifiers. Otherwise, any valid keys\n can be used.\n\n These are the operations that dictionaries support (and therefore,\n custom mapping types should support too):\n\n len(d)\n\n Return the number of items in the dictionary *d*.\n\n d[key]\n\n Return the item of *d* with key *key*. Raises a ``KeyError`` if\n *key* is not in the map.\n\n If a subclass of dict defines a method ``__missing__()``, if the\n key *key* is not present, the ``d[key]`` operation calls that\n method with the key *key* as argument. The ``d[key]`` operation\n then returns or raises whatever is returned or raised by the\n ``__missing__(key)`` call if the key is not present. No other\n operations or methods invoke ``__missing__()``. If\n ``__missing__()`` is not defined, ``KeyError`` is raised.\n ``__missing__()`` must be a method; it cannot be an instance\n variable:\n\n >>> class Counter(dict):\n ... def __missing__(self, key):\n ... return 0\n >>> c = Counter()\n >>> c[\'red\']\n 0\n >>> c[\'red\'] += 1\n >>> c[\'red\']\n 1\n\n See ``collections.Counter`` for a complete implementation\n including other methods helpful for accumulating and managing\n tallies.\n\n d[key] = value\n\n Set ``d[key]`` to *value*.\n\n del d[key]\n\n Remove ``d[key]`` from *d*. Raises a ``KeyError`` if *key* is\n not in the map.\n\n key in d\n\n Return ``True`` if *d* has a key *key*, else ``False``.\n\n key not in d\n\n Equivalent to ``not key in d``.\n\n iter(d)\n\n Return an iterator over the keys of the dictionary. This is a\n shortcut for ``iter(d.keys())``.\n\n clear()\n\n Remove all items from the dictionary.\n\n copy()\n\n Return a shallow copy of the dictionary.\n\n classmethod fromkeys(seq[, value])\n\n Create a new dictionary with keys from *seq* and values set to\n *value*.\n\n ``fromkeys()`` is a class method that returns a new dictionary.\n *value* defaults to ``None``.\n\n get(key[, default])\n\n Return the value for *key* if *key* is in the dictionary, else\n *default*. If *default* is not given, it defaults to ``None``,\n so that this method never raises a ``KeyError``.\n\n items()\n\n Return a new view of the dictionary\'s items (``(key, value)``\n pairs). See the *documentation of view objects*.\n\n keys()\n\n Return a new view of the dictionary\'s keys. See the\n *documentation of view objects*.\n\n pop(key[, default])\n\n If *key* is in the dictionary, remove it and return its value,\n else return *default*. If *default* is not given and *key* is\n not in the dictionary, a ``KeyError`` is raised.\n\n popitem()\n\n Remove and return an arbitrary ``(key, value)`` pair from the\n dictionary.\n\n ``popitem()`` is useful to destructively iterate over a\n dictionary, as often used in set algorithms. If the dictionary\n is empty, calling ``popitem()`` raises a ``KeyError``.\n\n setdefault(key[, default])\n\n If *key* is in the dictionary, return its value. If not, insert\n *key* with a value of *default* and return *default*. *default*\n defaults to ``None``.\n\n update([other])\n\n Update the dictionary with the key/value pairs from *other*,\n overwriting existing keys. Return ``None``.\n\n ``update()`` accepts either another dictionary object or an\n iterable of key/value pairs (as tuples or other iterables of\n length two). If keyword arguments are specified, the dictionary\n is then updated with those key/value pairs: ``d.update(red=1,\n blue=2)``.\n\n values()\n\n Return a new view of the dictionary\'s values. See the\n *documentation of view objects*.\n\nSee also:\n\n ``types.MappingProxyType`` can be used to create a read-only view\n of a ``dict``.\n\n\nDictionary view objects\n=======================\n\nThe objects returned by ``dict.keys()``, ``dict.values()`` and\n``dict.items()`` are *view objects*. They provide a dynamic view on\nthe dictionary\'s entries, which means that when the dictionary\nchanges, the view reflects these changes.\n\nDictionary views can be iterated over to yield their respective data,\nand support membership tests:\n\nlen(dictview)\n\n Return the number of entries in the dictionary.\n\niter(dictview)\n\n Return an iterator over the keys, values or items (represented as\n tuples of ``(key, value)``) in the dictionary.\n\n Keys and values are iterated over in an arbitrary order which is\n non-random, varies across Python implementations, and depends on\n the dictionary\'s history of insertions and deletions. If keys,\n values and items views are iterated over with no intervening\n modifications to the dictionary, the order of items will directly\n correspond. This allows the creation of ``(value, key)`` pairs\n using ``zip()``: ``pairs = zip(d.values(), d.keys())``. Another\n way to create the same list is ``pairs = [(v, k) for (k, v) in\n d.items()]``.\n\n Iterating views while adding or deleting entries in the dictionary\n may raise a ``RuntimeError`` or fail to iterate over all entries.\n\nx in dictview\n\n Return ``True`` if *x* is in the underlying dictionary\'s keys,\n values or items (in the latter case, *x* should be a ``(key,\n value)`` tuple).\n\nKeys views are set-like since their entries are unique and hashable.\nIf all values are hashable, so that ``(key, value)`` pairs are unique\nand hashable, then the items view is also set-like. (Values views are\nnot treated as set-like since the entries are generally not unique.)\nFor set-like views, all of the operations defined for the abstract\nbase class ``collections.abc.Set`` are available (for example, ``==``,\n``<``, or ``^``).\n\nAn example of dictionary view usage:\n\n >>> dishes = {\'eggs\': 2, \'sausage\': 1, \'bacon\': 1, \'spam\': 500}\n >>> keys = dishes.keys()\n >>> values = dishes.values()\n\n >>> # iteration\n >>> n = 0\n >>> for val in values:\n ... n += val\n >>> print(n)\n 504\n\n >>> # keys and values are iterated over in the same order\n >>> list(keys)\n [\'eggs\', \'bacon\', \'sausage\', \'spam\']\n >>> list(values)\n [2, 1, 1, 500]\n\n >>> # view objects are dynamic and reflect dict changes\n >>> del dishes[\'eggs\']\n >>> del dishes[\'sausage\']\n >>> list(keys)\n [\'spam\', \'bacon\']\n\n >>> # set operations\n >>> keys & {\'eggs\', \'bacon\', \'salad\'}\n {\'bacon\'}\n >>> keys ^ {\'sausage\', \'juice\'}\n {\'juice\', \'sausage\', \'bacon\', \'spam\'}\n',
'typesmethods': '\nMethods\n*******\n\nMethods are functions that are called using the attribute notation.\nThere are two flavors: built-in methods (such as ``append()`` on\nlists) and class instance methods. Built-in methods are described\nwith the types that support them.\n\nIf you access a method (a function defined in a class namespace)\nthrough an instance, you get a special object: a *bound method* (also\ncalled *instance method*) object. When called, it will add the\n``self`` argument to the argument list. Bound methods have two\nspecial read-only attributes: ``m.__self__`` is the object on which\nthe method operates, and ``m.__func__`` is the function implementing\nthe method. Calling ``m(arg-1, arg-2, ..., arg-n)`` is completely\nequivalent to calling ``m.__func__(m.__self__, arg-1, arg-2, ...,\narg-n)``.\n\nLike function objects, bound method objects support getting arbitrary\nattributes. However, since method attributes are actually stored on\nthe underlying function object (``meth.__func__``), setting method\nattributes on bound methods is disallowed. Attempting to set an\nattribute on a method results in an ``AttributeError`` being raised.\nIn order to set a method attribute, you need to explicitly set it on\nthe underlying function object:\n\n >>> class C:\n ... def method(self):\n ... pass\n ...\n >>> c = C()\n >>> c.method.whoami = \'my name is method\' # can\'t set on the method\n Traceback (most recent call last):\n File "<stdin>", line 1, in <module>\n AttributeError: \'method\' object has no attribute \'whoami\'\n >>> c.method.__func__.whoami = \'my name is method\'\n >>> c.method.whoami\n \'my name is method\'\n\nSee *The standard type hierarchy* for more information.\n',
'typesmodules': "\nModules\n*******\n\nThe only special operation on a module is attribute access:\n``m.name``, where *m* is a module and *name* accesses a name defined\nin *m*'s symbol table. Module attributes can be assigned to. (Note\nthat the ``import`` statement is not, strictly speaking, an operation\non a module object; ``import foo`` does not require a module object\nnamed *foo* to exist, rather it requires an (external) *definition*\nfor a module named *foo* somewhere.)\n\nA special attribute of every module is ``__dict__``. This is the\ndictionary containing the module's symbol table. Modifying this\ndictionary will actually change the module's symbol table, but direct\nassignment to the ``__dict__`` attribute is not possible (you can\nwrite ``m.__dict__['a'] = 1``, which defines ``m.a`` to be ``1``, but\nyou can't write ``m.__dict__ = {}``). Modifying ``__dict__`` directly\nis not recommended.\n\nModules built into the interpreter are written like this: ``<module\n'sys' (built-in)>``. If loaded from a file, they are written as\n``<module 'os' from '/usr/local/lib/pythonX.Y/os.pyc'>``.\n",
- 'typesseq': '\nSequence Types --- ``list``, ``tuple``, ``range``\n*************************************************\n\nThere are three basic sequence types: lists, tuples, and range\nobjects. Additional sequence types tailored for processing of *binary\ndata* and *text strings* are described in dedicated sections.\n\n\nCommon Sequence Operations\n==========================\n\nThe operations in the following table are supported by most sequence\ntypes, both mutable and immutable. The ``collections.abc.Sequence``\nABC is provided to make it easier to correctly implement these\noperations on custom sequence types.\n\nThis table lists the sequence operations sorted in ascending priority\n(operations in the same box have the same priority). In the table,\n*s* and *t* are sequences of the same type, *n*, *i*, *j* and *k* are\nintegers and *x* is an arbitrary object that meets any type and value\nrestrictions imposed by *s*.\n\nThe ``in`` and ``not in`` operations have the same priorities as the\ncomparison operations. The ``+`` (concatenation) and ``*``\n(repetition) operations have the same priority as the corresponding\nnumeric operations.\n\n+----------------------------+----------------------------------+------------+\n| Operation | Result | Notes |\n+============================+==================================+============+\n| ``x in s`` | ``True`` if an item of *s* is | (1) |\n| | equal to *x*, else ``False`` | |\n+----------------------------+----------------------------------+------------+\n| ``x not in s`` | ``False`` if an item of *s* is | (1) |\n| | equal to *x*, else ``True`` | |\n+----------------------------+----------------------------------+------------+\n| ``s + t`` | the concatenation of *s* and *t* | (6)(7) |\n+----------------------------+----------------------------------+------------+\n| ``s * n`` or ``n * s`` | *n* shallow copies of *s* | (2)(7) |\n| | concatenated | |\n+----------------------------+----------------------------------+------------+\n| ``s[i]`` | *i*th item of *s*, origin 0 | (3) |\n+----------------------------+----------------------------------+------------+\n| ``s[i:j]`` | slice of *s* from *i* to *j* | (3)(4) |\n+----------------------------+----------------------------------+------------+\n| ``s[i:j:k]`` | slice of *s* from *i* to *j* | (3)(5) |\n| | with step *k* | |\n+----------------------------+----------------------------------+------------+\n| ``len(s)`` | length of *s* | |\n+----------------------------+----------------------------------+------------+\n| ``min(s)`` | smallest item of *s* | |\n+----------------------------+----------------------------------+------------+\n| ``max(s)`` | largest item of *s* | |\n+----------------------------+----------------------------------+------------+\n| ``s.index(x[, i[, j]])`` | index of the first occurrence of | (8) |\n| | *x* in *s* (at or after index | |\n| | *i* and before index *j*) | |\n+----------------------------+----------------------------------+------------+\n| ``s.count(x)`` | total number of occurrences of | |\n| | *x* in *s* | |\n+----------------------------+----------------------------------+------------+\n\nSequences of the same type also support comparisons. In particular,\ntuples and lists are compared lexicographically by comparing\ncorresponding elements. This means that to compare equal, every\nelement must compare equal and the two sequences must be of the same\ntype and have the same length. (For full details see *Comparisons* in\nthe language reference.)\n\nNotes:\n\n1. While the ``in`` and ``not in`` operations are used only for simple\n containment testing in the general case, some specialised sequences\n (such as ``str``, ``bytes`` and ``bytearray``) also use them for\n subsequence testing:\n\n >>> "gg" in "eggs"\n True\n\n2. Values of *n* less than ``0`` are treated as ``0`` (which yields an\n empty sequence of the same type as *s*). Note also that the copies\n are shallow; nested structures are not copied. This often haunts\n new Python programmers; consider:\n\n >>> lists = [[]] * 3\n >>> lists\n [[], [], []]\n >>> lists[0].append(3)\n >>> lists\n [[3], [3], [3]]\n\n What has happened is that ``[[]]`` is a one-element list containing\n an empty list, so all three elements of ``[[]] * 3`` are (pointers\n to) this single empty list. Modifying any of the elements of\n ``lists`` modifies this single list. You can create a list of\n different lists this way:\n\n >>> lists = [[] for i in range(3)]\n >>> lists[0].append(3)\n >>> lists[1].append(5)\n >>> lists[2].append(7)\n >>> lists\n [[3], [5], [7]]\n\n3. If *i* or *j* is negative, the index is relative to the end of the\n string: ``len(s) + i`` or ``len(s) + j`` is substituted. But note\n that ``-0`` is still ``0``.\n\n4. The slice of *s* from *i* to *j* is defined as the sequence of\n items with index *k* such that ``i <= k < j``. If *i* or *j* is\n greater than ``len(s)``, use ``len(s)``. If *i* is omitted or\n ``None``, use ``0``. If *j* is omitted or ``None``, use\n ``len(s)``. If *i* is greater than or equal to *j*, the slice is\n empty.\n\n5. The slice of *s* from *i* to *j* with step *k* is defined as the\n sequence of items with index ``x = i + n*k`` such that ``0 <= n <\n (j-i)/k``. In other words, the indices are ``i``, ``i+k``,\n ``i+2*k``, ``i+3*k`` and so on, stopping when *j* is reached (but\n never including *j*). If *i* or *j* is greater than ``len(s)``,\n use ``len(s)``. If *i* or *j* are omitted or ``None``, they become\n "end" values (which end depends on the sign of *k*). Note, *k*\n cannot be zero. If *k* is ``None``, it is treated like ``1``.\n\n6. Concatenating immutable sequences always results in a new object.\n This means that building up a sequence by repeated concatenation\n will have a quadratic runtime cost in the total sequence length.\n To get a linear runtime cost, you must switch to one of the\n alternatives below:\n\n * if concatenating ``str`` objects, you can build a list and use\n ``str.join()`` at the end or else write to a ``io.StringIO``\n instance and retrieve its value when complete\n\n * if concatenating ``bytes`` objects, you can similarly use\n ``bytes.join()`` or ``io.BytesIO``, or you can do in-place\n concatenation with a ``bytearray`` object. ``bytearray`` objects\n are mutable and have an efficient overallocation mechanism\n\n * if concatenating ``tuple`` objects, extend a ``list`` instead\n\n * for other types, investigate the relevant class documentation\n\n7. Some sequence types (such as ``range``) only support item sequences\n that follow specific patterns, and hence don\'t support sequence\n concatenation or repetition.\n\n8. ``index`` raises ``ValueError`` when *x* is not found in *s*. When\n supported, the additional arguments to the index method allow\n efficient searching of subsections of the sequence. Passing the\n extra arguments is roughly equivalent to using ``s[i:j].index(x)``,\n only without copying any data and with the returned index being\n relative to the start of the sequence rather than the start of the\n slice.\n\n\nImmutable Sequence Types\n========================\n\nThe only operation that immutable sequence types generally implement\nthat is not also implemented by mutable sequence types is support for\nthe ``hash()`` built-in.\n\nThis support allows immutable sequences, such as ``tuple`` instances,\nto be used as ``dict`` keys and stored in ``set`` and ``frozenset``\ninstances.\n\nAttempting to hash an immutable sequence that contains unhashable\nvalues will result in ``TypeError``.\n\n\nMutable Sequence Types\n======================\n\nThe operations in the following table are defined on mutable sequence\ntypes. The ``collections.abc.MutableSequence`` ABC is provided to make\nit easier to correctly implement these operations on custom sequence\ntypes.\n\nIn the table *s* is an instance of a mutable sequence type, *t* is any\niterable object and *x* is an arbitrary object that meets any type and\nvalue restrictions imposed by *s* (for example, ``bytearray`` only\naccepts integers that meet the value restriction ``0 <= x <= 255``).\n\n+--------------------------------+----------------------------------+-----------------------+\n| Operation | Result | Notes |\n+================================+==================================+=======================+\n| ``s[i] = x`` | item *i* of *s* is replaced by | |\n| | *x* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s[i:j] = t`` | slice of *s* from *i* to *j* is | |\n| | replaced by the contents of the | |\n| | iterable *t* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``del s[i:j]`` | same as ``s[i:j] = []`` | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s[i:j:k] = t`` | the elements of ``s[i:j:k]`` are | (1) |\n| | replaced by those of *t* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``del s[i:j:k]`` | removes the elements of | |\n| | ``s[i:j:k]`` from the list | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.append(x)`` | appends *x* to the end of the | |\n| | sequence (same as | |\n| | ``s[len(s):len(s)] = [x]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.clear()`` | removes all items from ``s`` | (5) |\n| | (same as ``del s[:]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.copy()`` | creates a shallow copy of ``s`` | (5) |\n| | (same as ``s[:]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.extend(t)`` | extends *s* with the contents of | |\n| | *t* (same as ``s[len(s):len(s)] | |\n| | = t``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.insert(i, x)`` | inserts *x* into *s* at the | |\n| | index given by *i* (same as | |\n| | ``s[i:i] = [x]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.pop([i])`` | retrieves the item at *i* and | (2) |\n| | also removes it from *s* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.remove(x)`` | remove the first item from *s* | (3) |\n| | where ``s[i] == x`` | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.reverse()`` | reverses the items of *s* in | (4) |\n| | place | |\n+--------------------------------+----------------------------------+-----------------------+\n\nNotes:\n\n1. *t* must have the same length as the slice it is replacing.\n\n2. The optional argument *i* defaults to ``-1``, so that by default\n the last item is removed and returned.\n\n3. ``remove`` raises ``ValueError`` when *x* is not found in *s*.\n\n4. The ``reverse()`` method modifies the sequence in place for economy\n of space when reversing a large sequence. To remind users that it\n operates by side effect, it does not return the reversed sequence.\n\n5. ``clear()`` and ``copy()`` are included for consistency with the\n interfaces of mutable containers that don\'t support slicing\n operations (such as ``dict`` and ``set``)\n\n New in version 3.3: ``clear()`` and ``copy()`` methods.\n\n\nLists\n=====\n\nLists are mutable sequences, typically used to store collections of\nhomogeneous items (where the precise degree of similarity will vary by\napplication).\n\nclass class list([iterable])\n\n Lists may be constructed in several ways:\n\n * Using a pair of square brackets to denote the empty list: ``[]``\n\n * Using square brackets, separating items with commas: ``[a]``,\n ``[a, b, c]``\n\n * Using a list comprehension: ``[x for x in iterable]``\n\n * Using the type constructor: ``list()`` or ``list(iterable)``\n\n The constructor builds a list whose items are the same and in the\n same order as *iterable*\'s items. *iterable* may be either a\n sequence, a container that supports iteration, or an iterator\n object. If *iterable* is already a list, a copy is made and\n returned, similar to ``iterable[:]``. For example, ``list(\'abc\')``\n returns ``[\'a\', \'b\', \'c\']`` and ``list( (1, 2, 3) )`` returns ``[1,\n 2, 3]``. If no argument is given, the constructor creates a new\n empty list, ``[]``.\n\n Many other operations also produce lists, including the\n ``sorted()`` built-in.\n\n Lists implement all of the *common* and *mutable* sequence\n operations. Lists also provide the following additional method:\n\n sort(*, key=None, reverse=None)\n\n This method sorts the list in place, using only ``<``\n comparisons between items. Exceptions are not suppressed - if\n any comparison operations fail, the entire sort operation will\n fail (and the list will likely be left in a partially modified\n state).\n\n *key* specifies a function of one argument that is used to\n extract a comparison key from each list element (for example,\n ``key=str.lower``). The key corresponding to each item in the\n list is calculated once and then used for the entire sorting\n process. The default value of ``None`` means that list items are\n sorted directly without calculating a separate key value.\n\n The ``functools.cmp_to_key()`` utility is available to convert a\n 2.x style *cmp* function to a *key* function.\n\n *reverse* is a boolean value. If set to ``True``, then the list\n elements are sorted as if each comparison were reversed.\n\n This method modifies the sequence in place for economy of space\n when sorting a large sequence. To remind users that it operates\n by side effect, it does not return the sorted sequence (use\n ``sorted()`` to explicitly request a new sorted list instance).\n\n The ``sort()`` method is guaranteed to be stable. A sort is\n stable if it guarantees not to change the relative order of\n elements that compare equal --- this is helpful for sorting in\n multiple passes (for example, sort by department, then by salary\n grade).\n\n **CPython implementation detail:** While a list is being sorted,\n the effect of attempting to mutate, or even inspect, the list is\n undefined. The C implementation of Python makes the list appear\n empty for the duration, and raises ``ValueError`` if it can\n detect that the list has been mutated during a sort.\n\n\nTuples\n======\n\nTuples are immutable sequences, typically used to store collections of\nheterogeneous data (such as the 2-tuples produced by the\n``enumerate()`` built-in). Tuples are also used for cases where an\nimmutable sequence of homogeneous data is needed (such as allowing\nstorage in a ``set`` or ``dict`` instance).\n\nclass class tuple([iterable])\n\n Tuples may be constructed in a number of ways:\n\n * Using a pair of parentheses to denote the empty tuple: ``()``\n\n * Using a trailing comma for a singleton tuple: ``a,`` or ``(a,)``\n\n * Separating items with commas: ``a, b, c`` or ``(a, b, c)``\n\n * Using the ``tuple()`` built-in: ``tuple()`` or\n ``tuple(iterable)``\n\n The constructor builds a tuple whose items are the same and in the\n same order as *iterable*\'s items. *iterable* may be either a\n sequence, a container that supports iteration, or an iterator\n object. If *iterable* is already a tuple, it is returned\n unchanged. For example, ``tuple(\'abc\')`` returns ``(\'a\', \'b\',\n \'c\')`` and ``tuple( [1, 2, 3] )`` returns ``(1, 2, 3)``. If no\n argument is given, the constructor creates a new empty tuple,\n ``()``.\n\n Note that it is actually the comma which makes a tuple, not the\n parentheses. The parentheses are optional, except in the empty\n tuple case, or when they are needed to avoid syntactic ambiguity.\n For example, ``f(a, b, c)`` is a function call with three\n arguments, while ``f((a, b, c))`` is a function call with a 3-tuple\n as the sole argument.\n\n Tuples implement all of the *common* sequence operations.\n\nFor heterogeneous collections of data where access by name is clearer\nthan access by index, ``collections.namedtuple()`` may be a more\nappropriate choice than a simple tuple object.\n\n\nRanges\n======\n\nThe ``range`` type represents an immutable sequence of numbers and is\ncommonly used for looping a specific number of times in ``for`` loops.\n\nclass class range(stop)\nclass class range(start, stop[, step])\n\n The arguments to the range constructor must be integers (either\n built-in ``int`` or any object that implements the ``__index__``\n special method). If the *step* argument is omitted, it defaults to\n ``1``. If the *start* argument is omitted, it defaults to ``0``. If\n *step* is zero, ``ValueError`` is raised.\n\n For a positive *step*, the contents of a range ``r`` are determined\n by the formula ``r[i] = start + step*i`` where ``i >= 0`` and\n ``r[i] < stop``.\n\n For a negative *step*, the contents of the range are still\n determined by the formula ``r[i] = start + step*i``, but the\n constraints are ``i >= 0`` and ``r[i] > stop``.\n\n A range object will be empty if ``r[0]`` does not meet the value\n constraint. Ranges do support negative indices, but these are\n interpreted as indexing from the end of the sequence determined by\n the positive indices.\n\n Ranges containing absolute values larger than ``sys.maxsize`` are\n permitted but some features (such as ``len()``) may raise\n ``OverflowError``.\n\n Range examples:\n\n >>> list(range(10))\n [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n >>> list(range(1, 11))\n [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n >>> list(range(0, 30, 5))\n [0, 5, 10, 15, 20, 25]\n >>> list(range(0, 10, 3))\n [0, 3, 6, 9]\n >>> list(range(0, -10, -1))\n [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]\n >>> list(range(0))\n []\n >>> list(range(1, 0))\n []\n\n Ranges implement all of the *common* sequence operations except\n concatenation and repetition (due to the fact that range objects\n can only represent sequences that follow a strict pattern and\n repetition and concatenation will usually violate that pattern).\n\nThe advantage of the ``range`` type over a regular ``list`` or\n``tuple`` is that a ``range`` object will always take the same (small)\namount of memory, no matter the size of the range it represents (as it\nonly stores the ``start``, ``stop`` and ``step`` values, calculating\nindividual items and subranges as needed).\n\nRange objects implement the ``collections.abc.Sequence`` ABC, and\nprovide features such as containment tests, element index lookup,\nslicing and support for negative indices (see *Sequence Types ---\nlist, tuple, range*):\n\n>>> r = range(0, 20, 2)\n>>> r\nrange(0, 20, 2)\n>>> 11 in r\nFalse\n>>> 10 in r\nTrue\n>>> r.index(10)\n5\n>>> r[5]\n10\n>>> r[:5]\nrange(0, 10, 2)\n>>> r[-1]\n18\n\nTesting range objects for equality with ``==`` and ``!=`` compares\nthem as sequences. That is, two range objects are considered equal if\nthey represent the same sequence of values. (Note that two range\nobjects that compare equal might have different ``start``, ``stop``\nand ``step`` attributes, for example ``range(0) == range(2, 1, 3)`` or\n``range(0, 3, 2) == range(0, 4, 2)``.)\n\nChanged in version 3.2: Implement the Sequence ABC. Support slicing\nand negative indices. Test ``int`` objects for membership in constant\ntime instead of iterating through all items.\n\nChanged in version 3.3: Define \'==\' and \'!=\' to compare range objects\nbased on the sequence of values they define (instead of comparing\nbased on object identity).\n\nNew in version 3.3: The ``start``, ``stop`` and ``step`` attributes.\n',
+ 'typesseq': '\nSequence Types --- ``list``, ``tuple``, ``range``\n*************************************************\n\nThere are three basic sequence types: lists, tuples, and range\nobjects. Additional sequence types tailored for processing of *binary\ndata* and *text strings* are described in dedicated sections.\n\n\nCommon Sequence Operations\n==========================\n\nThe operations in the following table are supported by most sequence\ntypes, both mutable and immutable. The ``collections.abc.Sequence``\nABC is provided to make it easier to correctly implement these\noperations on custom sequence types.\n\nThis table lists the sequence operations sorted in ascending priority\n(operations in the same box have the same priority). In the table,\n*s* and *t* are sequences of the same type, *n*, *i*, *j* and *k* are\nintegers and *x* is an arbitrary object that meets any type and value\nrestrictions imposed by *s*.\n\nThe ``in`` and ``not in`` operations have the same priorities as the\ncomparison operations. The ``+`` (concatenation) and ``*``\n(repetition) operations have the same priority as the corresponding\nnumeric operations.\n\n+----------------------------+----------------------------------+------------+\n| Operation | Result | Notes |\n+============================+==================================+============+\n| ``x in s`` | ``True`` if an item of *s* is | (1) |\n| | equal to *x*, else ``False`` | |\n+----------------------------+----------------------------------+------------+\n| ``x not in s`` | ``False`` if an item of *s* is | (1) |\n| | equal to *x*, else ``True`` | |\n+----------------------------+----------------------------------+------------+\n| ``s + t`` | the concatenation of *s* and *t* | (6)(7) |\n+----------------------------+----------------------------------+------------+\n| ``s * n`` or ``n * s`` | *n* shallow copies of *s* | (2)(7) |\n| | concatenated | |\n+----------------------------+----------------------------------+------------+\n| ``s[i]`` | *i*th item of *s*, origin 0 | (3) |\n+----------------------------+----------------------------------+------------+\n| ``s[i:j]`` | slice of *s* from *i* to *j* | (3)(4) |\n+----------------------------+----------------------------------+------------+\n| ``s[i:j:k]`` | slice of *s* from *i* to *j* | (3)(5) |\n| | with step *k* | |\n+----------------------------+----------------------------------+------------+\n| ``len(s)`` | length of *s* | |\n+----------------------------+----------------------------------+------------+\n| ``min(s)`` | smallest item of *s* | |\n+----------------------------+----------------------------------+------------+\n| ``max(s)`` | largest item of *s* | |\n+----------------------------+----------------------------------+------------+\n| ``s.index(x[, i[, j]])`` | index of the first occurrence of | (8) |\n| | *x* in *s* (at or after index | |\n| | *i* and before index *j*) | |\n+----------------------------+----------------------------------+------------+\n| ``s.count(x)`` | total number of occurrences of | |\n| | *x* in *s* | |\n+----------------------------+----------------------------------+------------+\n\nSequences of the same type also support comparisons. In particular,\ntuples and lists are compared lexicographically by comparing\ncorresponding elements. This means that to compare equal, every\nelement must compare equal and the two sequences must be of the same\ntype and have the same length. (For full details see *Comparisons* in\nthe language reference.)\n\nNotes:\n\n1. While the ``in`` and ``not in`` operations are used only for simple\n containment testing in the general case, some specialised sequences\n (such as ``str``, ``bytes`` and ``bytearray``) also use them for\n subsequence testing:\n\n >>> "gg" in "eggs"\n True\n\n2. Values of *n* less than ``0`` are treated as ``0`` (which yields an\n empty sequence of the same type as *s*). Note also that the copies\n are shallow; nested structures are not copied. This often haunts\n new Python programmers; consider:\n\n >>> lists = [[]] * 3\n >>> lists\n [[], [], []]\n >>> lists[0].append(3)\n >>> lists\n [[3], [3], [3]]\n\n What has happened is that ``[[]]`` is a one-element list containing\n an empty list, so all three elements of ``[[]] * 3`` are (pointers\n to) this single empty list. Modifying any of the elements of\n ``lists`` modifies this single list. You can create a list of\n different lists this way:\n\n >>> lists = [[] for i in range(3)]\n >>> lists[0].append(3)\n >>> lists[1].append(5)\n >>> lists[2].append(7)\n >>> lists\n [[3], [5], [7]]\n\n3. If *i* or *j* is negative, the index is relative to the end of the\n string: ``len(s) + i`` or ``len(s) + j`` is substituted. But note\n that ``-0`` is still ``0``.\n\n4. The slice of *s* from *i* to *j* is defined as the sequence of\n items with index *k* such that ``i <= k < j``. If *i* or *j* is\n greater than ``len(s)``, use ``len(s)``. If *i* is omitted or\n ``None``, use ``0``. If *j* is omitted or ``None``, use\n ``len(s)``. If *i* is greater than or equal to *j*, the slice is\n empty.\n\n5. The slice of *s* from *i* to *j* with step *k* is defined as the\n sequence of items with index ``x = i + n*k`` such that ``0 <= n <\n (j-i)/k``. In other words, the indices are ``i``, ``i+k``,\n ``i+2*k``, ``i+3*k`` and so on, stopping when *j* is reached (but\n never including *j*). If *i* or *j* is greater than ``len(s)``,\n use ``len(s)``. If *i* or *j* are omitted or ``None``, they become\n "end" values (which end depends on the sign of *k*). Note, *k*\n cannot be zero. If *k* is ``None``, it is treated like ``1``.\n\n6. Concatenating immutable sequences always results in a new object.\n This means that building up a sequence by repeated concatenation\n will have a quadratic runtime cost in the total sequence length.\n To get a linear runtime cost, you must switch to one of the\n alternatives below:\n\n * if concatenating ``str`` objects, you can build a list and use\n ``str.join()`` at the end or else write to a ``io.StringIO``\n instance and retrieve its value when complete\n\n * if concatenating ``bytes`` objects, you can similarly use\n ``bytes.join()`` or ``io.BytesIO``, or you can do in-place\n concatenation with a ``bytearray`` object. ``bytearray`` objects\n are mutable and have an efficient overallocation mechanism\n\n * if concatenating ``tuple`` objects, extend a ``list`` instead\n\n * for other types, investigate the relevant class documentation\n\n7. Some sequence types (such as ``range``) only support item sequences\n that follow specific patterns, and hence don\'t support sequence\n concatenation or repetition.\n\n8. ``index`` raises ``ValueError`` when *x* is not found in *s*. When\n supported, the additional arguments to the index method allow\n efficient searching of subsections of the sequence. Passing the\n extra arguments is roughly equivalent to using ``s[i:j].index(x)``,\n only without copying any data and with the returned index being\n relative to the start of the sequence rather than the start of the\n slice.\n\n\nImmutable Sequence Types\n========================\n\nThe only operation that immutable sequence types generally implement\nthat is not also implemented by mutable sequence types is support for\nthe ``hash()`` built-in.\n\nThis support allows immutable sequences, such as ``tuple`` instances,\nto be used as ``dict`` keys and stored in ``set`` and ``frozenset``\ninstances.\n\nAttempting to hash an immutable sequence that contains unhashable\nvalues will result in ``TypeError``.\n\n\nMutable Sequence Types\n======================\n\nThe operations in the following table are defined on mutable sequence\ntypes. The ``collections.abc.MutableSequence`` ABC is provided to make\nit easier to correctly implement these operations on custom sequence\ntypes.\n\nIn the table *s* is an instance of a mutable sequence type, *t* is any\niterable object and *x* is an arbitrary object that meets any type and\nvalue restrictions imposed by *s* (for example, ``bytearray`` only\naccepts integers that meet the value restriction ``0 <= x <= 255``).\n\n+--------------------------------+----------------------------------+-----------------------+\n| Operation | Result | Notes |\n+================================+==================================+=======================+\n| ``s[i] = x`` | item *i* of *s* is replaced by | |\n| | *x* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s[i:j] = t`` | slice of *s* from *i* to *j* is | |\n| | replaced by the contents of the | |\n| | iterable *t* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``del s[i:j]`` | same as ``s[i:j] = []`` | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s[i:j:k] = t`` | the elements of ``s[i:j:k]`` are | (1) |\n| | replaced by those of *t* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``del s[i:j:k]`` | removes the elements of | |\n| | ``s[i:j:k]`` from the list | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.append(x)`` | appends *x* to the end of the | |\n| | sequence (same as | |\n| | ``s[len(s):len(s)] = [x]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.clear()`` | removes all items from ``s`` | (5) |\n| | (same as ``del s[:]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.copy()`` | creates a shallow copy of ``s`` | (5) |\n| | (same as ``s[:]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.extend(t)`` | extends *s* with the contents of | |\n| | *t* (same as ``s[len(s):len(s)] | |\n| | = t``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.insert(i, x)`` | inserts *x* into *s* at the | |\n| | index given by *i* (same as | |\n| | ``s[i:i] = [x]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.pop([i])`` | retrieves the item at *i* and | (2) |\n| | also removes it from *s* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.remove(x)`` | remove the first item from *s* | (3) |\n| | where ``s[i] == x`` | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.reverse()`` | reverses the items of *s* in | (4) |\n| | place | |\n+--------------------------------+----------------------------------+-----------------------+\n\nNotes:\n\n1. *t* must have the same length as the slice it is replacing.\n\n2. The optional argument *i* defaults to ``-1``, so that by default\n the last item is removed and returned.\n\n3. ``remove`` raises ``ValueError`` when *x* is not found in *s*.\n\n4. The ``reverse()`` method modifies the sequence in place for economy\n of space when reversing a large sequence. To remind users that it\n operates by side effect, it does not return the reversed sequence.\n\n5. ``clear()`` and ``copy()`` are included for consistency with the\n interfaces of mutable containers that don\'t support slicing\n operations (such as ``dict`` and ``set``)\n\n New in version 3.3: ``clear()`` and ``copy()`` methods.\n\n\nLists\n=====\n\nLists are mutable sequences, typically used to store collections of\nhomogeneous items (where the precise degree of similarity will vary by\napplication).\n\nclass class list([iterable])\n\n Lists may be constructed in several ways:\n\n * Using a pair of square brackets to denote the empty list: ``[]``\n\n * Using square brackets, separating items with commas: ``[a]``,\n ``[a, b, c]``\n\n * Using a list comprehension: ``[x for x in iterable]``\n\n * Using the type constructor: ``list()`` or ``list(iterable)``\n\n The constructor builds a list whose items are the same and in the\n same order as *iterable*\'s items. *iterable* may be either a\n sequence, a container that supports iteration, or an iterator\n object. If *iterable* is already a list, a copy is made and\n returned, similar to ``iterable[:]``. For example, ``list(\'abc\')``\n returns ``[\'a\', \'b\', \'c\']`` and ``list( (1, 2, 3) )`` returns ``[1,\n 2, 3]``. If no argument is given, the constructor creates a new\n empty list, ``[]``.\n\n Many other operations also produce lists, including the\n ``sorted()`` built-in.\n\n Lists implement all of the *common* and *mutable* sequence\n operations. Lists also provide the following additional method:\n\n sort(*, key=None, reverse=None)\n\n This method sorts the list in place, using only ``<``\n comparisons between items. Exceptions are not suppressed - if\n any comparison operations fail, the entire sort operation will\n fail (and the list will likely be left in a partially modified\n state).\n\n ``sort()`` accepts two arguments that can only be passed by\n keyword (*keyword-only arguments*):\n\n *key* specifies a function of one argument that is used to\n extract a comparison key from each list element (for example,\n ``key=str.lower``). The key corresponding to each item in the\n list is calculated once and then used for the entire sorting\n process. The default value of ``None`` means that list items are\n sorted directly without calculating a separate key value.\n\n The ``functools.cmp_to_key()`` utility is available to convert a\n 2.x style *cmp* function to a *key* function.\n\n *reverse* is a boolean value. If set to ``True``, then the list\n elements are sorted as if each comparison were reversed.\n\n This method modifies the sequence in place for economy of space\n when sorting a large sequence. To remind users that it operates\n by side effect, it does not return the sorted sequence (use\n ``sorted()`` to explicitly request a new sorted list instance).\n\n The ``sort()`` method is guaranteed to be stable. A sort is\n stable if it guarantees not to change the relative order of\n elements that compare equal --- this is helpful for sorting in\n multiple passes (for example, sort by department, then by salary\n grade).\n\n **CPython implementation detail:** While a list is being sorted,\n the effect of attempting to mutate, or even inspect, the list is\n undefined. The C implementation of Python makes the list appear\n empty for the duration, and raises ``ValueError`` if it can\n detect that the list has been mutated during a sort.\n\n\nTuples\n======\n\nTuples are immutable sequences, typically used to store collections of\nheterogeneous data (such as the 2-tuples produced by the\n``enumerate()`` built-in). Tuples are also used for cases where an\nimmutable sequence of homogeneous data is needed (such as allowing\nstorage in a ``set`` or ``dict`` instance).\n\nclass class tuple([iterable])\n\n Tuples may be constructed in a number of ways:\n\n * Using a pair of parentheses to denote the empty tuple: ``()``\n\n * Using a trailing comma for a singleton tuple: ``a,`` or ``(a,)``\n\n * Separating items with commas: ``a, b, c`` or ``(a, b, c)``\n\n * Using the ``tuple()`` built-in: ``tuple()`` or\n ``tuple(iterable)``\n\n The constructor builds a tuple whose items are the same and in the\n same order as *iterable*\'s items. *iterable* may be either a\n sequence, a container that supports iteration, or an iterator\n object. If *iterable* is already a tuple, it is returned\n unchanged. For example, ``tuple(\'abc\')`` returns ``(\'a\', \'b\',\n \'c\')`` and ``tuple( [1, 2, 3] )`` returns ``(1, 2, 3)``. If no\n argument is given, the constructor creates a new empty tuple,\n ``()``.\n\n Note that it is actually the comma which makes a tuple, not the\n parentheses. The parentheses are optional, except in the empty\n tuple case, or when they are needed to avoid syntactic ambiguity.\n For example, ``f(a, b, c)`` is a function call with three\n arguments, while ``f((a, b, c))`` is a function call with a 3-tuple\n as the sole argument.\n\n Tuples implement all of the *common* sequence operations.\n\nFor heterogeneous collections of data where access by name is clearer\nthan access by index, ``collections.namedtuple()`` may be a more\nappropriate choice than a simple tuple object.\n\n\nRanges\n======\n\nThe ``range`` type represents an immutable sequence of numbers and is\ncommonly used for looping a specific number of times in ``for`` loops.\n\nclass class range(stop)\nclass class range(start, stop[, step])\n\n The arguments to the range constructor must be integers (either\n built-in ``int`` or any object that implements the ``__index__``\n special method). If the *step* argument is omitted, it defaults to\n ``1``. If the *start* argument is omitted, it defaults to ``0``. If\n *step* is zero, ``ValueError`` is raised.\n\n For a positive *step*, the contents of a range ``r`` are determined\n by the formula ``r[i] = start + step*i`` where ``i >= 0`` and\n ``r[i] < stop``.\n\n For a negative *step*, the contents of the range are still\n determined by the formula ``r[i] = start + step*i``, but the\n constraints are ``i >= 0`` and ``r[i] > stop``.\n\n A range object will be empty if ``r[0]`` does not meet the value\n constraint. Ranges do support negative indices, but these are\n interpreted as indexing from the end of the sequence determined by\n the positive indices.\n\n Ranges containing absolute values larger than ``sys.maxsize`` are\n permitted but some features (such as ``len()``) may raise\n ``OverflowError``.\n\n Range examples:\n\n >>> list(range(10))\n [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n >>> list(range(1, 11))\n [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n >>> list(range(0, 30, 5))\n [0, 5, 10, 15, 20, 25]\n >>> list(range(0, 10, 3))\n [0, 3, 6, 9]\n >>> list(range(0, -10, -1))\n [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]\n >>> list(range(0))\n []\n >>> list(range(1, 0))\n []\n\n Ranges implement all of the *common* sequence operations except\n concatenation and repetition (due to the fact that range objects\n can only represent sequences that follow a strict pattern and\n repetition and concatenation will usually violate that pattern).\n\nThe advantage of the ``range`` type over a regular ``list`` or\n``tuple`` is that a ``range`` object will always take the same (small)\namount of memory, no matter the size of the range it represents (as it\nonly stores the ``start``, ``stop`` and ``step`` values, calculating\nindividual items and subranges as needed).\n\nRange objects implement the ``collections.abc.Sequence`` ABC, and\nprovide features such as containment tests, element index lookup,\nslicing and support for negative indices (see *Sequence Types ---\nlist, tuple, range*):\n\n>>> r = range(0, 20, 2)\n>>> r\nrange(0, 20, 2)\n>>> 11 in r\nFalse\n>>> 10 in r\nTrue\n>>> r.index(10)\n5\n>>> r[5]\n10\n>>> r[:5]\nrange(0, 10, 2)\n>>> r[-1]\n18\n\nTesting range objects for equality with ``==`` and ``!=`` compares\nthem as sequences. That is, two range objects are considered equal if\nthey represent the same sequence of values. (Note that two range\nobjects that compare equal might have different ``start``, ``stop``\nand ``step`` attributes, for example ``range(0) == range(2, 1, 3)`` or\n``range(0, 3, 2) == range(0, 4, 2)``.)\n\nChanged in version 3.2: Implement the Sequence ABC. Support slicing\nand negative indices. Test ``int`` objects for membership in constant\ntime instead of iterating through all items.\n\nChanged in version 3.3: Define \'==\' and \'!=\' to compare range objects\nbased on the sequence of values they define (instead of comparing\nbased on object identity).\n\nNew in version 3.3: The ``start``, ``stop`` and ``step`` attributes.\n',
'typesseq-mutable': "\nMutable Sequence Types\n**********************\n\nThe operations in the following table are defined on mutable sequence\ntypes. The ``collections.abc.MutableSequence`` ABC is provided to make\nit easier to correctly implement these operations on custom sequence\ntypes.\n\nIn the table *s* is an instance of a mutable sequence type, *t* is any\niterable object and *x* is an arbitrary object that meets any type and\nvalue restrictions imposed by *s* (for example, ``bytearray`` only\naccepts integers that meet the value restriction ``0 <= x <= 255``).\n\n+--------------------------------+----------------------------------+-----------------------+\n| Operation | Result | Notes |\n+================================+==================================+=======================+\n| ``s[i] = x`` | item *i* of *s* is replaced by | |\n| | *x* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s[i:j] = t`` | slice of *s* from *i* to *j* is | |\n| | replaced by the contents of the | |\n| | iterable *t* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``del s[i:j]`` | same as ``s[i:j] = []`` | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s[i:j:k] = t`` | the elements of ``s[i:j:k]`` are | (1) |\n| | replaced by those of *t* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``del s[i:j:k]`` | removes the elements of | |\n| | ``s[i:j:k]`` from the list | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.append(x)`` | appends *x* to the end of the | |\n| | sequence (same as | |\n| | ``s[len(s):len(s)] = [x]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.clear()`` | removes all items from ``s`` | (5) |\n| | (same as ``del s[:]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.copy()`` | creates a shallow copy of ``s`` | (5) |\n| | (same as ``s[:]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.extend(t)`` | extends *s* with the contents of | |\n| | *t* (same as ``s[len(s):len(s)] | |\n| | = t``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.insert(i, x)`` | inserts *x* into *s* at the | |\n| | index given by *i* (same as | |\n| | ``s[i:i] = [x]``) | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.pop([i])`` | retrieves the item at *i* and | (2) |\n| | also removes it from *s* | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.remove(x)`` | remove the first item from *s* | (3) |\n| | where ``s[i] == x`` | |\n+--------------------------------+----------------------------------+-----------------------+\n| ``s.reverse()`` | reverses the items of *s* in | (4) |\n| | place | |\n+--------------------------------+----------------------------------+-----------------------+\n\nNotes:\n\n1. *t* must have the same length as the slice it is replacing.\n\n2. The optional argument *i* defaults to ``-1``, so that by default\n the last item is removed and returned.\n\n3. ``remove`` raises ``ValueError`` when *x* is not found in *s*.\n\n4. The ``reverse()`` method modifies the sequence in place for economy\n of space when reversing a large sequence. To remind users that it\n operates by side effect, it does not return the reversed sequence.\n\n5. ``clear()`` and ``copy()`` are included for consistency with the\n interfaces of mutable containers that don't support slicing\n operations (such as ``dict`` and ``set``)\n\n New in version 3.3: ``clear()`` and ``copy()`` methods.\n",
'unary': '\nUnary arithmetic and bitwise operations\n***************************************\n\nAll unary arithmetic and bitwise operations have the same priority:\n\n u_expr ::= power | "-" u_expr | "+" u_expr | "~" u_expr\n\nThe unary ``-`` (minus) operator yields the negation of its numeric\nargument.\n\nThe unary ``+`` (plus) operator yields its numeric argument unchanged.\n\nThe unary ``~`` (invert) operator yields the bitwise inversion of its\ninteger argument. The bitwise inversion of ``x`` is defined as\n``-(x+1)``. It only applies to integral numbers.\n\nIn all three cases, if the argument does not have the proper type, a\n``TypeError`` exception is raised.\n',
'while': '\nThe ``while`` statement\n***********************\n\nThe ``while`` statement is used for repeated execution as long as an\nexpression is true:\n\n while_stmt ::= "while" expression ":" suite\n ["else" ":" suite]\n\nThis repeatedly tests the expression and, if it is true, executes the\nfirst suite; if the expression is false (which may be the first time\nit is tested) the suite of the ``else`` clause, if present, is\nexecuted and the loop terminates.\n\nA ``break`` statement executed in the first suite terminates the loop\nwithout executing the ``else`` clause\'s suite. A ``continue``\nstatement executed in the first suite skips the rest of the suite and\ngoes back to testing the expression.\n',
diff --git a/Lib/quopri.py b/Lib/quopri.py
index 3d0f0ac078..e5bd010d5b 100755
--- a/Lib/quopri.py
+++ b/Lib/quopri.py
@@ -223,7 +223,7 @@ def main():
else:
try:
fp = open(file, "rb")
- except IOError as msg:
+ except OSError as msg:
sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
sts = 1
continue
diff --git a/Lib/random.py b/Lib/random.py
index 14ac5e5b09..808175ab4a 100644
--- a/Lib/random.py
+++ b/Lib/random.py
@@ -41,7 +41,7 @@ from types import MethodType as _MethodType, BuiltinMethodType as _BuiltinMethod
from math import log as _log, exp as _exp, pi as _pi, e as _e, ceil as _ceil
from math import sqrt as _sqrt, acos as _acos, cos as _cos, sin as _sin
from os import urandom as _urandom
-from collections.abc import Set as _Set, Sequence as _Sequence
+from _collections_abc import Set as _Set, Sequence as _Sequence
from hashlib import sha512 as _sha512
__all__ = ["Random","seed","random","uniform","randint","choice","sample",
@@ -151,6 +151,9 @@ class Random(_random.Random):
## -------------------- pickle support -------------------
+ # Issue 17489: Since __reduce__ was defined to fix #759889 this is no
+ # longer called; we leave it here because it has been here since random was
+ # rewritten back in 2001 and why risk breaking something.
def __getstate__(self): # for pickle
return self.getstate()
@@ -216,10 +219,11 @@ class Random(_random.Random):
Method=_MethodType, BuiltinMethod=_BuiltinMethodType):
"Return a random int in the range [0,n). Raises ValueError if n==0."
+ random = self.random
getrandbits = self.getrandbits
# Only call self.getrandbits if the original random() builtin method
# has not been overridden or if a new getrandbits() was supplied.
- if type(self.random) is BuiltinMethod or type(getrandbits) is Method:
+ if type(random) is BuiltinMethod or type(getrandbits) is Method:
k = n.bit_length() # don't use (n-1) here because n can be 1
r = getrandbits(k) # 0 <= r < 2**k
while r >= n:
@@ -227,7 +231,6 @@ class Random(_random.Random):
return r
# There's an overriden random() method but no new getrandbits() method,
# so we can only use random() from here.
- random = self.random
if n >= maxsize:
_warn("Underlying random() generator does not supply \n"
"enough bits to choose from a population range this large.\n"
@@ -251,10 +254,11 @@ class Random(_random.Random):
return seq[i]
def shuffle(self, x, random=None):
- """x, random=random.random -> shuffle list x in place; return None.
+ """Shuffle list x in place, and return None.
- Optional arg random is a 0-argument function returning a random
- float in [0.0, 1.0); by default, the standard random.random.
+ Optional argument random is a 0-argument function returning a
+ random float in [0.0, 1.0); if it is the default None, the
+ standard random.random will be used.
"""
diff --git a/Lib/re.py b/Lib/re.py
index fcea190d33..77f5e3fadb 100644
--- a/Lib/re.py
+++ b/Lib/re.py
@@ -85,16 +85,17 @@ resulting RE will match the second character.
\\ Matches a literal backslash.
This module exports the following functions:
- match Match a regular expression pattern to the beginning of a string.
- search Search a string for the presence of a pattern.
- sub Substitute occurrences of a pattern found in a string.
- subn Same as sub, but also return the number of substitutions made.
- split Split a string by the occurrences of a pattern.
- findall Find all occurrences of a pattern in a string.
- finditer Return an iterator yielding a match object for each match.
- compile Compile a pattern into a RegexObject.
- purge Clear the regular expression cache.
- escape Backslash all non-alphanumerics in a string.
+ match Match a regular expression pattern to the beginning of a string.
+ fullmatch Match a regular expression pattern to all of a string.
+ search Search a string for the presence of a pattern.
+ sub Substitute occurrences of a pattern found in a string.
+ subn Same as sub, but also return the number of substitutions made.
+ split Split a string by the occurrences of a pattern.
+ findall Find all occurrences of a pattern in a string.
+ finditer Return an iterator yielding a match object for each match.
+ compile Compile a pattern into a RegexObject.
+ purge Clear the regular expression cache.
+ escape Backslash all non-alphanumerics in a string.
Some of the functions in this module takes flags as optional parameters:
A ASCII For string patterns, make \w, \W, \b, \B, \d, \D
@@ -121,10 +122,9 @@ This module also defines an exception 'error'.
import sys
import sre_compile
import sre_parse
-import functools
# public symbols
-__all__ = [ "match", "search", "sub", "subn", "split", "findall",
+__all__ = [ "match", "fullmatch", "search", "sub", "subn", "split", "findall",
"compile", "purge", "template", "escape", "A", "I", "L", "M", "S", "X",
"U", "ASCII", "IGNORECASE", "LOCALE", "MULTILINE", "DOTALL", "VERBOSE",
"UNICODE", "error" ]
@@ -155,6 +155,11 @@ def match(pattern, string, flags=0):
a match object, or None if no match was found."""
return _compile(pattern, flags).match(string)
+def fullmatch(pattern, string, flags=0):
+ """Try to apply the pattern to all of the string, returning
+ a match object, or None if no match was found."""
+ return _compile(pattern, flags).fullmatch(string)
+
def search(pattern, string, flags=0):
"""Scan through string looking for a match to the pattern, returning
a match object, or None if no match was found."""
diff --git a/Lib/rlcompleter.py b/Lib/rlcompleter.py
index 3f970536dd..94f9341bee 100644
--- a/Lib/rlcompleter.py
+++ b/Lib/rlcompleter.py
@@ -29,6 +29,7 @@ Notes:
"""
+import atexit
import builtins
import __main__
@@ -158,3 +159,7 @@ except ImportError:
pass
else:
readline.set_completer(Completer().complete)
+ # Release references early at shutdown (the readline module's
+ # contents are quasi-immortal, and the completer function holds a
+ # reference to globals).
+ atexit.register(lambda: readline.set_completer(None))
diff --git a/Lib/runpy.py b/Lib/runpy.py
index 39c0e9f7dd..1e0a2be4f0 100644
--- a/Lib/runpy.py
+++ b/Lib/runpy.py
@@ -13,7 +13,7 @@ importers when locating support scripts as well as when importing modules.
import os
import sys
import importlib.machinery # importlib first so we can test #15386 via -m
-import imp
+import types
from pkgutil import read_code, get_loader, get_importer
__all__ = [
@@ -24,7 +24,7 @@ class _TempModule(object):
"""Temporarily replace a module in sys.modules with an empty namespace"""
def __init__(self, mod_name):
self.mod_name = mod_name
- self.module = imp.new_module(mod_name)
+ self.module = types.ModuleType(mod_name)
self._saved_module = []
def __enter__(self):
@@ -223,7 +223,12 @@ def run_path(path_name, init_globals=None, run_name=None):
run_name = "<run_path>"
pkg_name = run_name.rpartition(".")[0]
importer = get_importer(path_name)
- if isinstance(importer, (type(None), imp.NullImporter)):
+ # Trying to avoid importing imp so as to not consume the deprecation warning.
+ is_NullImporter = False
+ if type(importer).__module__ == 'imp':
+ if type(importer).__name__ == 'NullImporter':
+ is_NullImporter = True
+ if isinstance(importer, type(None)) or is_NullImporter:
# Not a valid sys.path entry, so run the code directly
# execfile() doesn't help as we want to allow compiled files
code, mod_loader = _get_code_from_file(run_name, path_name)
diff --git a/Lib/sched.py b/Lib/sched.py
index b9a7ad1afa..2e6b00a2a2 100644
--- a/Lib/sched.py
+++ b/Lib/sched.py
@@ -71,10 +71,10 @@ class scheduler:
"""
if kwargs is _sentinel:
kwargs = {}
+ event = Event(time, priority, action, argument, kwargs)
with self._lock:
- event = Event(time, priority, action, argument, kwargs)
heapq.heappush(self._queue, event)
- return event # The ID
+ return event # The ID
def enter(self, delay, priority, action, argument=(), kwargs=_sentinel):
"""A variant that specifies the time as a relative time.
@@ -82,9 +82,8 @@ class scheduler:
This is actually the more commonly used interface.
"""
- with self._lock:
- time = self.timefunc() + delay
- return self.enterabs(time, priority, action, argument, kwargs)
+ time = self.timefunc() + delay
+ return self.enterabs(time, priority, action, argument, kwargs)
def cancel(self, event):
"""Remove an event from the queue.
@@ -165,4 +164,4 @@ class scheduler:
# the actual order they would be retrieved.
with self._lock:
events = self._queue[:]
- return list(map(heapq.heappop, [events]*len(events)))
+ return list(map(heapq.heappop, [events]*len(events)))
diff --git a/Lib/selectors.py b/Lib/selectors.py
new file mode 100644
index 0000000000..c533f13d69
--- /dev/null
+++ b/Lib/selectors.py
@@ -0,0 +1,455 @@
+"""Selectors module.
+
+This module allows high-level and efficient I/O multiplexing, built upon the
+`select` module primitives.
+"""
+
+
+from abc import ABCMeta, abstractmethod
+from collections import namedtuple, Mapping
+import functools
+import select
+import sys
+
+
+# generic events, that must be mapped to implementation-specific ones
+EVENT_READ = (1 << 0)
+EVENT_WRITE = (1 << 1)
+
+
+def _fileobj_to_fd(fileobj):
+ """Return a file descriptor from a file object.
+
+ Parameters:
+ fileobj -- file object or file descriptor
+
+ Returns:
+ corresponding file descriptor
+ """
+ if isinstance(fileobj, int):
+ fd = fileobj
+ else:
+ try:
+ fd = int(fileobj.fileno())
+ except (AttributeError, TypeError, ValueError):
+ raise ValueError("Invalid file object: "
+ "{!r}".format(fileobj)) from None
+ if fd < 0:
+ raise ValueError("Invalid file descriptor: {}".format(fd))
+ return fd
+
+
+SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])
+"""Object used to associate a file object to its backing file descriptor,
+selected event mask and attached data."""
+
+
+class _SelectorMapping(Mapping):
+ """Mapping of file objects to selector keys."""
+
+ def __init__(self, selector):
+ self._selector = selector
+
+ def __len__(self):
+ return len(self._selector._fd_to_key)
+
+ def __getitem__(self, fileobj):
+ try:
+ return self._selector._fd_to_key[_fileobj_to_fd(fileobj)]
+ except KeyError:
+ raise KeyError("{!r} is not registered".format(fileobj)) from None
+
+ def __iter__(self):
+ return iter(self._selector._fd_to_key)
+
+
+class BaseSelector(metaclass=ABCMeta):
+ """Selector abstract base class.
+
+ A selector supports registering file objects to be monitored for specific
+ I/O events.
+
+ A file object is a file descriptor or any object with a `fileno()` method.
+ An arbitrary object can be attached to the file object, which can be used
+ for example to store context information, a callback, etc.
+
+ A selector can use various implementations (select(), poll(), epoll()...)
+ depending on the platform. The default `Selector` class uses the most
+ performant implementation on the current platform.
+ """
+
+ @abstractmethod
+ def register(self, fileobj, events, data=None):
+ """Register a file object.
+
+ Parameters:
+ fileobj -- file object or file descriptor
+ events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
+ data -- attached data
+
+ Returns:
+ SelectorKey instance
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def unregister(self, fileobj):
+ """Unregister a file object.
+
+ Parameters:
+ fileobj -- file object or file descriptor
+
+ Returns:
+ SelectorKey instance
+ """
+ raise NotImplementedError
+
+ def modify(self, fileobj, events, data=None):
+ """Change a registered file object monitored events or attached data.
+
+ Parameters:
+ fileobj -- file object or file descriptor
+ events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)
+ data -- attached data
+
+ Returns:
+ SelectorKey instance
+ """
+ self.unregister(fileobj)
+ return self.register(fileobj, events, data)
+
+ @abstractmethod
+ def select(self, timeout=None):
+ """Perform the actual selection, until some monitored file objects are
+ ready or a timeout expires.
+
+ Parameters:
+ timeout -- if timeout > 0, this specifies the maximum wait time, in
+ seconds
+ if timeout <= 0, the select() call won't block, and will
+ report the currently ready file objects
+ if timeout is None, select() will block until a monitored
+ file object becomes ready
+
+ Returns:
+ list of (key, events) for ready file objects
+ `events` is a bitwise mask of EVENT_READ|EVENT_WRITE
+ """
+ raise NotImplementedError
+
+ def close(self):
+ """Close the selector.
+
+ This must be called to make sure that any underlying resource is freed.
+ """
+ pass
+
+ def get_key(self, fileobj):
+ """Return the key associated to a registered file object.
+
+ Returns:
+ SelectorKey for this file object
+ """
+ mapping = self.get_map()
+ try:
+ return mapping[fileobj]
+ except KeyError:
+ raise KeyError("{!r} is not registered".format(fileobj)) from None
+
+ @abstractmethod
+ def get_map(self):
+ """Return a mapping of file objects to selector keys."""
+ raise NotImplementedError
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
+
+class _BaseSelectorImpl(BaseSelector):
+ """Base selector implementation."""
+
+ def __init__(self):
+ # this maps file descriptors to keys
+ self._fd_to_key = {}
+ # read-only mapping returned by get_map()
+ self._map = _SelectorMapping(self)
+
+ def register(self, fileobj, events, data=None):
+ if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
+ raise ValueError("Invalid events: {!r}".format(events))
+
+ key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data)
+
+ if key.fd in self._fd_to_key:
+ raise KeyError("{!r} (FD {}) is already "
+ "registered".format(fileobj, key.fd))
+
+ self._fd_to_key[key.fd] = key
+ return key
+
+ def unregister(self, fileobj):
+ try:
+ key = self._fd_to_key.pop(_fileobj_to_fd(fileobj))
+ except KeyError:
+ raise KeyError("{!r} is not registered".format(fileobj)) from None
+ return key
+
+ def modify(self, fileobj, events, data=None):
+ # TODO: Subclasses can probably optimize this even further.
+ try:
+ key = self._fd_to_key[_fileobj_to_fd(fileobj)]
+ except KeyError:
+ raise KeyError("{!r} is not registered".format(fileobj)) from None
+ if events != key.events:
+ self.unregister(fileobj)
+ key = self.register(fileobj, events, data)
+ elif data != key.data:
+ # Use a shortcut to update the data.
+ key = key._replace(data=data)
+ self._fd_to_key[key.fd] = key
+ return key
+
+ def close(self):
+ self._fd_to_key.clear()
+
+ def get_map(self):
+ return self._map
+
+ def _key_from_fd(self, fd):
+ """Return the key associated to a given file descriptor.
+
+ Parameters:
+ fd -- file descriptor
+
+ Returns:
+ corresponding key, or None if not found
+ """
+ try:
+ return self._fd_to_key[fd]
+ except KeyError:
+ return None
+
+
+class SelectSelector(_BaseSelectorImpl):
+ """Select-based selector."""
+
+ def __init__(self):
+ super().__init__()
+ self._readers = set()
+ self._writers = set()
+
+ def register(self, fileobj, events, data=None):
+ key = super().register(fileobj, events, data)
+ if events & EVENT_READ:
+ self._readers.add(key.fd)
+ if events & EVENT_WRITE:
+ self._writers.add(key.fd)
+ return key
+
+ def unregister(self, fileobj):
+ key = super().unregister(fileobj)
+ self._readers.discard(key.fd)
+ self._writers.discard(key.fd)
+ return key
+
+ if sys.platform == 'win32':
+ def _select(self, r, w, _, timeout=None):
+ r, w, x = select.select(r, w, w, timeout)
+ return r, w + x, []
+ else:
+ _select = select.select
+
+ def select(self, timeout=None):
+ timeout = None if timeout is None else max(timeout, 0)
+ ready = []
+ try:
+ r, w, _ = self._select(self._readers, self._writers, [], timeout)
+ except InterruptedError:
+ return ready
+ r = set(r)
+ w = set(w)
+ for fd in r | w:
+ events = 0
+ if fd in r:
+ events |= EVENT_READ
+ if fd in w:
+ events |= EVENT_WRITE
+
+ key = self._key_from_fd(fd)
+ if key:
+ ready.append((key, events & key.events))
+ return ready
+
+
+if hasattr(select, 'poll'):
+
+ class PollSelector(_BaseSelectorImpl):
+ """Poll-based selector."""
+
+ def __init__(self):
+ super().__init__()
+ self._poll = select.poll()
+
+ def register(self, fileobj, events, data=None):
+ key = super().register(fileobj, events, data)
+ poll_events = 0
+ if events & EVENT_READ:
+ poll_events |= select.POLLIN
+ if events & EVENT_WRITE:
+ poll_events |= select.POLLOUT
+ self._poll.register(key.fd, poll_events)
+ return key
+
+ def unregister(self, fileobj):
+ key = super().unregister(fileobj)
+ self._poll.unregister(key.fd)
+ return key
+
+ def select(self, timeout=None):
+ timeout = None if timeout is None else max(int(1000 * timeout), 0)
+ ready = []
+ try:
+ fd_event_list = self._poll.poll(timeout)
+ except InterruptedError:
+ return ready
+ for fd, event in fd_event_list:
+ events = 0
+ if event & ~select.POLLIN:
+ events |= EVENT_WRITE
+ if event & ~select.POLLOUT:
+ events |= EVENT_READ
+
+ key = self._key_from_fd(fd)
+ if key:
+ ready.append((key, events & key.events))
+ return ready
+
+
+if hasattr(select, 'epoll'):
+
+ class EpollSelector(_BaseSelectorImpl):
+ """Epoll-based selector."""
+
+ def __init__(self):
+ super().__init__()
+ self._epoll = select.epoll()
+
+ def fileno(self):
+ return self._epoll.fileno()
+
+ def register(self, fileobj, events, data=None):
+ key = super().register(fileobj, events, data)
+ epoll_events = 0
+ if events & EVENT_READ:
+ epoll_events |= select.EPOLLIN
+ if events & EVENT_WRITE:
+ epoll_events |= select.EPOLLOUT
+ self._epoll.register(key.fd, epoll_events)
+ return key
+
+ def unregister(self, fileobj):
+ key = super().unregister(fileobj)
+ self._epoll.unregister(key.fd)
+ return key
+
+ def select(self, timeout=None):
+ timeout = -1 if timeout is None else max(timeout, 0)
+ max_ev = len(self._fd_to_key)
+ ready = []
+ try:
+ fd_event_list = self._epoll.poll(timeout, max_ev)
+ except InterruptedError:
+ return ready
+ for fd, event in fd_event_list:
+ events = 0
+ if event & ~select.EPOLLIN:
+ events |= EVENT_WRITE
+ if event & ~select.EPOLLOUT:
+ events |= EVENT_READ
+
+ key = self._key_from_fd(fd)
+ if key:
+ ready.append((key, events & key.events))
+ return ready
+
+ def close(self):
+ self._epoll.close()
+ super().close()
+
+
+if hasattr(select, 'kqueue'):
+
+ class KqueueSelector(_BaseSelectorImpl):
+ """Kqueue-based selector."""
+
+ def __init__(self):
+ super().__init__()
+ self._kqueue = select.kqueue()
+
+ def fileno(self):
+ return self._kqueue.fileno()
+
+ def register(self, fileobj, events, data=None):
+ key = super().register(fileobj, events, data)
+ if events & EVENT_READ:
+ kev = select.kevent(key.fd, select.KQ_FILTER_READ,
+ select.KQ_EV_ADD)
+ self._kqueue.control([kev], 0, 0)
+ if events & EVENT_WRITE:
+ kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
+ select.KQ_EV_ADD)
+ self._kqueue.control([kev], 0, 0)
+ return key
+
+ def unregister(self, fileobj):
+ key = super().unregister(fileobj)
+ if key.events & EVENT_READ:
+ kev = select.kevent(key.fd, select.KQ_FILTER_READ,
+ select.KQ_EV_DELETE)
+ self._kqueue.control([kev], 0, 0)
+ if key.events & EVENT_WRITE:
+ kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,
+ select.KQ_EV_DELETE)
+ self._kqueue.control([kev], 0, 0)
+ return key
+
+ def select(self, timeout=None):
+ timeout = None if timeout is None else max(timeout, 0)
+ max_ev = len(self._fd_to_key)
+ ready = []
+ try:
+ kev_list = self._kqueue.control(None, max_ev, timeout)
+ except InterruptedError:
+ return ready
+ for kev in kev_list:
+ fd = kev.ident
+ flag = kev.filter
+ events = 0
+ if flag == select.KQ_FILTER_READ:
+ events |= EVENT_READ
+ if flag == select.KQ_FILTER_WRITE:
+ events |= EVENT_WRITE
+
+ key = self._key_from_fd(fd)
+ if key:
+ ready.append((key, events & key.events))
+ return ready
+
+ def close(self):
+ self._kqueue.close()
+ super().close()
+
+
+# Choose the best implementation: roughly, epoll|kqueue > poll > select.
+# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
+if 'KqueueSelector' in globals():
+ DefaultSelector = KqueueSelector
+elif 'EpollSelector' in globals():
+ DefaultSelector = EpollSelector
+elif 'PollSelector' in globals():
+ DefaultSelector = PollSelector
+else:
+ DefaultSelector = SelectSelector
diff --git a/Lib/shelve.py b/Lib/shelve.py
index cc1815e3eb..cef580e5cd 100644
--- a/Lib/shelve.py
+++ b/Lib/shelve.py
@@ -61,7 +61,7 @@ from io import BytesIO
import collections
-__all__ = ["Shelf","BsdDbShelf","DbfilenameShelf","open"]
+__all__ = ["Shelf", "BsdDbShelf", "DbfilenameShelf", "open"]
class _ClosedDict(collections.MutableMapping):
'Marker for a closed dict. Access attempts raise a ValueError.'
@@ -131,6 +131,12 @@ class Shelf(collections.MutableMapping):
except KeyError:
pass
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
def close(self):
self.sync()
try:
@@ -147,6 +153,7 @@ class Shelf(collections.MutableMapping):
def __del__(self):
if not hasattr(self, 'writeback'):
# __init__ didn't succeed, so don't bother closing
+ # see http://bugs.python.org/issue1339007 for details
return
self.close()
diff --git a/Lib/shutil.py b/Lib/shutil.py
index a5b2d240fe..502bb6782c 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -39,17 +39,20 @@ __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
"ignore_patterns", "chown", "which"]
# disk_usage is added later, if available on the platform
-class Error(EnvironmentError):
+class Error(OSError):
pass
-class SpecialFileError(EnvironmentError):
+class SameFileError(Error):
+ """Raised when source and destination are the same file."""
+
+class SpecialFileError(OSError):
"""Raised when trying to do a kind of operation (e.g. copying) which is
not supported on a special file (e.g. a named pipe)"""
-class ExecError(EnvironmentError):
+class ExecError(OSError):
"""Raised when a command could not be executed"""
-class ReadError(EnvironmentError):
+class ReadError(OSError):
"""Raised when an archive cannot be read"""
class RegistryError(Exception):
@@ -57,11 +60,6 @@ class RegistryError(Exception):
and unpacking registeries fails"""
-try:
- WindowsError
-except NameError:
- WindowsError = None
-
def copyfileobj(fsrc, fdst, length=16*1024):
"""copy data from file-like object fsrc to file-like object fdst"""
while 1:
@@ -90,7 +88,7 @@ def copyfile(src, dst, *, follow_symlinks=True):
"""
if _samefile(src, dst):
- raise Error("`%s` and `%s` are the same file" % (src, dst))
+ raise SameFileError("{!r} and {!r} are the same file".format(src, dst))
for fn in [src, dst]:
try:
@@ -221,6 +219,9 @@ def copy(src, dst, *, follow_symlinks=True):
If follow_symlinks is false, symlinks won't be followed. This
resembles GNU's "cp -P src dst".
+ If source and destination are the same file, a SameFileError will be
+ raised.
+
"""
if os.path.isdir(dst):
dst = os.path.join(dst, os.path.basename(src))
@@ -329,15 +330,13 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
# continue with other files
except Error as err:
errors.extend(err.args[0])
- except EnvironmentError as why:
+ except OSError as why:
errors.append((srcname, dstname, str(why)))
try:
copystat(src, dst)
except OSError as why:
- if WindowsError is not None and isinstance(why, WindowsError):
- # Copying file access times may fail on Windows
- pass
- else:
+ # Copying file access times may fail on Windows
+ if why.winerror is None:
errors.append((src, dst, str(why)))
if errors:
raise Error(errors)
@@ -356,24 +355,24 @@ def _rmtree_unsafe(path, onerror):
names = []
try:
names = os.listdir(path)
- except os.error:
+ except OSError:
onerror(os.listdir, path, sys.exc_info())
for name in names:
fullname = os.path.join(path, name)
try:
mode = os.lstat(fullname).st_mode
- except os.error:
+ except OSError:
mode = 0
if stat.S_ISDIR(mode):
_rmtree_unsafe(fullname, onerror)
else:
try:
os.unlink(fullname)
- except os.error:
+ except OSError:
onerror(os.unlink, fullname, sys.exc_info())
try:
os.rmdir(path)
- except os.error:
+ except OSError:
onerror(os.rmdir, path, sys.exc_info())
# Version using fd-based APIs to protect against races
@@ -464,7 +463,7 @@ def rmtree(path, ignore_errors=False, onerror=None):
_rmtree_safe_fd(fd, path, onerror)
try:
os.rmdir(path)
- except os.error:
+ except OSError:
onerror(os.rmdir, path, sys.exc_info())
else:
try:
diff --git a/Lib/site.py b/Lib/site.py
index 7e09701415..b0d8268f81 100644
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -58,17 +58,20 @@ Note that bletch is omitted because it doesn't exist; bar precedes foo
because bar.pth comes alphabetically before foo.pth; and spam is
omitted because it is not mentioned in either path configuration file.
-After these path manipulations, an attempt is made to import a module
+The readline module is also automatically configured to enable
+completion for systems that support it. This can be overriden in
+sitecustomize, usercustomize or PYTHONSTARTUP.
+
+After these operations, an attempt is made to import a module
named sitecustomize, which can perform arbitrary additional
site-specific customizations. If this import fails with an
ImportError exception, it is silently ignored.
-
"""
import sys
import os
-import re
import builtins
+import _sitebuiltins
# Prefixes for site-packages; add additional prefixes like /usr/local here
PREFIXES = [sys.prefix, sys.exec_prefix]
@@ -146,14 +149,14 @@ def addpackage(sitedir, name, known_paths):
and add that to known_paths, or execute it if it starts with 'import '.
"""
if known_paths is None:
- _init_pathinfo()
+ known_paths = _init_pathinfo()
reset = 1
else:
reset = 0
fullname = os.path.join(sitedir, name)
try:
f = open(fullname, "r")
- except IOError:
+ except OSError:
return
with f:
for n, line in enumerate(f):
@@ -196,7 +199,7 @@ def addsitedir(sitedir, known_paths=None):
known_paths.add(sitedircase)
try:
names = os.listdir(sitedir)
- except os.error:
+ except OSError:
return
names = [name for name in names if name.endswith(".pth")]
for name in sorted(names):
@@ -300,9 +303,7 @@ def getsitepackages(prefixes=None):
continue
seen.add(prefix)
- if sys.platform in ('os2emx', 'riscos'):
- sitepackages.append(os.path.join(prefix, "Lib", "site-packages"))
- elif os.sep == '/':
+ if os.sep == '/':
sitepackages.append(os.path.join(prefix, "lib",
"python" + sys.version[:3],
"site-packages"))
@@ -325,27 +326,15 @@ def addsitepackages(known_paths, prefixes=None):
"""Add site-packages (and possibly site-python) to sys.path"""
for sitedir in getsitepackages(prefixes):
if os.path.isdir(sitedir):
+ if "site-python" in sitedir:
+ import warnings
+ warnings.warn('"site-python" directories will not be '
+ 'supported in 3.5 anymore',
+ DeprecationWarning)
addsitedir(sitedir, known_paths)
return known_paths
-def setBEGINLIBPATH():
- """The OS/2 EMX port has optional extension modules that do double duty
- as DLLs (and must use the .DLL file extension) for other extensions.
- The library search path needs to be amended so these will be found
- during module import. Use BEGINLIBPATH so that these are at the start
- of the library search path.
-
- """
- dllpath = os.path.join(sys.prefix, "Lib", "lib-dynload")
- libpath = os.environ['BEGINLIBPATH'].split(';')
- if libpath[-1]:
- libpath.append(dllpath)
- else:
- libpath[-1] = dllpath
- os.environ['BEGINLIBPATH'] = ';'.join(libpath)
-
-
def setquit():
"""Define new builtins 'quit' and 'exit'.
@@ -360,126 +349,89 @@ def setquit():
else:
eof = 'Ctrl-D (i.e. EOF)'
- class Quitter(object):
- def __init__(self, name):
- self.name = name
- def __repr__(self):
- return 'Use %s() or %s to exit' % (self.name, eof)
- def __call__(self, code=None):
- # Shells like IDLE catch the SystemExit, but listen when their
- # stdin wrapper is closed.
- try:
- sys.stdin.close()
- except:
- pass
- raise SystemExit(code)
- builtins.quit = Quitter('quit')
- builtins.exit = Quitter('exit')
-
-
-class _Printer(object):
- """interactive prompt objects for printing the license text, a list of
- contributors and the copyright notice."""
+ builtins.quit = _sitebuiltins.Quitter('quit', eof)
+ builtins.exit = _sitebuiltins.Quitter('exit', eof)
- MAXLINES = 23
-
- def __init__(self, name, data, files=(), dirs=()):
- self.__name = name
- self.__data = data
- self.__files = files
- self.__dirs = dirs
- self.__lines = None
-
- def __setup(self):
- if self.__lines:
- return
- data = None
- for dir in self.__dirs:
- for filename in self.__files:
- filename = os.path.join(dir, filename)
- try:
- fp = open(filename, "r")
- data = fp.read()
- fp.close()
- break
- except IOError:
- pass
- if data:
- break
- if not data:
- data = self.__data
- self.__lines = data.split('\n')
- self.__linecnt = len(self.__lines)
-
- def __repr__(self):
- self.__setup()
- if len(self.__lines) <= self.MAXLINES:
- return "\n".join(self.__lines)
- else:
- return "Type %s() to see the full %s text" % ((self.__name,)*2)
-
- def __call__(self):
- self.__setup()
- prompt = 'Hit Return for more, or q (and Return) to quit: '
- lineno = 0
- while 1:
- try:
- for i in range(lineno, lineno + self.MAXLINES):
- print(self.__lines[i])
- except IndexError:
- break
- else:
- lineno += self.MAXLINES
- key = None
- while key is None:
- key = input(prompt)
- if key not in ('', 'q'):
- key = None
- if key == 'q':
- break
def setcopyright():
"""Set 'copyright' and 'credits' in builtins"""
- builtins.copyright = _Printer("copyright", sys.copyright)
+ builtins.copyright = _sitebuiltins._Printer("copyright", sys.copyright)
if sys.platform[:4] == 'java':
- builtins.credits = _Printer(
+ builtins.credits = _sitebuiltins._Printer(
"credits",
"Jython is maintained by the Jython developers (www.jython.org).")
else:
- builtins.credits = _Printer("credits", """\
+ builtins.credits = _sitebuiltins._Printer("credits", """\
Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
for supporting Python development. See www.python.org for more information.""")
here = os.path.dirname(os.__file__)
- builtins.license = _Printer(
+ builtins.license = _sitebuiltins._Printer(
"license",
- "See http://www.python.org/download/releases/%.5s/license/" % sys.version,
+ "See http://www.python.org/download/releases/%.5s/license" % sys.version,
["LICENSE.txt", "LICENSE"],
[os.path.join(here, os.pardir), here, os.curdir])
-class _Helper(object):
- """Define the builtin 'help'.
- This is a wrapper around pydoc.help (with a twist).
+def sethelper():
+ builtins.help = _sitebuiltins._Helper()
+def enablerlcompleter():
+ """Enable default readline configuration on interactive prompts, by
+ registering a sys.__interactivehook__.
+
+ If the readline module can be imported, the hook will set the Tab key
+ as completion key and register ~/.python_history as history file.
+ This can be overriden in the sitecustomize or usercustomize module,
+ or in a PYTHONSTARTUP file.
"""
+ def register_readline():
+ import atexit
+ try:
+ import readline
+ import rlcompleter
+ except ImportError:
+ return
- def __repr__(self):
- return "Type help() for interactive help, " \
- "or help(object) for help about object."
- def __call__(self, *args, **kwds):
- import pydoc
- return pydoc.help(*args, **kwds)
+ # Reading the initialization (config) file may not be enough to set a
+ # completion key, so we set one first and then read the file.
+ readline_doc = getattr(readline, '__doc__', '')
+ if readline_doc is not None and 'libedit' in readline_doc:
+ readline.parse_and_bind('bind ^I rl_complete')
+ else:
+ readline.parse_and_bind('tab: complete')
-def sethelper():
- builtins.help = _Helper()
+ try:
+ readline.read_init_file()
+ except OSError:
+ # An OSError here could have many causes, but the most likely one
+ # is that there's no .inputrc file (or .editrc file in the case of
+ # Mac OS X + libedit) in the expected location. In that case, we
+ # want to ignore the exception.
+ pass
+
+ if readline.get_history_item(1) is None:
+ # If no history was loaded, default to .python_history.
+ # The guard is necessary to avoid doubling history size at
+ # each interpreter exit when readline was already configured
+ # through a PYTHONSTARTUP hook, see:
+ # http://bugs.python.org/issue5845#msg198636
+ history = os.path.join(os.path.expanduser('~'),
+ '.python_history')
+ try:
+ readline.read_history_file(history)
+ except IOError:
+ pass
+ atexit.register(readline.write_history_file, history)
+
+ sys.__interactivehook__ = register_readline
def aliasmbcs():
"""On Windows, some default encodings are not provided by Python,
while they are always available as "mbcs" in each locale. Make
them usable by aliasing to "mbcs" in such a case."""
if sys.platform == 'win32':
- import locale, codecs
- enc = locale.getdefaultlocale()[1]
+ import _bootlocale, codecs
+ enc = _bootlocale.getpreferredencoding(False)
if enc.startswith('cp'): # "cp***" ?
try:
codecs.lookup(enc)
@@ -488,8 +440,7 @@ def aliasmbcs():
encodings._cache[enc] = encodings._unknown
encodings.aliases.aliases[enc] = 'mbcs'
-
-CONFIG_LINE = re.compile(r'^(?P<key>(\w|[-_])+)\s*=\s*(?P<value>.*)\s*$')
+CONFIG_LINE = r'^(?P<key>(\w|[-_])+)\s*=\s*(?P<value>.*)\s*$'
def venv(known_paths):
global PREFIXES, ENABLE_USER_SITE
@@ -512,12 +463,14 @@ def venv(known_paths):
]
if candidate_confs:
+ import re
+ config_line = re.compile(CONFIG_LINE)
virtual_conf = candidate_confs[0]
system_site = "true"
with open(virtual_conf) as f:
for line in f:
line = line.strip()
- m = CONFIG_LINE.match(line)
+ m = config_line.match(line)
if m:
d = m.groupdict()
key, value = d['key'].lower(), d['value']
@@ -589,11 +542,10 @@ def main():
ENABLE_USER_SITE = check_enableusersite()
known_paths = addusersitepackages(known_paths)
known_paths = addsitepackages(known_paths)
- if sys.platform == 'os2emx':
- setBEGINLIBPATH()
setquit()
setcopyright()
sethelper()
+ enablerlcompleter()
aliasmbcs()
execsitecustomize()
if ENABLE_USER_SITE:
diff --git a/Lib/smtpd.py b/Lib/smtpd.py
index 778d6d6280..1fa157ade7 100755
--- a/Lib/smtpd.py
+++ b/Lib/smtpd.py
@@ -121,8 +121,9 @@ class SMTPChannel(asynchat.async_chat):
})
max_command_size_limit = max(command_size_limits.values())
- def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT):
- asynchat.async_chat.__init__(self, conn)
+ def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
+ map=None):
+ asynchat.async_chat.__init__(self, conn, map=map)
self.smtp_server = server
self.conn = conn
self.addr = addr
@@ -137,7 +138,7 @@ class SMTPChannel(asynchat.async_chat):
self.num_bytes = 0
try:
self.peer = conn.getpeername()
- except socket.error as err:
+ except OSError as err:
# a race condition may occur if the other end is closing
# before we can get the peername
self.close()
@@ -576,11 +577,11 @@ class SMTPServer(asyncore.dispatcher):
channel_class = SMTPChannel
def __init__(self, localaddr, remoteaddr,
- data_size_limit=DATA_SIZE_DEFAULT):
+ data_size_limit=DATA_SIZE_DEFAULT, map=None):
self._localaddr = localaddr
self._remoteaddr = remoteaddr
self.data_size_limit = data_size_limit
- asyncore.dispatcher.__init__(self)
+ asyncore.dispatcher.__init__(self, map=map)
try:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
# try to re-use a server port if possible
@@ -597,7 +598,8 @@ class SMTPServer(asyncore.dispatcher):
def handle_accepted(self, conn, addr):
print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
- channel = self.channel_class(self, conn, addr, self.data_size_limit)
+ channel = self.channel_class(self, conn, addr, self.data_size_limit,
+ self._map)
# API for "doing something useful with the message"
def process_message(self, peer, mailfrom, rcpttos, data):
@@ -668,7 +670,7 @@ class PureProxy(SMTPServer):
except smtplib.SMTPRecipientsRefused as e:
print('got SMTPRecipientsRefused', file=DEBUGSTREAM)
refused = e.recipients
- except (socket.error, smtplib.SMTPException) as e:
+ except (OSError, smtplib.SMTPException) as e:
print('got', e.__class__, file=DEBUGSTREAM)
# All recipients were refused. If the exception had an associated
# error code, use it. Otherwise,fake it with a non-triggering
@@ -778,7 +780,7 @@ def parseargs():
if opt in ('-h', '--help'):
usage(0)
elif opt in ('-V', '--version'):
- print(__version__, file=sys.stderr)
+ print(__version__)
sys.exit(0)
elif opt in ('-n', '--nosetuid'):
options.setuid = 0
@@ -850,8 +852,7 @@ if __name__ == '__main__':
nobody = pwd.getpwnam('nobody')[2]
try:
os.setuid(nobody)
- except OSError as e:
- if e.errno != errno.EPERM: raise
+ except PermissionError:
print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)
sys.exit(1)
try:
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index 072b973e0e..6fc65f6b6a 100644
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -66,7 +66,7 @@ bCRLF = b"\r\n"
OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
# Exception classes used by this module.
-class SMTPException(Exception):
+class SMTPException(OSError):
"""Base class for all exceptions raised by this module."""
class SMTPServerDisconnected(SMTPException):
@@ -311,7 +311,7 @@ class SMTP:
try:
port = int(port)
except ValueError:
- raise socket.error("nonnumeric port")
+ raise OSError("nonnumeric port")
if not port:
port = self.default_port
if self.debuglevel > 0:
@@ -332,7 +332,7 @@ class SMTP:
s = s.encode("ascii")
try:
self.sock.sendall(s)
- except socket.error:
+ except OSError:
self.close()
raise SMTPServerDisconnected('Server not connected')
else:
@@ -365,7 +365,7 @@ class SMTP:
while 1:
try:
line = self.file.readline()
- except socket.error as e:
+ except OSError as e:
self.close()
raise SMTPServerDisconnected("Connection unexpectedly closed: "
+ str(e))
@@ -579,7 +579,7 @@ class SMTP:
def encode_cram_md5(challenge, user, password):
challenge = base64.decodebytes(challenge)
response = user + " " + hmac.HMAC(password.encode('ascii'),
- challenge).hexdigest()
+ challenge, 'md5').hexdigest()
return encode_base64(response.encode('ascii'), eol='')
def encode_plain(user, password):
@@ -664,10 +664,10 @@ class SMTP:
if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually "
"exclusive")
- if context is not None:
- self.sock = context.wrap_socket(self.sock)
- else:
- self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
+ if context is None:
+ context = ssl._create_stdlib_context(certfile=certfile,
+ keyfile=keyfile)
+ self.sock = context.wrap_socket(self.sock)
self.file = None
# RFC 3207:
# The client MUST discard any knowledge obtained from
@@ -880,6 +880,9 @@ if _have_ssl:
"exclusive")
self.keyfile = keyfile
self.certfile = certfile
+ if context is None:
+ context = ssl._create_stdlib_context(certfile=certfile,
+ keyfile=keyfile)
self.context = context
SMTP.__init__(self, host, port, local_hostname, timeout,
source_address)
@@ -889,10 +892,7 @@ if _have_ssl:
print('connect:', (host, port), file=stderr)
new_socket = socket.create_connection((host, port), timeout,
self.source_address)
- if self.context is not None:
- new_socket = self.context.wrap_socket(new_socket)
- else:
- new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
+ new_socket = self.context.wrap_socket(new_socket)
return new_socket
__all__.append("SMTP_SSL")
@@ -934,7 +934,7 @@ class LMTP(SMTP):
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.file = None
self.sock.connect(host)
- except socket.error:
+ except OSError:
if self.debuglevel > 0:
print('connect fail:', host, file=stderr)
if self.sock:
diff --git a/Lib/sndhdr.py b/Lib/sndhdr.py
index 9f5dcc90d4..240e5072f8 100644
--- a/Lib/sndhdr.py
+++ b/Lib/sndhdr.py
@@ -11,7 +11,7 @@ The return tuple contains the following items, in this order:
- number of bits/sample, or 'U' for U-LAW, or 'A' for A-LAW
If the file doesn't have a recognizable type, it returns None.
-If the file can't be opened, IOError is raised.
+If the file can't be opened, OSError is raised.
To compute the total time, divide the number of frames by the
sampling rate (a frame contains a sample for each channel).
@@ -137,14 +137,17 @@ tests.append(test_voc)
def test_wav(h, f):
+ import wave
# 'RIFF' <len> 'WAVE' 'fmt ' <len>
if not h.startswith(b'RIFF') or h[8:12] != b'WAVE' or h[12:16] != b'fmt ':
return None
- style = get_short_le(h[20:22])
- nchannels = get_short_le(h[22:24])
- rate = get_long_le(h[24:28])
- sample_bits = get_short_le(h[34:36])
- return 'wav', rate, nchannels, -1, sample_bits
+ f.seek(0)
+ try:
+ w = wave.openfp(f, 'r')
+ except (EOFError, wave.Error):
+ return None
+ return ('wav', w.getframerate(), w.getnchannels(),
+ w.getnframes(), 8*w.getsampwidth())
tests.append(test_wav)
@@ -230,7 +233,7 @@ def testall(list, recursive, toplevel):
sys.stdout.flush()
try:
print(what(filename))
- except IOError:
+ except OSError:
print('*** not found ***')
if __name__ == '__main__':
diff --git a/Lib/socket.py b/Lib/socket.py
index d4f1b65e46..6d67b3d282 100644
--- a/Lib/socket.py
+++ b/Lib/socket.py
@@ -48,6 +48,7 @@ import _socket
from _socket import *
import os, sys, io
+from enum import IntEnum
try:
import errno
@@ -60,6 +61,30 @@ EWOULDBLOCK = getattr(errno, 'EWOULDBLOCK', 11)
__all__ = ["getfqdn", "create_connection"]
__all__.extend(os._get_exports_list(_socket))
+# Set up the socket.AF_* socket.SOCK_* constants as members of IntEnums for
+# nicer string representations.
+# Note that _socket only knows about the integer values. The public interface
+# in this module understands the enums and translates them back from integers
+# where needed (e.g. .family property of a socket object).
+AddressFamily = IntEnum('AddressFamily',
+ {name: value for name, value in globals().items()
+ if name.isupper() and name.startswith('AF_')})
+globals().update(AddressFamily.__members__)
+
+SocketType = IntEnum('SocketType',
+ {name: value for name, value in globals().items()
+ if name.isupper() and name.startswith('SOCK_')})
+globals().update(SocketType.__members__)
+
+def _intenum_converter(value, enum_klass):
+ """Convert a numeric family value to an IntEnum member.
+
+ If it's not a known member, return the numeric value itself.
+ """
+ try:
+ return enum_klass(value)
+ except ValueError:
+ return value
_realsocket = socket
@@ -91,6 +116,10 @@ class socket(_socket.socket):
__slots__ = ["__weakref__", "_io_refs", "_closed"]
def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None):
+ # For user code address family and type values are IntEnum members, but
+ # for the underlying _socket.socket they're just integers. The
+ # constructor of _socket.socket converts the given argument to an
+ # integer automatically.
_socket.socket.__init__(self, family, type, proto, fileno)
self._io_refs = 0
self._closed = False
@@ -103,13 +132,32 @@ class socket(_socket.socket):
self.close()
def __repr__(self):
- """Wrap __repr__() to reveal the real class name."""
- s = _socket.socket.__repr__(self)
- if s.startswith("<socket object"):
- s = "<%s.%s%s%s" % (self.__class__.__module__,
- self.__class__.__name__,
- getattr(self, '_closed', False) and " [closed] " or "",
- s[7:])
+ """Wrap __repr__() to reveal the real class name and socket
+ address(es).
+ """
+ closed = getattr(self, '_closed', False)
+ s = "<%s.%s%s fd=%i, family=%s, type=%s, proto=%i" \
+ % (self.__class__.__module__,
+ self.__class__.__name__,
+ " [closed]" if closed else "",
+ self.fileno(),
+ self.family,
+ self.type,
+ self.proto)
+ if not closed:
+ try:
+ laddr = self.getsockname()
+ if laddr:
+ s += ", laddr=%s" % str(laddr)
+ except error:
+ pass
+ try:
+ raddr = self.getpeername()
+ if raddr:
+ s += ", raddr=%s" % str(raddr)
+ except error:
+ pass
+ s += '>'
return s
def __getstate__(self):
@@ -118,7 +166,8 @@ class socket(_socket.socket):
def dup(self):
"""dup() -> socket object
- Return a new socket object connected to the same system resource.
+ Duplicate the socket. Return a new socket object connected to the same
+ system resource. The new socket is non-inheritable.
"""
fd = dup(self.fileno())
sock = self.__class__(self.family, self.type, self.proto, fileno=fd)
@@ -210,6 +259,31 @@ class socket(_socket.socket):
self._closed = True
return super().detach()
+ @property
+ def family(self):
+ """Read-only access to the address family for this socket.
+ """
+ return _intenum_converter(super().family, AddressFamily)
+
+ @property
+ def type(self):
+ """Read-only access to the socket type.
+ """
+ return _intenum_converter(super().type, SocketType)
+
+ if os.name == 'nt':
+ def get_inheritable(self):
+ return os.get_handle_inheritable(self.fileno())
+ def set_inheritable(self, inheritable):
+ os.set_handle_inheritable(self.fileno(), inheritable)
+ else:
+ def get_inheritable(self):
+ return os.get_inheritable(self.fileno())
+ def set_inheritable(self, inheritable):
+ os.set_inheritable(self.fileno(), inheritable)
+ get_inheritable.__doc__ = "Get the inheritable flag of the socket"
+ set_inheritable.__doc__ = "Set the inheritable flag of the socket"
+
def fromfd(fd, family, type, proto=0):
""" fromfd(fd, family, type[, proto]) -> socket object
@@ -291,7 +365,7 @@ class SocketIO(io.RawIOBase):
self._checkClosed()
self._checkReadable()
if self._timeout_occurred:
- raise IOError("cannot read from timed out object")
+ raise OSError("cannot read from timed out object")
while True:
try:
return self._sock.recv_into(b)
@@ -435,3 +509,27 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
raise err
else:
raise error("getaddrinfo returns an empty list")
+
+def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
+ """Resolve host and port into list of address info entries.
+
+ Translate the host/port argument into a sequence of 5-tuples that contain
+ all the necessary arguments for creating a socket connected to that service.
+ host is a domain name, a string representation of an IPv4/v6 address or
+ None. port is a string service name such as 'http', a numeric port number or
+ None. By passing None as the value of host and port, you can pass NULL to
+ the underlying C API.
+
+ The family, type and proto arguments can be optionally specified in order to
+ narrow the list of addresses returned. Passing zero as a value for each of
+ these arguments selects the full range of results.
+ """
+ # We override this function since we want to translate the numeric family
+ # and socket type values to enum constants.
+ addrlist = []
+ for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
+ af, socktype, proto, canonname, sa = res
+ addrlist.append((_intenum_converter(af, AddressFamily),
+ _intenum_converter(socktype, SocketType),
+ proto, canonname, sa))
+ return addrlist
diff --git a/Lib/socketserver.py b/Lib/socketserver.py
index 8332fdf66d..e9e4e4e4ad 100644
--- a/Lib/socketserver.py
+++ b/Lib/socketserver.py
@@ -299,7 +299,7 @@ class BaseServer:
"""
try:
request, client_address = self.get_request()
- except socket.error:
+ except OSError:
return
if self.verify_request(request, client_address):
try:
@@ -479,7 +479,7 @@ class TCPServer(BaseServer):
#explicitly shutdown. socket.close() merely releases
#the socket and waits for GC to perform the actual close.
request.shutdown(socket.SHUT_WR)
- except socket.error:
+ except OSError:
pass #some platforms may raise ENOTCONN here
self.close_request(request)
@@ -532,7 +532,7 @@ class ForkingMixIn:
# children.
try:
pid, status = os.waitpid(0, 0)
- except os.error:
+ except OSError:
pid = None
if pid not in self.active_children: continue
self.active_children.remove(pid)
@@ -545,7 +545,7 @@ class ForkingMixIn:
for child in self.active_children:
try:
pid, status = os.waitpid(child, os.WNOHANG)
- except os.error:
+ except OSError:
pid = None
if not pid: continue
try:
diff --git a/Lib/sqlite3/test/dbapi.py b/Lib/sqlite3/test/dbapi.py
index b7ec1ad0d9..04649fc549 100644
--- a/Lib/sqlite3/test/dbapi.py
+++ b/Lib/sqlite3/test/dbapi.py
@@ -28,6 +28,9 @@ try:
except ImportError:
threading = None
+from test.support import TESTFN, unlink
+
+
class ModuleTests(unittest.TestCase):
def CheckAPILevel(self):
self.assertEqual(sqlite.apilevel, "2.0",
@@ -163,6 +166,21 @@ class ConnectionTests(unittest.TestCase):
with self.assertRaises(AttributeError):
self.cx.in_transaction = True
+ def CheckOpenUri(self):
+ if sqlite.sqlite_version_info < (3, 7, 7):
+ with self.assertRaises(sqlite.NotSupportedError):
+ sqlite.connect(':memory:', uri=True)
+ return
+ self.addCleanup(unlink, TESTFN)
+ with sqlite.connect(TESTFN) as cx:
+ cx.execute('create table test(id integer)')
+ with sqlite.connect('file:' + TESTFN, uri=True) as cx:
+ cx.execute('insert into test(id) values(0)')
+ with sqlite.connect('file:' + TESTFN + '?mode=ro', uri=True) as cx:
+ with self.assertRaises(sqlite.OperationalError):
+ cx.execute('insert into test(id) values(1)')
+
+
class CursorTests(unittest.TestCase):
def setUp(self):
self.cx = sqlite.connect(":memory:")
diff --git a/Lib/sre_compile.py b/Lib/sre_compile.py
index b984a54b03..3a5083fde8 100644
--- a/Lib/sre_compile.py
+++ b/Lib/sre_compile.py
@@ -13,6 +13,7 @@
import _sre, sys
import sre_parse
from sre_constants import *
+from _sre import MAXREPEAT
assert _sre.MAGIC == MAGIC, "SRE module mismatch"
@@ -64,13 +65,6 @@ def _compile(code, pattern, flags):
elif op in REPEATING_CODES:
if flags & SRE_FLAG_TEMPLATE:
raise error("internal: unsupported template operator")
- emit(OPCODES[REPEAT])
- skip = _len(code); emit(0)
- emit(av[0])
- emit(av[1])
- _compile(code, av[2], flags)
- emit(OPCODES[SUCCESS])
- code[skip] = _len(code) - skip
elif _simple(av) and op is not REPEAT:
if op is MAX_REPEAT:
emit(OPCODES[REPEAT_ONE])
@@ -207,158 +201,143 @@ def _compile_charset(charset, flags, code, fixup=None):
def _optimize_charset(charset, fixup):
# internal: optimize character set
out = []
- outappend = out.append
- charmap = [0]*256
- try:
- for op, av in charset:
- if op is NEGATE:
- outappend((op, av))
- elif op is LITERAL:
- charmap[fixup(av)] = 1
- elif op is RANGE:
- for i in range(fixup(av[0]), fixup(av[1])+1):
- charmap[i] = 1
- elif op is CATEGORY:
- # XXX: could append to charmap tail
- return charset # cannot compress
- except IndexError:
- # character set contains unicode characters
- return _optimize_unicode(charset, fixup)
+ tail = []
+ charmap = bytearray(256)
+ for op, av in charset:
+ while True:
+ try:
+ if op is LITERAL:
+ charmap[fixup(av)] = 1
+ elif op is RANGE:
+ for i in range(fixup(av[0]), fixup(av[1])+1):
+ charmap[i] = 1
+ elif op is NEGATE:
+ out.append((op, av))
+ else:
+ tail.append((op, av))
+ except IndexError:
+ if len(charmap) == 256:
+ # character set contains non-UCS1 character codes
+ charmap += b'\0' * 0xff00
+ continue
+ # character set contains non-BMP character codes
+ tail.append((op, av))
+ break
+
# compress character map
- i = p = n = 0
runs = []
- runsappend = runs.append
- for c in charmap:
- if c:
- if n == 0:
- p = i
- n = n + 1
- elif n:
- runsappend((p, n))
- n = 0
- i = i + 1
- if n:
- runsappend((p, n))
- if len(runs) <= 2:
+ q = 0
+ while True:
+ p = charmap.find(1, q)
+ if p < 0:
+ break
+ if len(runs) >= 2:
+ runs = None
+ break
+ q = charmap.find(0, p)
+ if q < 0:
+ runs.append((p, len(charmap)))
+ break
+ runs.append((p, q))
+ if runs is not None:
# use literal/range
- for p, n in runs:
- if n == 1:
- outappend((LITERAL, p))
+ for p, q in runs:
+ if q - p == 1:
+ out.append((LITERAL, p))
else:
- outappend((RANGE, (p, p+n-1)))
+ out.append((RANGE, (p, q - 1)))
+ out += tail
if len(out) < len(charset):
return out
- else:
- # use bitmap
+ return charset
+
+ # use bitmap
+ if len(charmap) == 256:
data = _mk_bitmap(charmap)
- outappend((CHARSET, data))
+ out.append((CHARSET, data))
+ out += tail
return out
- return charset
-def _mk_bitmap(bits):
- data = []
- dataappend = data.append
- if _sre.CODESIZE == 2:
- start = (1, 0)
- else:
- start = (1, 0)
- m, v = start
- for c in bits:
- if c:
- v = v + m
- m = m + m
- if m > MAXCODE:
- dataappend(v)
- m, v = start
- return data
-
-# To represent a big charset, first a bitmap of all characters in the
-# set is constructed. Then, this bitmap is sliced into chunks of 256
-# characters, duplicate chunks are eliminated, and each chunk is
-# given a number. In the compiled expression, the charset is
-# represented by a 32-bit word sequence, consisting of one word for
-# the number of different chunks, a sequence of 256 bytes (64 words)
-# of chunk numbers indexed by their original chunk position, and a
-# sequence of 256-bit chunks (8 words each).
-
-# Compression is normally good: in a typical charset, large ranges of
-# Unicode will be either completely excluded (e.g. if only cyrillic
-# letters are to be matched), or completely included (e.g. if large
-# subranges of Kanji match). These ranges will be represented by
-# chunks of all one-bits or all zero-bits.
-
-# Matching can be also done efficiently: the more significant byte of
-# the Unicode character is an index into the chunk number, and the
-# less significant byte is a bit index in the chunk (just like the
-# CHARSET matching).
-
-# The BIGCHARSET opcode still supports only subsets
-# of the basic multilingual plane; an efficient representation
-# for all of Unicode has not yet been developed. This means,
-# in particular, that negated charsets cannot be represented as
-# bigcharsets.
-
-def _optimize_unicode(charset, fixup):
- try:
- import array
- except ImportError:
- return charset
- charmap = [0]*65536
- negate = 0
- try:
- for op, av in charset:
- if op is NEGATE:
- negate = 1
- elif op is LITERAL:
- charmap[fixup(av)] = 1
- elif op is RANGE:
- for i in range(fixup(av[0]), fixup(av[1])+1):
- charmap[i] = 1
- elif op is CATEGORY:
- # XXX: could expand category
- return charset # cannot compress
- except IndexError:
- # non-BMP characters; XXX now they should work
- return charset
- if negate:
- if sys.maxunicode != 65535:
- # XXX: negation does not work with big charsets
- # XXX2: now they should work, but removing this will make the
- # charmap 17 times bigger
- return charset
- for i in range(65536):
- charmap[i] = not charmap[i]
+ # To represent a big charset, first a bitmap of all characters in the
+ # set is constructed. Then, this bitmap is sliced into chunks of 256
+ # characters, duplicate chunks are eliminated, and each chunk is
+ # given a number. In the compiled expression, the charset is
+ # represented by a 32-bit word sequence, consisting of one word for
+ # the number of different chunks, a sequence of 256 bytes (64 words)
+ # of chunk numbers indexed by their original chunk position, and a
+ # sequence of 256-bit chunks (8 words each).
+
+ # Compression is normally good: in a typical charset, large ranges of
+ # Unicode will be either completely excluded (e.g. if only cyrillic
+ # letters are to be matched), or completely included (e.g. if large
+ # subranges of Kanji match). These ranges will be represented by
+ # chunks of all one-bits or all zero-bits.
+
+ # Matching can be also done efficiently: the more significant byte of
+ # the Unicode character is an index into the chunk number, and the
+ # less significant byte is a bit index in the chunk (just like the
+ # CHARSET matching).
+
+ charmap = bytes(charmap) # should be hashable
comps = {}
- mapping = [0]*256
+ mapping = bytearray(256)
block = 0
- data = []
- for i in range(256):
- chunk = tuple(charmap[i*256:(i+1)*256])
- new = comps.setdefault(chunk, block)
- mapping[i] = new
- if new == block:
- block = block + 1
- data = data + _mk_bitmap(chunk)
- header = [block]
- if _sre.CODESIZE == 2:
- code = 'H'
- else:
- code = 'I'
- # Convert block indices to byte array of 256 bytes
- mapping = array.array('B', mapping).tobytes()
- # Convert byte array to word array
- mapping = array.array(code, mapping)
- assert mapping.itemsize == _sre.CODESIZE
- assert len(mapping) * mapping.itemsize == 256
- header = header + mapping.tolist()
- data[0:0] = header
- return [(BIGCHARSET, data)]
+ data = bytearray()
+ for i in range(0, 65536, 256):
+ chunk = charmap[i: i + 256]
+ if chunk in comps:
+ mapping[i // 256] = comps[chunk]
+ else:
+ mapping[i // 256] = comps[chunk] = block
+ block += 1
+ data += chunk
+ data = _mk_bitmap(data)
+ data[0:0] = [block] + _bytes_to_codes(mapping)
+ out.append((BIGCHARSET, data))
+ out += tail
+ return out
+
+_CODEBITS = _sre.CODESIZE * 8
+_BITS_TRANS = b'0' + b'1' * 255
+def _mk_bitmap(bits, _CODEBITS=_CODEBITS, _int=int):
+ s = bits.translate(_BITS_TRANS)[::-1]
+ return [_int(s[i - _CODEBITS: i], 2)
+ for i in range(len(s), 0, -_CODEBITS)]
+
+def _bytes_to_codes(b):
+ # Convert block indices to word array
+ import array
+ a = array.array('I', b)
+ assert a.itemsize == _sre.CODESIZE
+ assert len(a) * a.itemsize == len(b)
+ return a.tolist()
def _simple(av):
# check if av is a "simple" operator
lo, hi = av[2].getwidth()
return lo == hi == 1 and av[2][0][0] != SUBPATTERN
+def _generate_overlap_table(prefix):
+ """
+ Generate an overlap table for the following prefix.
+ An overlap table is a table of the same size as the prefix which
+ informs about the potential self-overlap for each index in the prefix:
+ - if overlap[i] == 0, prefix[i:] can't overlap prefix[0:...]
+ - if overlap[i] == k with 0 < k <= i, prefix[i-k+1:i+1] overlaps with
+ prefix[0:k]
+ """
+ table = [0] * len(prefix)
+ for i in range(1, len(prefix)):
+ idx = table[i - 1]
+ while prefix[i] != prefix[idx]:
+ if idx == 0:
+ table[i] = 0
+ break
+ idx = table[idx - 1]
+ else:
+ table[i] = idx + 1
+ return table
+
def _compile_info(code, pattern, flags):
# internal: compile an info block. in the current version,
# this contains min/max pattern width, and an optional literal
@@ -455,12 +434,7 @@ def _compile_info(code, pattern, flags):
emit(prefix_skip) # skip
code.extend(prefix)
# generate overlap table
- table = [-1] + ([0]*len(prefix))
- for i in range(len(prefix)):
- table[i+1] = table[i]+1
- while table[i+1] > 0 and prefix[i] != prefix[table[i+1]-1]:
- table[i+1] = table[table[i+1]-1]+1
- code.extend(table[1:]) # don't store first entry
+ code.extend(_generate_overlap_table(prefix))
elif charset:
_compile_charset(charset, flags, code)
code[skip] = len(code) - skip
diff --git a/Lib/sre_constants.py b/Lib/sre_constants.py
index 3fb5eac322..23e3516006 100644
--- a/Lib/sre_constants.py
+++ b/Lib/sre_constants.py
@@ -15,11 +15,7 @@
MAGIC = 20031017
-try:
- from _sre import MAXREPEAT
-except ImportError:
- import _sre
- MAXREPEAT = _sre.MAXREPEAT = 65535
+from _sre import MAXREPEAT
# SRE standard exception (access as sre.error)
# should this really be here?
@@ -254,6 +250,8 @@ if __name__ == "__main__":
f.write("#define SRE_FLAG_DOTALL %d\n" % SRE_FLAG_DOTALL)
f.write("#define SRE_FLAG_UNICODE %d\n" % SRE_FLAG_UNICODE)
f.write("#define SRE_FLAG_VERBOSE %d\n" % SRE_FLAG_VERBOSE)
+ f.write("#define SRE_FLAG_DEBUG %d\n" % SRE_FLAG_DEBUG)
+ f.write("#define SRE_FLAG_ASCII %d\n" % SRE_FLAG_ASCII)
f.write("#define SRE_INFO_PREFIX %d\n" % SRE_INFO_PREFIX)
f.write("#define SRE_INFO_LITERAL %d\n" % SRE_INFO_LITERAL)
diff --git a/Lib/sre_parse.py b/Lib/sre_parse.py
index 8a77790b08..6583ef6c4a 100644
--- a/Lib/sre_parse.py
+++ b/Lib/sre_parse.py
@@ -15,6 +15,7 @@
import sys
from sre_constants import *
+from _sre import MAXREPEAT
SPECIAL_CHARS = ".\\[{()*+?^$|"
REPEAT_CHARS = "*+?{"
@@ -768,35 +769,33 @@ def parse_template(source, pattern):
# group references
s = Tokenizer(source)
sget = s.get
- p = []
- a = p.append
- def literal(literal, p=p, pappend=a):
- if p and p[-1][0] is LITERAL:
- p[-1] = LITERAL, p[-1][1] + literal
- else:
- pappend((LITERAL, literal))
- sep = source[:0]
- if isinstance(sep, str):
- makechar = chr
- else:
- makechar = chr
- while 1:
+ groups = []
+ literals = []
+ literal = []
+ lappend = literal.append
+ def addgroup(index):
+ if literal:
+ literals.append(''.join(literal))
+ del literal[:]
+ groups.append((len(literals), index))
+ literals.append(None)
+ while True:
this = sget()
if this is None:
break # end of replacement string
- if this and this[0] == "\\":
+ if this[0] == "\\":
# group
- c = this[1:2]
+ c = this[1]
if c == "g":
name = ""
if s.match("<"):
- while 1:
+ while True:
char = sget()
if char is None:
raise error("unterminated group name")
if char == ">":
break
- name = name + char
+ name += char
if not name:
raise error("missing group name")
try:
@@ -810,50 +809,38 @@ def parse_template(source, pattern):
index = pattern.groupindex[name]
except KeyError:
raise IndexError("unknown group name")
- a((MARK, index))
+ addgroup(index)
elif c == "0":
if s.next in OCTDIGITS:
- this = this + sget()
+ this += sget()
if s.next in OCTDIGITS:
- this = this + sget()
- literal(makechar(int(this[1:], 8) & 0xff))
+ this += sget()
+ lappend(chr(int(this[1:], 8) & 0xff))
elif c in DIGITS:
isoctal = False
if s.next in DIGITS:
- this = this + sget()
+ this += sget()
if (c in OCTDIGITS and this[2] in OCTDIGITS and
s.next in OCTDIGITS):
- this = this + sget()
+ this += sget()
isoctal = True
- literal(makechar(int(this[1:], 8) & 0xff))
+ lappend(chr(int(this[1:], 8) & 0xff))
if not isoctal:
- a((MARK, int(this[1:])))
+ addgroup(int(this[1:]))
else:
try:
- this = makechar(ESCAPES[this][1])
+ this = chr(ESCAPES[this][1])
except KeyError:
pass
- literal(this)
+ lappend(this)
else:
- literal(this)
- # convert template to groups and literals lists
- i = 0
- groups = []
- groupsappend = groups.append
- literals = [None] * len(p)
- if isinstance(source, str):
- encode = lambda x: x
- else:
+ lappend(this)
+ if literal:
+ literals.append(''.join(literal))
+ if not isinstance(source, str):
# The tokenizer implicitly decodes bytes objects as latin-1, we must
# therefore re-encode the final representation.
- encode = lambda x: x.encode('latin-1')
- for c, s in p:
- if c is MARK:
- groupsappend((i, s))
- # literal[i] is already None
- else:
- literals[i] = encode(s)
- i = i + 1
+ literals = [None if s is None else s.encode('latin-1') for s in literals]
return groups, literals
def expand_template(template, match):
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 06437b3046..4c155ea7d4 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -52,10 +52,47 @@ PROTOCOL_SSLv2
PROTOCOL_SSLv3
PROTOCOL_SSLv23
PROTOCOL_TLSv1
+PROTOCOL_TLSv1_1
+PROTOCOL_TLSv1_2
+
+The following constants identify various SSL alert message descriptions as per
+http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6
+
+ALERT_DESCRIPTION_CLOSE_NOTIFY
+ALERT_DESCRIPTION_UNEXPECTED_MESSAGE
+ALERT_DESCRIPTION_BAD_RECORD_MAC
+ALERT_DESCRIPTION_RECORD_OVERFLOW
+ALERT_DESCRIPTION_DECOMPRESSION_FAILURE
+ALERT_DESCRIPTION_HANDSHAKE_FAILURE
+ALERT_DESCRIPTION_BAD_CERTIFICATE
+ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE
+ALERT_DESCRIPTION_CERTIFICATE_REVOKED
+ALERT_DESCRIPTION_CERTIFICATE_EXPIRED
+ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN
+ALERT_DESCRIPTION_ILLEGAL_PARAMETER
+ALERT_DESCRIPTION_UNKNOWN_CA
+ALERT_DESCRIPTION_ACCESS_DENIED
+ALERT_DESCRIPTION_DECODE_ERROR
+ALERT_DESCRIPTION_DECRYPT_ERROR
+ALERT_DESCRIPTION_PROTOCOL_VERSION
+ALERT_DESCRIPTION_INSUFFICIENT_SECURITY
+ALERT_DESCRIPTION_INTERNAL_ERROR
+ALERT_DESCRIPTION_USER_CANCELLED
+ALERT_DESCRIPTION_NO_RENEGOTIATION
+ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION
+ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE
+ALERT_DESCRIPTION_UNRECOGNIZED_NAME
+ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE
+ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE
+ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY
"""
import textwrap
import re
+import sys
+import os
+from collections import namedtuple
+from enum import Enum as _Enum
import _ssl # if we can't import it, let the error propagate
@@ -66,35 +103,26 @@ from _ssl import (
SSLSyscallError, SSLEOFError,
)
from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
-from _ssl import (
- OP_ALL, OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_TLSv1,
- OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE
- )
-try:
- from _ssl import OP_NO_COMPRESSION
-except ImportError:
- pass
-try:
- from _ssl import OP_SINGLE_ECDH_USE
-except ImportError:
- pass
+from _ssl import (VERIFY_DEFAULT, VERIFY_CRL_CHECK_LEAF, VERIFY_CRL_CHECK_CHAIN,
+ VERIFY_X509_STRICT)
+from _ssl import txt2obj as _txt2obj, nid2obj as _nid2obj
from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes
-from _ssl import (
- SSL_ERROR_ZERO_RETURN,
- SSL_ERROR_WANT_READ,
- SSL_ERROR_WANT_WRITE,
- SSL_ERROR_WANT_X509_LOOKUP,
- SSL_ERROR_SYSCALL,
- SSL_ERROR_SSL,
- SSL_ERROR_WANT_CONNECT,
- SSL_ERROR_EOF,
- SSL_ERROR_INVALID_ERROR_CODE,
- )
+
+def _import_symbols(prefix):
+ for n in dir(_ssl):
+ if n.startswith(prefix):
+ globals()[n] = getattr(_ssl, n)
+
+_import_symbols('OP_')
+_import_symbols('ALERT_DESCRIPTION_')
+_import_symbols('SSL_ERROR_')
+
from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN
-from _ssl import (PROTOCOL_SSLv3, PROTOCOL_SSLv23,
- PROTOCOL_TLSv1)
+
+from _ssl import PROTOCOL_SSLv3, PROTOCOL_SSLv23, PROTOCOL_TLSv1
from _ssl import _OPENSSL_API_VERSION
+
_PROTOCOL_NAMES = {
PROTOCOL_TLSv1: "TLSv1",
PROTOCOL_SSLv23: "SSLv23",
@@ -108,13 +136,27 @@ except ImportError:
else:
_PROTOCOL_NAMES[PROTOCOL_SSLv2] = "SSLv2"
+try:
+ from _ssl import PROTOCOL_TLSv1_1, PROTOCOL_TLSv1_2
+except ImportError:
+ pass
+else:
+ _PROTOCOL_NAMES[PROTOCOL_TLSv1_1] = "TLSv1.1"
+ _PROTOCOL_NAMES[PROTOCOL_TLSv1_2] = "TLSv1.2"
+
+if sys.platform == "win32":
+ from _ssl import enum_certificates, enum_crls
+
from socket import getnameinfo as _getnameinfo
-from socket import error as socket_error
+from socket import SHUT_RDWR as _SHUT_RDWR
from socket import socket, AF_INET, SOCK_STREAM, create_connection
import base64 # for DER-to-PEM translation
import traceback
import errno
+
+socket_error = OSError # keep that public name in module namespace
+
if _ssl.HAS_TLS_UNIQUE:
CHANNEL_BINDING_TYPES = ['tls-unique']
else:
@@ -124,6 +166,13 @@ else:
# (OpenSSL's default setting is 'DEFAULT:!aNULL:!eNULL')
_DEFAULT_CIPHERS = 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2'
+# restricted and more secure ciphers
+# HIGH: high encryption cipher suites with key length >= 128 bits (no MD5)
+# !aNULL: only authenticated cipher suites (no anonymous DH)
+# !RC4: no RC4 streaming cipher, RC4 is broken
+# !DSS: RSA is preferred over DSA
+_RESTRICTED_CIPHERS = 'HIGH:!aNULL:!RC4:!DSS'
+
class CertificateError(ValueError):
pass
@@ -187,7 +236,9 @@ def match_hostname(cert, hostname):
returns nothing.
"""
if not cert:
- raise ValueError("empty or no certificate")
+ raise ValueError("empty or no certificate, match_hostname needs a "
+ "SSL socket or SSL context with either "
+ "CERT_OPTIONAL or CERT_REQUIRED")
dnsnames = []
san = cert.get('subjectAltName', ())
for key, value in san:
@@ -219,11 +270,58 @@ def match_hostname(cert, hostname):
"subjectAltName fields were found")
+DefaultVerifyPaths = namedtuple("DefaultVerifyPaths",
+ "cafile capath openssl_cafile_env openssl_cafile openssl_capath_env "
+ "openssl_capath")
+
+def get_default_verify_paths():
+ """Return paths to default cafile and capath.
+ """
+ parts = _ssl.get_default_verify_paths()
+
+ # environment vars shadow paths
+ cafile = os.environ.get(parts[0], parts[1])
+ capath = os.environ.get(parts[2], parts[3])
+
+ return DefaultVerifyPaths(cafile if os.path.isfile(cafile) else None,
+ capath if os.path.isdir(capath) else None,
+ *parts)
+
+
+class _ASN1Object(namedtuple("_ASN1Object", "nid shortname longname oid")):
+ """ASN.1 object identifier lookup
+ """
+ __slots__ = ()
+
+ def __new__(cls, oid):
+ return super().__new__(cls, *_txt2obj(oid, name=False))
+
+ @classmethod
+ def fromnid(cls, nid):
+ """Create _ASN1Object from OpenSSL numeric ID
+ """
+ return super().__new__(cls, *_nid2obj(nid))
+
+ @classmethod
+ def fromname(cls, name):
+ """Create _ASN1Object from short name, long name or OID
+ """
+ return super().__new__(cls, *_txt2obj(name, name=True))
+
+
+class Purpose(_ASN1Object, _Enum):
+ """SSLContext purpose flags with X509v3 Extended Key Usage objects
+ """
+ SERVER_AUTH = '1.3.6.1.5.5.7.3.1'
+ CLIENT_AUTH = '1.3.6.1.5.5.7.3.2'
+
+
class SSLContext(_SSLContext):
"""An SSLContext holds various SSL-related configuration options and
data, such as certificates and possibly a private key."""
- __slots__ = ('protocol',)
+ __slots__ = ('protocol', '__weakref__')
+ _windows_cert_stores = ("CA", "ROOT")
def __new__(cls, protocol, *args, **kwargs):
self = _SSLContext.__new__(cls, protocol)
@@ -255,6 +353,93 @@ class SSLContext(_SSLContext):
self._set_npn_protocols(protos)
+ def _load_windows_store_certs(self, storename, purpose):
+ certs = bytearray()
+ for cert, encoding, trust in enum_certificates(storename):
+ # CA certs are never PKCS#7 encoded
+ if encoding == "x509_asn":
+ if trust is True or purpose.oid in trust:
+ certs.extend(cert)
+ self.load_verify_locations(cadata=certs)
+ return certs
+
+ def load_default_certs(self, purpose=Purpose.SERVER_AUTH):
+ if not isinstance(purpose, _ASN1Object):
+ raise TypeError(purpose)
+ if sys.platform == "win32":
+ for storename in self._windows_cert_stores:
+ self._load_windows_store_certs(storename, purpose)
+ else:
+ self.set_default_verify_paths()
+
+
+def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
+ capath=None, cadata=None):
+ """Create a SSLContext object with default settings.
+
+ NOTE: The protocol and settings may change anytime without prior
+ deprecation. The values represent a fair balance between maximum
+ compatibility and security.
+ """
+ if not isinstance(purpose, _ASN1Object):
+ raise TypeError(purpose)
+ context = SSLContext(PROTOCOL_TLSv1)
+ # SSLv2 considered harmful.
+ context.options |= OP_NO_SSLv2
+ # disable compression to prevent CRIME attacks (OpenSSL 1.0+)
+ context.options |= getattr(_ssl, "OP_NO_COMPRESSION", 0)
+ # disallow ciphers with known vulnerabilities
+ context.set_ciphers(_RESTRICTED_CIPHERS)
+ # verify certs and host name in client mode
+ if purpose == Purpose.SERVER_AUTH:
+ context.verify_mode = CERT_REQUIRED
+ context.check_hostname = True
+ if cafile or capath or cadata:
+ context.load_verify_locations(cafile, capath, cadata)
+ elif context.verify_mode != CERT_NONE:
+ # no explicit cafile, capath or cadata but the verify mode is
+ # CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system
+ # root CA certificates for the given purpose. This may fail silently.
+ context.load_default_certs(purpose)
+ return context
+
+
+def _create_stdlib_context(protocol=PROTOCOL_SSLv23, *, cert_reqs=None,
+ purpose=Purpose.SERVER_AUTH,
+ certfile=None, keyfile=None,
+ cafile=None, capath=None, cadata=None):
+ """Create a SSLContext object for Python stdlib modules
+
+ All Python stdlib modules shall use this function to create SSLContext
+ objects in order to keep common settings in one place. The configuration
+ is less restrict than create_default_context()'s to increase backward
+ compatibility.
+ """
+ if not isinstance(purpose, _ASN1Object):
+ raise TypeError(purpose)
+
+ context = SSLContext(protocol)
+ # SSLv2 considered harmful.
+ context.options |= OP_NO_SSLv2
+
+ if cert_reqs is not None:
+ context.verify_mode = cert_reqs
+
+ if keyfile and not certfile:
+ raise ValueError("certfile must be specified")
+ if certfile or keyfile:
+ context.load_cert_chain(certfile, keyfile)
+
+ # load CA root certs
+ if cafile or capath or cadata:
+ context.load_verify_locations(cafile, capath, cadata)
+ elif context.verify_mode != CERT_NONE:
+ # no explicit cafile, capath or cadata but the verify mode is
+ # CERT_OPTIONAL or CERT_REQUIRED. Let's try to load default system
+ # root CA certificates for the given purpose. This may fail silently.
+ context.load_default_certs(purpose)
+
+ return context
class SSLSocket(socket):
"""This class implements a subtype of socket.socket that wraps
@@ -271,7 +456,7 @@ class SSLSocket(socket):
_context=None):
if _context:
- self.context = _context
+ self._context = _context
else:
if server_side and not certfile:
raise ValueError("certfile must be specified for server-side "
@@ -280,16 +465,16 @@ class SSLSocket(socket):
raise ValueError("certfile must be specified")
if certfile and not keyfile:
keyfile = certfile
- self.context = SSLContext(ssl_version)
- self.context.verify_mode = cert_reqs
+ self._context = SSLContext(ssl_version)
+ self._context.verify_mode = cert_reqs
if ca_certs:
- self.context.load_verify_locations(ca_certs)
+ self._context.load_verify_locations(ca_certs)
if certfile:
- self.context.load_cert_chain(certfile, keyfile)
+ self._context.load_cert_chain(certfile, keyfile)
if npn_protocols:
- self.context.set_npn_protocols(npn_protocols)
+ self._context.set_npn_protocols(npn_protocols)
if ciphers:
- self.context.set_ciphers(ciphers)
+ self._context.set_ciphers(ciphers)
self.keyfile = keyfile
self.certfile = certfile
self.cert_reqs = cert_reqs
@@ -299,11 +484,17 @@ class SSLSocket(socket):
if server_side and server_hostname:
raise ValueError("server_hostname can only be specified "
"in client mode")
+ if self._context.check_hostname and not server_hostname:
+ if HAS_SNI:
+ raise ValueError("check_hostname requires server_hostname")
+ else:
+ raise ValueError("check_hostname requires server_hostname, "
+ "but it's not supported by your OpenSSL "
+ "library")
self.server_side = server_side
self.server_hostname = server_hostname
self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs
- connected = False
if sock is not None:
socket.__init__(self,
family=sock.family,
@@ -311,27 +502,29 @@ class SSLSocket(socket):
proto=sock.proto,
fileno=sock.fileno())
self.settimeout(sock.gettimeout())
- # see if it's connected
- try:
- sock.getpeername()
- except socket_error as e:
- if e.errno != errno.ENOTCONN:
- raise
- else:
- connected = True
sock.detach()
elif fileno is not None:
socket.__init__(self, fileno=fileno)
else:
socket.__init__(self, family=family, type=type, proto=proto)
+ # See if we are connected
+ try:
+ self.getpeername()
+ except OSError as e:
+ if e.errno != errno.ENOTCONN:
+ raise
+ connected = False
+ else:
+ connected = True
+
self._closed = False
self._sslobj = None
self._connected = connected
if connected:
# create the SSL object
try:
- self._sslobj = self.context._wrap_socket(self, server_side,
+ self._sslobj = self._context._wrap_socket(self, server_side,
server_hostname)
if do_handshake_on_connect:
timeout = self.gettimeout()
@@ -340,9 +533,18 @@ class SSLSocket(socket):
raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
self.do_handshake()
- except socket_error as x:
+ except (OSError, ValueError):
self.close()
- raise x
+ raise
+
+ @property
+ def context(self):
+ return self._context
+
+ @context.setter
+ def context(self, ctx):
+ self._context = ctx
+ self._sslobj.context = ctx
def dup(self):
raise NotImplemented("Can't dup() %s instances" %
@@ -352,11 +554,21 @@ class SSLSocket(socket):
# raise an exception here if you wish to check for spurious closes
pass
+ def _check_connected(self):
+ if not self._connected:
+ # getpeername() will raise ENOTCONN if the socket is really
+ # not connected; note that we can be connected even without
+ # _connected being set, e.g. if connect() first returned
+ # EAGAIN.
+ self.getpeername()
+
def read(self, len=0, buffer=None):
"""Read up to LEN bytes and return them.
Return zero-length string on EOF."""
self._checkClosed()
+ if not self._sslobj:
+ raise ValueError("Read on closed or unwrapped SSL socket.")
try:
if buffer is not None:
v = self._sslobj.read(len, buffer)
@@ -377,6 +589,8 @@ class SSLSocket(socket):
number of bytes of DATA actually transmitted."""
self._checkClosed()
+ if not self._sslobj:
+ raise ValueError("Write on closed or unwrapped SSL socket.")
return self._sslobj.write(data)
def getpeercert(self, binary_form=False):
@@ -386,6 +600,7 @@ class SSLSocket(socket):
certificate was provided, but not validated."""
self._checkClosed()
+ self._check_connected()
return self._sslobj.peer_certificate(binary_form)
def selected_npn_protocol(self):
@@ -416,18 +631,17 @@ class SSLSocket(socket):
raise ValueError(
"non-zero flags not allowed in calls to send() on %s" %
self.__class__)
- while True:
- try:
- v = self._sslobj.write(data)
- except SSLError as x:
- if x.args[0] == SSL_ERROR_WANT_READ:
- return 0
- elif x.args[0] == SSL_ERROR_WANT_WRITE:
- return 0
- else:
- raise
+ try:
+ v = self._sslobj.write(data)
+ except SSLError as x:
+ if x.args[0] == SSL_ERROR_WANT_READ:
+ return 0
+ elif x.args[0] == SSL_ERROR_WANT_WRITE:
+ return 0
else:
- return v
+ raise
+ else:
+ return v
else:
return socket.send(self, data, flags)
@@ -535,12 +749,11 @@ class SSLSocket(socket):
def _real_close(self):
self._sslobj = None
- # self._closed = True
socket._real_close(self)
def do_handshake(self, block=False):
"""Perform a TLS/SSL handshake."""
-
+ self._check_connected()
timeout = self.gettimeout()
try:
if timeout == 0.0 and block:
@@ -549,6 +762,17 @@ class SSLSocket(socket):
finally:
self.settimeout(timeout)
+ if self.context.check_hostname:
+ try:
+ if not self.server_hostname:
+ raise ValueError("check_hostname needs server_hostname "
+ "argument")
+ match_hostname(self.getpeercert(), self.server_hostname)
+ except Exception:
+ self.shutdown(_SHUT_RDWR)
+ self.close()
+ raise
+
def _real_connect(self, addr, connect_ex):
if self.server_side:
raise ValueError("can't connect in server-side mode")
@@ -564,11 +788,11 @@ class SSLSocket(socket):
rc = None
socket.connect(self, addr)
if not rc:
+ self._connected = True
if self.do_handshake_on_connect:
self.do_handshake()
- self._connected = True
return rc
- except socket_error:
+ except (OSError, ValueError):
self._sslobj = None
raise
@@ -666,15 +890,16 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None):
If 'ssl_version' is specified, use it in the connection attempt."""
host, port = addr
- if (ca_certs is not None):
+ if ca_certs is not None:
cert_reqs = CERT_REQUIRED
else:
cert_reqs = CERT_NONE
- s = create_connection(addr)
- s = wrap_socket(s, ssl_version=ssl_version,
- cert_reqs=cert_reqs, ca_certs=ca_certs)
- dercert = s.getpeercert(True)
- s.close()
+ context = _create_stdlib_context(ssl_version,
+ cert_reqs=cert_reqs,
+ cafile=ca_certs)
+ with create_connection(addr) as sock:
+ with context.wrap_socket(sock) as sslsock:
+ dercert = sslsock.getpeercert(True)
return DER_cert_to_PEM_cert(dercert)
def get_protocol_name(protocol_code):
diff --git a/Lib/stat.py b/Lib/stat.py
index 704adfe2e1..3eecc3e0d3 100644
--- a/Lib/stat.py
+++ b/Lib/stat.py
@@ -147,3 +147,9 @@ def filemode(mode):
else:
perm.append("-")
return "".join(perm)
+
+# If available, use C implementation
+try:
+ from _stat import *
+except ImportError:
+ pass
diff --git a/Lib/statistics.py b/Lib/statistics.py
new file mode 100644
index 0000000000..a67a6d11cd
--- /dev/null
+++ b/Lib/statistics.py
@@ -0,0 +1,610 @@
+## Module statistics.py
+##
+## Copyright (c) 2013 Steven D'Aprano <steve+python@pearwood.info>.
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+
+
+"""
+Basic statistics module.
+
+This module provides functions for calculating statistics of data, including
+averages, variance, and standard deviation.
+
+Calculating averages
+--------------------
+
+================== =============================================
+Function Description
+================== =============================================
+mean Arithmetic mean (average) of data.
+median Median (middle value) of data.
+median_low Low median of data.
+median_high High median of data.
+median_grouped Median, or 50th percentile, of grouped data.
+mode Mode (most common value) of data.
+================== =============================================
+
+Calculate the arithmetic mean ("the average") of data:
+
+>>> mean([-1.0, 2.5, 3.25, 5.75])
+2.625
+
+
+Calculate the standard median of discrete data:
+
+>>> median([2, 3, 4, 5])
+3.5
+
+
+Calculate the median, or 50th percentile, of data grouped into class intervals
+centred on the data values provided. E.g. if your data points are rounded to
+the nearest whole number:
+
+>>> median_grouped([2, 2, 3, 3, 3, 4]) #doctest: +ELLIPSIS
+2.8333333333...
+
+This should be interpreted in this way: you have two data points in the class
+interval 1.5-2.5, three data points in the class interval 2.5-3.5, and one in
+the class interval 3.5-4.5. The median of these data points is 2.8333...
+
+
+Calculating variability or spread
+---------------------------------
+
+================== =============================================
+Function Description
+================== =============================================
+pvariance Population variance of data.
+variance Sample variance of data.
+pstdev Population standard deviation of data.
+stdev Sample standard deviation of data.
+================== =============================================
+
+Calculate the standard deviation of sample data:
+
+>>> stdev([2.5, 3.25, 5.5, 11.25, 11.75]) #doctest: +ELLIPSIS
+4.38961843444...
+
+If you have previously calculated the mean, you can pass it as the optional
+second argument to the four "spread" functions to avoid recalculating it:
+
+>>> data = [1, 2, 2, 4, 4, 4, 5, 6]
+>>> mu = mean(data)
+>>> pvariance(data, mu)
+2.5
+
+
+Exceptions
+----------
+
+A single exception is defined: StatisticsError is a subclass of ValueError.
+
+"""
+
+__all__ = [ 'StatisticsError',
+ 'pstdev', 'pvariance', 'stdev', 'variance',
+ 'median', 'median_low', 'median_high', 'median_grouped',
+ 'mean', 'mode',
+ ]
+
+
+import collections
+import math
+
+from fractions import Fraction
+from decimal import Decimal
+
+
+# === Exceptions ===
+
+class StatisticsError(ValueError):
+ pass
+
+
+# === Private utilities ===
+
+def _sum(data, start=0):
+ """_sum(data [, start]) -> value
+
+ Return a high-precision sum of the given numeric data. If optional
+ argument ``start`` is given, it is added to the total. If ``data`` is
+ empty, ``start`` (defaulting to 0) is returned.
+
+
+ Examples
+ --------
+
+ >>> _sum([3, 2.25, 4.5, -0.5, 1.0], 0.75)
+ 11.0
+
+ Some sources of round-off error will be avoided:
+
+ >>> _sum([1e50, 1, -1e50] * 1000) # Built-in sum returns zero.
+ 1000.0
+
+ Fractions and Decimals are also supported:
+
+ >>> from fractions import Fraction as F
+ >>> _sum([F(2, 3), F(7, 5), F(1, 4), F(5, 6)])
+ Fraction(63, 20)
+
+ >>> from decimal import Decimal as D
+ >>> data = [D("0.1375"), D("0.2108"), D("0.3061"), D("0.0419")]
+ >>> _sum(data)
+ Decimal('0.6963')
+
+ """
+ n, d = _exact_ratio(start)
+ T = type(start)
+ partials = {d: n} # map {denominator: sum of numerators}
+ # Micro-optimizations.
+ coerce_types = _coerce_types
+ exact_ratio = _exact_ratio
+ partials_get = partials.get
+ # Add numerators for each denominator, and track the "current" type.
+ for x in data:
+ T = _coerce_types(T, type(x))
+ n, d = exact_ratio(x)
+ partials[d] = partials_get(d, 0) + n
+ if None in partials:
+ assert issubclass(T, (float, Decimal))
+ assert not math.isfinite(partials[None])
+ return T(partials[None])
+ total = Fraction()
+ for d, n in sorted(partials.items()):
+ total += Fraction(n, d)
+ if issubclass(T, int):
+ assert total.denominator == 1
+ return T(total.numerator)
+ if issubclass(T, Decimal):
+ return T(total.numerator)/total.denominator
+ return T(total)
+
+
+def _exact_ratio(x):
+ """Convert Real number x exactly to (numerator, denominator) pair.
+
+ >>> _exact_ratio(0.25)
+ (1, 4)
+
+ x is expected to be an int, Fraction, Decimal or float.
+ """
+ try:
+ try:
+ # int, Fraction
+ return (x.numerator, x.denominator)
+ except AttributeError:
+ # float
+ try:
+ return x.as_integer_ratio()
+ except AttributeError:
+ # Decimal
+ try:
+ return _decimal_to_ratio(x)
+ except AttributeError:
+ msg = "can't convert type '{}' to numerator/denominator"
+ raise TypeError(msg.format(type(x).__name__)) from None
+ except (OverflowError, ValueError):
+ # INF or NAN
+ if __debug__:
+ # Decimal signalling NANs cannot be converted to float :-(
+ if isinstance(x, Decimal):
+ assert not x.is_finite()
+ else:
+ assert not math.isfinite(x)
+ return (x, None)
+
+
+# FIXME This is faster than Fraction.from_decimal, but still too slow.
+def _decimal_to_ratio(d):
+ """Convert Decimal d to exact integer ratio (numerator, denominator).
+
+ >>> from decimal import Decimal
+ >>> _decimal_to_ratio(Decimal("2.6"))
+ (26, 10)
+
+ """
+ sign, digits, exp = d.as_tuple()
+ if exp in ('F', 'n', 'N'): # INF, NAN, sNAN
+ assert not d.is_finite()
+ raise ValueError
+ num = 0
+ for digit in digits:
+ num = num*10 + digit
+ if sign:
+ num = -num
+ den = 10**-exp
+ return (num, den)
+
+
+def _coerce_types(T1, T2):
+ """Coerce types T1 and T2 to a common type.
+
+ >>> _coerce_types(int, float)
+ <class 'float'>
+
+ Coercion is performed according to this table, where "N/A" means
+ that a TypeError exception is raised.
+
+ +----------+-----------+-----------+-----------+----------+
+ | | int | Fraction | Decimal | float |
+ +----------+-----------+-----------+-----------+----------+
+ | int | int | Fraction | Decimal | float |
+ | Fraction | Fraction | Fraction | N/A | float |
+ | Decimal | Decimal | N/A | Decimal | float |
+ | float | float | float | float | float |
+ +----------+-----------+-----------+-----------+----------+
+
+ Subclasses trump their parent class; two subclasses of the same
+ base class will be coerced to the second of the two.
+
+ """
+ # Get the common/fast cases out of the way first.
+ if T1 is T2: return T1
+ if T1 is int: return T2
+ if T2 is int: return T1
+ # Subclasses trump their parent class.
+ if issubclass(T2, T1): return T2
+ if issubclass(T1, T2): return T1
+ # Floats trump everything else.
+ if issubclass(T2, float): return T2
+ if issubclass(T1, float): return T1
+ # Subclasses of the same base class give priority to the second.
+ if T1.__base__ is T2.__base__: return T2
+ # Otherwise, just give up.
+ raise TypeError('cannot coerce types %r and %r' % (T1, T2))
+
+
+def _counts(data):
+ # Generate a table of sorted (value, frequency) pairs.
+ if data is None:
+ raise TypeError('None is not iterable')
+ table = collections.Counter(data).most_common()
+ if not table:
+ return table
+ # Extract the values with the highest frequency.
+ maxfreq = table[0][1]
+ for i in range(1, len(table)):
+ if table[i][1] != maxfreq:
+ table = table[:i]
+ break
+ return table
+
+
+# === Measures of central tendency (averages) ===
+
+def mean(data):
+ """Return the sample arithmetic mean of data.
+
+ >>> mean([1, 2, 3, 4, 4])
+ 2.8
+
+ >>> from fractions import Fraction as F
+ >>> mean([F(3, 7), F(1, 21), F(5, 3), F(1, 3)])
+ Fraction(13, 21)
+
+ >>> from decimal import Decimal as D
+ >>> mean([D("0.5"), D("0.75"), D("0.625"), D("0.375")])
+ Decimal('0.5625')
+
+ If ``data`` is empty, StatisticsError will be raised.
+ """
+ if iter(data) is data:
+ data = list(data)
+ n = len(data)
+ if n < 1:
+ raise StatisticsError('mean requires at least one data point')
+ return _sum(data)/n
+
+
+# FIXME: investigate ways to calculate medians without sorting? Quickselect?
+def median(data):
+ """Return the median (middle value) of numeric data.
+
+ When the number of data points is odd, return the middle data point.
+ When the number of data points is even, the median is interpolated by
+ taking the average of the two middle values:
+
+ >>> median([1, 3, 5])
+ 3
+ >>> median([1, 3, 5, 7])
+ 4.0
+
+ """
+ data = sorted(data)
+ n = len(data)
+ if n == 0:
+ raise StatisticsError("no median for empty data")
+ if n%2 == 1:
+ return data[n//2]
+ else:
+ i = n//2
+ return (data[i - 1] + data[i])/2
+
+
+def median_low(data):
+ """Return the low median of numeric data.
+
+ When the number of data points is odd, the middle value is returned.
+ When it is even, the smaller of the two middle values is returned.
+
+ >>> median_low([1, 3, 5])
+ 3
+ >>> median_low([1, 3, 5, 7])
+ 3
+
+ """
+ data = sorted(data)
+ n = len(data)
+ if n == 0:
+ raise StatisticsError("no median for empty data")
+ if n%2 == 1:
+ return data[n//2]
+ else:
+ return data[n//2 - 1]
+
+
+def median_high(data):
+ """Return the high median of data.
+
+ When the number of data points is odd, the middle value is returned.
+ When it is even, the larger of the two middle values is returned.
+
+ >>> median_high([1, 3, 5])
+ 3
+ >>> median_high([1, 3, 5, 7])
+ 5
+
+ """
+ data = sorted(data)
+ n = len(data)
+ if n == 0:
+ raise StatisticsError("no median for empty data")
+ return data[n//2]
+
+
+def median_grouped(data, interval=1):
+ """"Return the 50th percentile (median) of grouped continuous data.
+
+ >>> median_grouped([1, 2, 2, 3, 4, 4, 4, 4, 4, 5])
+ 3.7
+ >>> median_grouped([52, 52, 53, 54])
+ 52.5
+
+ This calculates the median as the 50th percentile, and should be
+ used when your data is continuous and grouped. In the above example,
+ the values 1, 2, 3, etc. actually represent the midpoint of classes
+ 0.5-1.5, 1.5-2.5, 2.5-3.5, etc. The middle value falls somewhere in
+ class 3.5-4.5, and interpolation is used to estimate it.
+
+ Optional argument ``interval`` represents the class interval, and
+ defaults to 1. Changing the class interval naturally will change the
+ interpolated 50th percentile value:
+
+ >>> median_grouped([1, 3, 3, 5, 7], interval=1)
+ 3.25
+ >>> median_grouped([1, 3, 3, 5, 7], interval=2)
+ 3.5
+
+ This function does not check whether the data points are at least
+ ``interval`` apart.
+ """
+ data = sorted(data)
+ n = len(data)
+ if n == 0:
+ raise StatisticsError("no median for empty data")
+ elif n == 1:
+ return data[0]
+ # Find the value at the midpoint. Remember this corresponds to the
+ # centre of the class interval.
+ x = data[n//2]
+ for obj in (x, interval):
+ if isinstance(obj, (str, bytes)):
+ raise TypeError('expected number but got %r' % obj)
+ try:
+ L = x - interval/2 # The lower limit of the median interval.
+ except TypeError:
+ # Mixed type. For now we just coerce to float.
+ L = float(x) - float(interval)/2
+ cf = data.index(x) # Number of values below the median interval.
+ # FIXME The following line could be more efficient for big lists.
+ f = data.count(x) # Number of data points in the median interval.
+ return L + interval*(n/2 - cf)/f
+
+
+def mode(data):
+ """Return the most common data point from discrete or nominal data.
+
+ ``mode`` assumes discrete data, and returns a single value. This is the
+ standard treatment of the mode as commonly taught in schools:
+
+ >>> mode([1, 1, 2, 3, 3, 3, 3, 4])
+ 3
+
+ This also works with nominal (non-numeric) data:
+
+ >>> mode(["red", "blue", "blue", "red", "green", "red", "red"])
+ 'red'
+
+ If there is not exactly one most common value, ``mode`` will raise
+ StatisticsError.
+ """
+ # Generate a table of sorted (value, frequency) pairs.
+ table = _counts(data)
+ if len(table) == 1:
+ return table[0][0]
+ elif table:
+ raise StatisticsError(
+ 'no unique mode; found %d equally common values' % len(table)
+ )
+ else:
+ raise StatisticsError('no mode for empty data')
+
+
+# === Measures of spread ===
+
+# See http://mathworld.wolfram.com/Variance.html
+# http://mathworld.wolfram.com/SampleVariance.html
+# http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+#
+# Under no circumstances use the so-called "computational formula for
+# variance", as that is only suitable for hand calculations with a small
+# amount of low-precision data. It has terrible numeric properties.
+#
+# See a comparison of three computational methods here:
+# http://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/
+
+def _ss(data, c=None):
+ """Return sum of square deviations of sequence data.
+
+ If ``c`` is None, the mean is calculated in one pass, and the deviations
+ from the mean are calculated in a second pass. Otherwise, deviations are
+ calculated from ``c`` as given. Use the second case with care, as it can
+ lead to garbage results.
+ """
+ if c is None:
+ c = mean(data)
+ ss = _sum((x-c)**2 for x in data)
+ # The following sum should mathematically equal zero, but due to rounding
+ # error may not.
+ ss -= _sum((x-c) for x in data)**2/len(data)
+ assert not ss < 0, 'negative sum of square deviations: %f' % ss
+ return ss
+
+
+def variance(data, xbar=None):
+ """Return the sample variance of data.
+
+ data should be an iterable of Real-valued numbers, with at least two
+ values. The optional argument xbar, if given, should be the mean of
+ the data. If it is missing or None, the mean is automatically calculated.
+
+ Use this function when your data is a sample from a population. To
+ calculate the variance from the entire population, see ``pvariance``.
+
+ Examples:
+
+ >>> data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]
+ >>> variance(data)
+ 1.3720238095238095
+
+ If you have already calculated the mean of your data, you can pass it as
+ the optional second argument ``xbar`` to avoid recalculating it:
+
+ >>> m = mean(data)
+ >>> variance(data, m)
+ 1.3720238095238095
+
+ This function does not check that ``xbar`` is actually the mean of
+ ``data``. Giving arbitrary values for ``xbar`` may lead to invalid or
+ impossible results.
+
+ Decimals and Fractions are supported:
+
+ >>> from decimal import Decimal as D
+ >>> variance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")])
+ Decimal('31.01875')
+
+ >>> from fractions import Fraction as F
+ >>> variance([F(1, 6), F(1, 2), F(5, 3)])
+ Fraction(67, 108)
+
+ """
+ if iter(data) is data:
+ data = list(data)
+ n = len(data)
+ if n < 2:
+ raise StatisticsError('variance requires at least two data points')
+ ss = _ss(data, xbar)
+ return ss/(n-1)
+
+
+def pvariance(data, mu=None):
+ """Return the population variance of ``data``.
+
+ data should be an iterable of Real-valued numbers, with at least one
+ value. The optional argument mu, if given, should be the mean of
+ the data. If it is missing or None, the mean is automatically calculated.
+
+ Use this function to calculate the variance from the entire population.
+ To estimate the variance from a sample, the ``variance`` function is
+ usually a better choice.
+
+ Examples:
+
+ >>> data = [0.0, 0.25, 0.25, 1.25, 1.5, 1.75, 2.75, 3.25]
+ >>> pvariance(data)
+ 1.25
+
+ If you have already calculated the mean of the data, you can pass it as
+ the optional second argument to avoid recalculating it:
+
+ >>> mu = mean(data)
+ >>> pvariance(data, mu)
+ 1.25
+
+ This function does not check that ``mu`` is actually the mean of ``data``.
+ Giving arbitrary values for ``mu`` may lead to invalid or impossible
+ results.
+
+ Decimals and Fractions are supported:
+
+ >>> from decimal import Decimal as D
+ >>> pvariance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")])
+ Decimal('24.815')
+
+ >>> from fractions import Fraction as F
+ >>> pvariance([F(1, 4), F(5, 4), F(1, 2)])
+ Fraction(13, 72)
+
+ """
+ if iter(data) is data:
+ data = list(data)
+ n = len(data)
+ if n < 1:
+ raise StatisticsError('pvariance requires at least one data point')
+ ss = _ss(data, mu)
+ return ss/n
+
+
+def stdev(data, xbar=None):
+ """Return the square root of the sample variance.
+
+ See ``variance`` for arguments and other details.
+
+ >>> stdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75])
+ 1.0810874155219827
+
+ """
+ var = variance(data, xbar)
+ try:
+ return var.sqrt()
+ except AttributeError:
+ return math.sqrt(var)
+
+
+def pstdev(data, mu=None):
+ """Return the square root of the population variance.
+
+ See ``pvariance`` for arguments and other details.
+
+ >>> pstdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75])
+ 0.986893273527251
+
+ """
+ var = pvariance(data, mu)
+ try:
+ return var.sqrt()
+ except AttributeError:
+ return math.sqrt(var)
diff --git a/Lib/struct.py b/Lib/struct.py
index 9bfc23f8d5..d6bba58863 100644
--- a/Lib/struct.py
+++ b/Lib/struct.py
@@ -1,6 +1,7 @@
__all__ = [
# Functions
'calcsize', 'pack', 'pack_into', 'unpack', 'unpack_from',
+ 'iter_unpack',
# Classes
'Struct',
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index c3a278836c..0942d94ef7 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -145,11 +145,13 @@ check_call(*popenargs, **kwargs):
getstatusoutput(cmd):
Return (status, output) of executing cmd in a shell.
- Execute the string 'cmd' in a shell with os.popen() and return a 2-tuple
- (status, output). cmd is actually run as '{ cmd ; } 2>&1', so that the
- returned output will contain output or error messages. A trailing newline
- is stripped from the output. The exit status for the command can be
- interpreted according to the rules for the C function wait(). Example:
+ Execute the string 'cmd' in a shell with 'check_output' and
+ return a 2-tuple (status, output). Universal newlines mode is used,
+ meaning that the result with be decoded to a string.
+
+ A trailing newline is stripped from the output.
+ The exit status for the command can be interpreted
+ according to the rules for the function 'wait'. Example:
>>> subprocess.getstatusoutput('ls /bin/ls')
(0, '/bin/ls')
@@ -178,6 +180,9 @@ check_output(*popenargs, **kwargs):
>>> output = subprocess.check_output(["ls", "-l", "/dev/null"])
+ There is an additional optional argument, "input", allowing you to
+ pass a string to the subprocess's stdin. If you use this argument
+ you may not also use the Popen constructor's "stdin" argument.
Exceptions
----------
@@ -398,19 +403,24 @@ if mswindows:
hStdOutput = None
hStdError = None
wShowWindow = 0
- class pywintypes:
- error = IOError
else:
- import select
- _has_poll = hasattr(select, 'poll')
import _posixsubprocess
- _create_pipe = _posixsubprocess.cloexec_pipe
+ import select
+ import selectors
# When select or poll has indicated that the file is writable,
# we can write up to _PIPE_BUF bytes without risk of blocking.
# POSIX defines PIPE_BUF as >= 512.
_PIPE_BUF = getattr(select, 'PIPE_BUF', 512)
+ # poll/select have the advantage of not requiring any extra file
+ # descriptor, contrarily to epoll/kqueue (also, they require a single
+ # syscall).
+ if hasattr(selectors, 'PollSelector'):
+ _PopenSelector = selectors.PollSelector
+ else:
+ _PopenSelector = selectors.SelectSelector
+
__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput",
"getoutput", "check_output", "CalledProcessError", "DEVNULL"]
@@ -506,6 +516,8 @@ def _args_from_interpreter_flags():
for flag, opt in flag_opt_map.items():
v = getattr(sys.flags, flag)
if v > 0:
+ if flag == 'hash_randomization':
+ v = 1 # Handle specification of an exact seed
args.append('-' + opt * v)
for opt in sys.warnoptions:
args.append('-W' + opt)
@@ -568,14 +580,31 @@ def check_output(*popenargs, timeout=None, **kwargs):
... stderr=STDOUT)
b'ls: non_existent_file: No such file or directory\n'
+ There is an additional optional argument, "input", allowing you to
+ pass a string to the subprocess's stdin. If you use this argument
+ you may not also use the Popen constructor's "stdin" argument, as
+ it too will be used internally. Example:
+
+ >>> check_output(["sed", "-e", "s/foo/bar/"],
+ ... input=b"when in the course of fooman events\n")
+ b'when in the course of barman events\n'
+
If universal_newlines=True is passed, the return value will be a
string rather than bytes.
"""
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
+ if 'input' in kwargs:
+ if 'stdin' in kwargs:
+ raise ValueError('stdin and input arguments may not both be used.')
+ inputdata = kwargs['input']
+ del kwargs['input']
+ kwargs['stdin'] = PIPE
+ else:
+ inputdata = None
with Popen(*popenargs, stdout=PIPE, **kwargs) as process:
try:
- output, unused_err = process.communicate(timeout=timeout)
+ output, unused_err = process.communicate(inputdata, timeout=timeout)
except TimeoutExpired:
process.kill()
output, unused_err = process.communicate()
@@ -664,13 +693,15 @@ def list2cmdline(seq):
#
def getstatusoutput(cmd):
- """Return (status, output) of executing cmd in a shell.
+ """ Return (status, output) of executing cmd in a shell.
+
+ Execute the string 'cmd' in a shell with 'check_output' and
+ return a 2-tuple (status, output). Universal newlines mode is used,
+ meaning that the result with be decoded to a string.
- Execute the string 'cmd' in a shell with os.popen() and return a 2-tuple
- (status, output). cmd is actually run as '{ cmd ; } 2>&1', so that the
- returned output will contain output or error messages. A trailing newline
- is stripped from the output. The exit status for the command can be
- interpreted according to the rules for the C function wait(). Example:
+ A trailing newline is stripped from the output.
+ The exit status for the command can be interpreted
+ according to the rules for the function 'wait'. Example:
>>> import subprocess
>>> subprocess.getstatusoutput('ls /bin/ls')
@@ -820,7 +851,7 @@ class Popen(object):
for f in filter(None, (self.stdin, self.stdout, self.stderr)):
try:
f.close()
- except EnvironmentError:
+ except OSError:
pass # Ignore EBADF or other errors.
if not self._closed_child_pipe_fds:
@@ -836,7 +867,7 @@ class Popen(object):
for fd in to_close:
try:
os.close(fd)
- except EnvironmentError:
+ except OSError:
pass
raise
@@ -900,7 +931,7 @@ class Popen(object):
if input:
try:
self.stdin.write(input)
- except IOError as e:
+ except OSError as e:
if e.errno != errno.EPIPE and e.errno != errno.EINVAL:
raise
self.stdin.close()
@@ -1032,23 +1063,6 @@ class Popen(object):
return Handle(h)
- def _find_w9xpopen(self):
- """Find and return absolut path to w9xpopen.exe"""
- w9xpopen = os.path.join(
- os.path.dirname(_winapi.GetModuleFileName(0)),
- "w9xpopen.exe")
- if not os.path.exists(w9xpopen):
- # Eeek - file-not-found - possibly an embedding
- # situation - see if we can locate it in sys.exec_prefix
- w9xpopen = os.path.join(os.path.dirname(sys.base_exec_prefix),
- "w9xpopen.exe")
- if not os.path.exists(w9xpopen):
- raise RuntimeError("Cannot locate w9xpopen.exe, which is "
- "needed for Popen to work with your "
- "shell or platform.")
- return w9xpopen
-
-
def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env,
startupinfo, creationflags, shell,
@@ -1077,21 +1091,6 @@ class Popen(object):
startupinfo.wShowWindow = _winapi.SW_HIDE
comspec = os.environ.get("COMSPEC", "cmd.exe")
args = '{} /c "{}"'.format (comspec, args)
- if (_winapi.GetVersion() >= 0x80000000 or
- os.path.basename(comspec).lower() == "command.com"):
- # Win9x, or using command.com on NT. We need to
- # use the w9xpopen intermediate program. For more
- # information, see KB Q150956
- # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp)
- w9xpopen = self._find_w9xpopen()
- args = '"%s" %s' % (w9xpopen, args)
- # Not passing CREATE_NEW_CONSOLE has been known to
- # cause random failures on win9x. Specifically a
- # dialog: "Your program accessed mem currently in
- # use at xxx" and a hopeful warning about the
- # stability of your system. Cost is Ctrl+C won't
- # kill children.
- creationflags |= _winapi.CREATE_NEW_CONSOLE
# Start the process
try:
@@ -1103,12 +1102,6 @@ class Popen(object):
env,
cwd,
startupinfo)
- except pywintypes.error as e:
- # Translate pywintypes.error to WindowsError, which is
- # a subclass of OSError. FIXME: We should really
- # translate errno using _sys_errlist (or similar), but
- # how can this be done from Python?
- raise WindowsError(*e.args)
finally:
# Child is launched. Close the parent's copy of those pipe
# handles that only the child should have open. You need
@@ -1193,7 +1186,7 @@ class Popen(object):
if input is not None:
try:
self.stdin.write(input)
- except IOError as e:
+ except OSError as e:
if e.errno != errno.EPIPE:
raise
self.stdin.close()
@@ -1271,7 +1264,7 @@ class Popen(object):
if stdin is None:
pass
elif stdin == PIPE:
- p2cread, p2cwrite = _create_pipe()
+ p2cread, p2cwrite = os.pipe()
elif stdin == DEVNULL:
p2cread = self._get_devnull()
elif isinstance(stdin, int):
@@ -1283,7 +1276,7 @@ class Popen(object):
if stdout is None:
pass
elif stdout == PIPE:
- c2pread, c2pwrite = _create_pipe()
+ c2pread, c2pwrite = os.pipe()
elif stdout == DEVNULL:
c2pwrite = self._get_devnull()
elif isinstance(stdout, int):
@@ -1295,7 +1288,7 @@ class Popen(object):
if stderr is None:
pass
elif stderr == PIPE:
- errread, errwrite = _create_pipe()
+ errread, errwrite = os.pipe()
elif stderr == STDOUT:
errwrite = c2pwrite
elif stderr == DEVNULL:
@@ -1347,7 +1340,14 @@ class Popen(object):
# For transferring possible exec failure from child to parent.
# Data format: "exception name:hex errno:description"
# Pickle is not used; it is complex and involves memory allocation.
- errpipe_read, errpipe_write = _create_pipe()
+ errpipe_read, errpipe_write = os.pipe()
+ # errpipe_write must not be in the standard io 0, 1, or 2 fd range.
+ low_fds_to_close = []
+ while errpipe_write < 3:
+ low_fds_to_close.append(errpipe_write)
+ errpipe_write = os.dup(errpipe_write)
+ for low_fd in low_fds_to_close:
+ os.close(low_fd)
try:
try:
# We must avoid complex work that could involve
@@ -1417,13 +1417,13 @@ class Popen(object):
exception_name, hex_errno, err_msg = (
errpipe_data.split(b':', 2))
except ValueError:
- exception_name = b'RuntimeError'
+ exception_name = b'SubprocessError'
hex_errno = b'0'
err_msg = (b'Bad exception data from child: ' +
repr(errpipe_data))
child_exception_type = getattr(
builtins, exception_name.decode('ascii'),
- RuntimeError)
+ SubprocessError)
err_msg = err_msg.decode(errors="surrogatepass")
if issubclass(child_exception_type, OSError) and hex_errno:
errno_num = int(hex_errno, 16)
@@ -1453,11 +1453,11 @@ class Popen(object):
self.returncode = _WEXITSTATUS(sts)
else:
# Should never happen
- raise RuntimeError("Unknown child exit status!")
+ raise SubprocessError("Unknown child exit status!")
def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid,
- _WNOHANG=os.WNOHANG, _os_error=os.error, _ECHILD=errno.ECHILD):
+ _WNOHANG=os.WNOHANG, _ECHILD=errno.ECHILD):
"""Check if child process has terminated. Returns returncode
attribute.
@@ -1470,7 +1470,7 @@ class Popen(object):
pid, sts = _waitpid(self.pid, _WNOHANG)
if pid == self.pid:
self._handle_exitstatus(sts)
- except _os_error as e:
+ except OSError as e:
if _deadstate is not None:
self.returncode = _deadstate
elif e.errno == _ECHILD:
@@ -1544,12 +1544,65 @@ class Popen(object):
if not input:
self.stdin.close()
- if _has_poll:
- stdout, stderr = self._communicate_with_poll(input, endtime,
- orig_timeout)
- else:
- stdout, stderr = self._communicate_with_select(input, endtime,
- orig_timeout)
+ stdout = None
+ stderr = None
+
+ # Only create this mapping if we haven't already.
+ if not self._communication_started:
+ self._fileobj2output = {}
+ if self.stdout:
+ self._fileobj2output[self.stdout] = []
+ if self.stderr:
+ self._fileobj2output[self.stderr] = []
+
+ if self.stdout:
+ stdout = self._fileobj2output[self.stdout]
+ if self.stderr:
+ stderr = self._fileobj2output[self.stderr]
+
+ self._save_input(input)
+
+ with _PopenSelector() as selector:
+ if self.stdin and input:
+ selector.register(self.stdin, selectors.EVENT_WRITE)
+ if self.stdout:
+ selector.register(self.stdout, selectors.EVENT_READ)
+ if self.stderr:
+ selector.register(self.stderr, selectors.EVENT_READ)
+
+ while selector.get_map():
+ timeout = self._remaining_time(endtime)
+ if timeout is not None and timeout < 0:
+ raise TimeoutExpired(self.args, orig_timeout)
+
+ ready = selector.select(timeout)
+ self._check_timeout(endtime, orig_timeout)
+
+ # XXX Rewrite these to use non-blocking I/O on the file
+ # objects; they are no longer using C stdio!
+
+ for key, events in ready:
+ if key.fileobj is self.stdin:
+ chunk = self._input[self._input_offset :
+ self._input_offset + _PIPE_BUF]
+ try:
+ self._input_offset += os.write(key.fd, chunk)
+ except OSError as e:
+ if e.errno == errno.EPIPE:
+ selector.unregister(key.fileobj)
+ key.fileobj.close()
+ else:
+ raise
+ else:
+ if self._input_offset >= len(self._input):
+ selector.unregister(key.fileobj)
+ key.fileobj.close()
+ elif key.fileobj in (self.stdout, self.stderr):
+ data = os.read(key.fd, 4096)
+ if not data:
+ selector.unregister(key.fileobj)
+ key.fileobj.close()
+ self._fileobj2output[key.fileobj].append(data)
self.wait(timeout=self._remaining_time(endtime))
@@ -1583,167 +1636,6 @@ class Popen(object):
self._input = self._input.encode(self.stdin.encoding)
- def _communicate_with_poll(self, input, endtime, orig_timeout):
- stdout = None # Return
- stderr = None # Return
-
- if not self._communication_started:
- self._fd2file = {}
-
- poller = select.poll()
- def register_and_append(file_obj, eventmask):
- poller.register(file_obj.fileno(), eventmask)
- self._fd2file[file_obj.fileno()] = file_obj
-
- def close_unregister_and_remove(fd):
- poller.unregister(fd)
- self._fd2file[fd].close()
- self._fd2file.pop(fd)
-
- if self.stdin and input:
- register_and_append(self.stdin, select.POLLOUT)
-
- # Only create this mapping if we haven't already.
- if not self._communication_started:
- self._fd2output = {}
- if self.stdout:
- self._fd2output[self.stdout.fileno()] = []
- if self.stderr:
- self._fd2output[self.stderr.fileno()] = []
-
- select_POLLIN_POLLPRI = select.POLLIN | select.POLLPRI
- if self.stdout:
- register_and_append(self.stdout, select_POLLIN_POLLPRI)
- stdout = self._fd2output[self.stdout.fileno()]
- if self.stderr:
- register_and_append(self.stderr, select_POLLIN_POLLPRI)
- stderr = self._fd2output[self.stderr.fileno()]
-
- self._save_input(input)
-
- while self._fd2file:
- timeout = self._remaining_time(endtime)
- if timeout is not None and timeout < 0:
- raise TimeoutExpired(self.args, orig_timeout)
- try:
- ready = poller.poll(timeout)
- except select.error as e:
- if e.args[0] == errno.EINTR:
- continue
- raise
- self._check_timeout(endtime, orig_timeout)
-
- # XXX Rewrite these to use non-blocking I/O on the
- # file objects; they are no longer using C stdio!
-
- for fd, mode in ready:
- if mode & select.POLLOUT:
- chunk = self._input[self._input_offset :
- self._input_offset + _PIPE_BUF]
- try:
- self._input_offset += os.write(fd, chunk)
- except OSError as e:
- if e.errno == errno.EPIPE:
- close_unregister_and_remove(fd)
- else:
- raise
- else:
- if self._input_offset >= len(self._input):
- close_unregister_and_remove(fd)
- elif mode & select_POLLIN_POLLPRI:
- data = os.read(fd, 4096)
- if not data:
- close_unregister_and_remove(fd)
- self._fd2output[fd].append(data)
- else:
- # Ignore hang up or errors.
- close_unregister_and_remove(fd)
-
- return (stdout, stderr)
-
-
- def _communicate_with_select(self, input, endtime, orig_timeout):
- if not self._communication_started:
- self._read_set = []
- self._write_set = []
- if self.stdin and input:
- self._write_set.append(self.stdin)
- if self.stdout:
- self._read_set.append(self.stdout)
- if self.stderr:
- self._read_set.append(self.stderr)
-
- self._save_input(input)
-
- stdout = None # Return
- stderr = None # Return
-
- if self.stdout:
- if not self._communication_started:
- self._stdout_buff = []
- stdout = self._stdout_buff
- if self.stderr:
- if not self._communication_started:
- self._stderr_buff = []
- stderr = self._stderr_buff
-
- while self._read_set or self._write_set:
- timeout = self._remaining_time(endtime)
- if timeout is not None and timeout < 0:
- raise TimeoutExpired(self.args, orig_timeout)
- try:
- (rlist, wlist, xlist) = \
- select.select(self._read_set, self._write_set, [],
- timeout)
- except select.error as e:
- if e.args[0] == errno.EINTR:
- continue
- raise
-
- # According to the docs, returning three empty lists indicates
- # that the timeout expired.
- if not (rlist or wlist or xlist):
- raise TimeoutExpired(self.args, orig_timeout)
- # We also check what time it is ourselves for good measure.
- self._check_timeout(endtime, orig_timeout)
-
- # XXX Rewrite these to use non-blocking I/O on the
- # file objects; they are no longer using C stdio!
-
- if self.stdin in wlist:
- chunk = self._input[self._input_offset :
- self._input_offset + _PIPE_BUF]
- try:
- bytes_written = os.write(self.stdin.fileno(), chunk)
- except OSError as e:
- if e.errno == errno.EPIPE:
- self.stdin.close()
- self._write_set.remove(self.stdin)
- else:
- raise
- else:
- self._input_offset += bytes_written
- if self._input_offset >= len(self._input):
- self.stdin.close()
- self._write_set.remove(self.stdin)
-
- if self.stdout in rlist:
- data = os.read(self.stdout.fileno(), 1024)
- if not data:
- self.stdout.close()
- self._read_set.remove(self.stdout)
- stdout.append(data)
-
- if self.stderr in rlist:
- data = os.read(self.stderr.fileno(), 1024)
- if not data:
- self.stderr.close()
- self._read_set.remove(self.stderr)
- stderr.append(data)
-
- return (stdout, stderr)
-
-
def send_signal(self, sig):
"""Send a signal to the process
"""
diff --git a/Lib/sunau.py b/Lib/sunau.py
index 15f7b0354b..3c244925d1 100644
--- a/Lib/sunau.py
+++ b/Lib/sunau.py
@@ -51,7 +51,7 @@ This returns an instance of a class with the following public methods:
getcomptype() -- returns compression type ('NONE' or 'ULAW')
getcompname() -- returns human-readable version of
compression type ('not compressed' matches 'NONE')
- getparams() -- returns a tuple consisting of all of the
+ getparams() -- returns a namedtuple consisting of all of the
above in the above order
getmarkers() -- returns None (for compatibility with the
aifc module)
@@ -103,6 +103,11 @@ The close() method is called automatically when the class instance
is destroyed.
"""
+from collections import namedtuple
+
+_sunau_params = namedtuple('_sunau_params',
+ 'nchannels sampwidth framerate nframes comptype compname')
+
# from <multimedia/audio_filehdr.h>
AUDIO_FILE_MAGIC = 0x2e736e64
AUDIO_FILE_ENCODING_MULAW_8 = 1
@@ -163,6 +168,12 @@ class Au_read:
if self._file:
self.close()
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
def initfp(self, file):
self._file = file
self._soundpos = 0
@@ -246,9 +257,9 @@ class Au_read:
return 'not compressed'
def getparams(self):
- return self.getnchannels(), self.getsampwidth(), \
- self.getframerate(), self.getnframes(), \
- self.getcomptype(), self.getcompname()
+ return _sunau_params(self.getnchannels(), self.getsampwidth(),
+ self.getframerate(), self.getnframes(),
+ self.getcomptype(), self.getcompname())
def getmarkers(self):
return None
@@ -307,6 +318,12 @@ class Au_write:
self.close()
self._file = None
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
def initfp(self, file):
self._file = file
self._framerate = 0
@@ -335,7 +352,7 @@ class Au_write:
def setsampwidth(self, sampwidth):
if self._nframeswritten:
raise Error('cannot change parameters after starting to write')
- if sampwidth not in (1, 2, 4):
+ if sampwidth not in (1, 2, 3, 4):
raise Error('bad sample width')
self._sampwidth = sampwidth
@@ -390,14 +407,16 @@ class Au_write:
self.setcomptype(comptype, compname)
def getparams(self):
- return self.getnchannels(), self.getsampwidth(), \
- self.getframerate(), self.getnframes(), \
- self.getcomptype(), self.getcompname()
+ return _sunau_params(self.getnchannels(), self.getsampwidth(),
+ self.getframerate(), self.getnframes(),
+ self.getcomptype(), self.getcompname())
def tell(self):
return self._nframeswritten
def writeframesraw(self, data):
+ if not isinstance(data, (bytes, bytearray)):
+ data = memoryview(data).cast('B')
self._ensure_header_written()
if self._comptype == 'ULAW':
import audioop
@@ -421,9 +440,9 @@ class Au_write:
self._datalength != self._datawritten:
self._patchheader()
self._file.flush()
+ finally:
if self._opened and self._file:
self._file.close()
- finally:
self._file = None
#
@@ -448,6 +467,9 @@ class Au_write:
elif self._sampwidth == 2:
encoding = AUDIO_FILE_ENCODING_LINEAR_16
self._framesize = 2
+ elif self._sampwidth == 3:
+ encoding = AUDIO_FILE_ENCODING_LINEAR_24
+ self._framesize = 3
elif self._sampwidth == 4:
encoding = AUDIO_FILE_ENCODING_LINEAR_32
self._framesize = 4
diff --git a/Lib/symbol.py b/Lib/symbol.py
index 34143b5d8e..5cf4a65f22 100755
--- a/Lib/symbol.py
+++ b/Lib/symbol.py
@@ -100,7 +100,7 @@ for _name, _value in list(globals().items()):
sym_name[_value] = _name
-def main():
+def _main():
import sys
import token
if len(sys.argv) == 1:
@@ -108,4 +108,4 @@ def main():
token._main()
if __name__ == "__main__":
- main()
+ _main()
diff --git a/Lib/symtable.py b/Lib/symtable.py
index bb271968a4..e23313b031 100644
--- a/Lib/symtable.py
+++ b/Lib/symtable.py
@@ -232,7 +232,8 @@ class Symbol(object):
if __name__ == "__main__":
import os, sys
- src = open(sys.argv[0]).read()
+ with open(sys.argv[0]) as f:
+ src = f.read()
mod = symtable(src, os.path.split(sys.argv[0])[1], "exec")
for ident in mod.get_identifiers():
info = mod.lookup(ident)
diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
index 2a2160cc0d..dbf7767205 100644
--- a/Lib/sysconfig.py
+++ b/Lib/sysconfig.py
@@ -1,7 +1,6 @@
"""Access to Python's configuration information."""
import os
-import re
import sys
from os.path import pardir, realpath
@@ -52,25 +51,6 @@ _INSTALL_SCHEMES = {
'scripts': '{base}/Scripts',
'data': '{base}',
},
- 'os2': {
- 'stdlib': '{installed_base}/Lib',
- 'platstdlib': '{base}/Lib',
- 'purelib': '{base}/Lib/site-packages',
- 'platlib': '{base}/Lib/site-packages',
- 'include': '{installed_base}/Include',
- 'platinclude': '{installed_base}/Include',
- 'scripts': '{base}/Scripts',
- 'data': '{base}',
- },
- 'os2_home': {
- 'stdlib': '{userbase}/lib/python{py_version_short}',
- 'platstdlib': '{userbase}/lib/python{py_version_short}',
- 'purelib': '{userbase}/lib/python{py_version_short}/site-packages',
- 'platlib': '{userbase}/lib/python{py_version_short}/site-packages',
- 'include': '{userbase}/include/python{py_version_short}',
- 'scripts': '{userbase}/bin',
- 'data': '{userbase}',
- },
'nt_user': {
'stdlib': '{userbase}/Python{py_version_nodot}',
'platstdlib': '{userbase}/Python{py_version_nodot}',
@@ -210,7 +190,6 @@ def _getuserbase():
def joinuser(*args):
return os.path.expanduser(os.path.join(*args))
- # what about 'os2emx', 'riscos' ?
if os.name == "nt":
base = os.environ.get("APPDATA") or "~"
if env_base:
@@ -242,6 +221,7 @@ def _parse_makefile(filename, vars=None):
"""
# Regexes needed for parsing Makefile (and similar syntaxes,
# like old-style Setup files).
+ import re
_variable_rx = re.compile("([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
@@ -369,21 +349,21 @@ def _generate_posix_vars():
makefile = get_makefile_filename()
try:
_parse_makefile(makefile, vars)
- except IOError as e:
+ except OSError as e:
msg = "invalid Python installation: unable to open %s" % makefile
if hasattr(e, "strerror"):
msg = msg + " (%s)" % e.strerror
- raise IOError(msg)
+ raise OSError(msg)
# load the installed pyconfig.h:
config_h = get_config_h_filename()
try:
with open(config_h) as f:
parse_config_h(f, vars)
- except IOError as e:
+ except OSError as e:
msg = "invalid Python installation: unable to open %s" % config_h
if hasattr(e, "strerror"):
msg = msg + " (%s)" % e.strerror
- raise IOError(msg)
+ raise OSError(msg)
# On AIX, there are wrong paths to the linker scripts in the Makefile
# -- these paths are relative to the Python source, but when installed
# the scripts are in another directory.
@@ -403,8 +383,8 @@ def _generate_posix_vars():
# get_platform() succeeds.
name = '_sysconfigdata'
if 'darwin' in sys.platform:
- import imp
- module = imp.new_module(name)
+ import types
+ module = types.ModuleType(name)
module.build_time_vars = vars
sys.modules[name] = module
@@ -436,7 +416,6 @@ def _init_non_posix(vars):
vars['LIBDEST'] = get_path('stdlib')
vars['BINLIBDEST'] = get_path('platstdlib')
vars['INCLUDEPY'] = get_path('include')
- vars['SO'] = '.pyd'
vars['EXT_SUFFIX'] = '.pyd'
vars['EXE'] = '.exe'
vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
@@ -456,6 +435,7 @@ def parse_config_h(fp, vars=None):
"""
if vars is None:
vars = {}
+ import re
define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
@@ -552,10 +532,14 @@ def get_config_vars(*args):
# sys.abiflags may not be defined on all platforms.
_CONFIG_VARS['abiflags'] = ''
- if os.name in ('nt', 'os2'):
+ if os.name == 'nt':
_init_non_posix(_CONFIG_VARS)
if os.name == 'posix':
_init_posix(_CONFIG_VARS)
+ # For backward compatibility, see issue19555
+ SO = _CONFIG_VARS.get('EXT_SUFFIX')
+ if SO is not None:
+ _CONFIG_VARS['SO'] = SO
# Setting 'userbase' is done below the call to the
# init function to enable using 'get_config_var' in
# the init-function.
@@ -599,6 +583,9 @@ def get_config_var(name):
Equivalent to get_config_vars().get(name)
"""
+ if name == 'SO':
+ import warnings
+ warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2)
return get_config_vars().get(name)
@@ -679,6 +666,7 @@ def get_platform():
return "%s-%s.%s" % (osname, version, release)
elif osname[:6] == "cygwin":
osname = "cygwin"
+ import re
rel_re = re.compile(r'[\d.]+')
m = rel_re.match(release)
if m:
diff --git a/Lib/tabnanny.py b/Lib/tabnanny.py
index 5b9b444c1a..46e0f56a39 100755
--- a/Lib/tabnanny.py
+++ b/Lib/tabnanny.py
@@ -95,7 +95,7 @@ def check(file):
try:
f = tokenize.open(file)
- except IOError as msg:
+ except OSError as msg:
errprint("%r: I/O Error: %s" % (file, msg))
return
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index 16d338c738..f4df6c7b0a 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -56,9 +56,9 @@ except ImportError:
# os.symlink on Windows prior to 6.0 raises NotImplementedError
symlink_exception = (AttributeError, NotImplementedError)
try:
- # WindowsError (1314) will be raised if the caller does not hold the
+ # OSError (winerror=1314) will be raised if the caller does not hold the
# SeCreateSymbolicLinkPrivilege privilege
- symlink_exception += (WindowsError,)
+ symlink_exception += (OSError,)
except NameError:
pass
@@ -140,30 +140,6 @@ PAX_NUMBER_FIELDS = {
}
#---------------------------------------------------------
-# Bits used in the mode field, values in octal.
-#---------------------------------------------------------
-S_IFLNK = 0o120000 # symbolic link
-S_IFREG = 0o100000 # regular file
-S_IFBLK = 0o060000 # block device
-S_IFDIR = 0o040000 # directory
-S_IFCHR = 0o020000 # character device
-S_IFIFO = 0o010000 # fifo
-
-TSUID = 0o4000 # set UID on execution
-TSGID = 0o2000 # set GID on execution
-TSVTX = 0o1000 # reserved
-
-TUREAD = 0o400 # read by owner
-TUWRITE = 0o200 # write by owner
-TUEXEC = 0o100 # execute/search by owner
-TGREAD = 0o040 # read by group
-TGWRITE = 0o020 # write by group
-TGEXEC = 0o010 # execute/search by group
-TOREAD = 0o004 # read by other
-TOWRITE = 0o002 # write by other
-TOEXEC = 0o001 # execute/search by other
-
-#---------------------------------------------------------
# initialization
#---------------------------------------------------------
if os.name in ("nt", "ce"):
@@ -264,13 +240,13 @@ def copyfileobj(src, dst, length=None):
for b in range(blocks):
buf = src.read(BUFSIZE)
if len(buf) < BUFSIZE:
- raise IOError("end of file reached")
+ raise OSError("end of file reached")
dst.write(buf)
if remainder != 0:
buf = src.read(remainder)
if len(buf) < remainder:
- raise IOError("end of file reached")
+ raise OSError("end of file reached")
dst.write(buf)
return
@@ -399,7 +375,7 @@ class _Stream:
if mode == "r":
self.dbuf = b""
self.cmp = bz2.BZ2Decompressor()
- self.exception = IOError
+ self.exception = OSError
else:
self.cmp = bz2.BZ2Compressor()
@@ -1631,7 +1607,7 @@ class TarFile(object):
try:
fileobj = gzip.GzipFile(name, mode + "b", compresslevel, fileobj)
t = cls.taropen(name, mode, fileobj, **kwargs)
- except IOError:
+ except OSError:
if not extfileobj and fileobj is not None:
fileobj.close()
if fileobj is None:
@@ -1662,7 +1638,7 @@ class TarFile(object):
try:
t = cls.taropen(name, mode, fileobj, **kwargs)
- except (IOError, EOFError):
+ except (OSError, EOFError):
fileobj.close()
raise ReadError("not a bzip2 file")
t._extfileobj = False
@@ -2022,7 +1998,7 @@ class TarFile(object):
try:
self._extract_member(tarinfo, os.path.join(path, tarinfo.name),
set_attrs=set_attrs)
- except EnvironmentError as e:
+ except OSError as e:
if self.errorlevel > 0:
raise
else:
@@ -2211,9 +2187,8 @@ class TarFile(object):
if tarinfo.issym() and hasattr(os, "lchown"):
os.lchown(targetpath, u, g)
else:
- if sys.platform != "os2emx":
- os.chown(targetpath, u, g)
- except EnvironmentError as e:
+ os.chown(targetpath, u, g)
+ except OSError as e:
raise ExtractError("could not change owner")
def chmod(self, tarinfo, targetpath):
@@ -2222,7 +2197,7 @@ class TarFile(object):
if hasattr(os, 'chmod'):
try:
os.chmod(targetpath, tarinfo.mode)
- except EnvironmentError as e:
+ except OSError as e:
raise ExtractError("could not change mode")
def utime(self, tarinfo, targetpath):
@@ -2232,7 +2207,7 @@ class TarFile(object):
return
try:
os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime))
- except EnvironmentError as e:
+ except OSError as e:
raise ExtractError("could not change modification time")
#--------------------------------------------------------------------------
@@ -2323,9 +2298,9 @@ class TarFile(object):
corresponds to TarFile's mode.
"""
if self.closed:
- raise IOError("%s is closed" % self.__class__.__name__)
+ raise OSError("%s is closed" % self.__class__.__name__)
if mode is not None and self.mode not in mode:
- raise IOError("bad operation for mode %r" % self.mode)
+ raise OSError("bad operation for mode %r" % self.mode)
def _find_link_target(self, tarinfo):
"""Find the target member of a symlink or hardlink member in the
@@ -2429,3 +2404,97 @@ def is_tarfile(name):
bltn_open = open
open = TarFile.open
+
+
+def main():
+ import argparse
+
+ description = 'A simple command line interface for tarfile module.'
+ parser = argparse.ArgumentParser(description=description)
+ parser.add_argument('-v', '--verbose', action='store_true', default=False,
+ help='Verbose output')
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument('-l', '--list', metavar='<tarfile>',
+ help='Show listing of a tarfile')
+ group.add_argument('-e', '--extract', nargs='+',
+ metavar=('<tarfile>', '<output_dir>'),
+ help='Extract tarfile into target dir')
+ group.add_argument('-c', '--create', nargs='+',
+ metavar=('<name>', '<file>'),
+ help='Create tarfile from sources')
+ group.add_argument('-t', '--test', metavar='<tarfile>',
+ help='Test if a tarfile is valid')
+ args = parser.parse_args()
+
+ if args.test:
+ src = args.test
+ if is_tarfile(src):
+ with open(src, 'r') as tar:
+ tar.getmembers()
+ print(tar.getmembers(), file=sys.stderr)
+ if args.verbose:
+ print('{!r} is a tar archive.'.format(src))
+ else:
+ parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
+
+ elif args.list:
+ src = args.list
+ if is_tarfile(src):
+ with TarFile.open(src, 'r:*') as tf:
+ tf.list(verbose=args.verbose)
+ else:
+ parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
+
+ elif args.extract:
+ if len(args.extract) == 1:
+ src = args.extract[0]
+ curdir = os.curdir
+ elif len(args.extract) == 2:
+ src, curdir = args.extract
+ else:
+ parser.exit(1, parser.format_help())
+
+ if is_tarfile(src):
+ with TarFile.open(src, 'r:*') as tf:
+ tf.extractall(path=curdir)
+ if args.verbose:
+ if curdir == '.':
+ msg = '{!r} file is extracted.'.format(src)
+ else:
+ msg = ('{!r} file is extracted '
+ 'into {!r} directory.').format(src, curdir)
+ print(msg)
+ else:
+ parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
+
+ elif args.create:
+ tar_name = args.create.pop(0)
+ _, ext = os.path.splitext(tar_name)
+ compressions = {
+ # gz
+ 'gz': 'gz',
+ 'tgz': 'gz',
+ # xz
+ 'xz': 'xz',
+ 'txz': 'xz',
+ # bz2
+ 'bz2': 'bz2',
+ 'tbz': 'bz2',
+ 'tbz2': 'bz2',
+ 'tb2': 'bz2',
+ }
+ tar_mode = 'w:' + compressions[ext] if ext in compressions else 'w'
+ tar_files = args.create
+
+ with TarFile.open(tar_name, tar_mode) as tf:
+ for file_name in tar_files:
+ tf.add(file_name)
+
+ if args.verbose:
+ print('{!r} file created.'.format(tar_name))
+
+ else:
+ parser.exit(1, parser.format_help())
+
+if __name__ == '__main__':
+ main()
diff --git a/Lib/telnetlib.py b/Lib/telnetlib.py
index 14ca1b1904..0cacac85fa 100644
--- a/Lib/telnetlib.py
+++ b/Lib/telnetlib.py
@@ -17,13 +17,12 @@ guido Guido van Rossum pts/2 <Dec 2 11:10> snag.cnri.reston..
Note that read_all() won't read until eof -- it just reads some data
-- but it guarantees to read at least one byte unless EOF is hit.
-It is possible to pass a Telnet object to select.select() in order to
-wait until more data is available. Note that in this case,
-read_eager() may return b'' even if there was data on the socket,
-because the protocol negotiation may have eaten the data. This is why
-EOFError is needed in some cases to distinguish between "no data" and
-"connection closed" (since the socket also appears ready for reading
-when it is closed).
+It is possible to pass a Telnet object to a selector in order to wait until
+more data is available. Note that in this case, read_eager() may return b''
+even if there was data on the socket, because the protocol negotiation may have
+eaten the data. This is why EOFError is needed in some cases to distinguish
+between "no data" and "connection closed" (since the socket also appears ready
+for reading when it is closed).
To do:
- option negotiation
@@ -34,10 +33,9 @@ To do:
# Imported modules
-import errno
import sys
import socket
-import select
+import selectors
try:
from time import monotonic as _time
except ImportError:
@@ -134,6 +132,15 @@ PRAGMA_HEARTBEAT = bytes([140]) # TELOPT PRAGMA HEARTBEAT
EXOPL = bytes([255]) # Extended-Options-List
NOOPT = bytes([0])
+
+# poll/select have the advantage of not requiring any extra file descriptor,
+# contrarily to epoll/kqueue (also, they require a single syscall).
+if hasattr(selectors, 'PollSelector'):
+ _TelnetSelector = selectors.PollSelector
+else:
+ _TelnetSelector = selectors.SelectSelector
+
+
class Telnet:
"""Telnet interface class.
@@ -210,7 +217,6 @@ class Telnet:
self.sb = 0 # flag for SB and SE sequence.
self.sbdataq = b''
self.option_callback = None
- self._has_poll = hasattr(select, 'poll')
if host is not None:
self.open(host, port, timeout)
@@ -277,7 +283,7 @@ class Telnet:
"""Write a string to the socket, doubling any IAC characters.
Can block if the connection is blocked. May raise
- socket.error if the connection is closed.
+ OSError if the connection is closed.
"""
if IAC in buffer:
@@ -293,60 +299,6 @@ class Telnet:
is closed and no cooked data is available.
"""
- if self._has_poll:
- return self._read_until_with_poll(match, timeout)
- else:
- return self._read_until_with_select(match, timeout)
-
- def _read_until_with_poll(self, match, timeout):
- """Read until a given string is encountered or until timeout.
-
- This method uses select.poll() to implement the timeout.
- """
- n = len(match)
- call_timeout = timeout
- if timeout is not None:
- time_start = _time()
- self.process_rawq()
- i = self.cookedq.find(match)
- if i < 0:
- poller = select.poll()
- poll_in_or_priority_flags = select.POLLIN | select.POLLPRI
- poller.register(self, poll_in_or_priority_flags)
- while i < 0 and not self.eof:
- try:
- ready = poller.poll(call_timeout)
- except select.error as e:
- if e.errno == errno.EINTR:
- if timeout is not None:
- elapsed = _time() - time_start
- call_timeout = timeout-elapsed
- continue
- raise
- for fd, mode in ready:
- if mode & poll_in_or_priority_flags:
- i = max(0, len(self.cookedq)-n)
- self.fill_rawq()
- self.process_rawq()
- i = self.cookedq.find(match, i)
- if timeout is not None:
- elapsed = _time() - time_start
- if elapsed >= timeout:
- break
- call_timeout = timeout-elapsed
- poller.unregister(self)
- if i >= 0:
- i = i + n
- buf = self.cookedq[:i]
- self.cookedq = self.cookedq[i:]
- return buf
- return self.read_very_lazy()
-
- def _read_until_with_select(self, match, timeout=None):
- """Read until a given string is encountered or until timeout.
-
- The timeout is implemented using select.select().
- """
n = len(match)
self.process_rawq()
i = self.cookedq.find(match)
@@ -355,26 +307,25 @@ class Telnet:
buf = self.cookedq[:i]
self.cookedq = self.cookedq[i:]
return buf
- s_reply = ([self], [], [])
- s_args = s_reply
if timeout is not None:
- s_args = s_args + (timeout,)
- time_start = _time()
- while not self.eof and select.select(*s_args) == s_reply:
- i = max(0, len(self.cookedq)-n)
- self.fill_rawq()
- self.process_rawq()
- i = self.cookedq.find(match, i)
- if i >= 0:
- i = i+n
- buf = self.cookedq[:i]
- self.cookedq = self.cookedq[i:]
- return buf
- if timeout is not None:
- elapsed = _time() - time_start
- if elapsed >= timeout:
- break
- s_args = s_reply + (timeout-elapsed,)
+ deadline = _time() + timeout
+ with _TelnetSelector() as selector:
+ selector.register(self, selectors.EVENT_READ)
+ while not self.eof:
+ if selector.select(timeout):
+ i = max(0, len(self.cookedq)-n)
+ self.fill_rawq()
+ self.process_rawq()
+ i = self.cookedq.find(match, i)
+ if i >= 0:
+ i = i+n
+ buf = self.cookedq[:i]
+ self.cookedq = self.cookedq[i:]
+ return buf
+ if timeout is not None:
+ timeout = deadline - _time()
+ if timeout < 0:
+ break
return self.read_very_lazy()
def read_all(self):
@@ -579,29 +530,35 @@ class Telnet:
def sock_avail(self):
"""Test whether data is available on the socket."""
- return select.select([self], [], [], 0) == ([self], [], [])
+ with _TelnetSelector() as selector:
+ selector.register(self, selectors.EVENT_READ)
+ return bool(selector.select(0))
def interact(self):
"""Interaction function, emulates a very dumb telnet client."""
if sys.platform == "win32":
self.mt_interact()
return
- while 1:
- rfd, wfd, xfd = select.select([self, sys.stdin], [], [])
- if self in rfd:
- try:
- text = self.read_eager()
- except EOFError:
- print('*** Connection closed by remote host ***')
- break
- if text:
- sys.stdout.write(text.decode('ascii'))
- sys.stdout.flush()
- if sys.stdin in rfd:
- line = sys.stdin.readline().encode('ascii')
- if not line:
- break
- self.write(line)
+ with _TelnetSelector() as selector:
+ selector.register(self, selectors.EVENT_READ)
+ selector.register(sys.stdin, selectors.EVENT_READ)
+
+ while True:
+ for key, events in selector.select():
+ if key.fileobj is self:
+ try:
+ text = self.read_eager()
+ except EOFError:
+ print('*** Connection closed by remote host ***')
+ return
+ if text:
+ sys.stdout.write(text.decode('ascii'))
+ sys.stdout.flush()
+ elif key.fileobj is sys.stdin:
+ line = sys.stdin.readline().encode('ascii')
+ if not line:
+ return
+ self.write(line)
def mt_interact(self):
"""Multithreaded version of interact()."""
@@ -648,78 +605,6 @@ class Telnet:
results are undeterministic, and may depend on the I/O timing.
"""
- if self._has_poll:
- return self._expect_with_poll(list, timeout)
- else:
- return self._expect_with_select(list, timeout)
-
- def _expect_with_poll(self, expect_list, timeout=None):
- """Read until one from a list of a regular expressions matches.
-
- This method uses select.poll() to implement the timeout.
- """
- re = None
- expect_list = expect_list[:]
- indices = range(len(expect_list))
- for i in indices:
- if not hasattr(expect_list[i], "search"):
- if not re: import re
- expect_list[i] = re.compile(expect_list[i])
- call_timeout = timeout
- if timeout is not None:
- time_start = _time()
- self.process_rawq()
- m = None
- for i in indices:
- m = expect_list[i].search(self.cookedq)
- if m:
- e = m.end()
- text = self.cookedq[:e]
- self.cookedq = self.cookedq[e:]
- break
- if not m:
- poller = select.poll()
- poll_in_or_priority_flags = select.POLLIN | select.POLLPRI
- poller.register(self, poll_in_or_priority_flags)
- while not m and not self.eof:
- try:
- ready = poller.poll(call_timeout)
- except select.error as e:
- if e.errno == errno.EINTR:
- if timeout is not None:
- elapsed = _time() - time_start
- call_timeout = timeout-elapsed
- continue
- raise
- for fd, mode in ready:
- if mode & poll_in_or_priority_flags:
- self.fill_rawq()
- self.process_rawq()
- for i in indices:
- m = expect_list[i].search(self.cookedq)
- if m:
- e = m.end()
- text = self.cookedq[:e]
- self.cookedq = self.cookedq[e:]
- break
- if timeout is not None:
- elapsed = _time() - time_start
- if elapsed >= timeout:
- break
- call_timeout = timeout-elapsed
- poller.unregister(self)
- if m:
- return (i, m, text)
- text = self.read_very_lazy()
- if not text and self.eof:
- raise EOFError
- return (-1, None, text)
-
- def _expect_with_select(self, list, timeout=None):
- """Read until one from a list of a regular expressions matches.
-
- The timeout is implemented using select.select().
- """
re = None
list = list[:]
indices = range(len(list))
@@ -728,27 +613,27 @@ class Telnet:
if not re: import re
list[i] = re.compile(list[i])
if timeout is not None:
- time_start = _time()
- while 1:
- self.process_rawq()
- for i in indices:
- m = list[i].search(self.cookedq)
- if m:
- e = m.end()
- text = self.cookedq[:e]
- self.cookedq = self.cookedq[e:]
- return (i, m, text)
- if self.eof:
- break
- if timeout is not None:
- elapsed = _time() - time_start
- if elapsed >= timeout:
- break
- s_args = ([self.fileno()], [], [], timeout-elapsed)
- r, w, x = select.select(*s_args)
- if not r:
- break
- self.fill_rawq()
+ deadline = _time() + timeout
+ with _TelnetSelector() as selector:
+ selector.register(self, selectors.EVENT_READ)
+ while not self.eof:
+ self.process_rawq()
+ for i in indices:
+ m = list[i].search(self.cookedq)
+ if m:
+ e = m.end()
+ text = self.cookedq[:e]
+ self.cookedq = self.cookedq[e:]
+ return (i, m, text)
+ if timeout is not None:
+ ready = selector.select(timeout)
+ timeout = deadline - _time()
+ if not ready:
+ if timeout < 0:
+ break
+ else:
+ continue
+ self.fill_rawq()
text = self.read_very_lazy()
if not text and self.eof:
raise EOFError
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index 91332b61be..5d3462102f 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -35,31 +35,12 @@ import errno as _errno
from random import Random as _Random
try:
- import fcntl as _fcntl
-except ImportError:
- def _set_cloexec(fd):
- pass
-else:
- def _set_cloexec(fd):
- try:
- flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0)
- except OSError:
- pass
- else:
- # flags read successfully, modify
- flags |= _fcntl.FD_CLOEXEC
- _fcntl.fcntl(fd, _fcntl.F_SETFD, flags)
-
-
-try:
import _thread
except ImportError:
import _dummy_thread as _thread
_allocate_lock = _thread.allocate_lock
_text_openflags = _os.O_RDWR | _os.O_CREAT | _os.O_EXCL
-if hasattr(_os, 'O_NOINHERIT'):
- _text_openflags |= _os.O_NOINHERIT
if hasattr(_os, 'O_NOFOLLOW'):
_text_openflags |= _os.O_NOFOLLOW
@@ -88,8 +69,8 @@ else:
# Fallback. All we need is something that raises OSError if the
# file doesn't exist.
def _stat(fn):
- f = open(fn)
- f.close()
+ fd = _os.open(fn, _os.O_RDONLY)
+ os.close(fd)
def _exists(fn):
try:
@@ -123,7 +104,7 @@ class _RandomNameSequence:
def __next__(self):
c = self.characters
choose = self.rng.choice
- letters = [choose(c) for dummy in "123456"]
+ letters = [choose(c) for dummy in range(8)]
return ''.join(letters)
def _candidate_tempdir_list():
@@ -165,7 +146,7 @@ def _get_default_tempdir():
for dir in dirlist:
if dir != _os.curdir:
- dir = _os.path.normcase(_os.path.abspath(dir))
+ dir = _os.path.abspath(dir)
# Try only a few names per directory.
for seq in range(100):
name = next(namer)
@@ -215,7 +196,6 @@ def _mkstemp_inner(dir, pre, suf, flags):
file = _os.path.join(dir, pre + name + suf)
try:
fd = _os.open(file, flags, 0o600)
- _set_cloexec(fd)
return (fd, _os.path.abspath(file))
except FileExistsError:
continue # try again
@@ -691,7 +671,6 @@ class TemporaryDirectory(object):
_islink = staticmethod(_os.path.islink)
_remove = staticmethod(_os.remove)
_rmdir = staticmethod(_os.rmdir)
- _os_error = OSError
_warn = _warnings.warn
def _rmtree(self, path):
@@ -701,16 +680,16 @@ class TemporaryDirectory(object):
fullname = self._path_join(path, name)
try:
isdir = self._isdir(fullname) and not self._islink(fullname)
- except self._os_error:
+ except OSError:
isdir = False
if isdir:
self._rmtree(fullname)
else:
try:
self._remove(fullname)
- except self._os_error:
+ except OSError:
pass
try:
self._rmdir(path)
- except self._os_error:
+ except OSError:
pass
diff --git a/Lib/test/__main__.py b/Lib/test/__main__.py
index ce5615b889..d5fbe159d7 100644
--- a/Lib/test/__main__.py
+++ b/Lib/test/__main__.py
@@ -1,13 +1,3 @@
-from test import regrtest, support
+from test import regrtest
-
-TEMPDIR, TESTCWD = regrtest._make_temp_dir_for_build(regrtest.TEMPDIR)
-regrtest.TEMPDIR = TEMPDIR
-regrtest.TESTCWD = TESTCWD
-
-# Run the tests in a context manager that temporary changes the CWD to a
-# temporary and writable directory. If it's not possible to create or
-# change the CWD, the original CWD will be used. The original CWD is
-# available from support.SAVEDCWD.
-with support.temp_cwd(TESTCWD, quiet=True):
- regrtest.main()
+regrtest.main_in_temp_cwd()
diff --git a/Lib/test/test_multiprocessing.py b/Lib/test/_test_multiprocessing.py
index 27db029b3c..bd26e3d3f2 100644
--- a/Lib/test/test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -43,7 +43,7 @@ from multiprocessing import util
try:
from multiprocessing import reduction
- HAS_REDUCTION = True
+ HAS_REDUCTION = reduction.HAVE_SEND_HANDLE
except ImportError:
HAS_REDUCTION = False
@@ -99,6 +99,9 @@ try:
except:
MAXFD = 256
+# To speed up tests when using the forkserver, we can preload these:
+PRELOAD = ['__main__', 'test.test_multiprocessing_forkserver']
+
#
# Some tests require ctypes
#
@@ -294,17 +297,22 @@ class _TestProcess(BaseTestCase):
self.assertTimingAlmostEqual(join.elapsed, 0.0)
self.assertEqual(p.is_alive(), True)
+ # XXX maybe terminating too soon causes the problems on Gentoo...
+ time.sleep(1)
+
p.terminate()
if hasattr(signal, 'alarm'):
+ # On the Gentoo buildbot waitpid() often seems to block forever.
+ # We use alarm() to interrupt it if it blocks for too long.
def handler(*args):
raise RuntimeError('join took too long: %s' % p)
old_handler = signal.signal(signal.SIGALRM, handler)
try:
signal.alarm(10)
self.assertEqual(join(), None)
- signal.alarm(0)
finally:
+ signal.alarm(0)
signal.signal(signal.SIGALRM, old_handler)
else:
self.assertEqual(join(), None)
@@ -342,7 +350,6 @@ class _TestProcess(BaseTestCase):
@classmethod
def _test_recursion(cls, wconn, id):
- from multiprocessing import forking
wconn.send(id)
if len(id) < 2:
for i in range(2):
@@ -390,7 +397,7 @@ class _TestProcess(BaseTestCase):
self.assertFalse(wait_for_handle(sentinel, timeout=0.0))
event.set()
p.join()
- self.assertTrue(wait_for_handle(sentinel, timeout=DELTA))
+ self.assertTrue(wait_for_handle(sentinel, timeout=1))
#
#
@@ -1779,6 +1786,35 @@ class _TestPool(BaseTestCase):
self.assertEqual(r.get(), expected)
self.assertRaises(ValueError, p.map_async, sqr, L)
+ @classmethod
+ def _test_traceback(cls):
+ raise RuntimeError(123) # some comment
+
+ def test_traceback(self):
+ # We want ensure that the traceback from the child process is
+ # contained in the traceback raised in the main process.
+ if self.TYPE == 'processes':
+ with self.Pool(1) as p:
+ try:
+ p.apply(self._test_traceback)
+ except Exception as e:
+ exc = e
+ else:
+ raise AssertionError('expected RuntimeError')
+ self.assertIs(type(exc), RuntimeError)
+ self.assertEqual(exc.args, (123,))
+ cause = exc.__cause__
+ self.assertIs(type(cause), multiprocessing.pool.RemoteTraceback)
+ self.assertIn('raise RuntimeError(123) # some comment', cause.tb)
+
+ with test.support.captured_stderr() as f1:
+ try:
+ raise exc
+ except RuntimeError:
+ sys.excepthook(*sys.exc_info())
+ self.assertIn('raise RuntimeError(123) # some comment',
+ f1.getvalue())
+
def raising():
raise KeyError("key")
@@ -2486,7 +2522,7 @@ class _TestPicklingConnections(BaseTestCase):
@classmethod
def tearDownClass(cls):
- from multiprocessing.reduction import resource_sharer
+ from multiprocessing import resource_sharer
resource_sharer.stop(timeout=5)
@classmethod
@@ -2800,30 +2836,40 @@ class _TestFinalize(BaseTestCase):
# Test that from ... import * works for each module
#
-class _TestImportStar(BaseTestCase):
+class _TestImportStar(unittest.TestCase):
- ALLOWED_TYPES = ('processes',)
+ def get_module_names(self):
+ import glob
+ folder = os.path.dirname(multiprocessing.__file__)
+ pattern = os.path.join(folder, '*.py')
+ files = glob.glob(pattern)
+ modules = [os.path.splitext(os.path.split(f)[1])[0] for f in files]
+ modules = ['multiprocessing.' + m for m in modules]
+ modules.remove('multiprocessing.__init__')
+ modules.append('multiprocessing')
+ return modules
def test_import(self):
- modules = [
- 'multiprocessing', 'multiprocessing.connection',
- 'multiprocessing.heap', 'multiprocessing.managers',
- 'multiprocessing.pool', 'multiprocessing.process',
- 'multiprocessing.synchronize', 'multiprocessing.util'
- ]
-
- if HAS_REDUCTION:
- modules.append('multiprocessing.reduction')
+ modules = self.get_module_names()
+ if sys.platform == 'win32':
+ modules.remove('multiprocessing.popen_fork')
+ modules.remove('multiprocessing.popen_forkserver')
+ modules.remove('multiprocessing.popen_spawn_posix')
+ else:
+ modules.remove('multiprocessing.popen_spawn_win32')
+ if not HAS_REDUCTION:
+ modules.remove('multiprocessing.popen_forkserver')
- if c_int is not None:
+ if c_int is None:
# This module requires _ctypes
- modules.append('multiprocessing.sharedctypes')
+ modules.remove('multiprocessing.sharedctypes')
for name in modules:
__import__(name)
mod = sys.modules[name]
+ self.assertTrue(hasattr(mod, '__all__'), name)
- for attr in getattr(mod, '__all__', ()):
+ for attr in mod.__all__:
self.assertTrue(
hasattr(mod, attr),
'%r does not have attribute %r' % (mod, attr)
@@ -2906,7 +2952,7 @@ class _TestPollEintr(BaseTestCase):
@classmethod
def _killer(cls, pid):
- time.sleep(0.5)
+ time.sleep(0.1)
os.kill(pid, signal.SIGUSR1)
@unittest.skipUnless(hasattr(signal, 'SIGUSR1'), 'requires SIGUSR1')
@@ -2919,12 +2965,14 @@ class _TestPollEintr(BaseTestCase):
try:
killer = self.Process(target=self._killer, args=(pid,))
killer.start()
- p = self.Process(target=time.sleep, args=(1,))
- p.start()
- p.join()
+ try:
+ p = self.Process(target=time.sleep, args=(2,))
+ p.start()
+ p.join()
+ finally:
+ killer.join()
self.assertTrue(got_signal[0])
self.assertEqual(p.exitcode, 0)
- killer.join()
finally:
signal.signal(signal.SIGUSR1, oldhandler)
@@ -2937,8 +2985,11 @@ class TestInvalidHandle(unittest.TestCase):
@unittest.skipIf(WIN32, "skipped on Windows")
def test_invalid_handles(self):
conn = multiprocessing.connection.Connection(44977608)
+ # check that poll() doesn't crash
try:
- self.assertRaises((ValueError, OSError), conn.poll)
+ conn.poll()
+ except (ValueError, OSError):
+ pass
finally:
# Hack private attribute _handle to avoid printing an error
# in conn.__del__
@@ -2946,131 +2997,6 @@ class TestInvalidHandle(unittest.TestCase):
self.assertRaises((ValueError, OSError),
multiprocessing.connection.Connection, -1)
-#
-# Functions used to create test cases from the base ones in this module
-#
-
-def create_test_cases(Mixin, type):
- result = {}
- glob = globals()
- Type = type.capitalize()
- ALL_TYPES = {'processes', 'threads', 'manager'}
-
- for name in list(glob.keys()):
- if name.startswith('_Test'):
- base = glob[name]
- assert set(base.ALLOWED_TYPES) <= ALL_TYPES, set(base.ALLOWED_TYPES)
- if type in base.ALLOWED_TYPES:
- newname = 'With' + Type + name[1:]
- class Temp(base, Mixin, unittest.TestCase):
- pass
- result[newname] = Temp
- Temp.__name__ = Temp.__qualname__ = newname
- Temp.__module__ = Mixin.__module__
- return result
-
-#
-# Create test cases
-#
-
-class ProcessesMixin(object):
- TYPE = 'processes'
- Process = multiprocessing.Process
- connection = multiprocessing.connection
- current_process = staticmethod(multiprocessing.current_process)
- active_children = staticmethod(multiprocessing.active_children)
- Pool = staticmethod(multiprocessing.Pool)
- Pipe = staticmethod(multiprocessing.Pipe)
- Queue = staticmethod(multiprocessing.Queue)
- JoinableQueue = staticmethod(multiprocessing.JoinableQueue)
- Lock = staticmethod(multiprocessing.Lock)
- RLock = staticmethod(multiprocessing.RLock)
- Semaphore = staticmethod(multiprocessing.Semaphore)
- BoundedSemaphore = staticmethod(multiprocessing.BoundedSemaphore)
- Condition = staticmethod(multiprocessing.Condition)
- Event = staticmethod(multiprocessing.Event)
- Barrier = staticmethod(multiprocessing.Barrier)
- Value = staticmethod(multiprocessing.Value)
- Array = staticmethod(multiprocessing.Array)
- RawValue = staticmethod(multiprocessing.RawValue)
- RawArray = staticmethod(multiprocessing.RawArray)
-
-testcases_processes = create_test_cases(ProcessesMixin, type='processes')
-globals().update(testcases_processes)
-
-
-class ManagerMixin(object):
- TYPE = 'manager'
- Process = multiprocessing.Process
- Queue = property(operator.attrgetter('manager.Queue'))
- JoinableQueue = property(operator.attrgetter('manager.JoinableQueue'))
- Lock = property(operator.attrgetter('manager.Lock'))
- RLock = property(operator.attrgetter('manager.RLock'))
- Semaphore = property(operator.attrgetter('manager.Semaphore'))
- BoundedSemaphore = property(operator.attrgetter('manager.BoundedSemaphore'))
- Condition = property(operator.attrgetter('manager.Condition'))
- Event = property(operator.attrgetter('manager.Event'))
- Barrier = property(operator.attrgetter('manager.Barrier'))
- Value = property(operator.attrgetter('manager.Value'))
- Array = property(operator.attrgetter('manager.Array'))
- list = property(operator.attrgetter('manager.list'))
- dict = property(operator.attrgetter('manager.dict'))
- Namespace = property(operator.attrgetter('manager.Namespace'))
-
- @classmethod
- def Pool(cls, *args, **kwds):
- return cls.manager.Pool(*args, **kwds)
-
- @classmethod
- def setUpClass(cls):
- cls.manager = multiprocessing.Manager()
-
- @classmethod
- def tearDownClass(cls):
- # only the manager process should be returned by active_children()
- # but this can take a bit on slow machines, so wait a few seconds
- # if there are other children too (see #17395)
- t = 0.01
- while len(multiprocessing.active_children()) > 1 and t < 5:
- time.sleep(t)
- t *= 2
- gc.collect() # do garbage collection
- if cls.manager._number_of_objects() != 0:
- # This is not really an error since some tests do not
- # ensure that all processes which hold a reference to a
- # managed object have been joined.
- print('Shared objects which still exist at manager shutdown:')
- print(cls.manager._debug_info())
- cls.manager.shutdown()
- cls.manager.join()
- cls.manager = None
-
-testcases_manager = create_test_cases(ManagerMixin, type='manager')
-globals().update(testcases_manager)
-
-
-class ThreadsMixin(object):
- TYPE = 'threads'
- Process = multiprocessing.dummy.Process
- connection = multiprocessing.dummy.connection
- current_process = staticmethod(multiprocessing.dummy.current_process)
- active_children = staticmethod(multiprocessing.dummy.active_children)
- Pool = staticmethod(multiprocessing.Pool)
- Pipe = staticmethod(multiprocessing.dummy.Pipe)
- Queue = staticmethod(multiprocessing.dummy.Queue)
- JoinableQueue = staticmethod(multiprocessing.dummy.JoinableQueue)
- Lock = staticmethod(multiprocessing.dummy.Lock)
- RLock = staticmethod(multiprocessing.dummy.RLock)
- Semaphore = staticmethod(multiprocessing.dummy.Semaphore)
- BoundedSemaphore = staticmethod(multiprocessing.dummy.BoundedSemaphore)
- Condition = staticmethod(multiprocessing.dummy.Condition)
- Event = staticmethod(multiprocessing.dummy.Event)
- Barrier = staticmethod(multiprocessing.dummy.Barrier)
- Value = staticmethod(multiprocessing.dummy.Value)
- Array = staticmethod(multiprocessing.dummy.Array)
-
-testcases_threads = create_test_cases(ThreadsMixin, type='threads')
-globals().update(testcases_threads)
class OtherTest(unittest.TestCase):
@@ -3420,7 +3346,7 @@ class TestFlags(unittest.TestCase):
def test_flags(self):
import json, subprocess
# start child process using unusual flags
- prog = ('from test.test_multiprocessing import TestFlags; ' +
+ prog = ('from test._test_multiprocessing import TestFlags; ' +
'TestFlags.run_in_child()')
data = subprocess.check_output(
[sys.executable, '-E', '-S', '-O', '-c', prog])
@@ -3467,13 +3393,14 @@ class TestTimeouts(unittest.TestCase):
class TestNoForkBomb(unittest.TestCase):
def test_noforkbomb(self):
+ sm = multiprocessing.get_start_method()
name = os.path.join(os.path.dirname(__file__), 'mp_fork_bomb.py')
- if WIN32:
- rc, out, err = test.script_helper.assert_python_failure(name)
+ if sm != 'fork':
+ rc, out, err = test.script_helper.assert_python_failure(name, sm)
self.assertEqual('', out.decode('ascii'))
self.assertIn('RuntimeError', err.decode('ascii'))
else:
- rc, out, err = test.script_helper.assert_python_ok(name)
+ rc, out, err = test.script_helper.assert_python_ok(name, sm)
self.assertEqual('123', out.decode('ascii').rstrip())
self.assertEqual('', err.decode('ascii'))
@@ -3491,7 +3418,8 @@ class TestForkAwareThreadLock(unittest.TestCase):
if n > 1:
p = multiprocessing.Process(target=cls.child, args=(n-1, conn))
p.start()
- p.join()
+ conn.close()
+ p.join(timeout=5)
else:
conn.send(len(util._afterfork_registry))
conn.close()
@@ -3502,11 +3430,78 @@ class TestForkAwareThreadLock(unittest.TestCase):
old_size = len(util._afterfork_registry)
p = multiprocessing.Process(target=self.child, args=(5, w))
p.start()
+ w.close()
new_size = r.recv()
- p.join()
+ p.join(timeout=5)
self.assertLessEqual(new_size, old_size)
#
+# Check that non-forked child processes do not inherit unneeded fds/handles
+#
+
+class TestCloseFds(unittest.TestCase):
+
+ def get_high_socket_fd(self):
+ if WIN32:
+ # The child process will not have any socket handles, so
+ # calling socket.fromfd() should produce WSAENOTSOCK even
+ # if there is a handle of the same number.
+ return socket.socket().detach()
+ else:
+ # We want to produce a socket with an fd high enough that a
+ # freshly created child process will not have any fds as high.
+ fd = socket.socket().detach()
+ to_close = []
+ while fd < 50:
+ to_close.append(fd)
+ fd = os.dup(fd)
+ for x in to_close:
+ os.close(x)
+ return fd
+
+ def close(self, fd):
+ if WIN32:
+ socket.socket(fileno=fd).close()
+ else:
+ os.close(fd)
+
+ @classmethod
+ def _test_closefds(cls, conn, fd):
+ try:
+ s = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
+ except Exception as e:
+ conn.send(e)
+ else:
+ s.close()
+ conn.send(None)
+
+ def test_closefd(self):
+ if not HAS_REDUCTION:
+ raise unittest.SkipTest('requires fd pickling')
+
+ reader, writer = multiprocessing.Pipe()
+ fd = self.get_high_socket_fd()
+ try:
+ p = multiprocessing.Process(target=self._test_closefds,
+ args=(writer, fd))
+ p.start()
+ writer.close()
+ e = reader.recv()
+ p.join(timeout=5)
+ finally:
+ self.close(fd)
+ writer.close()
+ reader.close()
+
+ if multiprocessing.get_start_method() == 'fork':
+ self.assertIs(e, None)
+ else:
+ WSAENOTSOCK = 10038
+ self.assertIsInstance(e, OSError)
+ self.assertTrue(e.errno == errno.EBADF or
+ e.winerror == WSAENOTSOCK, e)
+
+#
# Issue #17097: EINTR should be ignored by recv(), send(), accept() etc
#
@@ -3550,10 +3545,10 @@ class TestIgnoreEINTR(unittest.TestCase):
def handler(signum, frame):
pass
signal.signal(signal.SIGUSR1, handler)
- l = multiprocessing.connection.Listener()
- conn.send(l.address)
- a = l.accept()
- a.send('welcome')
+ with multiprocessing.connection.Listener() as l:
+ conn.send(l.address)
+ a = l.accept()
+ a.send('welcome')
@unittest.skipUnless(hasattr(signal, 'SIGUSR1'), 'requires SIGUSR1')
def test_ignore_listener(self):
@@ -3574,26 +3569,267 @@ class TestIgnoreEINTR(unittest.TestCase):
finally:
conn.close()
+class TestStartMethod(unittest.TestCase):
+ @classmethod
+ def _check_context(cls, conn):
+ conn.send(multiprocessing.get_start_method())
+
+ def check_context(self, ctx):
+ r, w = ctx.Pipe(duplex=False)
+ p = ctx.Process(target=self._check_context, args=(w,))
+ p.start()
+ w.close()
+ child_method = r.recv()
+ r.close()
+ p.join()
+ self.assertEqual(child_method, ctx.get_start_method())
+
+ def test_context(self):
+ for method in ('fork', 'spawn', 'forkserver'):
+ try:
+ ctx = multiprocessing.get_context(method)
+ except ValueError:
+ continue
+ self.assertEqual(ctx.get_start_method(), method)
+ self.assertIs(ctx.get_context(), ctx)
+ self.assertRaises(ValueError, ctx.set_start_method, 'spawn')
+ self.assertRaises(ValueError, ctx.set_start_method, None)
+ self.check_context(ctx)
+
+ def test_set_get(self):
+ multiprocessing.set_forkserver_preload(PRELOAD)
+ count = 0
+ old_method = multiprocessing.get_start_method()
+ try:
+ for method in ('fork', 'spawn', 'forkserver'):
+ try:
+ multiprocessing.set_start_method(method, force=True)
+ except ValueError:
+ continue
+ self.assertEqual(multiprocessing.get_start_method(), method)
+ ctx = multiprocessing.get_context()
+ self.assertEqual(ctx.get_start_method(), method)
+ self.assertTrue(type(ctx).__name__.lower().startswith(method))
+ self.assertTrue(
+ ctx.Process.__name__.lower().startswith(method))
+ self.check_context(multiprocessing)
+ count += 1
+ finally:
+ multiprocessing.set_start_method(old_method, force=True)
+ self.assertGreaterEqual(count, 1)
+
+ def test_get_all(self):
+ methods = multiprocessing.get_all_start_methods()
+ if sys.platform == 'win32':
+ self.assertEqual(methods, ['spawn'])
+ else:
+ self.assertTrue(methods == ['fork', 'spawn'] or
+ methods == ['fork', 'spawn', 'forkserver'])
+
+#
+# Check that killing process does not leak named semaphores
+#
+
+@unittest.skipIf(sys.platform == "win32",
+ "test semantics don't make sense on Windows")
+class TestSemaphoreTracker(unittest.TestCase):
+ def test_semaphore_tracker(self):
+ import subprocess
+ cmd = '''if 1:
+ import multiprocessing as mp, time, os
+ mp.set_start_method("spawn")
+ lock1 = mp.Lock()
+ lock2 = mp.Lock()
+ os.write(%d, lock1._semlock.name.encode("ascii") + b"\\n")
+ os.write(%d, lock2._semlock.name.encode("ascii") + b"\\n")
+ time.sleep(10)
+ '''
+ r, w = os.pipe()
+ p = subprocess.Popen([sys.executable,
+ '-c', cmd % (w, w)],
+ pass_fds=[w],
+ stderr=subprocess.PIPE)
+ os.close(w)
+ with open(r, 'rb', closefd=True) as f:
+ name1 = f.readline().rstrip().decode('ascii')
+ name2 = f.readline().rstrip().decode('ascii')
+ _multiprocessing.sem_unlink(name1)
+ p.terminate()
+ p.wait()
+ time.sleep(1.0)
+ with self.assertRaises(OSError) as ctx:
+ _multiprocessing.sem_unlink(name2)
+ # docs say it should be ENOENT, but OSX seems to give EINVAL
+ self.assertIn(ctx.exception.errno, (errno.ENOENT, errno.EINVAL))
+ err = p.stderr.read().decode('utf-8')
+ p.stderr.close()
+ expected = 'semaphore_tracker: There appear to be 2 leaked semaphores'
+ self.assertRegex(err, expected)
+ self.assertRegex(err, 'semaphore_tracker: %r: \[Errno' % name1)
+
#
-#
+# Mixins
#
-def setUpModule():
- if sys.platform.startswith("linux"):
- try:
- lock = multiprocessing.RLock()
- except OSError:
- raise unittest.SkipTest("OSError raises on RLock creation, "
- "see issue 3111!")
- check_enough_semaphores()
- util.get_temp_dir() # creates temp directory for use by all processes
- multiprocessing.get_logger().setLevel(LOG_LEVEL)
+class ProcessesMixin(object):
+ TYPE = 'processes'
+ Process = multiprocessing.Process
+ connection = multiprocessing.connection
+ current_process = staticmethod(multiprocessing.current_process)
+ active_children = staticmethod(multiprocessing.active_children)
+ Pool = staticmethod(multiprocessing.Pool)
+ Pipe = staticmethod(multiprocessing.Pipe)
+ Queue = staticmethod(multiprocessing.Queue)
+ JoinableQueue = staticmethod(multiprocessing.JoinableQueue)
+ Lock = staticmethod(multiprocessing.Lock)
+ RLock = staticmethod(multiprocessing.RLock)
+ Semaphore = staticmethod(multiprocessing.Semaphore)
+ BoundedSemaphore = staticmethod(multiprocessing.BoundedSemaphore)
+ Condition = staticmethod(multiprocessing.Condition)
+ Event = staticmethod(multiprocessing.Event)
+ Barrier = staticmethod(multiprocessing.Barrier)
+ Value = staticmethod(multiprocessing.Value)
+ Array = staticmethod(multiprocessing.Array)
+ RawValue = staticmethod(multiprocessing.RawValue)
+ RawArray = staticmethod(multiprocessing.RawArray)
-def tearDownModule():
- # pause a bit so we don't get warning about dangling threads/processes
- time.sleep(0.5)
+class ManagerMixin(object):
+ TYPE = 'manager'
+ Process = multiprocessing.Process
+ Queue = property(operator.attrgetter('manager.Queue'))
+ JoinableQueue = property(operator.attrgetter('manager.JoinableQueue'))
+ Lock = property(operator.attrgetter('manager.Lock'))
+ RLock = property(operator.attrgetter('manager.RLock'))
+ Semaphore = property(operator.attrgetter('manager.Semaphore'))
+ BoundedSemaphore = property(operator.attrgetter('manager.BoundedSemaphore'))
+ Condition = property(operator.attrgetter('manager.Condition'))
+ Event = property(operator.attrgetter('manager.Event'))
+ Barrier = property(operator.attrgetter('manager.Barrier'))
+ Value = property(operator.attrgetter('manager.Value'))
+ Array = property(operator.attrgetter('manager.Array'))
+ list = property(operator.attrgetter('manager.list'))
+ dict = property(operator.attrgetter('manager.dict'))
+ Namespace = property(operator.attrgetter('manager.Namespace'))
+
+ @classmethod
+ def Pool(cls, *args, **kwds):
+ return cls.manager.Pool(*args, **kwds)
+
+ @classmethod
+ def setUpClass(cls):
+ cls.manager = multiprocessing.Manager()
+
+ @classmethod
+ def tearDownClass(cls):
+ # only the manager process should be returned by active_children()
+ # but this can take a bit on slow machines, so wait a few seconds
+ # if there are other children too (see #17395)
+ t = 0.01
+ while len(multiprocessing.active_children()) > 1 and t < 5:
+ time.sleep(t)
+ t *= 2
+ gc.collect() # do garbage collection
+ if cls.manager._number_of_objects() != 0:
+ # This is not really an error since some tests do not
+ # ensure that all processes which hold a reference to a
+ # managed object have been joined.
+ print('Shared objects which still exist at manager shutdown:')
+ print(cls.manager._debug_info())
+ cls.manager.shutdown()
+ cls.manager.join()
+ cls.manager = None
-if __name__ == '__main__':
- unittest.main()
+class ThreadsMixin(object):
+ TYPE = 'threads'
+ Process = multiprocessing.dummy.Process
+ connection = multiprocessing.dummy.connection
+ current_process = staticmethod(multiprocessing.dummy.current_process)
+ active_children = staticmethod(multiprocessing.dummy.active_children)
+ Pool = staticmethod(multiprocessing.Pool)
+ Pipe = staticmethod(multiprocessing.dummy.Pipe)
+ Queue = staticmethod(multiprocessing.dummy.Queue)
+ JoinableQueue = staticmethod(multiprocessing.dummy.JoinableQueue)
+ Lock = staticmethod(multiprocessing.dummy.Lock)
+ RLock = staticmethod(multiprocessing.dummy.RLock)
+ Semaphore = staticmethod(multiprocessing.dummy.Semaphore)
+ BoundedSemaphore = staticmethod(multiprocessing.dummy.BoundedSemaphore)
+ Condition = staticmethod(multiprocessing.dummy.Condition)
+ Event = staticmethod(multiprocessing.dummy.Event)
+ Barrier = staticmethod(multiprocessing.dummy.Barrier)
+ Value = staticmethod(multiprocessing.dummy.Value)
+ Array = staticmethod(multiprocessing.dummy.Array)
+
+#
+# Functions used to create test cases from the base ones in this module
+#
+
+def install_tests_in_module_dict(remote_globs, start_method):
+ __module__ = remote_globs['__name__']
+ local_globs = globals()
+ ALL_TYPES = {'processes', 'threads', 'manager'}
+
+ for name, base in local_globs.items():
+ if not isinstance(base, type):
+ continue
+ if issubclass(base, BaseTestCase):
+ if base is BaseTestCase:
+ continue
+ assert set(base.ALLOWED_TYPES) <= ALL_TYPES, base.ALLOWED_TYPES
+ for type_ in base.ALLOWED_TYPES:
+ newname = 'With' + type_.capitalize() + name[1:]
+ Mixin = local_globs[type_.capitalize() + 'Mixin']
+ class Temp(base, Mixin, unittest.TestCase):
+ pass
+ Temp.__name__ = Temp.__qualname__ = newname
+ Temp.__module__ = __module__
+ remote_globs[newname] = Temp
+ elif issubclass(base, unittest.TestCase):
+ class Temp(base, object):
+ pass
+ Temp.__name__ = Temp.__qualname__ = name
+ Temp.__module__ = __module__
+ remote_globs[name] = Temp
+
+ dangling = [None, None]
+ old_start_method = [None]
+
+ def setUpModule():
+ multiprocessing.set_forkserver_preload(PRELOAD)
+ multiprocessing.process._cleanup()
+ dangling[0] = multiprocessing.process._dangling.copy()
+ dangling[1] = threading._dangling.copy()
+ old_start_method[0] = multiprocessing.get_start_method(allow_none=True)
+ try:
+ multiprocessing.set_start_method(start_method, force=True)
+ except ValueError:
+ raise unittest.SkipTest(start_method +
+ ' start method not supported')
+
+ if sys.platform.startswith("linux"):
+ try:
+ lock = multiprocessing.RLock()
+ except OSError:
+ raise unittest.SkipTest("OSError raises on RLock creation, "
+ "see issue 3111!")
+ check_enough_semaphores()
+ util.get_temp_dir() # creates temp directory
+ multiprocessing.get_logger().setLevel(LOG_LEVEL)
+
+ def tearDownModule():
+ multiprocessing.set_start_method(old_start_method[0], force=True)
+ # pause a bit so we don't get warning about dangling threads/processes
+ time.sleep(0.5)
+ multiprocessing.process._cleanup()
+ gc.collect()
+ tmp = set(multiprocessing.process._dangling) - set(dangling[0])
+ if tmp:
+ print('Dangling processes:', tmp, file=sys.stderr)
+ del tmp
+ tmp = set(threading._dangling) - set(dangling[1])
+ if tmp:
+ print('Dangling threads:', tmp, file=sys.stderr)
+
+ remote_globs['setUpModule'] = setUpModule
+ remote_globs['tearDownModule'] = tearDownModule
diff --git a/Lib/test/audiodata/pluck-pcm24.au b/Lib/test/audiodata/pluck-pcm24.au
new file mode 100644
index 0000000000..0bb230418a
--- /dev/null
+++ b/Lib/test/audiodata/pluck-pcm24.au
Binary files differ
diff --git a/Lib/test/audiotests.py b/Lib/test/audiotests.py
index 008281f9af..b7497ce5b0 100644
--- a/Lib/test/audiotests.py
+++ b/Lib/test/audiotests.py
@@ -5,23 +5,12 @@ import io
import pickle
import sys
-def byteswap2(data):
- a = array.array('h')
- a.frombytes(data)
- a.byteswap()
- return a.tobytes()
+class UnseekableIO(io.FileIO):
+ def tell(self):
+ raise io.UnsupportedOperation
-def byteswap3(data):
- ba = bytearray(data)
- ba[::3] = data[2::3]
- ba[2::3] = data[::3]
- return bytes(ba)
-
-def byteswap4(data):
- a = array.array('i')
- a.frombytes(data)
- a.byteswap()
- return a.tobytes()
+ def seek(self, *args, **kwargs):
+ raise io.UnsupportedOperation
class AudioTests:
@@ -49,6 +38,12 @@ class AudioTests:
params = f.getparams()
self.assertEqual(params,
(nchannels, sampwidth, framerate, nframes, comptype, compname))
+ self.assertEqual(params.nchannels, nchannels)
+ self.assertEqual(params.sampwidth, sampwidth)
+ self.assertEqual(params.framerate, framerate)
+ self.assertEqual(params.nframes, nframes)
+ self.assertEqual(params.comptype, comptype)
+ self.assertEqual(params.compname, compname)
dump = pickle.dumps(params)
self.assertEqual(pickle.loads(dump), params)
@@ -65,15 +60,12 @@ class AudioWriteTests(AudioTests):
return f
def check_file(self, testfile, nframes, frames):
- f = self.module.open(testfile, 'rb')
- try:
+ with self.module.open(testfile, 'rb') as f:
self.assertEqual(f.getnchannels(), self.nchannels)
self.assertEqual(f.getsampwidth(), self.sampwidth)
self.assertEqual(f.getframerate(), self.framerate)
self.assertEqual(f.getnframes(), nframes)
self.assertEqual(f.readframes(nframes), frames)
- finally:
- f.close()
def test_write_params(self):
f = self.create_file(TESTFN)
@@ -83,6 +75,53 @@ class AudioWriteTests(AudioTests):
self.nframes, self.comptype, self.compname)
f.close()
+ def test_write_context_manager_calls_close(self):
+ # Close checks for a minimum header and will raise an error
+ # if it is not set, so this proves that close is called.
+ with self.assertRaises(self.module.Error):
+ with self.module.open(TESTFN, 'wb'):
+ pass
+ with self.assertRaises(self.module.Error):
+ with open(TESTFN, 'wb') as testfile:
+ with self.module.open(testfile):
+ pass
+
+ def test_context_manager_with_open_file(self):
+ with open(TESTFN, 'wb') as testfile:
+ with self.module.open(testfile) as f:
+ f.setnchannels(self.nchannels)
+ f.setsampwidth(self.sampwidth)
+ f.setframerate(self.framerate)
+ f.setcomptype(self.comptype, self.compname)
+ self.assertEqual(testfile.closed, self.close_fd)
+ with open(TESTFN, 'rb') as testfile:
+ with self.module.open(testfile) as f:
+ self.assertFalse(f.getfp().closed)
+ params = f.getparams()
+ self.assertEqual(params.nchannels, self.nchannels)
+ self.assertEqual(params.sampwidth, self.sampwidth)
+ self.assertEqual(params.framerate, self.framerate)
+ if not self.close_fd:
+ self.assertIsNone(f.getfp())
+ self.assertEqual(testfile.closed, self.close_fd)
+
+ def test_context_manager_with_filename(self):
+ # If the file doesn't get closed, this test won't fail, but it will
+ # produce a resource leak warning.
+ with self.module.open(TESTFN, 'wb') as f:
+ f.setnchannels(self.nchannels)
+ f.setsampwidth(self.sampwidth)
+ f.setframerate(self.framerate)
+ f.setcomptype(self.comptype, self.compname)
+ with self.module.open(TESTFN) as f:
+ self.assertFalse(f.getfp().closed)
+ params = f.getparams()
+ self.assertEqual(params.nchannels, self.nchannels)
+ self.assertEqual(params.sampwidth, self.sampwidth)
+ self.assertEqual(params.framerate, self.framerate)
+ if not self.close_fd:
+ self.assertIsNone(f.getfp())
+
def test_write(self):
f = self.create_file(TESTFN)
f.setnframes(self.nframes)
@@ -91,6 +130,30 @@ class AudioWriteTests(AudioTests):
self.check_file(TESTFN, self.nframes, self.frames)
+ def test_write_bytearray(self):
+ f = self.create_file(TESTFN)
+ f.setnframes(self.nframes)
+ f.writeframes(bytearray(self.frames))
+ f.close()
+
+ self.check_file(TESTFN, self.nframes, self.frames)
+
+ def test_write_array(self):
+ f = self.create_file(TESTFN)
+ f.setnframes(self.nframes)
+ f.writeframes(array.array('h', self.frames))
+ f.close()
+
+ self.check_file(TESTFN, self.nframes, self.frames)
+
+ def test_write_memoryview(self):
+ f = self.create_file(TESTFN)
+ f.setnframes(self.nframes)
+ f.writeframes(memoryview(self.frames))
+ f.close()
+
+ self.check_file(TESTFN, self.nframes, self.frames)
+
def test_incompleted_write(self):
with open(TESTFN, 'wb') as testfile:
testfile.write(b'ababagalamaga')
@@ -129,6 +192,59 @@ class AudioWriteTests(AudioTests):
self.assertEqual(testfile.read(13), b'ababagalamaga')
self.check_file(testfile, self.nframes, self.frames)
+ def test_unseekable_read(self):
+ with self.create_file(TESTFN) as f:
+ f.setnframes(self.nframes)
+ f.writeframes(self.frames)
+
+ with UnseekableIO(TESTFN, 'rb') as testfile:
+ self.check_file(testfile, self.nframes, self.frames)
+
+ def test_unseekable_write(self):
+ with UnseekableIO(TESTFN, 'wb') as testfile:
+ with self.create_file(testfile) as f:
+ f.setnframes(self.nframes)
+ f.writeframes(self.frames)
+
+ self.check_file(TESTFN, self.nframes, self.frames)
+
+ def test_unseekable_incompleted_write(self):
+ with UnseekableIO(TESTFN, 'wb') as testfile:
+ testfile.write(b'ababagalamaga')
+ f = self.create_file(testfile)
+ f.setnframes(self.nframes + 1)
+ try:
+ f.writeframes(self.frames)
+ except OSError:
+ pass
+ try:
+ f.close()
+ except OSError:
+ pass
+
+ with open(TESTFN, 'rb') as testfile:
+ self.assertEqual(testfile.read(13), b'ababagalamaga')
+ self.check_file(testfile, self.nframes + 1, self.frames)
+
+ def test_unseekable_overflowed_write(self):
+ with UnseekableIO(TESTFN, 'wb') as testfile:
+ testfile.write(b'ababagalamaga')
+ f = self.create_file(testfile)
+ f.setnframes(self.nframes - 1)
+ try:
+ f.writeframes(self.frames)
+ except OSError:
+ pass
+ try:
+ f.close()
+ except OSError:
+ pass
+
+ with open(TESTFN, 'rb') as testfile:
+ self.assertEqual(testfile.read(13), b'ababagalamaga')
+ framesize = self.nchannels * self.sampwidth
+ self.check_file(testfile, self.nframes - 1, self.frames[:-framesize])
+
class AudioTestsWithSourceFile(AudioTests):
@@ -205,12 +321,9 @@ class AudioTestsWithSourceFile(AudioTests):
with open(TESTFN, 'rb') as testfile:
self.assertEqual(testfile.read(13), b'ababagalamaga')
- f = self.module.open(testfile, 'rb')
- try:
+ with self.module.open(testfile, 'rb') as f:
self.assertEqual(f.getnchannels(), self.nchannels)
self.assertEqual(f.getsampwidth(), self.sampwidth)
self.assertEqual(f.getframerate(), self.framerate)
self.assertEqual(f.getnframes(), self.sndfilenframes)
self.assertEqual(f.readframes(self.nframes), self.frames)
- finally:
- f.close()
diff --git a/Lib/test/badsyntax_future10.py b/Lib/test/badsyntax_future10.py
new file mode 100644
index 0000000000..fa5ab67a98
--- /dev/null
+++ b/Lib/test/badsyntax_future10.py
@@ -0,0 +1,3 @@
+from __future__ import absolute_import
+"spam, bar, blah"
+from __future__ import print_function
diff --git a/Lib/test/buffer_tests.py b/Lib/test/buffer_tests.py
index cf54c28759..0a629400fd 100644
--- a/Lib/test/buffer_tests.py
+++ b/Lib/test/buffer_tests.py
@@ -160,14 +160,20 @@ class MixinBytesBufferCommonTests(object):
self.marshal(b'abc\rab\tdef\ng\thi').expandtabs(8))
self.assertEqual(b'abc\rab def\ng hi',
self.marshal(b'abc\rab\tdef\ng\thi').expandtabs(4))
+ self.assertEqual(b'abc\r\nab def\ng hi',
+ self.marshal(b'abc\r\nab\tdef\ng\thi').expandtabs())
+ self.assertEqual(b'abc\r\nab def\ng hi',
+ self.marshal(b'abc\r\nab\tdef\ng\thi').expandtabs(8))
self.assertEqual(b'abc\r\nab def\ng hi',
self.marshal(b'abc\r\nab\tdef\ng\thi').expandtabs(4))
- self.assertEqual(b'abc\rab def\ng hi',
- self.marshal(b'abc\rab\tdef\ng\thi').expandtabs())
- self.assertEqual(b'abc\rab def\ng hi',
- self.marshal(b'abc\rab\tdef\ng\thi').expandtabs(8))
self.assertEqual(b'abc\r\nab\r\ndef\ng\r\nhi',
- self.marshal(b'abc\r\nab\r\ndef\ng\r\nhi').expandtabs(4))
+ self.marshal(b'abc\r\nab\r\ndef\ng\r\nhi').expandtabs(4))
+ # check keyword args
+ self.assertEqual(b'abc\rab def\ng hi',
+ self.marshal(b'abc\rab\tdef\ng\thi').expandtabs(tabsize=8))
+ self.assertEqual(b'abc\rab def\ng hi',
+ self.marshal(b'abc\rab\tdef\ng\thi').expandtabs(tabsize=4))
+
self.assertEqual(b' a\n b', self.marshal(b' \ta\n\tb').expandtabs(1))
self.assertRaises(TypeError, self.marshal(b'hello').expandtabs, 42, 42)
diff --git a/Lib/test/bytecode_helper.py b/Lib/test/bytecode_helper.py
new file mode 100644
index 0000000000..58b4209f55
--- /dev/null
+++ b/Lib/test/bytecode_helper.py
@@ -0,0 +1,41 @@
+"""bytecode_helper - support tools for testing correct bytecode generation"""
+
+import unittest
+import dis
+import io
+
+_UNSPECIFIED = object()
+
+class BytecodeTestCase(unittest.TestCase):
+ """Custom assertion methods for inspecting bytecode."""
+
+ def get_disassembly_as_string(self, co):
+ s = io.StringIO()
+ dis.dis(co, file=s)
+ return s.getvalue()
+
+ def assertInBytecode(self, x, opname, argval=_UNSPECIFIED):
+ """Returns instr if op is found, otherwise throws AssertionError"""
+ for instr in dis.get_instructions(x):
+ if instr.opname == opname:
+ if argval is _UNSPECIFIED or instr.argval == argval:
+ return instr
+ disassembly = self.get_disassembly_as_string(x)
+ if argval is _UNSPECIFIED:
+ msg = '%s not found in bytecode:\n%s' % (opname, disassembly)
+ else:
+ msg = '(%s,%r) not found in bytecode:\n%s'
+ msg = msg % (opname, argval, disassembly)
+ self.fail(msg)
+
+ def assertNotInBytecode(self, x, opname, argval=_UNSPECIFIED):
+ """Throws AssertionError if op is found"""
+ for instr in dis.get_instructions(x):
+ if instr.opname == opname:
+ disassembly = self.get_disassembly_as_string(co)
+ if opargval is _UNSPECIFIED:
+ msg = '%s occurs in bytecode:\n%s' % (opname, disassembly)
+ elif instr.argval == argval:
+ msg = '(%s,%r) occurs in bytecode:\n%s'
+ msg = msg % (opname, argval, disassembly)
+ self.fail(msg)
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 1343f345a8..b67d28c8b6 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -619,6 +619,10 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
eq(td(hours=-.2/us_per_hour), td(0))
eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
+ # Test for a patch in Issue 8860
+ eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
+ eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
+
def test_massive_normalization(self):
td = timedelta(microseconds=-1)
self.assertEqual((td.days, td.seconds, td.microseconds),
diff --git a/Lib/test/final_a.py b/Lib/test/final_a.py
new file mode 100644
index 0000000000..390ee8895a
--- /dev/null
+++ b/Lib/test/final_a.py
@@ -0,0 +1,19 @@
+"""
+Fodder for module finalization tests in test_module.
+"""
+
+import shutil
+import test.final_b
+
+x = 'a'
+
+class C:
+ def __del__(self):
+ # Inspect module globals and builtins
+ print("x =", x)
+ print("final_b.x =", test.final_b.x)
+ print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None))
+ print("len =", getattr(len, '__name__', None))
+
+c = C()
+_underscored = C()
diff --git a/Lib/test/final_b.py b/Lib/test/final_b.py
new file mode 100644
index 0000000000..7228d82b88
--- /dev/null
+++ b/Lib/test/final_b.py
@@ -0,0 +1,19 @@
+"""
+Fodder for module finalization tests in test_module.
+"""
+
+import shutil
+import test.final_a
+
+x = 'b'
+
+class C:
+ def __del__(self):
+ # Inspect module globals and builtins
+ print("x =", x)
+ print("final_a.x =", test.final_a.x)
+ print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None))
+ print("len =", getattr(len, '__name__', None))
+
+c = C()
+_underscored = C()
diff --git a/Lib/test/fork_wait.py b/Lib/test/fork_wait.py
index 88527df25e..19b54ec736 100644
--- a/Lib/test/fork_wait.py
+++ b/Lib/test/fork_wait.py
@@ -28,7 +28,7 @@ class ForkWait(unittest.TestCase):
self.alive[id] = os.getpid()
try:
time.sleep(SHORTSLEEP)
- except IOError:
+ except OSError:
pass
def wait_impl(self, cpid):
diff --git a/Lib/test/keycert3.pem b/Lib/test/keycert3.pem
new file mode 100644
index 0000000000..5bfa62c4ca
--- /dev/null
+++ b/Lib/test/keycert3.pem
@@ -0,0 +1,73 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMLgD0kAKDb5cFyP
+jbwNfR5CtewdXC+kMXAWD8DLxiTTvhMW7qVnlwOm36mZlszHKvsRf05lT4pegiFM
+9z2j1OlaN+ci/X7NU22TNN6crYSiN77FjYJP464j876ndSxyD+rzys386T+1r1aZ
+aggEdkj1TsSsv1zWIYKlPIjlvhuxAgMBAAECgYA0aH+T2Vf3WOPv8KdkcJg6gCRe
+yJKXOWgWRcicx/CUzOEsTxmFIDPLxqAWA3k7v0B+3vjGw5Y9lycV/5XqXNoQI14j
+y09iNsumds13u5AKkGdTJnZhQ7UKdoVHfuP44ZdOv/rJ5/VD6F4zWywpe90pcbK+
+AWDVtusgGQBSieEl1QJBAOyVrUG5l2yoUBtd2zr/kiGm/DYyXlIthQO/A3/LngDW
+5/ydGxVsT7lAVOgCsoT+0L4efTh90PjzW8LPQrPBWVMCQQDS3h/FtYYd5lfz+FNL
+9CEe1F1w9l8P749uNUD0g317zv1tatIqVCsQWHfVHNdVvfQ+vSFw38OORO00Xqs9
+1GJrAkBkoXXEkxCZoy4PteheO/8IWWLGGr6L7di6MzFl1lIqwT6D8L9oaV2vynFT
+DnKop0pa09Unhjyw57KMNmSE2SUJAkEArloTEzpgRmCq4IK2/NpCeGdHS5uqRlbh
+1VIa/xGps7EWQl5Mn8swQDel/YP3WGHTjfx7pgSegQfkyaRtGpZ9OQJAa9Vumj8m
+JAAtI0Bnga8hgQx7BhTQY4CadDxyiRGOGYhwUzYVCqkb2sbVRH9HnwUaJT7cWBY3
+RnJdHOMXWem7/w==
+-----END PRIVATE KEY-----
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 12723342612721443281 (0xb09264b1f2da21d1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server
+ Validity
+ Not Before: Jan 4 19:47:07 2013 GMT
+ Not After : Nov 13 19:47:07 2022 GMT
+ Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:c2:e0:0f:49:00:28:36:f9:70:5c:8f:8d:bc:0d:
+ 7d:1e:42:b5:ec:1d:5c:2f:a4:31:70:16:0f:c0:cb:
+ c6:24:d3:be:13:16:ee:a5:67:97:03:a6:df:a9:99:
+ 96:cc:c7:2a:fb:11:7f:4e:65:4f:8a:5e:82:21:4c:
+ f7:3d:a3:d4:e9:5a:37:e7:22:fd:7e:cd:53:6d:93:
+ 34:de:9c:ad:84:a2:37:be:c5:8d:82:4f:e3:ae:23:
+ f3:be:a7:75:2c:72:0f:ea:f3:ca:cd:fc:e9:3f:b5:
+ af:56:99:6a:08:04:76:48:f5:4e:c4:ac:bf:5c:d6:
+ 21:82:a5:3c:88:e5:be:1b:b1
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha1WithRSAEncryption
+ 2f:42:5f:a3:09:2c:fa:51:88:c7:37:7f:ea:0e:63:f0:a2:9a:
+ e5:5a:e2:c8:20:f0:3f:60:bc:c8:0f:b6:c6:76:ce:db:83:93:
+ f5:a3:33:67:01:8e:04:cd:00:9a:73:fd:f3:35:86:fa:d7:13:
+ e2:46:c6:9d:c0:29:53:d4:a9:90:b8:77:4b:e6:83:76:e4:92:
+ d6:9c:50:cf:43:d0:c6:01:77:61:9a:de:9b:70:f7:72:cd:59:
+ 00:31:69:d9:b4:ca:06:9c:6d:c3:c7:80:8c:68:e6:b5:a2:f8:
+ ef:1d:bb:16:9f:77:77:ef:87:62:22:9b:4d:69:a4:3a:1a:f1:
+ 21:5e:8c:32:ac:92:fd:15:6b:18:c2:7f:15:0d:98:30:ca:75:
+ 8f:1a:71:df:da:1d:b2:ef:9a:e8:2d:2e:02:fd:4a:3c:aa:96:
+ 0b:06:5d:35:b3:3d:24:87:4b:e0:b0:58:60:2f:45:ac:2e:48:
+ 8a:b0:99:10:65:27:ff:cc:b1:d8:fd:bd:26:6b:b9:0c:05:2a:
+ f4:45:63:35:51:07:ed:83:85:fe:6f:69:cb:bb:40:a8:ae:b6:
+ 3b:56:4a:2d:a4:ed:6d:11:2c:4d:ed:17:24:fd:47:bc:d3:41:
+ a2:d3:06:fe:0c:90:d8:d8:94:26:c4:ff:cc:a1:d8:42:77:eb:
+ fc:a9:94:71
+-----BEGIN CERTIFICATE-----
+MIICpDCCAYwCCQCwkmSx8toh0TANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJY
+WTEmMCQGA1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNV
+BAMMDW91ci1jYS1zZXJ2ZXIwHhcNMTMwMTA0MTk0NzA3WhcNMjIxMTEzMTk0NzA3
+WjBfMQswCQYDVQQGEwJYWTEXMBUGA1UEBxMOQ2FzdGxlIEFudGhyYXgxIzAhBgNV
+BAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRIwEAYDVQQDEwlsb2NhbGhv
+c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMLgD0kAKDb5cFyPjbwNfR5C
+tewdXC+kMXAWD8DLxiTTvhMW7qVnlwOm36mZlszHKvsRf05lT4pegiFM9z2j1Ola
+N+ci/X7NU22TNN6crYSiN77FjYJP464j876ndSxyD+rzys386T+1r1aZaggEdkj1
+TsSsv1zWIYKlPIjlvhuxAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAC9CX6MJLPpR
+iMc3f+oOY/CimuVa4sgg8D9gvMgPtsZ2ztuDk/WjM2cBjgTNAJpz/fM1hvrXE+JG
+xp3AKVPUqZC4d0vmg3bkktacUM9D0MYBd2Ga3ptw93LNWQAxadm0ygacbcPHgIxo
+5rWi+O8duxafd3fvh2Iim01ppDoa8SFejDKskv0VaxjCfxUNmDDKdY8acd/aHbLv
+mugtLgL9SjyqlgsGXTWzPSSHS+CwWGAvRawuSIqwmRBlJ//Msdj9vSZruQwFKvRF
+YzVRB+2Dhf5vacu7QKiutjtWSi2k7W0RLE3tFyT9R7zTQaLTBv4MkNjYlCbE/8yh
+2EJ36/yplHE=
+-----END CERTIFICATE-----
diff --git a/Lib/test/keycert4.pem b/Lib/test/keycert4.pem
new file mode 100644
index 0000000000..53355c8a50
--- /dev/null
+++ b/Lib/test/keycert4.pem
@@ -0,0 +1,73 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAK5UQiMI5VkNs2Qv
+L7gUaiDdFevNUXRjU4DHAe3ZzzYLZNE69h9gO9VCSS16tJ5fT5VEu0EZyGr0e3V2
+NkX0ZoU0Hc/UaY4qx7LHmn5SYZpIxhJnkf7SyHJK1zUaGlU0/LxYqIuGCtF5dqx1
+L2OQhEx1GM6RydHdgX69G64LXcY5AgMBAAECgYAhsRMfJkb9ERLMl/oG/5sLQu9L
+pWDKt6+ZwdxzlZbggQ85CMYshjLKIod2DLL/sLf2x1PRXyRG131M1E3k8zkkz6de
+R1uDrIN/x91iuYzfLQZGh8bMY7Yjd2eoroa6R/7DjpElGejLxOAaDWO0ST2IFQy9
+myTGS2jSM97wcXfsSQJBANP3jelJoS5X6BRjTSneY21wcocxVuQh8pXpErALVNsT
+drrFTeaBuZp7KvbtnIM5g2WRNvaxLZlAY/hXPJvi6ncCQQDSix1cebml6EmPlEZS
+Mm8gwI2F9ufUunwJmBJcz826Do0ZNGByWDAM/JQZH4FX4GfAFNuj8PUb+GQfadkx
+i1DPAkEA0lVsNHojvuDsIo8HGuzarNZQT2beWjJ1jdxh9t7HrTx7LIps6rb/fhOK
+Zs0R6gVAJaEbcWAPZ2tFyECInAdnsQJAUjaeXXjuxFkjOFym5PvqpvhpivEx78Bu
+JPTr3rAKXmfGMxxfuOa0xK1wSyshP6ZR/RBn/+lcXPKubhHQDOegwwJAJF1DBQnN
++/tLmOPULtDwfP4Zixn+/8GmGOahFoRcu6VIGHmRilJTn6MOButw7Glv2YdeC6l/
+e83Gq6ffLVfKNQ==
+-----END PRIVATE KEY-----
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 12723342612721443282 (0xb09264b1f2da21d2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server
+ Validity
+ Not Before: Jan 4 19:47:07 2013 GMT
+ Not After : Nov 13 19:47:07 2022 GMT
+ Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=fakehostname
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:ae:54:42:23:08:e5:59:0d:b3:64:2f:2f:b8:14:
+ 6a:20:dd:15:eb:cd:51:74:63:53:80:c7:01:ed:d9:
+ cf:36:0b:64:d1:3a:f6:1f:60:3b:d5:42:49:2d:7a:
+ b4:9e:5f:4f:95:44:bb:41:19:c8:6a:f4:7b:75:76:
+ 36:45:f4:66:85:34:1d:cf:d4:69:8e:2a:c7:b2:c7:
+ 9a:7e:52:61:9a:48:c6:12:67:91:fe:d2:c8:72:4a:
+ d7:35:1a:1a:55:34:fc:bc:58:a8:8b:86:0a:d1:79:
+ 76:ac:75:2f:63:90:84:4c:75:18:ce:91:c9:d1:dd:
+ 81:7e:bd:1b:ae:0b:5d:c6:39
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha1WithRSAEncryption
+ ad:45:8a:8e:ef:c6:ef:04:41:5c:2c:4a:84:dc:02:76:0c:d0:
+ 66:0f:f0:16:04:58:4d:fd:68:b7:b8:d3:a8:41:a5:5c:3c:6f:
+ 65:3c:d1:f8:ce:43:35:e7:41:5f:53:3d:c9:2c:c3:7d:fc:56:
+ 4a:fa:47:77:38:9d:bb:97:28:0a:3b:91:19:7f:bc:74:ae:15:
+ 6b:bd:20:36:67:45:a5:1e:79:d7:75:e6:89:5c:6d:54:84:d1:
+ 95:d7:a7:b4:33:3c:af:37:c4:79:8f:5e:75:dc:75:c2:18:fb:
+ 61:6f:2d:dc:38:65:5b:ba:67:28:d0:88:d7:8d:b9:23:5a:8e:
+ e8:c6:bb:db:ce:d5:b8:41:2a:ce:93:08:b6:95:ad:34:20:18:
+ d5:3b:37:52:74:50:0b:07:2c:b0:6d:a4:4c:7b:f4:e0:fd:d1:
+ af:17:aa:20:cd:62:e3:f0:9d:37:69:db:41:bd:d4:1c:fb:53:
+ 20:da:88:9d:76:26:67:ce:01:90:a7:80:1d:a9:5b:39:73:68:
+ 54:0a:d1:2a:03:1b:8f:3c:43:5d:5d:c4:51:f1:a7:e7:11:da:
+ 31:2c:49:06:af:04:f4:b8:3c:99:c4:20:b9:06:36:a2:00:92:
+ 61:1d:0c:6d:24:05:e2:82:e1:47:db:a0:5f:ba:b9:fb:ba:fa:
+ 49:12:1e:ce
+-----BEGIN CERTIFICATE-----
+MIICpzCCAY8CCQCwkmSx8toh0jANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJY
+WTEmMCQGA1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNV
+BAMMDW91ci1jYS1zZXJ2ZXIwHhcNMTMwMTA0MTk0NzA3WhcNMjIxMTEzMTk0NzA3
+WjBiMQswCQYDVQQGEwJYWTEXMBUGA1UEBxMOQ2FzdGxlIEFudGhyYXgxIzAhBgNV
+BAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRUwEwYDVQQDEwxmYWtlaG9z
+dG5hbWUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAK5UQiMI5VkNs2QvL7gU
+aiDdFevNUXRjU4DHAe3ZzzYLZNE69h9gO9VCSS16tJ5fT5VEu0EZyGr0e3V2NkX0
+ZoU0Hc/UaY4qx7LHmn5SYZpIxhJnkf7SyHJK1zUaGlU0/LxYqIuGCtF5dqx1L2OQ
+hEx1GM6RydHdgX69G64LXcY5AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAK1Fio7v
+xu8EQVwsSoTcAnYM0GYP8BYEWE39aLe406hBpVw8b2U80fjOQzXnQV9TPcksw338
+Vkr6R3c4nbuXKAo7kRl/vHSuFWu9IDZnRaUeedd15olcbVSE0ZXXp7QzPK83xHmP
+XnXcdcIY+2FvLdw4ZVu6ZyjQiNeNuSNajujGu9vO1bhBKs6TCLaVrTQgGNU7N1J0
+UAsHLLBtpEx79OD90a8XqiDNYuPwnTdp20G91Bz7UyDaiJ12JmfOAZCngB2pWzlz
+aFQK0SoDG488Q11dxFHxp+cR2jEsSQavBPS4PJnEILkGNqIAkmEdDG0kBeKC4Ufb
+oF+6ufu6+kkSHs4=
+-----END CERTIFICATE-----
diff --git a/Lib/test/leakers/test_gestalt.py b/Lib/test/leakers/test_gestalt.py
deleted file mode 100644
index e0081c1fc5..0000000000
--- a/Lib/test/leakers/test_gestalt.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import sys
-
-if sys.platform != 'darwin':
- raise ValueError("This test only leaks on Mac OS X")
-
-def leak():
- # taken from platform._mac_ver_lookup()
- from gestalt import gestalt
- import MacOS
-
- try:
- gestalt('sysu')
- except MacOS.Error:
- pass
diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py
index bfbf44e0db..1cbcea2343 100644
--- a/Lib/test/lock_tests.py
+++ b/Lib/test/lock_tests.py
@@ -80,6 +80,11 @@ class BaseLockTests(BaseTestCase):
lock = self.locktype()
del lock
+ def test_repr(self):
+ lock = self.locktype()
+ repr(lock)
+ del lock
+
def test_acquire_destroy(self):
lock = self.locktype()
lock.acquire()
@@ -413,6 +418,17 @@ class ConditionTests(BaseTestCase):
self.assertRaises(RuntimeError, cond.notify)
def _check_notify(self, cond):
+ # Note that this test is sensitive to timing. If the worker threads
+ # don't execute in a timely fashion, the main thread may think they
+ # are further along then they are. The main thread therefore issues
+ # _wait() statements to try to make sure that it doesn't race ahead
+ # of the workers.
+ # Secondly, this test assumes that condition variables are not subject
+ # to spurious wakeups. The absence of spurious wakeups is an implementation
+ # detail of Condition Cariables in current CPython, but in general, not
+ # a guaranteed property of condition variables as a programming
+ # construct. In particular, it is possible that this can no longer
+ # be conveniently guaranteed should their implementation ever change.
N = 5
results1 = []
results2 = []
@@ -440,6 +456,9 @@ class ConditionTests(BaseTestCase):
_wait()
self.assertEqual(results1, [(True, 1)] * 3)
self.assertEqual(results2, [])
+ # first wait, to ensure all workers settle into cond.wait() before
+ # we continue. See issue #8799
+ _wait()
# Notify 5 threads: they might be in their first or second wait
cond.acquire()
cond.notify(5)
@@ -450,6 +469,7 @@ class ConditionTests(BaseTestCase):
_wait()
self.assertEqual(results1, [(True, 1)] * 3 + [(True, 2)] * 2)
self.assertEqual(results2, [(True, 2)] * 3)
+ _wait() # make sure all workers settle into cond.wait()
# Notify all threads: they are all in their second wait
cond.acquire()
cond.notify_all()
diff --git a/Lib/test/make_ssl_certs.py b/Lib/test/make_ssl_certs.py
index 48d2e57f4b..4251d5524f 100644
--- a/Lib/test/make_ssl_certs.py
+++ b/Lib/test/make_ssl_certs.py
@@ -2,6 +2,7 @@
and friends."""
import os
+import shutil
import sys
import tempfile
from subprocess import *
@@ -20,11 +21,54 @@ req_template = """
[req_x509_extensions]
subjectAltName = DNS:{hostname}
+
+ [ ca ]
+ default_ca = CA_default
+
+ [ CA_default ]
+ dir = cadir
+ database = $dir/index.txt
+ crlnumber = $dir/crl.txt
+ default_md = sha1
+ default_days = 3600
+ default_crl_days = 3600
+ certificate = pycacert.pem
+ private_key = pycakey.pem
+ serial = $dir/serial
+ RANDFILE = $dir/.rand
+
+ policy = policy_match
+
+ [ policy_match ]
+ countryName = match
+ stateOrProvinceName = optional
+ organizationName = match
+ organizationalUnitName = optional
+ commonName = supplied
+ emailAddress = optional
+
+ [ policy_anything ]
+ countryName = optional
+ stateOrProvinceName = optional
+ localityName = optional
+ organizationName = optional
+ organizationalUnitName = optional
+ commonName = supplied
+ emailAddress = optional
+
+
+ [ v3_ca ]
+
+ subjectKeyIdentifier=hash
+ authorityKeyIdentifier=keyid:always,issuer
+ basicConstraints = CA:true
+
"""
here = os.path.abspath(os.path.dirname(__file__))
-def make_cert_key(hostname):
+def make_cert_key(hostname, sign=False):
+ print("creating cert for " + hostname)
tempnames = []
for i in range(3):
with tempfile.NamedTemporaryFile(delete=False) as f:
@@ -33,10 +77,25 @@ def make_cert_key(hostname):
try:
with open(req_file, 'w') as f:
f.write(req_template.format(hostname=hostname))
- args = ['req', '-new', '-days', '3650', '-nodes', '-x509',
+ args = ['req', '-new', '-days', '3650', '-nodes',
'-newkey', 'rsa:1024', '-keyout', key_file,
- '-out', cert_file, '-config', req_file]
+ '-config', req_file]
+ if sign:
+ with tempfile.NamedTemporaryFile(delete=False) as f:
+ tempnames.append(f.name)
+ reqfile = f.name
+ args += ['-out', reqfile ]
+
+ else:
+ args += ['-x509', '-out', cert_file ]
check_call(['openssl'] + args)
+
+ if sign:
+ args = ['ca', '-config', req_file, '-out', cert_file, '-outdir', 'cadir',
+ '-policy', 'policy_anything', '-batch', '-infiles', reqfile ]
+ check_call(['openssl'] + args)
+
+
with open(cert_file, 'r') as f:
cert = f.read()
with open(key_file, 'r') as f:
@@ -46,6 +105,36 @@ def make_cert_key(hostname):
for name in tempnames:
os.remove(name)
+TMP_CADIR = 'cadir'
+
+def unmake_ca():
+ shutil.rmtree(TMP_CADIR)
+
+def make_ca():
+ os.mkdir(TMP_CADIR)
+ with open(os.path.join('cadir','index.txt'),'a+') as f:
+ pass # empty file
+ with open(os.path.join('cadir','crl.txt'),'a+') as f:
+ r.write("00")
+ with open(os.path.join('cadir','index.txt.attr'),'w+') as f:
+ f.write('unique_subject = no')
+
+ with tempfile.NamedTemporaryFile("w") as t:
+ t.write(req_template.format(hostname='our-ca-server'))
+ t.flush()
+ with tempfile.NamedTemporaryFile() as f:
+ args = ['req', '-new', '-days', '3650', '-extensions', 'v3_ca', '-nodes',
+ '-newkey', 'rsa:2048', '-keyout', 'pycakey.pem',
+ '-out', f.name,
+ '-subj', '/C=XY/L=Castle Anthrax/O=Python Software Foundation CA/CN=our-ca-server']
+ check_call(['openssl'] + args)
+ args = ['ca', '-config', t.name, '-create_serial',
+ '-out', 'pycacert.pem', '-batch', '-outdir', TMP_CADIR,
+ '-keyfile', 'pycakey.pem', '-days', '3650',
+ '-selfsign', '-extensions', 'v3_ca', '-infiles', f.name ]
+ check_call(['openssl'] + args)
+ args = ['ca', '-config', t.name, '-gencrl', '-out', 'revocation.crl']
+ check_call(['openssl'] + args)
if __name__ == '__main__':
os.chdir(here)
@@ -54,11 +143,34 @@ if __name__ == '__main__':
f.write(cert)
with open('ssl_key.pem', 'w') as f:
f.write(key)
+ print("password protecting ssl_key.pem in ssl_key.passwd.pem")
+ check_call(['openssl','rsa','-in','ssl_key.pem','-out','ssl_key.passwd.pem','-des3','-passout','pass:somepass'])
+ check_call(['openssl','rsa','-in','ssl_key.pem','-out','keycert.passwd.pem','-des3','-passout','pass:somepass'])
+
with open('keycert.pem', 'w') as f:
f.write(key)
f.write(cert)
+
+ with open('keycert.passwd.pem', 'a+') as f:
+ f.write(cert)
+
# For certificate matching tests
+ make_ca()
cert, key = make_cert_key('fakehostname')
with open('keycert2.pem', 'w') as f:
f.write(key)
f.write(cert)
+
+ cert, key = make_cert_key('localhost', True)
+ with open('keycert3.pem', 'w') as f:
+ f.write(key)
+ f.write(cert)
+
+ cert, key = make_cert_key('fakehostname', True)
+ with open('keycert4.pem', 'w') as f:
+ f.write(key)
+ f.write(cert)
+
+ unmake_ca()
+ print("\n\nPlease change the values in test_ssl.py, test_parse_cert function related to notAfter,notBefore and serialNumber")
+ check_call(['openssl','x509','-in','keycert.pem','-dates','-serial','-noout'])
diff --git a/Lib/test/mock_socket.py b/Lib/test/mock_socket.py
index d09e78c1d5..8ef0ec8c8d 100644
--- a/Lib/test/mock_socket.py
+++ b/Lib/test/mock_socket.py
@@ -140,12 +140,8 @@ def gethostbyname(name):
return ""
-class gaierror(Exception):
- pass
-
-
-class error(Exception):
- pass
+gaierror = socket_module.gaierror
+error = socket_module.error
# Constants
diff --git a/Lib/test/mp_fork_bomb.py b/Lib/test/mp_fork_bomb.py
index 908afe3045..017e010ba0 100644
--- a/Lib/test/mp_fork_bomb.py
+++ b/Lib/test/mp_fork_bomb.py
@@ -7,6 +7,11 @@ def foo():
# correctly on Windows. However, we should get a RuntimeError rather
# than the Windows equivalent of a fork bomb.
+if len(sys.argv) > 1:
+ multiprocessing.set_start_method(sys.argv[1])
+else:
+ multiprocessing.set_start_method('spawn')
+
p = multiprocessing.Process(target=foo)
p.start()
p.join()
diff --git a/Lib/test/multibytecodec_support.py b/Lib/test/multibytecodec_support.py
index 26bac7be10..dcaae7b95c 100644
--- a/Lib/test/multibytecodec_support.py
+++ b/Lib/test/multibytecodec_support.py
@@ -282,7 +282,7 @@ class TestBase_Mapping(unittest.TestCase):
unittest.TestCase.__init__(self, *args, **kw)
try:
self.open_mapping_file().close() # test it to report the error early
- except (IOError, HTTPException):
+ except (OSError, HTTPException):
self.skipTest("Could not retrieve "+self.mapfileurl)
def open_mapping_file(self):
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py
index 5ce46ddf49..30b21bf62c 100644
--- a/Lib/test/pickletester.py
+++ b/Lib/test/pickletester.py
@@ -1,9 +1,10 @@
+import copyreg
import io
-import unittest
import pickle
import pickletools
+import random
import sys
-import copyreg
+import unittest
import weakref
from http.cookies import SimpleCookie
@@ -95,6 +96,9 @@ class E(C):
def __getinitargs__(self):
return ()
+class H(object):
+ pass
+
import __main__
__main__.C = C
C.__module__ = "__main__"
@@ -102,6 +106,8 @@ __main__.D = D
D.__module__ = "__main__"
__main__.E = E
E.__module__ = "__main__"
+__main__.H = H
+H.__module__ = "__main__"
class myint(int):
def __init__(self, x):
@@ -493,31 +499,52 @@ def create_data():
x.append(5)
return x
+
class AbstractPickleTests(unittest.TestCase):
# Subclass must define self.dumps, self.loads.
+ optimized = False
+
_testdata = create_data()
def setUp(self):
pass
+ def assert_is_copy(self, obj, objcopy, msg=None):
+ """Utility method to verify if two objects are copies of each others.
+ """
+ if msg is None:
+ msg = "{!r} is not a copy of {!r}".format(obj, objcopy)
+ self.assertEqual(obj, objcopy, msg=msg)
+ self.assertIs(type(obj), type(objcopy), msg=msg)
+ if hasattr(obj, '__dict__'):
+ self.assertDictEqual(obj.__dict__, objcopy.__dict__, msg=msg)
+ self.assertIsNot(obj.__dict__, objcopy.__dict__, msg=msg)
+ if hasattr(obj, '__slots__'):
+ self.assertListEqual(obj.__slots__, objcopy.__slots__, msg=msg)
+ for slot in obj.__slots__:
+ self.assertEqual(
+ hasattr(obj, slot), hasattr(objcopy, slot), msg=msg)
+ self.assertEqual(getattr(obj, slot, None),
+ getattr(objcopy, slot, None), msg=msg)
+
def test_misc(self):
# test various datatypes not tested by testdata
for proto in protocols:
x = myint(4)
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
x = (1, ())
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
x = initarg(1, x)
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
# XXX test __reduce__ protocol?
@@ -526,16 +553,16 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(expected, proto)
got = self.loads(s)
- self.assertEqual(expected, got)
+ self.assert_is_copy(expected, got)
def test_load_from_data0(self):
- self.assertEqual(self._testdata, self.loads(DATA0))
+ self.assert_is_copy(self._testdata, self.loads(DATA0))
def test_load_from_data1(self):
- self.assertEqual(self._testdata, self.loads(DATA1))
+ self.assert_is_copy(self._testdata, self.loads(DATA1))
def test_load_from_data2(self):
- self.assertEqual(self._testdata, self.loads(DATA2))
+ self.assert_is_copy(self._testdata, self.loads(DATA2))
def test_load_classic_instance(self):
# See issue5180. Test loading 2.x pickles that
@@ -557,7 +584,7 @@ class AbstractPickleTests(unittest.TestCase):
b"X\n"
b"p0\n"
b"(dp1\nb.").replace(b'X', xname)
- self.assertEqual(X(*args), self.loads(pickle0))
+ self.assert_is_copy(X(*args), self.loads(pickle0))
# Protocol 1 (binary mode pickle)
"""
@@ -574,7 +601,7 @@ class AbstractPickleTests(unittest.TestCase):
pickle1 = (b'(c__main__\n'
b'X\n'
b'q\x00oq\x01}q\x02b.').replace(b'X', xname)
- self.assertEqual(X(*args), self.loads(pickle1))
+ self.assert_is_copy(X(*args), self.loads(pickle1))
# Protocol 2 (pickle2 = b'\x80\x02' + pickle1)
"""
@@ -592,7 +619,7 @@ class AbstractPickleTests(unittest.TestCase):
pickle2 = (b'\x80\x02(c__main__\n'
b'X\n'
b'q\x00oq\x01}q\x02b.').replace(b'X', xname)
- self.assertEqual(X(*args), self.loads(pickle2))
+ self.assert_is_copy(X(*args), self.loads(pickle2))
# There are gratuitous differences between pickles produced by
# pickle and cPickle, largely because cPickle starts PUT indices at
@@ -617,6 +644,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(l, proto)
x = self.loads(s)
+ self.assertIsInstance(x, list)
self.assertEqual(len(x), 1)
self.assertTrue(x is x[0])
@@ -626,6 +654,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(t, proto)
x = self.loads(s)
+ self.assertIsInstance(x, tuple)
self.assertEqual(len(x), 1)
self.assertEqual(len(x[0]), 1)
self.assertTrue(x is x[0][0])
@@ -636,15 +665,39 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(d, proto)
x = self.loads(s)
+ self.assertIsInstance(x, dict)
self.assertEqual(list(x.keys()), [1])
self.assertTrue(x[1] is x)
+ def test_recursive_set(self):
+ h = H()
+ y = set({h})
+ h.attr = y
+ for proto in protocols:
+ s = self.dumps(y, proto)
+ x = self.loads(s)
+ self.assertIsInstance(x, set)
+ self.assertIs(list(x)[0].attr, x)
+ self.assertEqual(len(x), 1)
+
+ def test_recursive_frozenset(self):
+ h = H()
+ y = frozenset({h})
+ h.attr = y
+ for proto in protocols:
+ s = self.dumps(y, proto)
+ x = self.loads(s)
+ self.assertIsInstance(x, frozenset)
+ self.assertIs(list(x)[0].attr, x)
+ self.assertEqual(len(x), 1)
+
def test_recursive_inst(self):
i = C()
i.attr = i
for proto in protocols:
s = self.dumps(i, proto)
x = self.loads(s)
+ self.assertIsInstance(x, C)
self.assertEqual(dir(x), dir(i))
self.assertIs(x.attr, x)
@@ -657,6 +710,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(l, proto)
x = self.loads(s)
+ self.assertIsInstance(x, list)
self.assertEqual(len(x), 1)
self.assertEqual(dir(x[0]), dir(i))
self.assertEqual(list(x[0].attr.keys()), [1])
@@ -664,31 +718,8 @@ class AbstractPickleTests(unittest.TestCase):
def test_get(self):
self.assertRaises(KeyError, self.loads, b'g0\np0')
- self.assertEqual(self.loads(b'((Kdtp0\nh\x00l.))'), [(100,), (100,)])
-
- def test_insecure_strings(self):
- # XXX Some of these tests are temporarily disabled
- insecure = [b"abc", b"2 + 2", # not quoted
- ## b"'abc' + 'def'", # not a single quoted string
- b"'abc", # quote is not closed
- b"'abc\"", # open quote and close quote don't match
- b"'abc' ?", # junk after close quote
- b"'\\'", # trailing backslash
- # Variations on issue #17710
- b"'",
- b'"',
- b"' ",
- b"' ",
- b"' ",
- b"' ",
- b'" ',
- # some tests of the quoting rules
- ## b"'abc\"\''",
- ## b"'\\\\a\'\'\'\\\'\\\\\''",
- ]
- for b in insecure:
- buf = b"S" + b + b"\012p0\012."
- self.assertRaises(ValueError, self.loads, buf)
+ self.assert_is_copy([(100,), (100,)],
+ self.loads(b'((Kdtp0\nh\x00l.))'))
def test_unicode(self):
endcases = ['', '<\\u>', '<\\\u1234>', '<\n>',
@@ -699,26 +730,26 @@ class AbstractPickleTests(unittest.TestCase):
for u in endcases:
p = self.dumps(u, proto)
u2 = self.loads(p)
- self.assertEqual(u2, u)
+ self.assert_is_copy(u, u2)
def test_unicode_high_plane(self):
t = '\U00012345'
for proto in protocols:
p = self.dumps(t, proto)
t2 = self.loads(p)
- self.assertEqual(t2, t)
+ self.assert_is_copy(t, t2)
def test_bytes(self):
for proto in protocols:
for s in b'', b'xyz', b'xyz'*100:
p = self.dumps(s, proto)
- self.assertEqual(self.loads(p), s)
+ self.assert_is_copy(s, self.loads(p))
for s in [bytes([i]) for i in range(256)]:
p = self.dumps(s, proto)
- self.assertEqual(self.loads(p), s)
+ self.assert_is_copy(s, self.loads(p))
for s in [bytes([i, i]) for i in range(256)]:
p = self.dumps(s, proto)
- self.assertEqual(self.loads(p), s)
+ self.assert_is_copy(s, self.loads(p))
def test_ints(self):
import sys
@@ -728,14 +759,14 @@ class AbstractPickleTests(unittest.TestCase):
for expected in (-n, n):
s = self.dumps(expected, proto)
n2 = self.loads(s)
- self.assertEqual(expected, n2)
+ self.assert_is_copy(expected, n2)
n = n >> 1
def test_maxint64(self):
maxint64 = (1 << 63) - 1
data = b'I' + str(maxint64).encode("ascii") + b'\n.'
got = self.loads(data)
- self.assertEqual(got, maxint64)
+ self.assert_is_copy(maxint64, got)
# Try too with a bogus literal.
data = b'I' + str(maxint64).encode("ascii") + b'JUNK\n.'
@@ -750,7 +781,7 @@ class AbstractPickleTests(unittest.TestCase):
for n in npos, -npos:
pickle = self.dumps(n, proto)
got = self.loads(pickle)
- self.assertEqual(n, got)
+ self.assert_is_copy(n, got)
# Try a monster. This is quadratic-time in protos 0 & 1, so don't
# bother with those.
nbase = int("deadbeeffeedface", 16)
@@ -758,6 +789,10 @@ class AbstractPickleTests(unittest.TestCase):
for n in nbase, -nbase:
p = self.dumps(n, 2)
got = self.loads(p)
+ # assert_is_copy is very expensive here as it precomputes
+ # a failure message by computing the repr() of n and got,
+ # we just do the check ourselves.
+ self.assertIs(type(got), int)
self.assertEqual(n, got)
def test_float(self):
@@ -768,7 +803,7 @@ class AbstractPickleTests(unittest.TestCase):
for value in test_values:
pickle = self.dumps(value, proto)
got = self.loads(pickle)
- self.assertEqual(value, got)
+ self.assert_is_copy(value, got)
@run_with_locale('LC_ALL', 'de_DE', 'fr_FR')
def test_float_format(self):
@@ -800,6 +835,7 @@ class AbstractPickleTests(unittest.TestCase):
s = self.dumps(a, proto)
b = self.loads(s)
self.assertEqual(a, b)
+ self.assertIs(type(a), type(b))
def test_structseq(self):
import time
@@ -809,29 +845,29 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(t, proto)
u = self.loads(s)
- self.assertEqual(t, u)
+ self.assert_is_copy(t, u)
if hasattr(os, "stat"):
t = os.stat(os.curdir)
s = self.dumps(t, proto)
u = self.loads(s)
- self.assertEqual(t, u)
+ self.assert_is_copy(t, u)
if hasattr(os, "statvfs"):
t = os.statvfs(os.curdir)
s = self.dumps(t, proto)
u = self.loads(s)
- self.assertEqual(t, u)
+ self.assert_is_copy(t, u)
def test_ellipsis(self):
for proto in protocols:
s = self.dumps(..., proto)
u = self.loads(s)
- self.assertEqual(..., u)
+ self.assertIs(..., u)
def test_notimplemented(self):
for proto in protocols:
s = self.dumps(NotImplemented, proto)
u = self.loads(s)
- self.assertEqual(NotImplemented, u)
+ self.assertIs(NotImplemented, u)
def test_singleton_types(self):
# Issue #6477: Test that types of built-in singletons can be pickled.
@@ -845,21 +881,21 @@ class AbstractPickleTests(unittest.TestCase):
# Tests for protocol 2
def test_proto(self):
- build_none = pickle.NONE + pickle.STOP
for proto in protocols:
- expected = build_none
+ pickled = self.dumps(None, proto)
if proto >= 2:
- expected = pickle.PROTO + bytes([proto]) + expected
- p = self.dumps(None, proto)
- self.assertEqual(p, expected)
+ proto_header = pickle.PROTO + bytes([proto])
+ self.assertTrue(pickled.startswith(proto_header))
+ else:
+ self.assertEqual(count_opcode(pickle.PROTO, pickled), 0)
oob = protocols[-1] + 1 # a future protocol
+ build_none = pickle.NONE + pickle.STOP
badpickle = pickle.PROTO + bytes([oob]) + build_none
try:
self.loads(badpickle)
- except ValueError as detail:
- self.assertTrue(str(detail).startswith(
- "unsupported pickle protocol"))
+ except ValueError as err:
+ self.assertIn("unsupported pickle protocol", str(err))
else:
self.fail("expected bad protocol number to raise ValueError")
@@ -868,7 +904,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
self.assertEqual(opcode_in_pickle(pickle.LONG1, s), proto >= 2)
def test_long4(self):
@@ -876,7 +912,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
self.assertEqual(opcode_in_pickle(pickle.LONG4, s), proto >= 2)
def test_short_tuples(self):
@@ -914,9 +950,9 @@ class AbstractPickleTests(unittest.TestCase):
for x in a, b, c, d, e:
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y, (proto, x, s, y))
- expected = expected_opcode[proto, len(x)]
- self.assertEqual(opcode_in_pickle(expected, s), True)
+ self.assert_is_copy(x, y)
+ expected = expected_opcode[min(proto, 3), len(x)]
+ self.assertTrue(opcode_in_pickle(expected, s))
def test_singletons(self):
# Map (proto, singleton) to expected opcode.
@@ -940,8 +976,8 @@ class AbstractPickleTests(unittest.TestCase):
s = self.dumps(x, proto)
y = self.loads(s)
self.assertTrue(x is y, (proto, x, s, y))
- expected = expected_opcode[proto, x]
- self.assertEqual(opcode_in_pickle(expected, s), True)
+ expected = expected_opcode[min(proto, 3), x]
+ self.assertTrue(opcode_in_pickle(expected, s))
def test_newobj_tuple(self):
x = MyTuple([1, 2, 3])
@@ -950,8 +986,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(tuple(x), tuple(y))
- self.assertEqual(x.__dict__, y.__dict__)
+ self.assert_is_copy(x, y)
def test_newobj_list(self):
x = MyList([1, 2, 3])
@@ -960,8 +995,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(list(x), list(y))
- self.assertEqual(x.__dict__, y.__dict__)
+ self.assert_is_copy(x, y)
def test_newobj_generic(self):
for proto in protocols:
@@ -972,6 +1006,7 @@ class AbstractPickleTests(unittest.TestCase):
s = self.dumps(x, proto)
y = self.loads(s)
detail = (proto, C, B, x, y, type(y))
+ self.assert_is_copy(x, y) # XXX revisit
self.assertEqual(B(x), B(y), detail)
self.assertEqual(x.__dict__, y.__dict__, detail)
@@ -1010,11 +1045,10 @@ class AbstractPickleTests(unittest.TestCase):
s1 = self.dumps(x, 1)
self.assertIn(__name__.encode("utf-8"), s1)
self.assertIn(b"MyList", s1)
- self.assertEqual(opcode_in_pickle(opcode, s1), False)
+ self.assertFalse(opcode_in_pickle(opcode, s1))
y = self.loads(s1)
- self.assertEqual(list(x), list(y))
- self.assertEqual(x.__dict__, y.__dict__)
+ self.assert_is_copy(x, y)
# Dump using protocol 2 for test.
s2 = self.dumps(x, 2)
@@ -1023,9 +1057,7 @@ class AbstractPickleTests(unittest.TestCase):
self.assertEqual(opcode_in_pickle(opcode, s2), True, repr(s2))
y = self.loads(s2)
- self.assertEqual(list(x), list(y))
- self.assertEqual(x.__dict__, y.__dict__)
-
+ self.assert_is_copy(x, y)
finally:
e.restore()
@@ -1049,7 +1081,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
num_appends = count_opcode(pickle.APPENDS, s)
self.assertEqual(num_appends, proto > 0)
@@ -1058,7 +1090,7 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
num_appends = count_opcode(pickle.APPENDS, s)
if proto == 0:
self.assertEqual(num_appends, 0)
@@ -1072,7 +1104,7 @@ class AbstractPickleTests(unittest.TestCase):
s = self.dumps(x, proto)
self.assertIsInstance(s, bytes_types)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
num_setitems = count_opcode(pickle.SETITEMS, s)
self.assertEqual(num_setitems, proto > 0)
@@ -1081,22 +1113,49 @@ class AbstractPickleTests(unittest.TestCase):
for proto in protocols:
s = self.dumps(x, proto)
y = self.loads(s)
- self.assertEqual(x, y)
+ self.assert_is_copy(x, y)
num_setitems = count_opcode(pickle.SETITEMS, s)
if proto == 0:
self.assertEqual(num_setitems, 0)
else:
self.assertTrue(num_setitems >= 2)
+ def test_set_chunking(self):
+ n = 10 # too small to chunk
+ x = set(range(n))
+ for proto in protocols:
+ s = self.dumps(x, proto)
+ y = self.loads(s)
+ self.assert_is_copy(x, y)
+ num_additems = count_opcode(pickle.ADDITEMS, s)
+ if proto < 4:
+ self.assertEqual(num_additems, 0)
+ else:
+ self.assertEqual(num_additems, 1)
+
+ n = 2500 # expect at least two chunks when proto >= 4
+ x = set(range(n))
+ for proto in protocols:
+ s = self.dumps(x, proto)
+ y = self.loads(s)
+ self.assert_is_copy(x, y)
+ num_additems = count_opcode(pickle.ADDITEMS, s)
+ if proto < 4:
+ self.assertEqual(num_additems, 0)
+ else:
+ self.assertGreaterEqual(num_additems, 2)
+
def test_simple_newobj(self):
x = object.__new__(SimpleNewObj) # avoid __init__
x.abc = 666
for proto in protocols:
s = self.dumps(x, proto)
- self.assertEqual(opcode_in_pickle(pickle.NEWOBJ, s), proto >= 2)
+ self.assertEqual(opcode_in_pickle(pickle.NEWOBJ, s),
+ 2 <= proto < 4)
+ self.assertEqual(opcode_in_pickle(pickle.NEWOBJ_EX, s),
+ proto >= 4)
y = self.loads(s) # will raise TypeError if __init__ called
- self.assertEqual(y.abc, 666)
- self.assertEqual(x.__dict__, y.__dict__)
+ self.assert_is_copy(x, y)
def test_newobj_list_slots(self):
x = SlotList([1, 2, 3])
@@ -1104,10 +1163,7 @@ class AbstractPickleTests(unittest.TestCase):
x.bar = "hello"
s = self.dumps(x, 2)
y = self.loads(s)
- self.assertEqual(list(x), list(y))
- self.assertEqual(x.__dict__, y.__dict__)
- self.assertEqual(x.foo, y.foo)
- self.assertEqual(x.bar, y.bar)
+ self.assert_is_copy(x, y)
def test_reduce_overrides_default_reduce_ex(self):
for proto in protocols:
@@ -1156,11 +1212,10 @@ class AbstractPickleTests(unittest.TestCase):
@no_tracing
def test_bad_getattr(self):
+ # Issue #3514: crash when there is an infinite loop in __getattr__
x = BadGetattr()
- for proto in 0, 1:
+ for proto in protocols:
self.assertRaises(RuntimeError, self.dumps, x, proto)
- # protocol 2 don't raise a RuntimeError.
- d = self.dumps(x, 2)
def test_reduce_bad_iterator(self):
# Issue4176: crash when 4th and 5th items of __reduce__()
@@ -1193,11 +1248,10 @@ class AbstractPickleTests(unittest.TestCase):
obj = [dict(large_dict), dict(large_dict), dict(large_dict)]
for proto in protocols:
- dumped = self.dumps(obj, proto)
- loaded = self.loads(dumped)
- self.assertEqual(loaded, obj,
- "Failed protocol %d: %r != %r"
- % (proto, obj, loaded))
+ with self.subTest(proto=proto):
+ dumped = self.dumps(obj, proto)
+ loaded = self.loads(dumped)
+ self.assert_is_copy(obj, loaded)
def test_attribute_name_interning(self):
# Test that attribute names of pickled objects are interned when
@@ -1268,11 +1322,14 @@ class AbstractPickleTests(unittest.TestCase):
def test_int_pickling_efficiency(self):
# Test compacity of int representation (see issue #12744)
for proto in protocols:
- sizes = [len(self.dumps(2**n, proto)) for n in range(70)]
- # the size function is monotonic
- self.assertEqual(sorted(sizes), sizes)
- if proto >= 2:
- self.assertLessEqual(sizes[-1], 14)
+ with self.subTest(proto=proto):
+ pickles = [self.dumps(2**n, proto) for n in range(70)]
+ sizes = list(map(len, pickles))
+ # the size function is monotonic
+ self.assertEqual(sorted(sizes), sizes)
+ if proto >= 2:
+ for p in pickles:
+ self.assertFalse(opcode_in_pickle(pickle.LONG, p))
def check_negative_32b_binXXX(self, dumped):
if sys.maxsize > 2**32:
@@ -1303,6 +1360,35 @@ class AbstractPickleTests(unittest.TestCase):
dumped = b'\x80\x03X\x01\x00\x00\x00ar\xff\xff\xff\xff.'
self.assertRaises(ValueError, self.loads, dumped)
+ def test_badly_escaped_string(self):
+ self.assertRaises(ValueError, self.loads, b"S'\\'\n.")
+
+ def test_badly_quoted_string(self):
+ # Issue #17710
+ badpickles = [b"S'\n.",
+ b'S"\n.',
+ b'S\' \n.',
+ b'S" \n.',
+ b'S\'"\n.',
+ b'S"\'\n.',
+ b"S' ' \n.",
+ b'S" " \n.',
+ b"S ''\n.",
+ b'S ""\n.',
+ b'S \n.',
+ b'S\n.',
+ b'S.']
+ for p in badpickles:
+ self.assertRaises(pickle.UnpicklingError, self.loads, p)
+
+ def test_correctly_quoted_string(self):
+ goodpickles = [(b"S''\n.", ''),
+ (b'S""\n.', ''),
+ (b'S"\\n"\n.', '\n'),
+ (b"S'\\n'\n.", '\n')]
+ for p, expected in goodpickles:
+ self.assertEqual(self.loads(p), expected)
+
def _check_pickling_with_opcode(self, obj, opcode, proto):
pickled = self.dumps(obj, proto)
self.assertTrue(opcode_in_pickle(opcode, pickled))
@@ -1326,6 +1412,173 @@ class AbstractPickleTests(unittest.TestCase):
else:
self._check_pickling_with_opcode(obj, pickle.SETITEMS, proto)
+ # Exercise framing (proto >= 4) for significant workloads
+
+ FRAME_SIZE_TARGET = 64 * 1024
+
+ def test_framing_many_objects(self):
+ obj = list(range(10**5))
+ for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
+ with self.subTest(proto=proto):
+ pickled = self.dumps(obj, proto)
+ unpickled = self.loads(pickled)
+ self.assertEqual(obj, unpickled)
+ bytes_per_frame = (len(pickled) /
+ count_opcode(pickle.FRAME, pickled))
+ self.assertGreater(bytes_per_frame,
+ self.FRAME_SIZE_TARGET / 2)
+ self.assertLessEqual(bytes_per_frame,
+ self.FRAME_SIZE_TARGET * 1)
+
+ def test_framing_large_objects(self):
+ N = 1024 * 1024
+ obj = [b'x' * N, b'y' * N, b'z' * N]
+ for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
+ with self.subTest(proto=proto):
+ pickled = self.dumps(obj, proto)
+ unpickled = self.loads(pickled)
+ self.assertEqual(obj, unpickled)
+ n_frames = count_opcode(pickle.FRAME, pickled)
+ self.assertGreaterEqual(n_frames, len(obj))
+
+ def test_optional_frames(self):
+ if pickle.HIGHEST_PROTOCOL < 4:
+ return
+
+ def remove_frames(pickled, keep_frame=None):
+ """Remove frame opcodes from the given pickle."""
+ frame_starts = []
+ # 1 byte for the opcode and 8 for the argument
+ frame_opcode_size = 9
+ for opcode, _, pos in pickletools.genops(pickled):
+ if opcode.name == 'FRAME':
+ frame_starts.append(pos)
+
+ newpickle = bytearray()
+ last_frame_end = 0
+ for i, pos in enumerate(frame_starts):
+ if keep_frame and keep_frame(i):
+ continue
+ newpickle += pickled[last_frame_end:pos]
+ last_frame_end = pos + frame_opcode_size
+ newpickle += pickled[last_frame_end:]
+ return newpickle
+
+ frame_size = self.FRAME_SIZE_TARGET
+ num_frames = 20
+ obj = [bytes([i]) * frame_size for i in range(num_frames)]
+
+ for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
+ pickled = self.dumps(obj, proto)
+
+ frameless_pickle = remove_frames(pickled)
+ self.assertEqual(count_opcode(pickle.FRAME, frameless_pickle), 0)
+ self.assertEqual(obj, self.loads(frameless_pickle))
+
+ some_frames_pickle = remove_frames(pickled, lambda i: i % 2)
+ self.assertLess(count_opcode(pickle.FRAME, some_frames_pickle),
+ count_opcode(pickle.FRAME, pickled))
+ self.assertEqual(obj, self.loads(some_frames_pickle))
+
+ def test_nested_names(self):
+ global Nested
+ class Nested:
+ class A:
+ class B:
+ class C:
+ pass
+
+ for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
+ for obj in [Nested.A, Nested.A.B, Nested.A.B.C]:
+ with self.subTest(proto=proto, obj=obj):
+ unpickled = self.loads(self.dumps(obj, proto))
+ self.assertIs(obj, unpickled)
+
+ def test_py_methods(self):
+ global PyMethodsTest
+ class PyMethodsTest:
+ @staticmethod
+ def cheese():
+ return "cheese"
+ @classmethod
+ def wine(cls):
+ assert cls is PyMethodsTest
+ return "wine"
+ def biscuits(self):
+ assert isinstance(self, PyMethodsTest)
+ return "biscuits"
+ class Nested:
+ "Nested class"
+ @staticmethod
+ def ketchup():
+ return "ketchup"
+ @classmethod
+ def maple(cls):
+ assert cls is PyMethodsTest.Nested
+ return "maple"
+ def pie(self):
+ assert isinstance(self, PyMethodsTest.Nested)
+ return "pie"
+
+ py_methods = (
+ PyMethodsTest.cheese,
+ PyMethodsTest.wine,
+ PyMethodsTest().biscuits,
+ PyMethodsTest.Nested.ketchup,
+ PyMethodsTest.Nested.maple,
+ PyMethodsTest.Nested().pie
+ )
+ py_unbound_methods = (
+ (PyMethodsTest.biscuits, PyMethodsTest),
+ (PyMethodsTest.Nested.pie, PyMethodsTest.Nested)
+ )
+ for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
+ for method in py_methods:
+ with self.subTest(proto=proto, method=method):
+ unpickled = self.loads(self.dumps(method, proto))
+ self.assertEqual(method(), unpickled())
+ for method, cls in py_unbound_methods:
+ obj = cls()
+ with self.subTest(proto=proto, method=method):
+ unpickled = self.loads(self.dumps(method, proto))
+ self.assertEqual(method(obj), unpickled(obj))
+
+
+ def test_c_methods(self):
+ global Subclass
+ class Subclass(tuple):
+ class Nested(str):
+ pass
+
+ c_methods = (
+ # bound built-in method
+ ("abcd".index, ("c",)),
+ # unbound built-in method
+ (str.index, ("abcd", "c")),
+ # bound "slot" method
+ ([1, 2, 3].__len__, ()),
+ # unbound "slot" method
+ (list.__len__, ([1, 2, 3],)),
+ # bound "coexist" method
+ ({1, 2}.__contains__, (2,)),
+ # unbound "coexist" method
+ (set.__contains__, ({1, 2}, 2)),
+ # built-in class method
+ (dict.fromkeys, (("a", 1), ("b", 2))),
+ # built-in static method
+ (bytearray.maketrans, (b"abc", b"xyz")),
+ # subclass methods
+ (Subclass([1,2,2]).count, (2,)),
+ (Subclass.count, (Subclass([1,2,2]), 2)),
+ (Subclass.Nested("sweet").count, ("e",)),
+ (Subclass.Nested.count, (Subclass.Nested("sweet"), "e")),
+ )
+ for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
+ for method, args in c_methods:
+ with self.subTest(proto=proto, method=method):
+ unpickled = self.loads(self.dumps(method, proto))
+ self.assertEqual(method(*args), unpickled(*args))
+
class BigmemPickleTests(unittest.TestCase):
@@ -1336,10 +1589,11 @@ class BigmemPickleTests(unittest.TestCase):
data = 1 << (8 * size)
try:
for proto in protocols:
- if proto < 2:
- continue
- with self.assertRaises((ValueError, OverflowError)):
- self.dumps(data, protocol=proto)
+ with self.subTest(proto=proto):
+ if proto < 2:
+ continue
+ with self.assertRaises((ValueError, OverflowError)):
+ self.dumps(data, protocol=proto)
finally:
data = None
@@ -1352,14 +1606,15 @@ class BigmemPickleTests(unittest.TestCase):
data = b"abcd" * (size // 4)
try:
for proto in protocols:
- if proto < 3:
- continue
- try:
- pickled = self.dumps(data, protocol=proto)
- self.assertTrue(b"abcd" in pickled[:15])
- self.assertTrue(b"abcd" in pickled[-15:])
- finally:
- pickled = None
+ with self.subTest(proto=proto):
+ if proto < 3:
+ continue
+ try:
+ pickled = self.dumps(data, protocol=proto)
+ self.assertTrue(b"abcd" in pickled[:19])
+ self.assertTrue(b"abcd" in pickled[-18:])
+ finally:
+ pickled = None
finally:
data = None
@@ -1368,10 +1623,11 @@ class BigmemPickleTests(unittest.TestCase):
data = b"a" * size
try:
for proto in protocols:
- if proto < 3:
- continue
- with self.assertRaises((ValueError, OverflowError)):
- self.dumps(data, protocol=proto)
+ with self.subTest(proto=proto):
+ if proto < 3:
+ continue
+ with self.assertRaises((ValueError, OverflowError)):
+ self.dumps(data, protocol=proto)
finally:
data = None
@@ -1383,27 +1639,38 @@ class BigmemPickleTests(unittest.TestCase):
data = "abcd" * (size // 4)
try:
for proto in protocols:
- try:
- pickled = self.dumps(data, protocol=proto)
- self.assertTrue(b"abcd" in pickled[:15])
- self.assertTrue(b"abcd" in pickled[-15:])
- finally:
- pickled = None
+ with self.subTest(proto=proto):
+ try:
+ pickled = self.dumps(data, protocol=proto)
+ self.assertTrue(b"abcd" in pickled[:19])
+ self.assertTrue(b"abcd" in pickled[-18:])
+ finally:
+ pickled = None
finally:
data = None
- # BINUNICODE (protocols 1, 2 and 3) cannot carry more than
- # 2**32 - 1 bytes of utf-8 encoded unicode.
+ # BINUNICODE (protocols 1, 2 and 3) cannot carry more than 2**32 - 1 bytes
+ # of utf-8 encoded unicode. BINUNICODE8 (protocol 4) supports these huge
+ # unicode strings however.
- @bigmemtest(size=_4G, memuse=1 + ascii_char_size, dry_run=False)
+ @bigmemtest(size=_4G, memuse=2 + ascii_char_size, dry_run=False)
def test_huge_str_64b(self, size):
- data = "a" * size
+ data = "abcd" * (size // 4)
try:
for proto in protocols:
- if proto == 0:
- continue
- with self.assertRaises((ValueError, OverflowError)):
- self.dumps(data, protocol=proto)
+ with self.subTest(proto=proto):
+ if proto == 0:
+ continue
+ if proto < 4:
+ with self.assertRaises((ValueError, OverflowError)):
+ self.dumps(data, protocol=proto)
+ else:
+ try:
+ pickled = self.dumps(data, protocol=proto)
+ self.assertTrue(b"abcd" in pickled[:19])
+ self.assertTrue(b"abcd" in pickled[-18:])
+ finally:
+ pickled = None
finally:
data = None
@@ -1447,8 +1714,8 @@ class REX_five(object):
return object.__reduce__(self)
class REX_six(object):
- """This class is used to check the 4th argument (list iterator) of the reduce
- protocol.
+ """This class is used to check the 4th argument (list iterator) of
+ the reduce protocol.
"""
def __init__(self, items=None):
self.items = items if items is not None else []
@@ -1460,8 +1727,8 @@ class REX_six(object):
return type(self), (), None, iter(self.items), None
class REX_seven(object):
- """This class is used to check the 5th argument (dict iterator) of the reduce
- protocol.
+ """This class is used to check the 5th argument (dict iterator) of
+ the reduce protocol.
"""
def __init__(self, table=None):
self.table = table if table is not None else {}
@@ -1499,10 +1766,16 @@ class MyList(list):
class MyDict(dict):
sample = {"a": 1, "b": 2}
+class MySet(set):
+ sample = {"a", "b"}
+
+class MyFrozenSet(frozenset):
+ sample = frozenset({"a", "b"})
+
myclasses = [MyInt, MyFloat,
MyComplex,
MyStr, MyUnicode,
- MyTuple, MyList, MyDict]
+ MyTuple, MyList, MyDict, MySet, MyFrozenSet]
class SlotList(MyList):
@@ -1512,6 +1785,8 @@ class SimpleNewObj(object):
def __init__(self, a, b, c):
# raise an error, to make sure this isn't called
raise TypeError("SimpleNewObj.__init__() didn't expect to get called")
+ def __eq__(self, other):
+ return self.__dict__ == other.__dict__
class BadGetattr:
def __getattr__(self, key):
@@ -1548,7 +1823,7 @@ class AbstractPickleModuleTests(unittest.TestCase):
def test_highest_protocol(self):
# Of course this needs to be changed when HIGHEST_PROTOCOL changes.
- self.assertEqual(pickle.HIGHEST_PROTOCOL, 3)
+ self.assertEqual(pickle.HIGHEST_PROTOCOL, 4)
def test_callapi(self):
f = io.BytesIO()
@@ -1733,22 +2008,23 @@ class AbstractPicklerUnpicklerObjectTests(unittest.TestCase):
def _check_multiple_unpicklings(self, ioclass):
for proto in protocols:
- data1 = [(x, str(x)) for x in range(2000)] + [b"abcde", len]
- f = ioclass()
- pickler = self.pickler_class(f, protocol=proto)
- pickler.dump(data1)
- pickled = f.getvalue()
-
- N = 5
- f = ioclass(pickled * N)
- unpickler = self.unpickler_class(f)
- for i in range(N):
- if f.seekable():
- pos = f.tell()
- self.assertEqual(unpickler.load(), data1)
- if f.seekable():
- self.assertEqual(f.tell(), pos + len(pickled))
- self.assertRaises(EOFError, unpickler.load)
+ with self.subTest(proto=proto):
+ data1 = [(x, str(x)) for x in range(2000)] + [b"abcde", len]
+ f = ioclass()
+ pickler = self.pickler_class(f, protocol=proto)
+ pickler.dump(data1)
+ pickled = f.getvalue()
+
+ N = 5
+ f = ioclass(pickled * N)
+ unpickler = self.unpickler_class(f)
+ for i in range(N):
+ if f.seekable():
+ pos = f.tell()
+ self.assertEqual(unpickler.load(), data1)
+ if f.seekable():
+ self.assertEqual(f.tell(), pos + len(pickled))
+ self.assertRaises(EOFError, unpickler.load)
def test_multiple_unpicklings_seekable(self):
self._check_multiple_unpicklings(io.BytesIO)
diff --git a/Lib/test/pycacert.pem b/Lib/test/pycacert.pem
new file mode 100644
index 0000000000..09b1f3e08a
--- /dev/null
+++ b/Lib/test/pycacert.pem
@@ -0,0 +1,78 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 12723342612721443280 (0xb09264b1f2da21d0)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server
+ Validity
+ Not Before: Jan 4 19:47:07 2013 GMT
+ Not After : Jan 2 19:47:07 2023 GMT
+ Subject: C=XY, O=Python Software Foundation CA, CN=our-ca-server
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e7:de:e9:e3:0c:9f:00:b6:a1:fd:2b:5b:96:d2:
+ 6f:cc:e0:be:86:b9:20:5e:ec:03:7a:55:ab:ea:a4:
+ e9:f9:49:85:d2:66:d5:ed:c7:7a:ea:56:8e:2d:8f:
+ e7:42:e2:62:28:a9:9f:d6:1b:8e:eb:b5:b4:9c:9f:
+ 14:ab:df:e6:94:8b:76:1d:3e:6d:24:61:ed:0c:bf:
+ 00:8a:61:0c:df:5c:c8:36:73:16:00:cd:47:ba:6d:
+ a4:a4:74:88:83:23:0a:19:fc:09:a7:3c:4a:4b:d3:
+ e7:1d:2d:e4:ea:4c:54:21:f3:26:db:89:37:18:d4:
+ 02:bb:40:32:5f:a4:ff:2d:1c:f7:d4:bb:ec:8e:cf:
+ 5c:82:ac:e6:7c:08:6c:48:85:61:07:7f:25:e0:5c:
+ e0:bc:34:5f:e0:b9:04:47:75:c8:47:0b:8d:bc:d6:
+ c8:68:5f:33:83:62:d2:20:44:35:b1:ad:81:1a:8a:
+ cd:bc:35:b0:5c:8b:47:d6:18:e9:9c:18:97:cc:01:
+ 3c:29:cc:e8:1e:e4:e4:c1:b8:de:e7:c2:11:18:87:
+ 5a:93:34:d8:a6:25:f7:14:71:eb:e4:21:a2:d2:0f:
+ 2e:2e:d4:62:00:35:d3:d6:ef:5c:60:4b:4c:a9:14:
+ e2:dd:15:58:46:37:33:26:b7:e7:2e:5d:ed:42:e4:
+ c5:4d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ BC:DD:62:D9:76:DA:1B:D2:54:6B:CF:E0:66:9B:1E:1E:7B:56:0C:0B
+ X509v3 Authority Key Identifier:
+ keyid:BC:DD:62:D9:76:DA:1B:D2:54:6B:CF:E0:66:9B:1E:1E:7B:56:0C:0B
+
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ 7d:0a:f5:cb:8d:d3:5d:bd:99:8e:f8:2b:0f:ba:eb:c2:d9:a6:
+ 27:4f:2e:7b:2f:0e:64:d8:1c:35:50:4e:ee:fc:90:b9:8d:6d:
+ a8:c5:c6:06:b0:af:f3:2d:bf:3b:b8:42:07:dd:18:7d:6d:95:
+ 54:57:85:18:60:47:2f:eb:78:1b:f9:e8:17:fd:5a:0d:87:17:
+ 28:ac:4c:6a:e6:bc:29:f4:f4:55:70:29:42:de:85:ea:ab:6c:
+ 23:06:64:30:75:02:8e:53:bc:5e:01:33:37:cc:1e:cd:b8:a4:
+ fd:ca:e4:5f:65:3b:83:1c:86:f1:55:02:a0:3a:8f:db:91:b7:
+ 40:14:b4:e7:8d:d2:ee:73:ba:e3:e5:34:2d:bc:94:6f:4e:24:
+ 06:f7:5f:8b:0e:a7:8e:6b:de:5e:75:f4:32:9a:50:b1:44:33:
+ 9a:d0:05:e2:78:82:ff:db:da:8a:63:eb:a9:dd:d1:bf:a0:61:
+ ad:e3:9e:8a:24:5d:62:0e:e7:4c:91:7f:ef:df:34:36:3b:2f:
+ 5d:f5:84:b2:2f:c4:6d:93:96:1a:6f:30:28:f1:da:12:9a:64:
+ b4:40:33:1d:bd:de:2b:53:a8:ea:be:d6:bc:4e:96:f5:44:fb:
+ 32:18:ae:d5:1f:f6:69:af:b6:4e:7b:1d:58:ec:3b:a9:53:a3:
+ 5e:58:c8:9e
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIJALCSZLHy2iHQMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV
+BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW
+MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xMzAxMDQxOTQ3MDdaFw0yMzAxMDIx
+OTQ3MDdaME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg
+Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAOfe6eMMnwC2of0rW5bSb8zgvoa5IF7sA3pV
+q+qk6flJhdJm1e3HeupWji2P50LiYiipn9Ybjuu1tJyfFKvf5pSLdh0+bSRh7Qy/
+AIphDN9cyDZzFgDNR7ptpKR0iIMjChn8Cac8SkvT5x0t5OpMVCHzJtuJNxjUArtA
+Ml+k/y0c99S77I7PXIKs5nwIbEiFYQd/JeBc4Lw0X+C5BEd1yEcLjbzWyGhfM4Ni
+0iBENbGtgRqKzbw1sFyLR9YY6ZwYl8wBPCnM6B7k5MG43ufCERiHWpM02KYl9xRx
+6+QhotIPLi7UYgA109bvXGBLTKkU4t0VWEY3Mya35y5d7ULkxU0CAwEAAaNQME4w
+HQYDVR0OBBYEFLzdYtl22hvSVGvP4GabHh57VgwLMB8GA1UdIwQYMBaAFLzdYtl2
+2hvSVGvP4GabHh57VgwLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
+AH0K9cuN0129mY74Kw+668LZpidPLnsvDmTYHDVQTu78kLmNbajFxgawr/Mtvzu4
+QgfdGH1tlVRXhRhgRy/reBv56Bf9Wg2HFyisTGrmvCn09FVwKULeheqrbCMGZDB1
+Ao5TvF4BMzfMHs24pP3K5F9lO4MchvFVAqA6j9uRt0AUtOeN0u5zuuPlNC28lG9O
+JAb3X4sOp45r3l519DKaULFEM5rQBeJ4gv/b2opj66nd0b+gYa3jnookXWIO50yR
+f+/fNDY7L131hLIvxG2TlhpvMCjx2hKaZLRAMx293itTqOq+1rxOlvVE+zIYrtUf
+9mmvtk57HVjsO6lTo15YyJ4=
+-----END CERTIFICATE-----
diff --git a/Lib/test/pycakey.pem b/Lib/test/pycakey.pem
new file mode 100644
index 0000000000..fc6effefb2
--- /dev/null
+++ b/Lib/test/pycakey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDn3unjDJ8AtqH9
+K1uW0m/M4L6GuSBe7AN6VavqpOn5SYXSZtXtx3rqVo4tj+dC4mIoqZ/WG47rtbSc
+nxSr3+aUi3YdPm0kYe0MvwCKYQzfXMg2cxYAzUe6baSkdIiDIwoZ/AmnPEpL0+cd
+LeTqTFQh8ybbiTcY1AK7QDJfpP8tHPfUu+yOz1yCrOZ8CGxIhWEHfyXgXOC8NF/g
+uQRHdchHC4281shoXzODYtIgRDWxrYEais28NbBci0fWGOmcGJfMATwpzOge5OTB
+uN7nwhEYh1qTNNimJfcUcevkIaLSDy4u1GIANdPW71xgS0ypFOLdFVhGNzMmt+cu
+Xe1C5MVNAgMBAAECggEBAJPM7QuUrPn4cLN/Ysd15lwTWn9oHDFFgkYFvCs66gXE
+ju/6Kx2BjWE4wTJby09AHM/MqB0DvguT7Mf1Q2j3tPQ1HZowg8OwRDleuwp6KIls
+jBbhL0Jdl/5HC67ktWvZ9wNvO/wFG1rQfT6FVajf9LUbWEaSZbOG2SLhHfsHorzu
+xjTJaI3bQ/0+79B1exwk5ruwhzFRd/XpY8hls7D/RfPIuHDlBghkW3N59KFWrf5h
+6bNEh2THm0+IyGcGqs0FD+QCOXyvsjwSUswqrr2ctLREOeDcd5ReUjSxYgjcJRrm
+J7ceIY/+uwDJxw/OlnmBvF6pQMkKwYW2gFztu+g2t4UCgYEA/9yo01Exz4crxXsy
+tAlnDJM++nZcm07rtFjTKHUfKY/cCgNTa8udM0svnfwlid/dpgLsI38gx04HHC1i
+EZ4acz+ToIWedLxM0nq73//xeRWEazOvCz1mMTZaMldahTWAyzN8qVK2B/625Yy4
+wNYWyweBBwEB8MzaCs73spksXOsCgYEA5/7wvhiofYGFAfMuANeJIwDL2OtBnoOv
+mVNfCmi3GC38fzwyi5ZpskWDiS2woJ+LQfs9Qu4EcZbUFLd7gbeOvb5gmFUtYope
+LitUUKunIR18MkQ+mQDBpQPQPhk4QJP5reCbWkrfTu7b5o/iS41s6fBTFmuzhLcT
+C71vFdCyeKcCgYAiCCqYeOtELDmBOeLDmaCQRqGQ1N96dOPbCBmF/xYXBCCDYG/f
+HaUaJnz96YTgstsbcrYP/p/Qgqtlbw/lQf9IpwMuzbcG1ejt8g89OyDWNyt2ytgU
+iaUnFJCos3/Byh0Iah/BsdOueo2/OJl2ZMOBW80orlSgv86cs2y037TL4wKBgQDm
+OOyW+MlbowhnIvfoBfwlLEkefnej4nKD6WRLZBcue5Qyf355X06Mhsc9foXlH+6G
+D9h/bswiHNdhp6N82rdgPGiHQx/CxiUoE/+b/nvgNO5mw6qLE2EXbG1e8pAMJcyE
+bHw+YkawggDfELI036fRj5gki8SeUz8nS1nNgElbyQKBgCRDX9Jh+MwSLu4QBWdt
+/fi+lv3K6kun/fI7EOV1vCV/j871tICu7pu5BrOLxAHqoVfU9AUX299/2KjCb5pv
+kjogiUK6qWCWBlfuqDNWGCoUGt1rhznUva0nNjSMy5rinBhhjpROZC2pw48lOluP
+UuvXsaPph7GTqPuy4Kab12YC
+-----END PRIVATE KEY-----
diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py
index ae62c6e7a0..c1c831ff86 100755
--- a/Lib/test/regrtest.py
+++ b/Lib/test/regrtest.py
@@ -1,11 +1,18 @@
#! /usr/bin/env python3
"""
-Usage:
+Script to run Python regression tests.
+Run this script with -h or --help for documentation.
+"""
+
+USAGE = """\
python -m test [options] [test_name1 [test_name2 ...]]
python path/to/Lib/test/regrtest.py [options] [test_name1 [test_name2 ...]]
+"""
+DESCRIPTION = """\
+Run Python regression tests.
If no arguments or options are provided, finds all files matching
the pattern "test_*" in the Lib/test subdirectory and runs
@@ -15,63 +22,10 @@ For more rigorous testing, it is useful to use the following
command line:
python -E -Wd -m test [options] [test_name1 ...]
+"""
-
-Options:
-
--h/--help -- print this text and exit
---timeout TIMEOUT
- -- dump the traceback and exit if a test takes more
- than TIMEOUT seconds; disabled if TIMEOUT is negative
- or equals to zero
---wait -- wait for user input, e.g., allow a debugger to be attached
-
-Verbosity
-
--v/--verbose -- run tests in verbose mode with output to stdout
--w/--verbose2 -- re-run failed tests in verbose mode
--W/--verbose3 -- display test output on failure
--d/--debug -- print traceback for failed tests
--q/--quiet -- no output unless one or more tests fail
--o/--slow -- print the slowest 10 tests
- --header -- print header with interpreter info
-
-Selecting tests
-
--r/--randomize -- randomize test execution order (see below)
- --randseed -- pass a random seed to reproduce a previous random run
--f/--fromfile -- read names of tests to run from a file (see below)
--x/--exclude -- arguments are tests to *exclude*
--s/--single -- single step through a set of tests (see below)
--m/--match PAT -- match test cases and methods with glob pattern PAT
--G/--failfast -- fail as soon as a test fails (only with -v or -W)
--u/--use RES1,RES2,...
- -- specify which special resource intensive tests to run
--M/--memlimit LIMIT
- -- run very large memory-consuming tests
- --testdir DIR
- -- execute test files in the specified directory (instead
- of the Python stdlib test suite)
-
-Special runs
-
--l/--findleaks -- if GC is available detect tests that leak memory
--L/--runleaks -- run the leaks(1) command just before exit
--R/--huntrleaks RUNCOUNTS
- -- search for reference leaks (needs debug build, v. slow)
--j/--multiprocess PROCESSES
- -- run PROCESSES processes at once
--T/--coverage -- turn on code coverage tracing using the trace module
--D/--coverdir DIRECTORY
- -- Directory where coverage files are put
--N/--nocoverdir -- Put coverage files alongside modules
--t/--threshold THRESHOLD
- -- call gc.set_threshold(THRESHOLD)
--n/--nowindows -- suppress error message boxes on Windows
--F/--forever -- run the specified tests in a loop, until an error happens
-
-
-Additional Option Details:
+EPILOG = """\
+Additional option details:
-r randomizes test execution order. You can use --randseed=int to provide a
int seed value for the randomizer; this is useful for reproducing troublesome
@@ -168,11 +122,12 @@ option '-uall,-gui'.
# We import importlib *ASAP* in order to test #15386
import importlib
+import argparse
import builtins
import faulthandler
-import getopt
import io
import json
+import locale
import logging
import os
import platform
@@ -194,7 +149,7 @@ try:
except ImportError:
threading = None
try:
- import multiprocessing.process
+ import _multiprocessing, multiprocessing.process
except ImportError:
multiprocessing = None
@@ -246,20 +201,262 @@ from test import support
RESOURCE_NAMES = ('audio', 'curses', 'largefile', 'network',
'decimal', 'cpu', 'subprocess', 'urlfetch', 'gui')
-TEMPDIR = os.path.abspath(tempfile.gettempdir())
-
-def usage(msg):
- print(msg, file=sys.stderr)
- print("Use --help for usage", file=sys.stderr)
- sys.exit(2)
-
+# When tests are run from the Python build directory, it is best practice
+# to keep the test files in a subfolder. This eases the cleanup of leftover
+# files using the "make distclean" command.
+if sysconfig.is_python_build():
+ TEMPDIR = os.path.join(sysconfig.get_config_var('srcdir'), 'build')
+else:
+ TEMPDIR = tempfile.gettempdir()
+TEMPDIR = os.path.abspath(TEMPDIR)
+
+class _ArgParser(argparse.ArgumentParser):
+
+ def error(self, message):
+ super().error(message + "\nPass -h or --help for complete help.")
+
+def _create_parser():
+ # Set prog to prevent the uninformative "__main__.py" from displaying in
+ # error messages when using "python -m test ...".
+ parser = _ArgParser(prog='regrtest.py',
+ usage=USAGE,
+ description=DESCRIPTION,
+ epilog=EPILOG,
+ add_help=False,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+
+ # Arguments with this clause added to its help are described further in
+ # the epilog's "Additional option details" section.
+ more_details = ' See the section at bottom for more details.'
+
+ group = parser.add_argument_group('General options')
+ # We add help explicitly to control what argument group it renders under.
+ group.add_argument('-h', '--help', action='help',
+ help='show this help message and exit')
+ group.add_argument('--timeout', metavar='TIMEOUT', type=float,
+ help='dump the traceback and exit if a test takes '
+ 'more than TIMEOUT seconds; disabled if TIMEOUT '
+ 'is negative or equals to zero')
+ group.add_argument('--wait', action='store_true',
+ help='wait for user input, e.g., allow a debugger '
+ 'to be attached')
+ group.add_argument('--slaveargs', metavar='ARGS')
+ group.add_argument('-S', '--start', metavar='START',
+ help='the name of the test at which to start.' +
+ more_details)
+
+ group = parser.add_argument_group('Verbosity')
+ group.add_argument('-v', '--verbose', action='count',
+ help='run tests in verbose mode with output to stdout')
+ group.add_argument('-w', '--verbose2', action='store_true',
+ help='re-run failed tests in verbose mode')
+ group.add_argument('-W', '--verbose3', action='store_true',
+ help='display test output on failure')
+ group.add_argument('-q', '--quiet', action='store_true',
+ help='no output unless one or more tests fail')
+ group.add_argument('-o', '--slow', action='store_true', dest='print_slow',
+ help='print the slowest 10 tests')
+ group.add_argument('--header', action='store_true',
+ help='print header with interpreter info')
+
+ group = parser.add_argument_group('Selecting tests')
+ group.add_argument('-r', '--randomize', action='store_true',
+ help='randomize test execution order.' + more_details)
+ group.add_argument('--randseed', metavar='SEED',
+ dest='random_seed', type=int,
+ help='pass a random seed to reproduce a previous '
+ 'random run')
+ group.add_argument('-f', '--fromfile', metavar='FILE',
+ help='read names of tests to run from a file.' +
+ more_details)
+ group.add_argument('-x', '--exclude', action='store_true',
+ help='arguments are tests to *exclude*')
+ group.add_argument('-s', '--single', action='store_true',
+ help='single step through a set of tests.' +
+ more_details)
+ group.add_argument('-m', '--match', metavar='PAT',
+ dest='match_tests',
+ help='match test cases and methods with glob pattern PAT')
+ group.add_argument('-G', '--failfast', action='store_true',
+ help='fail as soon as a test fails (only with -v or -W)')
+ group.add_argument('-u', '--use', metavar='RES1,RES2,...',
+ action='append', type=resources_list,
+ help='specify which special resource intensive tests '
+ 'to run.' + more_details)
+ group.add_argument('-M', '--memlimit', metavar='LIMIT',
+ help='run very large memory-consuming tests.' +
+ more_details)
+ group.add_argument('--testdir', metavar='DIR',
+ type=relative_filename,
+ help='execute test files in the specified directory '
+ '(instead of the Python stdlib test suite)')
+
+ group = parser.add_argument_group('Special runs')
+ group.add_argument('-l', '--findleaks', action='store_true',
+ help='if GC is available detect tests that leak memory')
+ group.add_argument('-L', '--runleaks', action='store_true',
+ help='run the leaks(1) command just before exit.' +
+ more_details)
+ group.add_argument('-R', '--huntrleaks', metavar='RUNCOUNTS',
+ type=huntrleaks,
+ help='search for reference leaks (needs debug build, '
+ 'very slow).' + more_details)
+ group.add_argument('-j', '--multiprocess', metavar='PROCESSES',
+ dest='use_mp', type=int,
+ help='run PROCESSES processes at once')
+ group.add_argument('-T', '--coverage', action='store_true',
+ dest='trace',
+ help='turn on code coverage tracing using the trace '
+ 'module')
+ group.add_argument('-D', '--coverdir', metavar='DIR',
+ type=relative_filename,
+ help='directory where coverage files are put')
+ group.add_argument('-N', '--nocoverdir',
+ action='store_const', const=None, dest='coverdir',
+ help='put coverage files alongside modules')
+ group.add_argument('-t', '--threshold', metavar='THRESHOLD',
+ type=int,
+ help='call gc.set_threshold(THRESHOLD)')
+ group.add_argument('-n', '--nowindows', action='store_true',
+ help='suppress error message boxes on Windows')
+ group.add_argument('-F', '--forever', action='store_true',
+ help='run the specified tests in a loop, until an '
+ 'error happens')
+
+ parser.add_argument('args', nargs=argparse.REMAINDER,
+ help=argparse.SUPPRESS)
+
+ return parser
+
+def relative_filename(string):
+ # CWD is replaced with a temporary dir before calling main(), so we
+ # join it with the saved CWD so it ends up where the user expects.
+ return os.path.join(support.SAVEDCWD, string)
+
+def huntrleaks(string):
+ args = string.split(':')
+ if len(args) not in (2, 3):
+ raise argparse.ArgumentTypeError(
+ 'needs 2 or 3 colon-separated arguments')
+ nwarmup = int(args[0]) if args[0] else 5
+ ntracked = int(args[1]) if args[1] else 4
+ fname = args[2] if len(args) > 2 and args[2] else 'reflog.txt'
+ return nwarmup, ntracked, fname
+
+def resources_list(string):
+ u = [x.lower() for x in string.split(',')]
+ for r in u:
+ if r == 'all' or r == 'none':
+ continue
+ if r[0] == '-':
+ r = r[1:]
+ if r not in RESOURCE_NAMES:
+ raise argparse.ArgumentTypeError('invalid resource: ' + r)
+ return u
-def main(tests=None, testdir=None, verbose=0, quiet=False,
+def _parse_args(args, **kwargs):
+ # Defaults
+ ns = argparse.Namespace(testdir=None, verbose=0, quiet=False,
exclude=False, single=False, randomize=False, fromfile=None,
findleaks=False, use_resources=None, trace=False, coverdir='coverage',
runleaks=False, huntrleaks=False, verbose2=False, print_slow=False,
random_seed=None, use_mp=None, verbose3=False, forever=False,
- header=False, failfast=False, match_tests=None):
+ header=False, failfast=False, match_tests=None)
+ for k, v in kwargs.items():
+ if not hasattr(ns, k):
+ raise TypeError('%r is an invalid keyword argument '
+ 'for this function' % k)
+ setattr(ns, k, v)
+ if ns.use_resources is None:
+ ns.use_resources = []
+
+ parser = _create_parser()
+ parser.parse_args(args=args, namespace=ns)
+
+ if ns.single and ns.fromfile:
+ parser.error("-s and -f don't go together!")
+ if ns.use_mp and ns.trace:
+ parser.error("-T and -j don't go together!")
+ if ns.use_mp and ns.findleaks:
+ parser.error("-l and -j don't go together!")
+ if ns.use_mp and ns.memlimit:
+ parser.error("-M and -j don't go together!")
+ if ns.failfast and not (ns.verbose or ns.verbose3):
+ parser.error("-G/--failfast needs either -v or -W")
+
+ if ns.quiet:
+ ns.verbose = 0
+ if ns.timeout is not None:
+ if hasattr(faulthandler, 'dump_traceback_later'):
+ if ns.timeout <= 0:
+ ns.timeout = None
+ else:
+ print("Warning: The timeout option requires "
+ "faulthandler.dump_traceback_later")
+ ns.timeout = None
+ if ns.use_mp is not None:
+ if ns.use_mp <= 0:
+ # Use all cores + extras for tests that like to sleep
+ ns.use_mp = 2 + (os.cpu_count() or 1)
+ if ns.use_mp == 1:
+ ns.use_mp = None
+ if ns.use:
+ for a in ns.use:
+ for r in a:
+ if r == 'all':
+ ns.use_resources[:] = RESOURCE_NAMES
+ continue
+ if r == 'none':
+ del ns.use_resources[:]
+ continue
+ remove = False
+ if r[0] == '-':
+ remove = True
+ r = r[1:]
+ if remove:
+ if r in ns.use_resources:
+ ns.use_resources.remove(r)
+ elif r not in ns.use_resources:
+ ns.use_resources.append(r)
+ if ns.random_seed is not None:
+ ns.randomize = True
+
+ return ns
+
+
+def run_test_in_subprocess(testname, ns):
+ """Run the given test in a subprocess with --slaveargs.
+
+ ns is the option Namespace parsed from command-line arguments. regrtest
+ is invoked in a subprocess with the --slaveargs argument; when the
+ subprocess exits, its return code, stdout and stderr are returned as a
+ 3-tuple.
+ """
+ from subprocess import Popen, PIPE
+ base_cmd = ([sys.executable] + support.args_from_interpreter_flags() +
+ ['-X', 'faulthandler', '-m', 'test.regrtest'])
+
+ slaveargs = (
+ (testname, ns.verbose, ns.quiet),
+ dict(huntrleaks=ns.huntrleaks,
+ use_resources=ns.use_resources,
+ output_on_failure=ns.verbose3,
+ timeout=ns.timeout, failfast=ns.failfast,
+ match_tests=ns.match_tests))
+ # Running the child from the same working directory as regrtest's original
+ # invocation ensures that TEMPDIR for the child is the same when
+ # sysconfig.is_python_build() is true. See issue 15300.
+ popen = Popen(base_cmd + ['--slaveargs', json.dumps(slaveargs)],
+ stdout=PIPE, stderr=PIPE,
+ universal_newlines=True,
+ close_fds=(os.name != 'nt'),
+ cwd=support.SAVEDCWD)
+ stdout, stderr = popen.communicate()
+ retcode = popen.wait()
+ return retcode, stdout, stderr
+
+
+def main(tests=None, **kwargs):
"""Execute a test suite.
This also parses command-line options and modifies its behavior
@@ -282,7 +479,6 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
directly to set the values that would normally be set by flags
on the command line.
"""
-
# Display the Python traceback on fatal errors (e.g. segfault)
faulthandler.enable(all_threads=True)
@@ -298,187 +494,51 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
replace_stdout()
support.record_original_stdout(sys.stdout)
- try:
- opts, args = getopt.getopt(sys.argv[1:], 'hvqxsoS:rf:lu:t:TD:NLR:FdwWM:nj:Gm:',
- ['help', 'verbose', 'verbose2', 'verbose3', 'quiet',
- 'exclude', 'single', 'slow', 'randomize', 'fromfile=', 'findleaks',
- 'use=', 'threshold=', 'coverdir=', 'nocoverdir',
- 'runleaks', 'huntrleaks=', 'memlimit=', 'randseed=',
- 'multiprocess=', 'coverage', 'slaveargs=', 'forever', 'debug',
- 'start=', 'nowindows', 'header', 'testdir=', 'timeout=', 'wait',
- 'failfast', 'match='])
- except getopt.error as msg:
- usage(msg)
- # Defaults
- if random_seed is None:
- random_seed = random.randrange(10000000)
- if use_resources is None:
- use_resources = []
- debug = False
- start = None
- timeout = None
- for o, a in opts:
- if o in ('-h', '--help'):
- print(__doc__)
- return
- elif o in ('-v', '--verbose'):
- verbose += 1
- elif o in ('-w', '--verbose2'):
- verbose2 = True
- elif o in ('-d', '--debug'):
- debug = True
- elif o in ('-W', '--verbose3'):
- verbose3 = True
- elif o in ('-G', '--failfast'):
- failfast = True
- elif o in ('-q', '--quiet'):
- quiet = True;
- verbose = 0
- elif o in ('-x', '--exclude'):
- exclude = True
- elif o in ('-S', '--start'):
- start = a
- elif o in ('-s', '--single'):
- single = True
- elif o in ('-o', '--slow'):
- print_slow = True
- elif o in ('-r', '--randomize'):
- randomize = True
- elif o == '--randseed':
- randomize = True
- random_seed = int(a)
- elif o in ('-f', '--fromfile'):
- fromfile = a
- elif o in ('-m', '--match'):
- match_tests = a
- elif o in ('-l', '--findleaks'):
- findleaks = True
- elif o in ('-L', '--runleaks'):
- runleaks = True
- elif o in ('-t', '--threshold'):
- import gc
- gc.set_threshold(int(a))
- elif o in ('-T', '--coverage'):
- trace = True
- elif o in ('-D', '--coverdir'):
- # CWD is replaced with a temporary dir before calling main(), so we
- # need join it with the saved CWD so it goes where the user expects.
- coverdir = os.path.join(support.SAVEDCWD, a)
- elif o in ('-N', '--nocoverdir'):
- coverdir = None
- elif o in ('-R', '--huntrleaks'):
- huntrleaks = a.split(':')
- if len(huntrleaks) not in (2, 3):
- print(a, huntrleaks)
- usage('-R takes 2 or 3 colon-separated arguments')
- if not huntrleaks[0]:
- huntrleaks[0] = 5
- else:
- huntrleaks[0] = int(huntrleaks[0])
- if not huntrleaks[1]:
- huntrleaks[1] = 4
- else:
- huntrleaks[1] = int(huntrleaks[1])
- if len(huntrleaks) == 2 or not huntrleaks[2]:
- huntrleaks[2:] = ["reflog.txt"]
- # Avoid false positives due to various caches
- # filling slowly with random data:
- warm_caches()
- elif o in ('-M', '--memlimit'):
- support.set_memlimit(a)
- elif o in ('-u', '--use'):
- u = [x.lower() for x in a.split(',')]
- for r in u:
- if r == 'all':
- use_resources[:] = RESOURCE_NAMES
- continue
- if r == 'none':
- del use_resources[:]
- continue
- remove = False
- if r[0] == '-':
- remove = True
- r = r[1:]
- if r not in RESOURCE_NAMES:
- usage('Invalid -u/--use option: ' + a)
- if remove:
- if r in use_resources:
- use_resources.remove(r)
- elif r not in use_resources:
- use_resources.append(r)
- elif o in ('-n', '--nowindows'):
- import msvcrt
- msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS|
- msvcrt.SEM_NOALIGNMENTFAULTEXCEPT|
- msvcrt.SEM_NOGPFAULTERRORBOX|
- msvcrt.SEM_NOOPENFILEERRORBOX)
- try:
- msvcrt.CrtSetReportMode
- except AttributeError:
- # release build
- pass
- else:
- for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]:
- msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE)
- msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR)
- elif o in ('-F', '--forever'):
- forever = True
- elif o in ('-j', '--multiprocess'):
- use_mp = int(a)
- if use_mp <= 0:
- try:
- import multiprocessing
- # Use all cores + extras for tests that like to sleep
- use_mp = 2 + multiprocessing.cpu_count()
- except (ImportError, NotImplementedError):
- use_mp = 3
- if use_mp == 1:
- use_mp = None
- elif o == '--header':
- header = True
- elif o == '--slaveargs':
- args, kwargs = json.loads(a)
- try:
- result = runtest(*args, **kwargs)
- except KeyboardInterrupt:
- result = INTERRUPTED, ''
- except BaseException as e:
- traceback.print_exc()
- result = CHILD_ERROR, str(e)
- sys.stdout.flush()
- print() # Force a newline (just in case)
- print(json.dumps(result))
- sys.exit(0)
- elif o == '--testdir':
- # CWD is replaced with a temporary dir before calling main(), so we
- # join it with the saved CWD so it ends up where the user expects.
- testdir = os.path.join(support.SAVEDCWD, a)
- elif o == '--timeout':
- if hasattr(faulthandler, 'dump_traceback_later'):
- timeout = float(a)
- if timeout <= 0:
- timeout = None
- else:
- print("Warning: The timeout option requires "
- "faulthandler.dump_traceback_later")
- timeout = None
- elif o == '--wait':
- input("Press any key to continue...")
+ ns = _parse_args(sys.argv[1:], **kwargs)
+
+ if ns.huntrleaks:
+ # Avoid false positives due to various caches
+ # filling slowly with random data:
+ warm_caches()
+ if ns.memlimit is not None:
+ support.set_memlimit(ns.memlimit)
+ if ns.threshold is not None:
+ import gc
+ gc.set_threshold(ns.threshold)
+ if ns.nowindows:
+ import msvcrt
+ msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS|
+ msvcrt.SEM_NOALIGNMENTFAULTEXCEPT|
+ msvcrt.SEM_NOGPFAULTERRORBOX|
+ msvcrt.SEM_NOOPENFILEERRORBOX)
+ try:
+ msvcrt.CrtSetReportMode
+ except AttributeError:
+ # release build
+ pass
else:
- print(("No handler for option {}. Please report this as a bug "
- "at http://bugs.python.org.").format(o), file=sys.stderr)
- sys.exit(1)
- if single and fromfile:
- usage("-s and -f don't go together!")
- if use_mp and trace:
- usage("-T and -j don't go together!")
- if use_mp and findleaks:
- usage("-l and -j don't go together!")
- if use_mp and support.max_memuse:
- usage("-M and -j don't go together!")
- if failfast and not (verbose or verbose3):
- usage("-G/--failfast needs either -v or -W")
+ for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]:
+ msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE)
+ msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR)
+ if ns.wait:
+ input("Press any key to continue...")
+
+ if ns.slaveargs is not None:
+ args, kwargs = json.loads(ns.slaveargs)
+ if kwargs.get('huntrleaks'):
+ unittest.BaseTestSuite._cleanup = False
+ try:
+ result = runtest(*args, **kwargs)
+ except KeyboardInterrupt:
+ result = INTERRUPTED, ''
+ except BaseException as e:
+ traceback.print_exc()
+ result = CHILD_ERROR, str(e)
+ sys.stdout.flush()
+ print() # Force a newline (just in case)
+ print(json.dumps(result))
+ sys.exit(0)
good = []
bad = []
@@ -487,12 +547,12 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
environment_changed = []
interrupted = False
- if findleaks:
+ if ns.findleaks:
try:
import gc
except ImportError:
print('No GC available, disabling findleaks.')
- findleaks = False
+ ns.findleaks = False
else:
# Uncomment the line below to report garbage that is not
# freeable by reference counting alone. By default only
@@ -500,82 +560,87 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
#gc.set_debug(gc.DEBUG_SAVEALL)
found_garbage = []
- if single:
+ if ns.huntrleaks:
+ unittest.BaseTestSuite._cleanup = False
+
+ if ns.single:
filename = os.path.join(TEMPDIR, 'pynexttest')
try:
- fp = open(filename, 'r')
- next_test = fp.read().strip()
- tests = [next_test]
- fp.close()
- except IOError:
+ with open(filename, 'r') as fp:
+ next_test = fp.read().strip()
+ tests = [next_test]
+ except OSError:
pass
- if fromfile:
+ if ns.fromfile:
tests = []
- fp = open(os.path.join(support.SAVEDCWD, fromfile))
- count_pat = re.compile(r'\[\s*\d+/\s*\d+\]')
- for line in fp:
- line = count_pat.sub('', line)
- guts = line.split() # assuming no test has whitespace in its name
- if guts and not guts[0].startswith('#'):
- tests.extend(guts)
- fp.close()
+ with open(os.path.join(support.SAVEDCWD, ns.fromfile)) as fp:
+ count_pat = re.compile(r'\[\s*\d+/\s*\d+\]')
+ for line in fp:
+ line = count_pat.sub('', line)
+ guts = line.split() # assuming no test has whitespace in its name
+ if guts and not guts[0].startswith('#'):
+ tests.extend(guts)
# Strip .py extensions.
- removepy(args)
+ removepy(ns.args)
removepy(tests)
stdtests = STDTESTS[:]
nottests = NOTTESTS.copy()
- if exclude:
- for arg in args:
+ if ns.exclude:
+ for arg in ns.args:
if arg in stdtests:
stdtests.remove(arg)
nottests.add(arg)
- args = []
+ ns.args = []
# For a partial run, we do not need to clutter the output.
- if verbose or header or not (quiet or single or tests or args):
+ if ns.verbose or ns.header or not (ns.quiet or ns.single or tests or ns.args):
# Print basic platform information
print("==", platform.python_implementation(), *sys.version.split())
print("== ", platform.platform(aliased=True),
"%s-endian" % sys.byteorder)
+ print("== ", "hash algorithm:", sys.hash_info.algorithm,
+ "64bit" if sys.maxsize > 2**32 else "32bit")
print("== ", os.getcwd())
print("Testing with flags:", sys.flags)
# if testdir is set, then we are not running the python tests suite, so
# don't add default tests to be executed or skipped (pass empty values)
- if testdir:
- alltests = findtests(testdir, list(), set())
+ if ns.testdir:
+ alltests = findtests(ns.testdir, list(), set())
else:
- alltests = findtests(testdir, stdtests, nottests)
+ alltests = findtests(ns.testdir, stdtests, nottests)
- selected = tests or args or alltests
- if single:
+ selected = tests or ns.args or alltests
+ if ns.single:
selected = selected[:1]
try:
next_single_test = alltests[alltests.index(selected[0])+1]
except IndexError:
next_single_test = None
# Remove all the selected tests that precede start if it's set.
- if start:
+ if ns.start:
try:
- del selected[:selected.index(start)]
+ del selected[:selected.index(ns.start)]
except ValueError:
- print("Couldn't find starting test (%s), using all tests" % start)
- if randomize:
- random.seed(random_seed)
- print("Using random seed", random_seed)
+ print("Couldn't find starting test (%s), using all tests" % ns.start)
+ if ns.randomize:
+ if ns.random_seed is None:
+ ns.random_seed = random.randrange(10000000)
+ random.seed(ns.random_seed)
+ print("Using random seed", ns.random_seed)
random.shuffle(selected)
- if trace:
+ if ns.trace:
import trace, tempfile
tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,
tempfile.gettempdir()],
trace=False, count=True)
test_times = []
- support.verbose = verbose # Tell tests to be moderately quiet
- support.use_resources = use_resources
+ support.verbose = ns.verbose # Tell tests to be moderately quiet
+ support.use_resources = ns.use_resources
save_modules = sys.modules.keys()
def accumulate_result(test, result):
@@ -593,7 +658,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
skipped.append(test)
resource_denieds.append(test)
- if forever:
+ if ns.forever:
def test_forever(tests=list(selected)):
while True:
for test in tests:
@@ -608,19 +673,16 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
test_count = '/{}'.format(len(selected))
test_count_width = len(test_count) - 1
- if use_mp:
+ if ns.use_mp:
try:
from threading import Thread
except ImportError:
print("Multiprocess option requires thread support")
sys.exit(2)
from queue import Queue
- from subprocess import Popen, PIPE
- debug_output_pat = re.compile(r"\[\d+ refs\]$")
+ debug_output_pat = re.compile(r"\[\d+ refs, \d+ blocks\]$")
output = Queue()
pending = MultiprocessTests(tests)
- opt_args = support.args_from_interpreter_flags()
- base_cmd = [sys.executable] + opt_args + ['-m', 'test.regrtest']
def work():
# A worker thread.
try:
@@ -630,24 +692,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
except StopIteration:
output.put((None, None, None, None))
return
- args_tuple = (
- (test, verbose, quiet),
- dict(huntrleaks=huntrleaks, use_resources=use_resources,
- debug=debug, output_on_failure=verbose3,
- timeout=timeout, failfast=failfast,
- match_tests=match_tests)
- )
- # -E is needed by some tests, e.g. test_import
- # Running the child from the same working directory ensures
- # that TEMPDIR for the child is the same when
- # sysconfig.is_python_build() is true. See issue 15300.
- popen = Popen(base_cmd + ['--slaveargs', json.dumps(args_tuple)],
- stdout=PIPE, stderr=PIPE,
- universal_newlines=True,
- close_fds=(os.name != 'nt'),
- cwd=support.SAVEDCWD)
- stdout, stderr = popen.communicate()
- retcode = popen.wait()
+ retcode, stdout, stderr = run_test_in_subprocess(test, ns)
# Strip last refcount output line if it exists, since it
# comes from the shutdown of the interpreter in the subcommand.
stderr = debug_output_pat.sub("", stderr)
@@ -664,19 +709,19 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
except BaseException:
output.put((None, None, None, None))
raise
- workers = [Thread(target=work) for i in range(use_mp)]
+ workers = [Thread(target=work) for i in range(ns.use_mp)]
for worker in workers:
worker.start()
finished = 0
test_index = 1
try:
- while finished < use_mp:
+ while finished < ns.use_mp:
test, stdout, stderr, result = output.get()
if test is None:
finished += 1
continue
accumulate_result(test, result)
- if not quiet:
+ if not ns.quiet:
fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
print(fmt.format(
test_count_width, test_index, test_count,
@@ -699,29 +744,30 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
worker.join()
else:
for test_index, test in enumerate(tests, 1):
- if not quiet:
+ if not ns.quiet:
fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
print(fmt.format(
test_count_width, test_index, test_count, len(bad), test))
sys.stdout.flush()
- if trace:
+ if ns.trace:
# If we're tracing code coverage, then we don't exit with status
# if on a false return value from main.
- tracer.runctx('runtest(test, verbose, quiet, timeout=timeout)',
+ tracer.runctx('runtest(test, ns.verbose, ns.quiet, timeout=ns.timeout)',
globals=globals(), locals=vars())
else:
try:
- result = runtest(test, verbose, quiet, huntrleaks, debug,
- output_on_failure=verbose3,
- timeout=timeout, failfast=failfast,
- match_tests=match_tests)
+ result = runtest(test, ns.verbose, ns.quiet,
+ ns.huntrleaks,
+ output_on_failure=ns.verbose3,
+ timeout=ns.timeout, failfast=ns.failfast,
+ match_tests=ns.match_tests)
accumulate_result(test, result)
except KeyboardInterrupt:
interrupted = True
break
except:
raise
- if findleaks:
+ if ns.findleaks:
gc.collect()
if gc.garbage:
print("Warning: test created", len(gc.garbage), end=' ')
@@ -742,11 +788,11 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
omitted = set(selected) - set(good) - set(bad) - set(skipped)
print(count(len(omitted), "test"), "omitted:")
printlist(omitted)
- if good and not quiet:
+ if good and not ns.quiet:
if not bad and not skipped and not interrupted and len(good) > 1:
print("All", end=' ')
print(count(len(good), "test"), "OK.")
- if print_slow:
+ if ns.print_slow:
test_times.sort(reverse=True)
print("10 slowest tests:")
for time, test in test_times[:10]:
@@ -760,32 +806,19 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
print("{} altered the execution environment:".format(
count(len(environment_changed), "test")))
printlist(environment_changed)
- if skipped and not quiet:
+ if skipped and not ns.quiet:
print(count(len(skipped), "test"), "skipped:")
printlist(skipped)
- e = _ExpectedSkips()
- plat = sys.platform
- if e.isvalid():
- surprise = set(skipped) - e.getexpected() - set(resource_denieds)
- if surprise:
- print(count(len(surprise), "skip"), \
- "unexpected on", plat + ":")
- printlist(surprise)
- else:
- print("Those skips are all expected on", plat + ".")
- else:
- print("Ask someone to teach regrtest.py about which tests are")
- print("expected to get skipped on", plat + ".")
-
- if verbose2 and bad:
+ if ns.verbose2 and bad:
print("Re-running failed tests in verbose mode")
for test in bad:
print("Re-running test %r in verbose mode" % test)
sys.stdout.flush()
try:
- verbose = True
- ok = runtest(test, True, quiet, huntrleaks, debug, timeout=timeout)
+ ns.verbose = True
+ ok = runtest(test, True, ns.quiet, ns.huntrleaks,
+ timeout=ns.timeout)
except KeyboardInterrupt:
# print a newline separate from the ^C
print()
@@ -793,18 +826,18 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
except:
raise
- if single:
+ if ns.single:
if next_single_test:
with open(filename, 'w') as fp:
fp.write(next_single_test + '\n')
else:
os.unlink(filename)
- if trace:
+ if ns.trace:
r = tracer.results()
- r.write_results(show_missing=True, summary=True, coverdir=coverdir)
+ r.write_results(show_missing=True, summary=True, coverdir=ns.coverdir)
- if runleaks:
+ if ns.runleaks:
os.system("leaks %d" % os.getpid())
sys.exit(len(bad) > 0 or interrupted)
@@ -877,7 +910,7 @@ def replace_stdout():
atexit.register(restore_stdout)
def runtest(test, verbose, quiet,
- huntrleaks=False, debug=False, use_resources=None,
+ huntrleaks=False, use_resources=None,
output_on_failure=False, failfast=False, match_tests=None,
timeout=None):
"""Run a single test.
@@ -885,14 +918,15 @@ def runtest(test, verbose, quiet,
test -- the name of the test
verbose -- if true, print more messages
quiet -- if true, don't print 'skipped' messages (probably redundant)
- test_times -- a list of (time, test_name) pairs
huntrleaks -- run multiple times to test for leaks; requires a debug
build; a triple corresponding to -R's three arguments
+ use_resources -- list of extra resources to use
output_on_failure -- if true, display test output on failure
timeout -- dump the traceback and exit if a test takes more than
timeout seconds
+ failfast, match_tests -- See regrtest command-line flags for these.
- Returns one of the test result constants:
+ Returns the tuple result, test_time, where result is one of the constants:
INTERRUPTED KeyboardInterrupt when run under -j
RESOURCE_DENIED test skipped because resource denied
SKIPPED test skipped for some other reason
@@ -930,7 +964,7 @@ def runtest(test, verbose, quiet,
sys.stdout = stream
sys.stderr = stream
result = runtest_inner(test, verbose, quiet, huntrleaks,
- debug, display_failure=False)
+ display_failure=False)
if result[0] == FAILED:
output = stream.getvalue()
orig_stderr.write(output)
@@ -940,7 +974,7 @@ def runtest(test, verbose, quiet,
sys.stderr = orig_stderr
else:
support.verbose = verbose # Tell tests to be moderately quiet
- result = runtest_inner(test, verbose, quiet, huntrleaks, debug,
+ result = runtest_inner(test, verbose, quiet, huntrleaks,
display_failure=not verbose)
return result
finally:
@@ -992,10 +1026,12 @@ class saved_test_environment:
'os.environ', 'sys.path', 'sys.path_hooks', '__import__',
'warnings.filters', 'asyncore.socket_map',
'logging._handlers', 'logging._handlerList', 'sys.gettrace',
- 'sys.warnoptions', 'threading._dangling',
- 'multiprocessing.process._dangling',
+ 'sys.warnoptions',
+ # multiprocessing.process._cleanup() may release ref
+ # to a thread, so check processes first.
+ 'multiprocessing.process._dangling', 'threading._dangling',
'sysconfig._CONFIG_VARS', 'sysconfig._INSTALL_SCHEMES',
- 'support.TESTFN',
+ 'support.TESTFN', 'locale', 'warnings.showwarning',
)
def get_sys_argv(self):
@@ -1123,6 +1159,8 @@ class saved_test_environment:
def get_multiprocessing_process__dangling(self):
if not multiprocessing:
return None
+ # Unjoined process objects can survive after process exits
+ multiprocessing.process._cleanup()
# This copies the weakrefs without making any strong reference
return multiprocessing.process._dangling.copy()
def restore_multiprocessing_process__dangling(self, saved):
@@ -1164,6 +1202,25 @@ class saved_test_environment:
elif os.path.isdir(support.TESTFN):
shutil.rmtree(support.TESTFN)
+ _lc = [getattr(locale, lc) for lc in dir(locale)
+ if lc.startswith('LC_')]
+ def get_locale(self):
+ pairings = []
+ for lc in self._lc:
+ try:
+ pairings.append((lc, locale.setlocale(lc, None)))
+ except (TypeError, ValueError):
+ continue
+ return pairings
+ def restore_locale(self, saved):
+ for lc, setting in saved:
+ locale.setlocale(lc, setting)
+
+ def get_warnings_showwarning(self):
+ return warnings.showwarning
+ def restore_warnings_showwarning(self, fxn):
+ warnings.showwarning = fxn
+
def resource_info(self):
for name in self.resources:
method_suffix = name.replace('.', '_')
@@ -1198,7 +1255,7 @@ class saved_test_environment:
def runtest_inner(test, verbose, quiet,
- huntrleaks=False, debug=False, display_failure=True):
+ huntrleaks=False, display_failure=True):
support.unload(test)
test_time = 0.0
@@ -1211,8 +1268,7 @@ def runtest_inner(test, verbose, quiet,
abstest = 'test.' + test
with saved_test_environment(test, verbose, quiet) as environment:
start_time = time.time()
- the_package = __import__(abstest, globals(), locals(), [])
- the_module = getattr(the_package, test)
+ the_module = importlib.import_module(abstest)
# If the test has a test_main, that will run the appropriate
# tests. If not, use normal unittest test loading.
test_runner = getattr(the_module, "test_main", None)
@@ -1221,8 +1277,7 @@ def runtest_inner(test, verbose, quiet,
test_runner = lambda: support.run_unittest(tests)
test_runner()
if huntrleaks:
- refleak = dash_R(the_module, test, test_runner,
- huntrleaks)
+ refleak = dash_R(the_module, test, test_runner, huntrleaks)
test_time = time.time() - start_time
except support.ResourceDenied as msg:
if not quiet:
@@ -1328,41 +1383,50 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
for obj in abc.__subclasses__() + [abc]:
abcs[obj] = obj._abc_registry.copy()
- if indirect_test:
- def run_the_test():
- indirect_test()
- else:
- def run_the_test():
- del sys.modules[the_module.__name__]
- exec('import ' + the_module.__name__)
-
- deltas = []
nwarmup, ntracked, fname = huntrleaks
fname = os.path.join(support.SAVEDCWD, fname)
repcount = nwarmup + ntracked
+ rc_deltas = [0] * repcount
+ alloc_deltas = [0] * repcount
+
print("beginning", repcount, "repetitions", file=sys.stderr)
print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr)
sys.stderr.flush()
- dash_R_cleanup(fs, ps, pic, zdc, abcs)
for i in range(repcount):
- rc_before = sys.gettotalrefcount()
- run_the_test()
+ indirect_test()
+ alloc_after, rc_after = dash_R_cleanup(fs, ps, pic, zdc, abcs)
sys.stderr.write('.')
sys.stderr.flush()
- dash_R_cleanup(fs, ps, pic, zdc, abcs)
- rc_after = sys.gettotalrefcount()
if i >= nwarmup:
- deltas.append(rc_after - rc_before)
+ rc_deltas[i] = rc_after - rc_before
+ alloc_deltas[i] = alloc_after - alloc_before
+ alloc_before, rc_before = alloc_after, rc_after
print(file=sys.stderr)
- if any(deltas):
- msg = '%s leaked %s references, sum=%s' % (test, deltas, sum(deltas))
- print(msg, file=sys.stderr)
- sys.stderr.flush()
- with open(fname, "a") as refrep:
- print(msg, file=refrep)
- refrep.flush()
- return True
- return False
+ # These checkers return False on success, True on failure
+ def check_rc_deltas(deltas):
+ return any(deltas)
+ def check_alloc_deltas(deltas):
+ # At least 1/3rd of 0s
+ if 3 * deltas.count(0) < len(deltas):
+ return True
+ # Nothing else than 1s, 0s and -1s
+ if not set(deltas) <= {1,0,-1}:
+ return True
+ return False
+ failed = False
+ for deltas, item_name, checker in [
+ (rc_deltas, 'references', check_rc_deltas),
+ (alloc_deltas, 'memory blocks', check_alloc_deltas)]:
+ if checker(deltas):
+ msg = '%s leaked %s %s, sum=%s' % (
+ test, deltas[nwarmup:], item_name, sum(deltas))
+ print(msg, file=sys.stderr)
+ sys.stderr.flush()
+ with open(fname, "a") as refrep:
+ print(msg, file=refrep)
+ refrep.flush()
+ failed = True
+ return failed
def dash_R_cleanup(fs, ps, pic, zdc, abcs):
import gc, copyreg
@@ -1428,8 +1492,11 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
else:
ctypes._reset_cache()
- # Collect cyclic trash.
+ # Collect cyclic trash and read memory statistics immediately after.
+ func1 = sys.getallocatedblocks
+ func2 = sys.gettotalrefcount
gc.collect()
+ return func1(), func2()
def warm_caches():
# char cache
@@ -1472,307 +1539,10 @@ def printlist(x, width=70, indent=4):
print(fill(' '.join(str(elt) for elt in sorted(x)), width,
initial_indent=blanks, subsequent_indent=blanks))
-# Map sys.platform to a string containing the basenames of tests
-# expected to be skipped on that platform.
-#
-# Special cases:
-# test_pep277
-# The _ExpectedSkips constructor adds this to the set of expected
-# skips if not os.path.supports_unicode_filenames.
-# test_timeout
-# Controlled by test_timeout.skip_expected. Requires the network
-# resource and a socket module.
-#
-# Tests that are expected to be skipped everywhere except on one platform
-# are also handled separately.
-
-_expectations = (
- ('win32',
- """
- test__locale
- test_crypt
- test_curses
- test_dbm
- test_devpoll
- test_fcntl
- test_fork1
- test_epoll
- test_dbm_gnu
- test_dbm_ndbm
- test_grp
- test_ioctl
- test_largefile
- test_kqueue
- test_openpty
- test_ossaudiodev
- test_pipes
- test_poll
- test_posix
- test_pty
- test_pwd
- test_resource
- test_signal
- test_syslog
- test_threadsignals
- test_wait3
- test_wait4
- """),
- ('linux',
- """
- test_curses
- test_devpoll
- test_largefile
- test_kqueue
- test_ossaudiodev
- """),
- ('unixware',
- """
- test_epoll
- test_largefile
- test_kqueue
- test_minidom
- test_openpty
- test_pyexpat
- test_sax
- test_sundry
- """),
- ('openunix',
- """
- test_epoll
- test_largefile
- test_kqueue
- test_minidom
- test_openpty
- test_pyexpat
- test_sax
- test_sundry
- """),
- ('sco_sv',
- """
- test_asynchat
- test_fork1
- test_epoll
- test_gettext
- test_largefile
- test_locale
- test_kqueue
- test_minidom
- test_openpty
- test_pyexpat
- test_queue
- test_sax
- test_sundry
- test_thread
- test_threaded_import
- test_threadedtempfile
- test_threading
- """),
- ('darwin',
- """
- test__locale
- test_curses
- test_devpoll
- test_epoll
- test_dbm_gnu
- test_gdb
- test_largefile
- test_locale
- test_minidom
- test_ossaudiodev
- test_poll
- """),
- ('sunos',
- """
- test_curses
- test_dbm
- test_epoll
- test_kqueue
- test_dbm_gnu
- test_gzip
- test_openpty
- test_zipfile
- test_zlib
- """),
- ('hp-ux',
- """
- test_curses
- test_epoll
- test_dbm_gnu
- test_gzip
- test_largefile
- test_locale
- test_kqueue
- test_minidom
- test_openpty
- test_pyexpat
- test_sax
- test_zipfile
- test_zlib
- """),
- ('cygwin',
- """
- test_curses
- test_dbm
- test_devpoll
- test_epoll
- test_ioctl
- test_kqueue
- test_largefile
- test_locale
- test_ossaudiodev
- test_socketserver
- """),
- ('os2emx',
- """
- test_audioop
- test_curses
- test_epoll
- test_kqueue
- test_largefile
- test_mmap
- test_openpty
- test_ossaudiodev
- test_pty
- test_resource
- test_signal
- """),
- ('freebsd',
- """
- test_devpoll
- test_epoll
- test_dbm_gnu
- test_locale
- test_ossaudiodev
- test_pep277
- test_pty
- test_socketserver
- test_tcl
- test_tk
- test_ttk_guionly
- test_ttk_textonly
- test_timeout
- test_urllibnet
- test_multiprocessing
- """),
- ('aix',
- """
- test_bz2
- test_epoll
- test_dbm_gnu
- test_gzip
- test_kqueue
- test_ossaudiodev
- test_tcl
- test_tk
- test_ttk_guionly
- test_ttk_textonly
- test_zipimport
- test_zlib
- """),
- ('openbsd',
- """
- test_ctypes
- test_devpoll
- test_epoll
- test_dbm_gnu
- test_locale
- test_normalization
- test_ossaudiodev
- test_pep277
- test_tcl
- test_tk
- test_ttk_guionly
- test_ttk_textonly
- test_multiprocessing
- """),
- ('netbsd',
- """
- test_ctypes
- test_curses
- test_devpoll
- test_epoll
- test_dbm_gnu
- test_locale
- test_ossaudiodev
- test_pep277
- test_tcl
- test_tk
- test_ttk_guionly
- test_ttk_textonly
- test_multiprocessing
- """),
-)
-
-class _ExpectedSkips:
- def __init__(self):
- import os.path
- from test import test_timeout
-
- self.valid = False
- expected = None
- for item in _expectations:
- if sys.platform.startswith(item[0]):
- expected = item[1]
- break
- if expected is not None:
- self.expected = set(expected.split())
-
- # These are broken tests, for now skipped on every platform.
- # XXX Fix these!
- self.expected.add('test_nis')
-
- # expected to be skipped on every platform, even Linux
- if not os.path.supports_unicode_filenames:
- self.expected.add('test_pep277')
-
- # doctest, profile and cProfile tests fail when the codec for the
- # fs encoding isn't built in because PyUnicode_Decode() adds two
- # calls into Python.
- encs = ("utf-8", "latin-1", "ascii", "mbcs", "utf-16", "utf-32")
- if sys.getfilesystemencoding().lower() not in encs:
- self.expected.add('test_profile')
- self.expected.add('test_cProfile')
- self.expected.add('test_doctest')
-
- if test_timeout.skip_expected:
- self.expected.add('test_timeout')
-
- if sys.platform != "win32":
- # test_sqlite is only reliable on Windows where the library
- # is distributed with Python
- WIN_ONLY = {"test_unicode_file", "test_winreg",
- "test_winsound", "test_startfile",
- "test_sqlite", "test_msilib"}
- self.expected |= WIN_ONLY
-
- if sys.platform != 'sunos5':
- self.expected.add('test_nis')
-
- if support.python_is_optimized():
- self.expected.add("test_gdb")
-
- self.valid = True
-
- def isvalid(self):
- "Return true iff _ExpectedSkips knows about the current platform."
- return self.valid
-
- def getexpected(self):
- """Return set of test names we expect to skip on current platform.
-
- self.isvalid() must be true.
- """
-
- assert self.isvalid()
- return self.expected
-
-def _make_temp_dir_for_build(TEMPDIR):
- # When tests are run from the Python build directory, it is best practice
- # to keep the test files in a subfolder. It eases the cleanup of leftover
- # files using command "make distclean".
+
+def main_in_temp_cwd():
+ """Run main() in a temporary working directory."""
if sysconfig.is_python_build():
- TEMPDIR = os.path.join(sysconfig.get_config_var('srcdir'), 'build')
- TEMPDIR = os.path.abspath(TEMPDIR)
try:
os.mkdir(TEMPDIR)
except FileExistsError:
@@ -1781,10 +1551,16 @@ def _make_temp_dir_for_build(TEMPDIR):
# Define a writable temp dir that will be used as cwd while running
# the tests. The name of the dir includes the pid to allow parallel
# testing (see the -j option).
- TESTCWD = 'test_python_{}'.format(os.getpid())
+ test_cwd = 'test_python_{}'.format(os.getpid())
+ test_cwd = os.path.join(TEMPDIR, test_cwd)
+
+ # Run the tests in a context manager that temporarily changes the CWD to a
+ # temporary and writable directory. If it's not possible to create or
+ # change the CWD, the original CWD will be used. The original CWD is
+ # available from support.SAVEDCWD.
+ with support.temp_cwd(test_cwd, quiet=True):
+ main()
- TESTCWD = os.path.join(TEMPDIR, TESTCWD)
- return TEMPDIR, TESTCWD
if __name__ == '__main__':
# Remove regrtest.py's own directory from the module search path. Despite
@@ -1808,11 +1584,4 @@ if __name__ == '__main__':
# sanity check
assert __file__ == os.path.abspath(sys.argv[0])
- TEMPDIR, TESTCWD = _make_temp_dir_for_build(TEMPDIR)
-
- # Run the tests in a context manager that temporary changes the CWD to a
- # temporary and writable directory. If it's not possible to create or
- # change the CWD, the original CWD will be used. The original CWD is
- # available from support.SAVEDCWD.
- with support.temp_cwd(TESTCWD, quiet=True):
- main()
+ main_in_temp_cwd()
diff --git a/Lib/test/revocation.crl b/Lib/test/revocation.crl
new file mode 100644
index 0000000000..6d89b08ebe
--- /dev/null
+++ b/Lib/test/revocation.crl
@@ -0,0 +1,11 @@
+-----BEGIN X509 CRL-----
+MIIBpjCBjwIBATANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJYWTEmMCQGA1UE
+CgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNVBAMMDW91ci1j
+YS1zZXJ2ZXIXDTEzMTEyMTE3MDg0N1oXDTIzMDkzMDE3MDg0N1qgDjAMMAoGA1Ud
+FAQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQCNJXC2mVKauEeN3LlQ3ZtM5gkH3ExH
++i4bmJjtJn497WwvvoIeUdrmVXgJQR93RtV37hZwN0SXMLlNmUZPH4rHhihayw4m
+unCzVj/OhCCY7/TPjKuJ1O/0XhaLBpBVjQN7R/1ujoRKbSia/CD3vcn7Fqxzw7LK
+fSRCKRGTj1CZiuxrphtFchwALXSiFDy9mr2ZKhImcyq1PydfgEzU78APpOkMQsIC
+UNJ/cf3c9emzf+dUtcMEcejQ3mynBo4eIGg1EW42bz4q4hSjzQlKcBV0muw5qXhc
+HOxH2iTFhQ7SrvVuK/dM14rYM4B5mSX3nRC1kNmXpS9j3wJDhuwmjHed
+-----END X509 CRL-----
diff --git a/Lib/test/script_helper.py b/Lib/test/script_helper.py
index ab201649e5..4d5c1f120a 100644
--- a/Lib/test/script_helper.py
+++ b/Lib/test/script_helper.py
@@ -12,13 +12,22 @@ import contextlib
import shutil
import zipfile
-from imp import source_from_cache
+from importlib.util import source_from_cache
from test.support import make_legacy_pyc, strip_python_stderr, temp_dir
# Executing the interpreter in a subprocess
def _assert_python(expected_success, *args, **env_vars):
- cmd_line = [sys.executable]
- if not env_vars:
+ if '__isolated' in env_vars:
+ isolated = env_vars.pop('__isolated')
+ else:
+ isolated = not env_vars
+ cmd_line = [sys.executable, '-X', 'faulthandler']
+ if isolated:
+ # isolated mode: ignore Python environment variables, ignore user
+ # site-packages, and don't add the current directory to sys.path
+ cmd_line.append('-I')
+ elif not env_vars:
+ # ignore Python environment variables
cmd_line.append('-E')
# Need to preserve the original environment, for in-place testing of
# shared library builds.
@@ -39,7 +48,7 @@ def _assert_python(expected_success, *args, **env_vars):
p.stdout.close()
p.stderr.close()
rc = p.returncode
- err = strip_python_stderr(err)
+ err = strip_python_stderr(err)
if (rc and expected_success) or (not rc and not expected_success):
raise AssertionError(
"Process return code is %d, "
@@ -49,18 +58,32 @@ def _assert_python(expected_success, *args, **env_vars):
def assert_python_ok(*args, **env_vars):
"""
Assert that running the interpreter with `args` and optional environment
- variables `env_vars` is ok and return a (return code, stdout, stderr) tuple.
+ variables `env_vars` succeeds (rc == 0) and return a (return code, stdout,
+ stderr) tuple.
+
+ If the __cleanenv keyword is set, env_vars is used a fresh environment.
+
+ Python is started in isolated mode (command line option -I),
+ except if the __isolated keyword is set to False.
"""
return _assert_python(True, *args, **env_vars)
def assert_python_failure(*args, **env_vars):
"""
Assert that running the interpreter with `args` and optional environment
- variables `env_vars` fails and return a (return code, stdout, stderr) tuple.
+ variables `env_vars` fails (rc != 0) and return a (return code, stdout,
+ stderr) tuple.
+
+ See assert_python_ok() for more options.
"""
return _assert_python(False, *args, **env_vars)
def spawn_python(*args, **kw):
+ """Run a Python subprocess with the given arguments.
+
+ kw is extra keyword args to pass to subprocess.Popen. Returns a Popen
+ object.
+ """
cmd_line = [sys.executable, '-E']
cmd_line.extend(args)
return subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
@@ -68,6 +91,7 @@ def spawn_python(*args, **kw):
**kw)
def kill_python(p):
+ """Run the given Popen process until completion and return stdout."""
p.stdin.close()
data = p.stdout.read()
p.stdout.close()
diff --git a/Lib/test/sortperf.py b/Lib/test/sortperf.py
index af7c0b4bd1..90722f7abc 100644
--- a/Lib/test/sortperf.py
+++ b/Lib/test/sortperf.py
@@ -22,7 +22,7 @@ def randfloats(n):
fn = os.path.join(td, "rr%06d" % n)
try:
fp = open(fn, "rb")
- except IOError:
+ except OSError:
r = random.random
result = [r() for i in range(n)]
try:
@@ -35,9 +35,9 @@ def randfloats(n):
if fp:
try:
os.unlink(fn)
- except os.error:
+ except OSError:
pass
- except IOError as msg:
+ except OSError as msg:
print("can't write", fn, ":", msg)
else:
result = marshal.load(fp)
diff --git a/Lib/test/ssl_servers.py b/Lib/test/ssl_servers.py
index 8686153a17..759b3f487e 100644
--- a/Lib/test/ssl_servers.py
+++ b/Lib/test/ssl_servers.py
@@ -35,7 +35,7 @@ class HTTPSServer(_HTTPServer):
try:
sock, addr = self.socket.accept()
sslconn = self.context.wrap_socket(sock, server_side=True)
- except socket.error as e:
+ except OSError as e:
# socket errors are silenced by the caller, print them here
if support.verbose:
sys.stderr.write("Got an error:\n%s\n" % e)
@@ -147,9 +147,11 @@ class HTTPSServerThread(threading.Thread):
self.server.shutdown()
-def make_https_server(case, certfile=CERTFILE, host=HOST, handler_class=None):
- # we assume the certfile contains both private key and certificate
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+def make_https_server(case, *, context=None, certfile=CERTFILE,
+ host=HOST, handler_class=None):
+ if context is None:
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ # We assume the certfile contains both private key and certificate
context.load_cert_chain(certfile)
server = HTTPSServerThread(context, host, handler_class)
flag = threading.Event()
diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py
index 1d70385d05..30784d4ec5 100644
--- a/Lib/test/string_tests.py
+++ b/Lib/test/string_tests.py
@@ -328,13 +328,26 @@ class BaseTest:
self.checkraises(TypeError, 'hello', 'upper', 42)
def test_expandtabs(self):
- self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi', 'expandtabs')
- self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi', 'expandtabs', 8)
- self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi', 'expandtabs', 4)
- self.checkequal('abc\r\nab def\ng hi', 'abc\r\nab\tdef\ng\thi', 'expandtabs', 4)
- self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi', 'expandtabs')
- self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi', 'expandtabs', 8)
- self.checkequal('abc\r\nab\r\ndef\ng\r\nhi', 'abc\r\nab\r\ndef\ng\r\nhi', 'expandtabs', 4)
+ self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi',
+ 'expandtabs')
+ self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi',
+ 'expandtabs', 8)
+ self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi',
+ 'expandtabs', 4)
+ self.checkequal('abc\r\nab def\ng hi', 'abc\r\nab\tdef\ng\thi',
+ 'expandtabs')
+ self.checkequal('abc\r\nab def\ng hi', 'abc\r\nab\tdef\ng\thi',
+ 'expandtabs', 8)
+ self.checkequal('abc\r\nab def\ng hi', 'abc\r\nab\tdef\ng\thi',
+ 'expandtabs', 4)
+ self.checkequal('abc\r\nab\r\ndef\ng\r\nhi', 'abc\r\nab\r\ndef\ng\r\nhi',
+ 'expandtabs', 4)
+ # check keyword args
+ self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi',
+ 'expandtabs', tabsize=8)
+ self.checkequal('abc\rab def\ng hi', 'abc\rab\tdef\ng\thi',
+ 'expandtabs', tabsize=4)
+
self.checkequal(' a\n b', ' \ta\n\tb', 'expandtabs', 1)
self.checkraises(TypeError, 'hello', 'expandtabs', 42, 42)
diff --git a/Lib/test/subprocessdata/fd_status.py b/Lib/test/subprocessdata/fd_status.py
index 1f61e13a34..d12bd95abe 100644
--- a/Lib/test/subprocessdata/fd_status.py
+++ b/Lib/test/subprocessdata/fd_status.py
@@ -1,17 +1,27 @@
"""When called as a script, print a comma-separated list of the open
-file descriptors on stdout."""
+file descriptors on stdout.
+
+Usage:
+fd_stats.py: check all file descriptors
+fd_status.py fd1 fd2 ...: check only specified file descriptors
+"""
import errno
import os
-
-try:
- _MAXFD = os.sysconf("SC_OPEN_MAX")
-except:
- _MAXFD = 256
+import stat
+import sys
if __name__ == "__main__":
fds = []
- for fd in range(0, _MAXFD):
+ if len(sys.argv) == 1:
+ try:
+ _MAXFD = os.sysconf("SC_OPEN_MAX")
+ except:
+ _MAXFD = 256
+ test_fds = range(0, _MAXFD)
+ else:
+ test_fds = map(int, sys.argv[1:])
+ for fd in test_fds:
try:
st = os.fstat(fd)
except OSError as e:
@@ -19,6 +29,6 @@ if __name__ == "__main__":
continue
raise
# Ignore Solaris door files
- if st.st_mode & 0xF000 != 0xd000:
+ if not stat.S_ISDOOR(st.st_mode):
fds.append(fd)
print(','.join(map(str, fds)))
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 233b13adcd..a242a4727d 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -15,10 +15,10 @@ import shutil
import warnings
import unittest
import importlib
+import importlib.util
import collections.abc
import re
import subprocess
-import imp
import time
import sysconfig
import fnmatch
@@ -57,26 +57,49 @@ try:
except ImportError:
lzma = None
+try:
+ import resource
+except ImportError:
+ resource = None
+
__all__ = [
- "Error", "TestFailed", "ResourceDenied", "import_module", "verbose",
- "use_resources", "max_memuse", "record_original_stdout",
- "get_original_stdout", "unload", "unlink", "rmtree", "forget",
+ # globals
+ "PIPE_MAX_SIZE", "verbose", "max_memuse", "use_resources", "failfast",
+ # exceptions
+ "Error", "TestFailed", "ResourceDenied",
+ # imports
+ "import_module", "import_fresh_module", "CleanImport",
+ # modules
+ "unload", "forget",
+ # io
+ "record_original_stdout", "get_original_stdout", "captured_stdout",
+ "captured_stdin", "captured_stderr",
+ # filesystem
+ "TESTFN", "SAVEDCWD", "unlink", "rmtree", "temp_cwd", "findfile",
+ "create_empty_file", "can_symlink", "fs_is_case_insensitive",
+ # unittest
"is_resource_enabled", "requires", "requires_freebsd_version",
- "requires_linux_version", "requires_mac_ver", "find_unused_port",
- "bind_port", "IPV6_ENABLED", "is_jython", "TESTFN", "HOST", "SAVEDCWD",
- "temp_cwd", "findfile", "create_empty_file", "sortdict",
- "check_syntax_error", "open_urlresource", "check_warnings", "CleanImport",
- "EnvironmentVarGuard", "TransientResource", "captured_stdout",
- "captured_stdin", "captured_stderr", "time_out", "socket_peer_reset",
- "ioerror_peer_reset", "run_with_locale", 'temp_umask',
- "transient_internet", "set_memlimit", "bigmemtest", "bigaddrspacetest",
- "BasicTestRunner", "run_unittest", "run_doctest", "threading_setup",
- "threading_cleanup", "reap_children", "cpython_only", "check_impl_detail",
- "get_attribute", "swap_item", "swap_attr", "requires_IEEE_754",
- "TestHandler", "Matcher", "can_symlink", "skip_unless_symlink",
- "skip_unless_xattr", "import_fresh_module", "requires_zlib",
- "PIPE_MAX_SIZE", "failfast", "anticipate_failure", "run_with_tz",
- "requires_gzip", "requires_bz2", "requires_lzma", "suppress_crash_popup",
+ "requires_linux_version", "requires_mac_ver", "check_syntax_error",
+ "TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset",
+ "transient_internet", "BasicTestRunner", "run_unittest", "run_doctest",
+ "skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma",
+ "bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
+ "requires_IEEE_754", "skip_unless_xattr", "requires_zlib",
+ "anticipate_failure",
+ # sys
+ "is_jython", "check_impl_detail",
+ # network
+ "HOST", "IPV6_ENABLED", "find_unused_port", "bind_port", "open_urlresource",
+ # processes
+ 'temp_umask', "reap_children",
+ # logging
+ "TestHandler",
+ # threads
+ "threading_setup", "threading_cleanup",
+ # miscellaneous
+ "check_warnings", "EnvironmentVarGuard", "run_with_locale", "swap_item",
+ "swap_attr", "Matcher", "set_memlimit", "SuppressCrashReport", "sortdict",
+ "run_with_tz",
]
class Error(Exception):
@@ -98,7 +121,8 @@ def _ignore_deprecated_imports(ignore=True):
"""Context manager to suppress package and module deprecation
warnings when importing them.
- If ignore is False, this context manager has no effect."""
+ If ignore is False, this context manager has no effect.
+ """
if ignore:
with warnings.catch_warnings():
warnings.filterwarnings("ignore", ".+ (module|package)",
@@ -108,16 +132,21 @@ def _ignore_deprecated_imports(ignore=True):
yield
-def import_module(name, deprecated=False):
+def import_module(name, deprecated=False, *, required_on=()):
"""Import and return the module to be tested, raising SkipTest if
it is not available.
If deprecated is True, any module or package deprecation messages
- will be suppressed."""
+ will be suppressed. If a module is required on a platform but optional for
+ others, set required_on to an iterable of platform prefixes which will be
+ compared against sys.platform.
+ """
with _ignore_deprecated_imports(deprecated):
try:
return importlib.import_module(name)
except ImportError as msg:
+ if sys.platform.startswith(tuple(required_on)):
+ raise
raise unittest.SkipTest(str(msg))
@@ -303,25 +332,20 @@ else:
def unlink(filename):
try:
_unlink(filename)
- except OSError as error:
- # The filename need not exist.
- if error.errno not in (errno.ENOENT, errno.ENOTDIR):
- raise
+ except (FileNotFoundError, NotADirectoryError):
+ pass
def rmdir(dirname):
try:
_rmdir(dirname)
- except OSError as error:
- # The directory need not exist.
- if error.errno != errno.ENOENT:
- raise
+ except FileNotFoundError:
+ pass
def rmtree(path):
try:
_rmtree(path)
- except OSError as error:
- if error.errno != errno.ENOENT:
- raise
+ except FileNotFoundError:
+ pass
def make_legacy_pyc(source):
"""Move a PEP 3147 pyc/pyo file to its legacy pyc/pyo location.
@@ -333,7 +357,7 @@ def make_legacy_pyc(source):
does not need to exist, however the PEP 3147 pyc file must exist.
:return: The file system path to the legacy pyc file.
"""
- pyc_file = imp.cache_from_source(source)
+ pyc_file = importlib.util.cache_from_source(source)
up_one = os.path.dirname(os.path.abspath(source))
legacy_pyc = os.path.join(up_one, source + ('c' if __debug__ else 'o'))
os.rename(pyc_file, legacy_pyc)
@@ -352,8 +376,8 @@ def forget(modname):
# combinations of PEP 3147 and legacy pyc and pyo files.
unlink(source + 'c')
unlink(source + 'o')
- unlink(imp.cache_from_source(source, debug_override=True))
- unlink(imp.cache_from_source(source, debug_override=False))
+ unlink(importlib.util.cache_from_source(source, debug_override=True))
+ unlink(importlib.util.cache_from_source(source, debug_override=False))
# On some platforms, should not run gui test even if it is allowed
# in `use_resources'.
@@ -514,7 +538,7 @@ def find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM):
the SO_REUSEADDR socket option having different semantics on Windows versus
Unix/Linux. On Unix, you can't have two AF_INET SOCK_STREAM sockets bind,
listen and then accept connections on identical host/ports. An EADDRINUSE
- socket.error will be raised at some point (depending on the platform and
+ OSError will be raised at some point (depending on the platform and
the order bind and listen were called on each socket).
However, on Windows, if SO_REUSEADDR is set on the sockets, no EADDRINUSE
@@ -592,9 +616,9 @@ def _is_ipv6_enabled():
sock = None
try:
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
- sock.bind(('::1', 0))
+ sock.bind((HOSTv6, 0))
return True
- except (socket.error, socket.gaierror):
+ except OSError:
pass
finally:
if sock:
@@ -1181,9 +1205,9 @@ class TransientResource(object):
# Context managers that raise ResourceDenied when various issues
# with the Internet connection manifest themselves as exceptions.
# XXX deprecate these and use transient_internet() instead
-time_out = TransientResource(IOError, errno=errno.ETIMEDOUT)
-socket_peer_reset = TransientResource(socket.error, errno=errno.ECONNRESET)
-ioerror_peer_reset = TransientResource(IOError, errno=errno.ECONNRESET)
+time_out = TransientResource(OSError, errno=errno.ETIMEDOUT)
+socket_peer_reset = TransientResource(OSError, errno=errno.ECONNRESET)
+ioerror_peer_reset = TransientResource(OSError, errno=errno.ECONNRESET)
@contextlib.contextmanager
@@ -1229,17 +1253,17 @@ def transient_internet(resource_name, *, timeout=30.0, errnos=()):
if timeout is not None:
socket.setdefaulttimeout(timeout)
yield
- except IOError as err:
+ except OSError as err:
# urllib can wrap original socket errors multiple times (!), we must
# unwrap to get at the original error.
while True:
a = err.args
- if len(a) >= 1 and isinstance(a[0], IOError):
+ if len(a) >= 1 and isinstance(a[0], OSError):
err = a[0]
# The error can also be wrapped as args[1]:
# except socket.error as msg:
- # raise IOError('socket error', msg).with_traceback(sys.exc_info()[2])
- elif len(a) >= 2 and isinstance(a[1], IOError):
+ # raise OSError('socket error', msg).with_traceback(sys.exc_info()[2])
+ elif len(a) >= 2 and isinstance(a[1], OSError):
err = a[1]
else:
break
@@ -1697,9 +1721,18 @@ def run_unittest(*classes):
#=======================================================================
# Check for the presence of docstrings.
-HAVE_DOCSTRINGS = (check_impl_detail(cpython=False) or
- sys.platform == 'win32' or
- sysconfig.get_config_var('WITH_DOC_STRINGS'))
+# Rather than trying to enumerate all the cases where docstrings may be
+# disabled, we just check for that directly
+
+def _check_docstrings():
+ """Just used to check if docstrings are enabled"""
+
+MISSING_C_DOCSTRINGS = (check_impl_detail() and
+ sys.platform != 'win32' and
+ not sysconfig.get_config_var('WITH_DOC_STRINGS'))
+
+HAVE_DOCSTRINGS = (_check_docstrings.__doc__ is not None and
+ not MISSING_C_DOCSTRINGS)
requires_docstrings = unittest.skipUnless(HAVE_DOCSTRINGS,
"test requires docstrings")
@@ -1774,12 +1807,12 @@ def threading_setup():
def threading_cleanup(*original_values):
if not _thread:
return
- _MAX_COUNT = 10
+ _MAX_COUNT = 100
for count in range(_MAX_COUNT):
values = _thread._count(), threading._dangling
if values == original_values:
break
- time.sleep(0.1)
+ time.sleep(0.01)
gc_collect()
# XXX print a warning in case of failure?
@@ -1881,7 +1914,7 @@ def strip_python_stderr(stderr):
This will typically be run on the result of the communicate() method
of a subprocess.Popen object.
"""
- stderr = re.sub(br"\[\d+ refs\]\r?\n?", b"", stderr).strip()
+ stderr = re.sub(br"\[\d+ refs, \d+ blocks\]\r?\n?", b"", stderr).strip()
return stderr
def args_from_interpreter_flags():
@@ -2012,27 +2045,81 @@ def skip_unless_xattr(test):
return test if ok else unittest.skip(msg)(test)
-if sys.platform.startswith('win'):
- @contextlib.contextmanager
- def suppress_crash_popup():
- """Disable Windows Error Reporting dialogs using SetErrorMode."""
- # see http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621%28v=vs.85%29.aspx
- # GetErrorMode is not available on Windows XP and Windows Server 2003,
- # but SetErrorMode returns the previous value, so we can use that
- import ctypes
- k32 = ctypes.windll.kernel32
- SEM_NOGPFAULTERRORBOX = 0x02
- old_error_mode = k32.SetErrorMode(SEM_NOGPFAULTERRORBOX)
- k32.SetErrorMode(old_error_mode | SEM_NOGPFAULTERRORBOX)
- try:
- yield
- finally:
- k32.SetErrorMode(old_error_mode)
-else:
- # this is a no-op for other platforms
- @contextlib.contextmanager
- def suppress_crash_popup():
- yield
+def fs_is_case_insensitive(directory):
+ """Detects if the file system for the specified directory is case-insensitive."""
+ base_fp, base_path = tempfile.mkstemp(dir=directory)
+ case_path = base_path.upper()
+ if case_path == base_path:
+ case_path = base_path.lower()
+ try:
+ return os.path.samefile(base_path, case_path)
+ except FileNotFoundError:
+ return False
+ finally:
+ os.unlink(base_path)
+
+
+class SuppressCrashReport:
+ """Try to prevent a crash report from popping up.
+
+ On Windows, don't display the Windows Error Reporting dialog. On UNIX,
+ disable the creation of coredump file.
+ """
+ old_value = None
+
+ def __enter__(self):
+ """On Windows, disable Windows Error Reporting dialogs using
+ SetErrorMode.
+
+ On UNIX, try to save the previous core file size limit, then set
+ soft limit to 0.
+ """
+ if sys.platform.startswith('win'):
+ # see http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx
+ # GetErrorMode is not available on Windows XP and Windows Server 2003,
+ # but SetErrorMode returns the previous value, so we can use that
+ import ctypes
+ self._k32 = ctypes.windll.kernel32
+ SEM_NOGPFAULTERRORBOX = 0x02
+ self.old_value = self._k32.SetErrorMode(SEM_NOGPFAULTERRORBOX)
+ self._k32.SetErrorMode(self.old_value | SEM_NOGPFAULTERRORBOX)
+ else:
+ if resource is not None:
+ try:
+ self.old_value = resource.getrlimit(resource.RLIMIT_CORE)
+ resource.setrlimit(resource.RLIMIT_CORE,
+ (0, self.old_value[1]))
+ except (ValueError, OSError):
+ pass
+ if sys.platform == 'darwin':
+ # Check if the 'Crash Reporter' on OSX was configured
+ # in 'Developer' mode and warn that it will get triggered
+ # when it is.
+ #
+ # This assumes that this context manager is used in tests
+ # that might trigger the next manager.
+ value = subprocess.Popen(['/usr/bin/defaults', 'read',
+ 'com.apple.CrashReporter', 'DialogType'],
+ stdout=subprocess.PIPE).communicate()[0]
+ if value.strip() == b'developer':
+ print("this test triggers the Crash Reporter, "
+ "that is intentional", end='', flush=True)
+
+ return self
+
+ def __exit__(self, *ignore_exc):
+ """Restore Windows ErrorMode or core file behavior to initial value."""
+ if self.old_value is None:
+ return
+
+ if sys.platform.startswith('win'):
+ self._k32.SetErrorMode(self.old_value)
+ else:
+ if resource is not None:
+ try:
+ resource.setrlimit(resource.RLIMIT_CORE, self.old_value)
+ except (ValueError, OSError):
+ pass
def patch(test_instance, object_to_patch, attr_name, new_value):
@@ -2067,3 +2154,22 @@ def patch(test_instance, object_to_patch, attr_name, new_value):
# actually override the attribute
setattr(object_to_patch, attr_name, new_value)
+
+
+def run_in_subinterp(code):
+ """
+ Run code in a subinterpreter. Raise unittest.SkipTest if the tracemalloc
+ module is enabled.
+ """
+ # Issue #10915, #15751: PyGILState_*() functions don't work with
+ # sub-interpreters, the tracemalloc module uses these functions internally
+ try:
+ import tracemalloc
+ except ImportError:
+ pass
+ else:
+ if tracemalloc.is_tracing():
+ raise unittest.SkipTest("run_in_subinterp() cannot be used "
+ "if tracemalloc module is tracing "
+ "memory allocations")
+ return _testcapi.run_in_subinterp(code)
diff --git a/Lib/test/test___all__.py b/Lib/test/test___all__.py
index 608ec01f14..8cc285f70a 100644
--- a/Lib/test/test___all__.py
+++ b/Lib/test/test___all__.py
@@ -29,17 +29,20 @@ class AllTest(unittest.TestCase):
if not hasattr(sys.modules[modname], "__all__"):
raise NoAll(modname)
names = {}
- try:
- exec("from %s import *" % modname, names)
- except Exception as e:
- # Include the module name in the exception string
- self.fail("__all__ failure in {}: {}: {}".format(
- modname, e.__class__.__name__, e))
- if "__builtins__" in names:
- del names["__builtins__"]
- keys = set(names)
- all = set(sys.modules[modname].__all__)
- self.assertEqual(keys, all)
+ with self.subTest(module=modname):
+ try:
+ exec("from %s import *" % modname, names)
+ except Exception as e:
+ # Include the module name in the exception string
+ self.fail("__all__ failure in {}: {}: {}".format(
+ modname, e.__class__.__name__, e))
+ if "__builtins__" in names:
+ del names["__builtins__"]
+ keys = set(names)
+ all_list = sys.modules[modname].__all__
+ all_set = set(all_list)
+ self.assertCountEqual(all_set, all_list, "in module {}".format(modname))
+ self.assertEqual(keys, all_set, "in module {}".format(modname))
def walk_modules(self, basedir, modpath):
for fn in sorted(os.listdir(basedir)):
@@ -110,8 +113,5 @@ class AllTest(unittest.TestCase):
print('Following modules failed to be imported:', failed_imports)
-def test_main():
- support.run_unittest(AllTest)
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py
new file mode 100644
index 0000000000..0152e9d94d
--- /dev/null
+++ b/Lib/test/test__opcode.py
@@ -0,0 +1,23 @@
+import dis
+from test.support import run_unittest, import_module
+import unittest
+
+_opcode = import_module("_opcode")
+
+class OpcodeTests(unittest.TestCase):
+
+ def test_stack_effect(self):
+ self.assertEqual(_opcode.stack_effect(dis.opmap['POP_TOP']), -1)
+ self.assertEqual(_opcode.stack_effect(dis.opmap['DUP_TOP_TWO']), 2)
+ self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
+ self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 1), -1)
+ self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 3), -2)
+ self.assertRaises(ValueError, _opcode.stack_effect, 30000)
+ self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['BUILD_SLICE'])
+ self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['POP_TOP'], 0)
+
+def test_main():
+ run_unittest(OpcodeTests)
+
+if __name__ == "__main__":
+ test_main()
diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py
index d4d7556518..93f9dae215 100644
--- a/Lib/test/test_abc.py
+++ b/Lib/test/test_abc.py
@@ -68,6 +68,19 @@ class TestLegacyAPI(unittest.TestCase):
class TestABC(unittest.TestCase):
+ def test_ABC_helper(self):
+ # create an ABC using the helper class and perform basic checks
+ class C(abc.ABC):
+ @classmethod
+ @abc.abstractmethod
+ def foo(cls): return cls.__name__
+ self.assertEqual(type(C), abc.ABCMeta)
+ self.assertRaises(TypeError, C)
+ class D(C):
+ @classmethod
+ def foo(cls): return super().foo()
+ self.assertEqual(D.foo(), 'D')
+
def test_abstractmethod_basics(self):
@abc.abstractmethod
def foo(self): pass
@@ -288,7 +301,10 @@ class TestABC(unittest.TestCase):
b = B()
self.assertFalse(isinstance(b, A))
self.assertFalse(isinstance(b, (A,)))
+ token_old = abc.get_cache_token()
A.register(B)
+ token_new = abc.get_cache_token()
+ self.assertNotEqual(token_old, token_new)
self.assertTrue(isinstance(b, A))
self.assertTrue(isinstance(b, (A,)))
diff --git a/Lib/test/test_aifc.py b/Lib/test/test_aifc.py
index b77354bb07..041b23688c 100644
--- a/Lib/test/test_aifc.py
+++ b/Lib/test/test_aifc.py
@@ -1,6 +1,7 @@
from test.support import findfile, TESTFN, unlink
import unittest
from test import audiotests
+from audioop import byteswap
import os
import io
import sys
@@ -8,10 +9,17 @@ import struct
import aifc
-class AifcPCM8Test(audiotests.AudioWriteTests,
- audiotests.AudioTestsWithSourceFile,
- unittest.TestCase):
+class AifcTest(audiotests.AudioWriteTests,
+ audiotests.AudioTestsWithSourceFile):
module = aifc
+ close_fd = True
+ test_unseekable_read = None
+ test_unseekable_write = None
+ test_unseekable_incompleted_write = None
+ test_unseekable_overflowed_write = None
+
+
+class AifcPCM8Test(AifcTest, unittest.TestCase):
sndfilename = 'pluck-pcm8.aiff'
sndfilenframes = 3307
nchannels = 2
@@ -26,13 +34,9 @@ class AifcPCM8Test(audiotests.AudioWriteTests,
11FA 3EFB BCFC 66FF CF04 4309 C10E 5112 EE17 8216 7F14 8012 \
490E 520D EF0F CE0F E40C 630A 080A 2B0B 510E 8B11 B60E 440A \
""")
- close_fd = True
-class AifcPCM16Test(audiotests.AudioWriteTests,
- audiotests.AudioTestsWithSourceFile,
- unittest.TestCase):
- module = aifc
+class AifcPCM16Test(AifcTest, unittest.TestCase):
sndfilename = 'pluck-pcm16.aiff'
sndfilenframes = 3307
nchannels = 2
@@ -49,13 +53,9 @@ class AifcPCM16Test(audiotests.AudioWriteTests,
EEE21753 82071665 7FFF1443 8004128F 49A20EAF 52BB0DBA EFB40F60 CE3C0FBF \
E4B30CEC 63430A5C 08C80A20 2BBB0B08 514A0E43 8BCF1139 B6F60EEB 44120A5E \
""")
- close_fd = True
-class AifcPCM24Test(audiotests.AudioWriteTests,
- audiotests.AudioTestsWithSourceFile,
- unittest.TestCase):
- module = aifc
+class AifcPCM24Test(AifcTest, unittest.TestCase):
sndfilename = 'pluck-pcm24.aiff'
sndfilenframes = 3307
nchannels = 2
@@ -78,13 +78,9 @@ class AifcPCM24Test(audiotests.AudioWriteTests,
E4B49C0CEA2D 6344A80A5A7C 08C8FE0A1FFE 2BB9860B0A0E \
51486F0E44E1 8BCC64113B05 B6F4EC0EEB36 4413170A5B48 \
""")
- close_fd = True
-class AifcPCM32Test(audiotests.AudioWriteTests,
- audiotests.AudioTestsWithSourceFile,
- unittest.TestCase):
- module = aifc
+class AifcPCM32Test(AifcTest, unittest.TestCase):
sndfilename = 'pluck-pcm32.aiff'
sndfilenframes = 3307
nchannels = 2
@@ -107,13 +103,9 @@ class AifcPCM32Test(audiotests.AudioWriteTests,
E4B49CC00CEA2D90 6344A8800A5A7CA0 08C8FE800A1FFEE0 2BB986C00B0A0E00 \
51486F800E44E190 8BCC6480113B0580 B6F4EC000EEB3630 441317800A5B48A0 \
""")
- close_fd = True
-class AifcULAWTest(audiotests.AudioWriteTests,
- audiotests.AudioTestsWithSourceFile,
- unittest.TestCase):
- module = aifc
+class AifcULAWTest(AifcTest, unittest.TestCase):
sndfilename = 'pluck-ulaw.aifc'
sndfilenframes = 3307
nchannels = 2
@@ -131,14 +123,10 @@ class AifcULAWTest(audiotests.AudioWriteTests,
E5040CBC 617C0A3C 08BC0A3C 2C7C0B3C 517C0E3C 8A8410FC B6840EBC 457C0A3C \
""")
if sys.byteorder != 'big':
- frames = audiotests.byteswap2(frames)
- close_fd = True
+ frames = byteswap(frames, 2)
-class AifcALAWTest(audiotests.AudioWriteTests,
- audiotests.AudioTestsWithSourceFile,
- unittest.TestCase):
- module = aifc
+class AifcALAWTest(AifcTest, unittest.TestCase):
sndfilename = 'pluck-alaw.aifc'
sndfilenframes = 3307
nchannels = 2
@@ -156,8 +144,7 @@ class AifcALAWTest(audiotests.AudioWriteTests,
E4800CC0 62000A40 08C00A40 2B000B40 52000E40 8A001180 B6000EC0 46000A40 \
""")
if sys.byteorder != 'big':
- frames = audiotests.byteswap2(frames)
- close_fd = True
+ frames = byteswap(frames, 2)
class AifcMiscTest(audiotests.AudioTests, unittest.TestCase):
@@ -166,6 +153,21 @@ class AifcMiscTest(audiotests.AudioTests, unittest.TestCase):
#This file contains chunk types aifc doesn't recognize.
self.f = aifc.open(findfile('Sine-1000Hz-300ms.aif'))
+ def test_params_added(self):
+ f = self.f = aifc.open(TESTFN, 'wb')
+ f.aiff()
+ f.setparams((1, 1, 1, 1, b'NONE', b''))
+ f.close()
+
+ f = self.f = aifc.open(TESTFN, 'rb')
+ params = f.getparams()
+ self.assertEqual(params.nchannels, f.getnchannels())
+ self.assertEqual(params.sampwidth, f.getsampwidth())
+ self.assertEqual(params.framerate, f.getframerate())
+ self.assertEqual(params.nframes, f.getnframes())
+ self.assertEqual(params.comptype, f.getcomptype())
+ self.assertEqual(params.compname, f.getcompname())
+
def test_write_header_comptype_sampwidth(self):
for comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
fout = aifc.open(io.BytesIO(), 'wb')
@@ -369,12 +371,14 @@ class AIFCLowLevelTest(unittest.TestCase):
def test_write_aiff_by_extension(self):
sampwidth = 2
- fout = self.fout = aifc.open(TESTFN + '.aiff', 'wb')
+ filename = TESTFN + '.aiff'
+ fout = self.fout = aifc.open(filename, 'wb')
+ self.addCleanup(unlink, filename)
fout.setparams((1, sampwidth, 1, 1, b'ULAW', b''))
frames = b'\x00' * fout.getnchannels() * sampwidth
fout.writeframes(frames)
fout.close()
- f = self.f = aifc.open(TESTFN + '.aiff', 'rb')
+ f = self.f = aifc.open(filename, 'rb')
self.assertEqual(f.getcomptype(), b'NONE')
f.close()
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index c06c940bf2..c10c5909bf 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -14,6 +14,7 @@ import argparse
from io import StringIO
from test import support
+from unittest import mock
class StdIOBuffer(StringIO):
pass
@@ -1421,6 +1422,19 @@ class TestFileTypeRepr(TestCase):
type = argparse.FileType('wb', 1)
self.assertEqual("FileType('wb', 1)", repr(type))
+ def test_r_latin(self):
+ type = argparse.FileType('r', encoding='latin_1')
+ self.assertEqual("FileType('r', encoding='latin_1')", repr(type))
+
+ def test_w_big5_ignore(self):
+ type = argparse.FileType('w', encoding='big5', errors='ignore')
+ self.assertEqual("FileType('w', encoding='big5', errors='ignore')",
+ repr(type))
+
+ def test_r_1_replace(self):
+ type = argparse.FileType('r', 1, errors='replace')
+ self.assertEqual("FileType('r', 1, errors='replace')", repr(type))
+
class RFile(object):
seen = {}
@@ -1557,6 +1571,24 @@ class TestFileTypeWB(TempDirMixin, ParserTestCase):
]
+class TestFileTypeOpenArgs(TestCase):
+ """Test that open (the builtin) is correctly called"""
+
+ def test_open_args(self):
+ FT = argparse.FileType
+ cases = [
+ (FT('rb'), ('rb', -1, None, None)),
+ (FT('w', 1), ('w', 1, None, None)),
+ (FT('w', errors='replace'), ('w', -1, None, 'replace')),
+ (FT('wb', encoding='big5'), ('wb', -1, 'big5', None)),
+ (FT('w', 0, 'l1', 'strict'), ('w', 0, 'l1', 'strict')),
+ ]
+ with mock.patch('builtins.open') as m:
+ for type, args in cases:
+ type('foo')
+ m.assert_called_with('foo', *args)
+
+
class TestTypeCallable(ParserTestCase):
"""Test some callables as option/argument types"""
@@ -4327,7 +4359,7 @@ class TestOptionalsHelpVersionActions(TestCase):
def test_version_format(self):
parser = ErrorRaisingArgumentParser(prog='PPP')
parser.add_argument('-v', '--version', action='version', version='%(prog)s 3.5')
- msg = self._get_error(parser.parse_args, ['-v']).stderr
+ msg = self._get_error(parser.parse_args, ['-v']).stdout
self.assertEqual('PPP 3.5\n', msg)
def test_version_no_help(self):
@@ -4340,7 +4372,7 @@ class TestOptionalsHelpVersionActions(TestCase):
def test_version_action(self):
parser = ErrorRaisingArgumentParser(prog='XXX')
parser.add_argument('-V', action='version', version='%(prog)s 3.7')
- msg = self._get_error(parser.parse_args, ['-V']).stderr
+ msg = self._get_error(parser.parse_args, ['-V']).stdout
self.assertEqual('XXX 3.7\n', msg)
def test_no_help(self):
diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py
index cccb705b0d..409d586088 100755
--- a/Lib/test/test_array.py
+++ b/Lib/test/test_array.py
@@ -354,12 +354,12 @@ class BaseTest:
support.unlink(support.TESTFN)
def test_fromfile_ioerror(self):
- # Issue #5395: Check if fromfile raises a proper IOError
+ # Issue #5395: Check if fromfile raises a proper OSError
# instead of EOFError.
a = array.array(self.typecode)
f = open(support.TESTFN, 'wb')
try:
- self.assertRaises(IOError, a.fromfile, f, len(self.example))
+ self.assertRaises(OSError, a.fromfile, f, len(self.example))
finally:
f.close()
support.unlink(support.TESTFN)
@@ -1027,6 +1027,18 @@ class BaseTest:
basesize = support.calcvobjsize('Pn2Pi')
support.check_sizeof(self, a, basesize)
+ def test_initialize_with_unicode(self):
+ if self.typecode != 'u':
+ with self.assertRaises(TypeError) as cm:
+ a = array.array(self.typecode, 'foo')
+ self.assertIn("cannot use a str", str(cm.exception))
+ with self.assertRaises(TypeError) as cm:
+ a = array.array(self.typecode, array.array('u', 'foo'))
+ self.assertIn("cannot use a unicode array", str(cm.exception))
+ else:
+ a = array.array(self.typecode, "foo")
+ a = array.array(self.typecode, array.array('u', 'foo'))
+
class StringTest(BaseTest):
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index 63528885e9..e69422a292 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -180,20 +180,36 @@ eval_tests = [
class AST_Tests(unittest.TestCase):
- def _assertTrueorder(self, ast_node, parent_pos):
+ def _assertTrueorder(self, ast_node, parent_pos, reverse_check = False):
+ def should_reverse_check(parent, child):
+ # In some situations, the children of nodes occur before
+ # their parents, for example in a.b.c, a occurs before b
+ # but a is a child of b.
+ if isinstance(parent, ast.Call):
+ if parent.func == child:
+ return True
+ if isinstance(parent, (ast.Attribute, ast.Subscript)):
+ return True
+ return False
+
if not isinstance(ast_node, ast.AST) or ast_node._fields is None:
return
if isinstance(ast_node, (ast.expr, ast.stmt, ast.excepthandler)):
node_pos = (ast_node.lineno, ast_node.col_offset)
- self.assertTrue(node_pos >= parent_pos)
+ if reverse_check:
+ self.assertTrue(node_pos <= parent_pos)
+ else:
+ self.assertTrue(node_pos >= parent_pos)
parent_pos = (ast_node.lineno, ast_node.col_offset)
for name in ast_node._fields:
value = getattr(ast_node, name)
if isinstance(value, list):
for child in value:
- self._assertTrueorder(child, parent_pos)
+ self._assertTrueorder(child, parent_pos,
+ should_reverse_check(ast_node, child))
elif value is not None:
- self._assertTrueorder(value, parent_pos)
+ self._assertTrueorder(value, parent_pos,
+ should_reverse_check(ast_node, value))
def test_AST_objects(self):
x = ast.AST()
@@ -262,14 +278,14 @@ class AST_Tests(unittest.TestCase):
def test_arguments(self):
x = ast.arguments()
- self.assertEqual(x._fields, ('args', 'vararg', 'varargannotation',
- 'kwonlyargs', 'kwarg', 'kwargannotation',
- 'defaults', 'kw_defaults'))
+ self.assertEqual(x._fields, ('args', 'vararg',
+ 'kwonlyargs', 'kw_defaults',
+ 'kwarg', 'defaults'))
with self.assertRaises(AttributeError):
x.vararg
- x = ast.arguments(*range(1, 9))
+ x = ast.arguments(*range(1, 7))
self.assertEqual(x.vararg, 2)
def test_field_attr_writable(self):
@@ -439,7 +455,7 @@ class ASTHelpers_Test(unittest.TestCase):
"lineno=1, col_offset=0), args=[Name(id='eggs', ctx=Load(), "
"lineno=1, col_offset=5), Str(s='and cheese', lineno=1, "
"col_offset=11)], keywords=[], starargs=None, kwargs=None, "
- "lineno=1, col_offset=0), lineno=1, col_offset=0)])"
+ "lineno=1, col_offset=4), lineno=1, col_offset=0)])"
)
def test_copy_location(self):
@@ -460,7 +476,7 @@ class ASTHelpers_Test(unittest.TestCase):
"Module(body=[Expr(value=Call(func=Name(id='write', ctx=Load(), "
"lineno=1, col_offset=0), args=[Str(s='spam', lineno=1, "
"col_offset=6)], keywords=[], starargs=None, kwargs=None, "
- "lineno=1, col_offset=0), lineno=1, col_offset=0), "
+ "lineno=1, col_offset=5), lineno=1, col_offset=0), "
"Expr(value=Call(func=Name(id='spam', ctx=Load(), lineno=1, "
"col_offset=0), args=[Str(s='eggs', lineno=1, col_offset=0)], "
"keywords=[], starargs=None, kwargs=None, lineno=1, "
@@ -560,8 +576,8 @@ class ASTValidatorTests(unittest.TestCase):
self.mod(m, "must have Load context", "eval")
def _check_arguments(self, fac, check):
- def arguments(args=None, vararg=None, varargannotation=None,
- kwonlyargs=None, kwarg=None, kwargannotation=None,
+ def arguments(args=None, vararg=None,
+ kwonlyargs=None, kwarg=None,
defaults=None, kw_defaults=None):
if args is None:
args = []
@@ -571,20 +587,12 @@ class ASTValidatorTests(unittest.TestCase):
defaults = []
if kw_defaults is None:
kw_defaults = []
- args = ast.arguments(args, vararg, varargannotation, kwonlyargs,
- kwarg, kwargannotation, defaults, kw_defaults)
+ args = ast.arguments(args, vararg, kwonlyargs, kw_defaults,
+ kwarg, defaults)
return fac(args)
args = [ast.arg("x", ast.Name("x", ast.Store()))]
check(arguments(args=args), "must have Load context")
- check(arguments(varargannotation=ast.Num(3)),
- "varargannotation but no vararg")
- check(arguments(varargannotation=ast.Name("x", ast.Store()), vararg="x"),
- "must have Load context")
check(arguments(kwonlyargs=args), "must have Load context")
- check(arguments(kwargannotation=ast.Num(42)),
- "kwargannotation but no kwarg")
- check(arguments(kwargannotation=ast.Name("x", ast.Store()),
- kwarg="x"), "must have Load context")
check(arguments(defaults=[ast.Num(3)]),
"more positional defaults than args")
check(arguments(kw_defaults=[ast.Num(4)]),
@@ -599,7 +607,7 @@ class ASTValidatorTests(unittest.TestCase):
"must have Load context")
def test_funcdef(self):
- a = ast.arguments([], None, None, [], None, None, [], [])
+ a = ast.arguments([], None, [], [], None, [])
f = ast.FunctionDef("x", a, [], [], None)
self.stmt(f, "empty body on FunctionDef")
f = ast.FunctionDef("x", a, [ast.Pass()], [ast.Name("x", ast.Store())],
@@ -770,7 +778,7 @@ class ASTValidatorTests(unittest.TestCase):
self.expr(u, "must have Load context")
def test_lambda(self):
- a = ast.arguments([], None, None, [], None, None, [], [])
+ a = ast.arguments([], None, [], [], None, [])
self.expr(ast.Lambda(a, ast.Name("x", ast.Store())),
"must have Load context")
def fac(args):
@@ -928,6 +936,9 @@ class ASTValidatorTests(unittest.TestCase):
def test_tuple(self):
self._sequence(ast.Tuple)
+ def test_nameconstant(self):
+ self.expr(ast.NameConstant(4), "singleton must be True, False, or None")
+
def test_stdlib_validates(self):
stdlib = os.path.dirname(ast.__file__)
tests = [fn for fn in os.listdir(stdlib) if fn.endswith(".py")]
@@ -936,13 +947,10 @@ class ASTValidatorTests(unittest.TestCase):
fn = os.path.join(stdlib, module)
with open(fn, "r", encoding="utf-8") as fp:
source = fp.read()
- mod = ast.parse(source)
+ mod = ast.parse(source, fn)
compile(mod, fn, "exec")
-def test_main():
- support.run_unittest(AST_Tests, ASTHelpers_Test, ASTValidatorTests)
-
def main():
if __name__ != '__main__':
return
@@ -955,20 +963,20 @@ def main():
print("]")
print("main()")
raise SystemExit
- test_main()
+ unittest.main()
#### EVERYTHING BELOW IS GENERATED #####
exec_results = [
-('Module', [('Expr', (1, 0), ('Name', (1, 0), 'None', ('Load',)))]),
-('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, None, [], None, None, [], []), [('Pass', (1, 9))], [], None)]),
-('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [('arg', 'a', None)], None, None, [], None, None, [], []), [('Pass', (1, 10))], [], None)]),
-('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [('arg', 'a', None)], None, None, [], None, None, [('Num', (1, 8), 0)], []), [('Pass', (1, 12))], [], None)]),
-('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], 'args', None, [], None, None, [], []), [('Pass', (1, 14))], [], None)]),
-('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, None, [], 'kwargs', None, [], []), [('Pass', (1, 17))], [], None)]),
-('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [('arg', 'a', None), ('arg', 'b', None), ('arg', 'c', None), ('arg', 'd', None), ('arg', 'e', None)], 'args', None, [], 'kwargs', None, [('Num', (1, 11), 1), ('Name', (1, 16), 'None', ('Load',)), ('List', (1, 24), [], ('Load',)), ('Dict', (1, 30), [], [])], []), [('Pass', (1, 52))], [], None)]),
+('Module', [('Expr', (1, 0), ('NameConstant', (1, 0), None))]),
+('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, [], [], None, []), [('Pass', (1, 9))], [], None)]),
+('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [('arg', (1, 6), 'a', None)], None, [], [], None, []), [('Pass', (1, 10))], [], None)]),
+('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [('arg', (1, 6), 'a', None)], None, [], [], None, [('Num', (1, 8), 0)]), [('Pass', (1, 12))], [], None)]),
+('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], ('arg', (1, 7), 'args', None), [], [], None, []), [('Pass', (1, 14))], [], None)]),
+('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, [], [], ('arg', (1, 8), 'kwargs', None), []), [('Pass', (1, 17))], [], None)]),
+('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [('arg', (1, 6), 'a', None), ('arg', (1, 9), 'b', None), ('arg', (1, 14), 'c', None), ('arg', (1, 22), 'd', None), ('arg', (1, 28), 'e', None)], ('arg', (1, 35), 'args', None), [], [], ('arg', (1, 43), 'kwargs', None), [('Num', (1, 11), 1), ('NameConstant', (1, 16), None), ('List', (1, 24), [], ('Load',)), ('Dict', (1, 30), [], [])]), [('Pass', (1, 52))], [], None)]),
('Module', [('ClassDef', (1, 0), 'C', [], [], None, None, [('Pass', (1, 8))], [])]),
('Module', [('ClassDef', (1, 0), 'C', [('Name', (1, 8), 'object', ('Load',))], [], None, None, [('Pass', (1, 17))], [])]),
-('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, None, [], None, None, [], []), [('Return', (1, 8), ('Num', (1, 15), 1))], [], None)]),
+('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, [], [], None, []), [('Return', (1, 8), ('Num', (1, 15), 1))], [], None)]),
('Module', [('Delete', (1, 0), [('Name', (1, 4), 'v', ('Del',))])]),
('Module', [('Assign', (1, 0), [('Name', (1, 0), 'v', ('Store',))], ('Num', (1, 4), 1))]),
('Module', [('AugAssign', (1, 0), ('Name', (1, 0), 'v', ('Store',)), ('Add',), ('Num', (1, 5), 1))]),
@@ -977,7 +985,7 @@ exec_results = [
('Module', [('If', (1, 0), ('Name', (1, 3), 'v', ('Load',)), [('Pass', (1, 5))], [])]),
('Module', [('With', (1, 0), [('withitem', ('Name', (1, 5), 'x', ('Load',)), ('Name', (1, 10), 'y', ('Store',)))], [('Pass', (1, 13))])]),
('Module', [('With', (1, 0), [('withitem', ('Name', (1, 5), 'x', ('Load',)), ('Name', (1, 10), 'y', ('Store',))), ('withitem', ('Name', (1, 13), 'z', ('Load',)), ('Name', (1, 18), 'q', ('Store',)))], [('Pass', (1, 21))])]),
-('Module', [('Raise', (1, 0), ('Call', (1, 6), ('Name', (1, 6), 'Exception', ('Load',)), [('Str', (1, 16), 'string')], [], None, None), None)]),
+('Module', [('Raise', (1, 0), ('Call', (1, 15), ('Name', (1, 6), 'Exception', ('Load',)), [('Str', (1, 16), 'string')], [], None, None), None)]),
('Module', [('Try', (1, 0), [('Pass', (2, 2))], [('ExceptHandler', (3, 0), ('Name', (3, 7), 'Exception', ('Load',)), None, [('Pass', (4, 2))])], [], [])]),
('Module', [('Try', (1, 0), [('Pass', (2, 2))], [], [], [('Pass', (4, 2))])]),
('Module', [('Assert', (1, 0), ('Name', (1, 7), 'v', ('Load',)), None)]),
@@ -1002,29 +1010,29 @@ single_results = [
('Interactive', [('Expr', (1, 0), ('BinOp', (1, 0), ('Num', (1, 0), 1), ('Add',), ('Num', (1, 2), 2)))]),
]
eval_results = [
-('Expression', ('Name', (1, 0), 'None', ('Load',))),
+('Expression', ('NameConstant', (1, 0), None)),
('Expression', ('BoolOp', (1, 0), ('And',), [('Name', (1, 0), 'a', ('Load',)), ('Name', (1, 6), 'b', ('Load',))])),
('Expression', ('BinOp', (1, 0), ('Name', (1, 0), 'a', ('Load',)), ('Add',), ('Name', (1, 4), 'b', ('Load',)))),
('Expression', ('UnaryOp', (1, 0), ('Not',), ('Name', (1, 4), 'v', ('Load',)))),
-('Expression', ('Lambda', (1, 0), ('arguments', [], None, None, [], None, None, [], []), ('Name', (1, 7), 'None', ('Load',)))),
+('Expression', ('Lambda', (1, 0), ('arguments', [], None, [], [], None, []), ('NameConstant', (1, 7), None))),
('Expression', ('Dict', (1, 0), [('Num', (1, 2), 1)], [('Num', (1, 4), 2)])),
('Expression', ('Dict', (1, 0), [], [])),
-('Expression', ('Set', (1, 0), [('Name', (1, 1), 'None', ('Load',))])),
+('Expression', ('Set', (1, 0), [('NameConstant', (1, 1), None)])),
('Expression', ('Dict', (1, 0), [('Num', (2, 6), 1)], [('Num', (4, 10), 2)])),
('Expression', ('ListComp', (1, 1), ('Name', (1, 1), 'a', ('Load',)), [('comprehension', ('Name', (1, 7), 'b', ('Store',)), ('Name', (1, 12), 'c', ('Load',)), [('Name', (1, 17), 'd', ('Load',))])])),
('Expression', ('GeneratorExp', (1, 1), ('Name', (1, 1), 'a', ('Load',)), [('comprehension', ('Name', (1, 7), 'b', ('Store',)), ('Name', (1, 12), 'c', ('Load',)), [('Name', (1, 17), 'd', ('Load',))])])),
('Expression', ('Compare', (1, 0), ('Num', (1, 0), 1), [('Lt',), ('Lt',)], [('Num', (1, 4), 2), ('Num', (1, 8), 3)])),
-('Expression', ('Call', (1, 0), ('Name', (1, 0), 'f', ('Load',)), [('Num', (1, 2), 1), ('Num', (1, 4), 2)], [('keyword', 'c', ('Num', (1, 8), 3))], ('Name', (1, 11), 'd', ('Load',)), ('Name', (1, 15), 'e', ('Load',)))),
+('Expression', ('Call', (1, 1), ('Name', (1, 0), 'f', ('Load',)), [('Num', (1, 2), 1), ('Num', (1, 4), 2)], [('keyword', 'c', ('Num', (1, 8), 3))], ('Name', (1, 11), 'd', ('Load',)), ('Name', (1, 15), 'e', ('Load',)))),
('Expression', ('Num', (1, 0), 10)),
('Expression', ('Str', (1, 0), 'string')),
-('Expression', ('Attribute', (1, 0), ('Name', (1, 0), 'a', ('Load',)), 'b', ('Load',))),
-('Expression', ('Subscript', (1, 0), ('Name', (1, 0), 'a', ('Load',)), ('Slice', ('Name', (1, 2), 'b', ('Load',)), ('Name', (1, 4), 'c', ('Load',)), None), ('Load',))),
+('Expression', ('Attribute', (1, 2), ('Name', (1, 0), 'a', ('Load',)), 'b', ('Load',))),
+('Expression', ('Subscript', (1, 2), ('Name', (1, 0), 'a', ('Load',)), ('Slice', ('Name', (1, 2), 'b', ('Load',)), ('Name', (1, 4), 'c', ('Load',)), None), ('Load',))),
('Expression', ('Name', (1, 0), 'v', ('Load',))),
('Expression', ('List', (1, 0), [('Num', (1, 1), 1), ('Num', (1, 3), 2), ('Num', (1, 5), 3)], ('Load',))),
('Expression', ('List', (1, 0), [], ('Load',))),
('Expression', ('Tuple', (1, 0), [('Num', (1, 0), 1), ('Num', (1, 2), 2), ('Num', (1, 4), 3)], ('Load',))),
('Expression', ('Tuple', (1, 1), [('Num', (1, 1), 1), ('Num', (1, 3), 2), ('Num', (1, 5), 3)], ('Load',))),
('Expression', ('Tuple', (1, 0), [], ('Load',))),
-('Expression', ('Call', (1, 0), ('Attribute', (1, 0), ('Attribute', (1, 0), ('Attribute', (1, 0), ('Name', (1, 0), 'a', ('Load',)), 'b', ('Load',)), 'c', ('Load',)), 'd', ('Load',)), [('Subscript', (1, 8), ('Attribute', (1, 8), ('Name', (1, 8), 'a', ('Load',)), 'b', ('Load',)), ('Slice', ('Num', (1, 12), 1), ('Num', (1, 14), 2), None), ('Load',))], [], None, None)),
+('Expression', ('Call', (1, 7), ('Attribute', (1, 6), ('Attribute', (1, 4), ('Attribute', (1, 2), ('Name', (1, 0), 'a', ('Load',)), 'b', ('Load',)), 'c', ('Load',)), 'd', ('Load',)), [('Subscript', (1, 12), ('Attribute', (1, 10), ('Name', (1, 8), 'a', ('Load',)), 'b', ('Load',)), ('Slice', ('Num', (1, 12), 1), ('Num', (1, 14), 2), None), ('Load',))], [], None, None)),
]
main()
diff --git a/Lib/test/test_asynchat.py b/Lib/test/test_asynchat.py
index c79fe6f613..f93a52d8c9 100644
--- a/Lib/test/test_asynchat.py
+++ b/Lib/test/test_asynchat.py
@@ -15,6 +15,7 @@ except ImportError:
HOST = support.HOST
SERVER_QUIT = b'QUIT\n'
+TIMEOUT = 3.0
if threading:
class echo_server(threading.Thread):
@@ -123,7 +124,9 @@ class TestAsynchat(unittest.TestCase):
c.push(b"I'm not dead yet!" + term)
c.push(SERVER_QUIT)
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
- s.join()
+ s.join(timeout=TIMEOUT)
+ if s.is_alive():
+ self.fail("join() timed out")
self.assertEqual(c.contents, [b"hello world", b"I'm not dead yet!"])
@@ -154,7 +157,9 @@ class TestAsynchat(unittest.TestCase):
c.push(data)
c.push(SERVER_QUIT)
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
- s.join()
+ s.join(timeout=TIMEOUT)
+ if s.is_alive():
+ self.fail("join() timed out")
self.assertEqual(c.contents, [data[:termlen]])
@@ -174,7 +179,9 @@ class TestAsynchat(unittest.TestCase):
c.push(data)
c.push(SERVER_QUIT)
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
- s.join()
+ s.join(timeout=TIMEOUT)
+ if s.is_alive():
+ self.fail("join() timed out")
self.assertEqual(c.contents, [])
self.assertEqual(c.buffer, data)
@@ -186,7 +193,9 @@ class TestAsynchat(unittest.TestCase):
p = asynchat.simple_producer(data+SERVER_QUIT, buffer_size=8)
c.push_with_producer(p)
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
- s.join()
+ s.join(timeout=TIMEOUT)
+ if s.is_alive():
+ self.fail("join() timed out")
self.assertEqual(c.contents, [b"hello world", b"I'm not dead yet!"])
@@ -196,7 +205,9 @@ class TestAsynchat(unittest.TestCase):
data = b"hello world\nI'm not dead yet!\n"
c.push_with_producer(data+SERVER_QUIT)
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
- s.join()
+ s.join(timeout=TIMEOUT)
+ if s.is_alive():
+ self.fail("join() timed out")
self.assertEqual(c.contents, [b"hello world", b"I'm not dead yet!"])
@@ -207,7 +218,9 @@ class TestAsynchat(unittest.TestCase):
c.push(b"hello world\n\nI'm not dead yet!\n")
c.push(SERVER_QUIT)
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
- s.join()
+ s.join(timeout=TIMEOUT)
+ if s.is_alive():
+ self.fail("join() timed out")
self.assertEqual(c.contents,
[b"hello world", b"", b"I'm not dead yet!"])
@@ -226,7 +239,9 @@ class TestAsynchat(unittest.TestCase):
# where the server echoes all of its data before we can check that it
# got any down below.
s.start_resend_event.set()
- s.join()
+ s.join(timeout=TIMEOUT)
+ if s.is_alive():
+ self.fail("join() timed out")
self.assertEqual(c.contents, [])
# the server might have been able to send a byte or two back, but this
@@ -268,9 +283,5 @@ class TestFifo(unittest.TestCase):
self.assertEqual(f.pop(), (0, None))
-def test_main(verbose=None):
- support.run_unittest(TestAsynchat, TestAsynchat_WithPoll,
- TestHelperFunctions, TestFifo)
-
if __name__ == "__main__":
- test_main(verbose=True)
+ unittest.main()
diff --git a/Lib/test/test_asyncio/__init__.py b/Lib/test/test_asyncio/__init__.py
new file mode 100644
index 0000000000..23ce5e8059
--- /dev/null
+++ b/Lib/test/test_asyncio/__init__.py
@@ -0,0 +1,31 @@
+import os
+import sys
+import unittest
+from test.support import run_unittest, import_module
+
+# Skip tests if we don't have threading.
+import_module('threading')
+# Skip tests if we don't have concurrent.futures.
+import_module('concurrent.futures')
+
+
+def suite():
+ tests_file = os.path.join(os.path.dirname(__file__), 'tests.txt')
+ with open(tests_file) as fp:
+ test_names = fp.read().splitlines()
+ tests = unittest.TestSuite()
+ loader = unittest.TestLoader()
+ for test_name in test_names:
+ mod_name = 'test.' + test_name
+ try:
+ __import__(mod_name)
+ except unittest.SkipTest:
+ pass
+ else:
+ mod = sys.modules[mod_name]
+ tests.addTests(loader.loadTestsFromModule(mod))
+ return tests
+
+
+def test_main():
+ run_unittest(suite())
diff --git a/Lib/test/test_asyncio/__main__.py b/Lib/test/test_asyncio/__main__.py
new file mode 100644
index 0000000000..b549492038
--- /dev/null
+++ b/Lib/test/test_asyncio/__main__.py
@@ -0,0 +1,5 @@
+from . import test_main
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/test/test_asyncio/echo.py b/Lib/test/test_asyncio/echo.py
new file mode 100644
index 0000000000..006364bb00
--- /dev/null
+++ b/Lib/test/test_asyncio/echo.py
@@ -0,0 +1,8 @@
+import os
+
+if __name__ == '__main__':
+ while True:
+ buf = os.read(0, 1024)
+ if not buf:
+ break
+ os.write(1, buf)
diff --git a/Lib/test/test_asyncio/echo2.py b/Lib/test/test_asyncio/echo2.py
new file mode 100644
index 0000000000..e83ca09fb7
--- /dev/null
+++ b/Lib/test/test_asyncio/echo2.py
@@ -0,0 +1,6 @@
+import os
+
+if __name__ == '__main__':
+ buf = os.read(0, 1024)
+ os.write(1, b'OUT:'+buf)
+ os.write(2, b'ERR:'+buf)
diff --git a/Lib/test/test_asyncio/echo3.py b/Lib/test/test_asyncio/echo3.py
new file mode 100644
index 0000000000..064496736b
--- /dev/null
+++ b/Lib/test/test_asyncio/echo3.py
@@ -0,0 +1,11 @@
+import os
+
+if __name__ == '__main__':
+ while True:
+ buf = os.read(0, 1024)
+ if not buf:
+ break
+ try:
+ os.write(1, b'OUT:'+buf)
+ except OSError as ex:
+ os.write(2, b'ERR:' + ex.__class__.__name__.encode('ascii'))
diff --git a/Lib/test/test_asyncio/sample.crt b/Lib/test/test_asyncio/sample.crt
new file mode 100644
index 0000000000..6a1e3f3c2e
--- /dev/null
+++ b/Lib/test/test_asyncio/sample.crt
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICMzCCAZwCCQDFl4ys0fU7iTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQGEwJV
+UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuLUZyYW5jaXNjbzEi
+MCAGA1UECgwZUHl0aG9uIFNvZnR3YXJlIEZvbmRhdGlvbjAeFw0xMzAzMTgyMDA3
+MjhaFw0yMzAzMTYyMDA3MjhaMF4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
+Zm9ybmlhMRYwFAYDVQQHDA1TYW4tRnJhbmNpc2NvMSIwIAYDVQQKDBlQeXRob24g
+U29mdHdhcmUgRm9uZGF0aW9uMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCn
+t3s+J7L0xP/YdAQOacpPi9phlrzKZhcXL3XMu2LCUg2fNJpx/47Vc5TZSaO11uO7
+gdwVz3Z7Q2epAgwo59JLffLt5fia8+a/SlPweI/j4+wcIIIiqusnLfpqR8cIAavg
+Z06cLYCDvb9wMlheIvSJY12skc1nnphWS2YJ0Xm6uQIDAQABMA0GCSqGSIb3DQEB
+BQUAA4GBAE9PknG6pv72+5z/gsDGYy8sK5UNkbWSNr4i4e5lxVsF03+/M71H+3AB
+MxVX4+A+Vlk2fmU+BrdHIIUE0r1dDcO3josQ9hc9OJpp5VLSQFP8VeuJCmzYPp9I
+I8WbW93cnXnChTrYQVdgVoFdv7GE9YgU7NYkrGIM0nZl1/f/bHPB
+-----END CERTIFICATE-----
diff --git a/Lib/test/test_asyncio/sample.key b/Lib/test/test_asyncio/sample.key
new file mode 100644
index 0000000000..edfea8dcab
--- /dev/null
+++ b/Lib/test/test_asyncio/sample.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQCnt3s+J7L0xP/YdAQOacpPi9phlrzKZhcXL3XMu2LCUg2fNJpx
+/47Vc5TZSaO11uO7gdwVz3Z7Q2epAgwo59JLffLt5fia8+a/SlPweI/j4+wcIIIi
+qusnLfpqR8cIAavgZ06cLYCDvb9wMlheIvSJY12skc1nnphWS2YJ0Xm6uQIDAQAB
+AoGABfm8k19Yue3W68BecKEGS0VBV57GRTPT+MiBGvVGNIQ15gk6w3sGfMZsdD1y
+bsUkQgcDb2d/4i5poBTpl/+Cd41V+c20IC/sSl5X1IEreHMKSLhy/uyjyiyfXlP1
+iXhToFCgLWwENWc8LzfUV8vuAV5WG6oL9bnudWzZxeqx8V0CQQDR7xwVj6LN70Eb
+DUhSKLkusmFw5Gk9NJ/7wZ4eHg4B8c9KNVvSlLCLhcsVTQXuqYeFpOqytI45SneP
+lr0vrvsDAkEAzITYiXu6ox5huDCG7imX2W9CAYuX638urLxBqBXMS7GqBzojD6RL
+21Q8oPwJWJquERa3HDScq1deiQbM9uKIkwJBAIa1PLslGN216Xv3UPHPScyKD/aF
+ynXIv+OnANPoiyp6RH4ksQ/18zcEGiVH8EeNpvV9tlAHhb+DZibQHgNr74sCQQC0
+zhToplu/bVKSlUQUNO0rqrI9z30FErDewKeCw5KSsIRSU1E/uM3fHr9iyq4wiL6u
+GNjUtKZ0y46lsT9uW6LFAkB5eqeEQnshAdr3X5GykWHJ8DDGBXPPn6Rce1NX4RSq
+V9khG2z1bFyfo+hMqpYnF2k32hVq3E54RS8YYnwBsVof
+-----END RSA PRIVATE KEY-----
diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py
new file mode 100644
index 0000000000..96f29750c7
--- /dev/null
+++ b/Lib/test/test_asyncio/test_base_events.py
@@ -0,0 +1,684 @@
+"""Tests for base_events.py"""
+
+import errno
+import logging
+import socket
+import time
+import unittest
+import unittest.mock
+from test.support import find_unused_port, IPV6_ENABLED
+
+from asyncio import base_events
+from asyncio import constants
+from asyncio import events
+from asyncio import futures
+from asyncio import protocols
+from asyncio import tasks
+from asyncio import test_utils
+
+
+class BaseEventLoopTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = base_events.BaseEventLoop()
+ self.loop._selector = unittest.mock.Mock()
+ events.set_event_loop(None)
+
+ def test_not_implemented(self):
+ m = unittest.mock.Mock()
+ self.assertRaises(
+ NotImplementedError,
+ self.loop._make_socket_transport, m, m)
+ self.assertRaises(
+ NotImplementedError,
+ self.loop._make_ssl_transport, m, m, m, m)
+ self.assertRaises(
+ NotImplementedError,
+ self.loop._make_datagram_transport, m, m)
+ self.assertRaises(
+ NotImplementedError, self.loop._process_events, [])
+ self.assertRaises(
+ NotImplementedError, self.loop._write_to_self)
+ self.assertRaises(
+ NotImplementedError, self.loop._read_from_self)
+ self.assertRaises(
+ NotImplementedError,
+ self.loop._make_read_pipe_transport, m, m)
+ self.assertRaises(
+ NotImplementedError,
+ self.loop._make_write_pipe_transport, m, m)
+ gen = self.loop._make_subprocess_transport(m, m, m, m, m, m, m)
+ self.assertRaises(NotImplementedError, next, iter(gen))
+
+ def test__add_callback_handle(self):
+ h = events.Handle(lambda: False, ())
+
+ self.loop._add_callback(h)
+ self.assertFalse(self.loop._scheduled)
+ self.assertIn(h, self.loop._ready)
+
+ def test__add_callback_timer(self):
+ h = events.TimerHandle(time.monotonic()+10, lambda: False, ())
+
+ self.loop._add_callback(h)
+ self.assertIn(h, self.loop._scheduled)
+
+ def test__add_callback_cancelled_handle(self):
+ h = events.Handle(lambda: False, ())
+ h.cancel()
+
+ self.loop._add_callback(h)
+ self.assertFalse(self.loop._scheduled)
+ self.assertFalse(self.loop._ready)
+
+ def test_set_default_executor(self):
+ executor = unittest.mock.Mock()
+ self.loop.set_default_executor(executor)
+ self.assertIs(executor, self.loop._default_executor)
+
+ def test_getnameinfo(self):
+ sockaddr = unittest.mock.Mock()
+ self.loop.run_in_executor = unittest.mock.Mock()
+ self.loop.getnameinfo(sockaddr)
+ self.assertEqual(
+ (None, socket.getnameinfo, sockaddr, 0),
+ self.loop.run_in_executor.call_args[0])
+
+ def test_call_soon(self):
+ def cb():
+ pass
+
+ h = self.loop.call_soon(cb)
+ self.assertEqual(h._callback, cb)
+ self.assertIsInstance(h, events.Handle)
+ self.assertIn(h, self.loop._ready)
+
+ def test_call_later(self):
+ def cb():
+ pass
+
+ h = self.loop.call_later(10.0, cb)
+ self.assertIsInstance(h, events.TimerHandle)
+ self.assertIn(h, self.loop._scheduled)
+ self.assertNotIn(h, self.loop._ready)
+
+ def test_call_later_negative_delays(self):
+ calls = []
+
+ def cb(arg):
+ calls.append(arg)
+
+ self.loop._process_events = unittest.mock.Mock()
+ self.loop.call_later(-1, cb, 'a')
+ self.loop.call_later(-2, cb, 'b')
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(calls, ['b', 'a'])
+
+ def test_time_and_call_at(self):
+ def cb():
+ self.loop.stop()
+
+ self.loop._process_events = unittest.mock.Mock()
+ when = self.loop.time() + 0.1
+ self.loop.call_at(when, cb)
+ t0 = self.loop.time()
+ self.loop.run_forever()
+ t1 = self.loop.time()
+ self.assertTrue(0.09 <= t1-t0 <= 0.9, t1-t0)
+
+ def test_run_once_in_executor_handle(self):
+ def cb():
+ pass
+
+ self.assertRaises(
+ AssertionError, self.loop.run_in_executor,
+ None, events.Handle(cb, ()), ('',))
+ self.assertRaises(
+ AssertionError, self.loop.run_in_executor,
+ None, events.TimerHandle(10, cb, ()))
+
+ def test_run_once_in_executor_cancelled(self):
+ def cb():
+ pass
+ h = events.Handle(cb, ())
+ h.cancel()
+
+ f = self.loop.run_in_executor(None, h)
+ self.assertIsInstance(f, futures.Future)
+ self.assertTrue(f.done())
+ self.assertIsNone(f.result())
+
+ def test_run_once_in_executor_plain(self):
+ def cb():
+ pass
+ h = events.Handle(cb, ())
+ f = futures.Future(loop=self.loop)
+ executor = unittest.mock.Mock()
+ executor.submit.return_value = f
+
+ self.loop.set_default_executor(executor)
+
+ res = self.loop.run_in_executor(None, h)
+ self.assertIs(f, res)
+
+ executor = unittest.mock.Mock()
+ executor.submit.return_value = f
+ res = self.loop.run_in_executor(executor, h)
+ self.assertIs(f, res)
+ self.assertTrue(executor.submit.called)
+
+ f.cancel() # Don't complain about abandoned Future.
+
+ def test__run_once(self):
+ h1 = events.TimerHandle(time.monotonic() + 5.0, lambda: True, ())
+ h2 = events.TimerHandle(time.monotonic() + 10.0, lambda: True, ())
+
+ h1.cancel()
+
+ self.loop._process_events = unittest.mock.Mock()
+ self.loop._scheduled.append(h1)
+ self.loop._scheduled.append(h2)
+ self.loop._run_once()
+
+ t = self.loop._selector.select.call_args[0][0]
+ self.assertTrue(9.5 < t < 10.5, t)
+ self.assertEqual([h2], self.loop._scheduled)
+ self.assertTrue(self.loop._process_events.called)
+
+ @unittest.mock.patch('asyncio.base_events.time')
+ @unittest.mock.patch('asyncio.base_events.logger')
+ def test__run_once_logging(self, m_logging, m_time):
+ # Log to INFO level if timeout > 1.0 sec.
+ idx = -1
+ data = [10.0, 10.0, 12.0, 13.0]
+
+ def monotonic():
+ nonlocal data, idx
+ idx += 1
+ return data[idx]
+
+ m_time.monotonic = monotonic
+ m_logging.INFO = logging.INFO
+ m_logging.DEBUG = logging.DEBUG
+
+ self.loop._scheduled.append(
+ events.TimerHandle(11.0, lambda: True, ()))
+ self.loop._process_events = unittest.mock.Mock()
+ self.loop._run_once()
+ self.assertEqual(logging.INFO, m_logging.log.call_args[0][0])
+
+ idx = -1
+ data = [10.0, 10.0, 10.3, 13.0]
+ self.loop._scheduled = [events.TimerHandle(11.0, lambda:True, ())]
+ self.loop._run_once()
+ self.assertEqual(logging.DEBUG, m_logging.log.call_args[0][0])
+
+ def test__run_once_schedule_handle(self):
+ handle = None
+ processed = False
+
+ def cb(loop):
+ nonlocal processed, handle
+ processed = True
+ handle = loop.call_soon(lambda: True)
+
+ h = events.TimerHandle(time.monotonic() - 1, cb, (self.loop,))
+
+ self.loop._process_events = unittest.mock.Mock()
+ self.loop._scheduled.append(h)
+ self.loop._run_once()
+
+ self.assertTrue(processed)
+ self.assertEqual([handle], list(self.loop._ready))
+
+ def test_run_until_complete_type_error(self):
+ self.assertRaises(
+ TypeError, self.loop.run_until_complete, 'blah')
+
+
+class MyProto(protocols.Protocol):
+ done = None
+
+ def __init__(self, create_future=False):
+ self.state = 'INITIAL'
+ self.nbytes = 0
+ if create_future:
+ self.done = futures.Future()
+
+ def connection_made(self, transport):
+ self.transport = transport
+ assert self.state == 'INITIAL', self.state
+ self.state = 'CONNECTED'
+ transport.write(b'GET / HTTP/1.0\r\nHost: example.com\r\n\r\n')
+
+ def data_received(self, data):
+ assert self.state == 'CONNECTED', self.state
+ self.nbytes += len(data)
+
+ def eof_received(self):
+ assert self.state == 'CONNECTED', self.state
+ self.state = 'EOF'
+
+ def connection_lost(self, exc):
+ assert self.state in ('CONNECTED', 'EOF'), self.state
+ self.state = 'CLOSED'
+ if self.done:
+ self.done.set_result(None)
+
+
+class MyDatagramProto(protocols.DatagramProtocol):
+ done = None
+
+ def __init__(self, create_future=False):
+ self.state = 'INITIAL'
+ self.nbytes = 0
+ if create_future:
+ self.done = futures.Future()
+
+ def connection_made(self, transport):
+ self.transport = transport
+ assert self.state == 'INITIAL', self.state
+ self.state = 'INITIALIZED'
+
+ def datagram_received(self, data, addr):
+ assert self.state == 'INITIALIZED', self.state
+ self.nbytes += len(data)
+
+ def error_received(self, exc):
+ assert self.state == 'INITIALIZED', self.state
+
+ def connection_lost(self, exc):
+ assert self.state == 'INITIALIZED', self.state
+ self.state = 'CLOSED'
+ if self.done:
+ self.done.set_result(None)
+
+
+class BaseEventLoopWithSelectorTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = events.new_event_loop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+ @unittest.mock.patch('asyncio.base_events.socket')
+ def test_create_connection_multiple_errors(self, m_socket):
+
+ class MyProto(protocols.Protocol):
+ pass
+
+ @tasks.coroutine
+ def getaddrinfo(*args, **kw):
+ yield from []
+ return [(2, 1, 6, '', ('107.6.106.82', 80)),
+ (2, 1, 6, '', ('107.6.106.82', 80))]
+
+ def getaddrinfo_task(*args, **kwds):
+ return tasks.Task(getaddrinfo(*args, **kwds), loop=self.loop)
+
+ idx = -1
+ errors = ['err1', 'err2']
+
+ def _socket(*args, **kw):
+ nonlocal idx, errors
+ idx += 1
+ raise OSError(errors[idx])
+
+ m_socket.socket = _socket
+
+ self.loop.getaddrinfo = getaddrinfo_task
+
+ coro = self.loop.create_connection(MyProto, 'example.com', 80)
+ with self.assertRaises(OSError) as cm:
+ self.loop.run_until_complete(coro)
+
+ self.assertEqual(str(cm.exception), 'Multiple exceptions: err1, err2')
+
+ def test_create_connection_host_port_sock(self):
+ coro = self.loop.create_connection(
+ MyProto, 'example.com', 80, sock=object())
+ self.assertRaises(ValueError, self.loop.run_until_complete, coro)
+
+ def test_create_connection_no_host_port_sock(self):
+ coro = self.loop.create_connection(MyProto)
+ self.assertRaises(ValueError, self.loop.run_until_complete, coro)
+
+ def test_create_connection_no_getaddrinfo(self):
+ @tasks.coroutine
+ def getaddrinfo(*args, **kw):
+ yield from []
+
+ def getaddrinfo_task(*args, **kwds):
+ return tasks.Task(getaddrinfo(*args, **kwds), loop=self.loop)
+
+ self.loop.getaddrinfo = getaddrinfo_task
+ coro = self.loop.create_connection(MyProto, 'example.com', 80)
+ self.assertRaises(
+ OSError, self.loop.run_until_complete, coro)
+
+ def test_create_connection_connect_err(self):
+ @tasks.coroutine
+ def getaddrinfo(*args, **kw):
+ yield from []
+ return [(2, 1, 6, '', ('107.6.106.82', 80))]
+
+ def getaddrinfo_task(*args, **kwds):
+ return tasks.Task(getaddrinfo(*args, **kwds), loop=self.loop)
+
+ self.loop.getaddrinfo = getaddrinfo_task
+ self.loop.sock_connect = unittest.mock.Mock()
+ self.loop.sock_connect.side_effect = OSError
+
+ coro = self.loop.create_connection(MyProto, 'example.com', 80)
+ self.assertRaises(
+ OSError, self.loop.run_until_complete, coro)
+
+ def test_create_connection_multiple(self):
+ @tasks.coroutine
+ def getaddrinfo(*args, **kw):
+ return [(2, 1, 6, '', ('0.0.0.1', 80)),
+ (2, 1, 6, '', ('0.0.0.2', 80))]
+
+ def getaddrinfo_task(*args, **kwds):
+ return tasks.Task(getaddrinfo(*args, **kwds), loop=self.loop)
+
+ self.loop.getaddrinfo = getaddrinfo_task
+ self.loop.sock_connect = unittest.mock.Mock()
+ self.loop.sock_connect.side_effect = OSError
+
+ coro = self.loop.create_connection(
+ MyProto, 'example.com', 80, family=socket.AF_INET)
+ with self.assertRaises(OSError):
+ self.loop.run_until_complete(coro)
+
+ @unittest.mock.patch('asyncio.base_events.socket')
+ def test_create_connection_multiple_errors_local_addr(self, m_socket):
+
+ def bind(addr):
+ if addr[0] == '0.0.0.1':
+ err = OSError('Err')
+ err.strerror = 'Err'
+ raise err
+
+ m_socket.socket.return_value.bind = bind
+
+ @tasks.coroutine
+ def getaddrinfo(*args, **kw):
+ return [(2, 1, 6, '', ('0.0.0.1', 80)),
+ (2, 1, 6, '', ('0.0.0.2', 80))]
+
+ def getaddrinfo_task(*args, **kwds):
+ return tasks.Task(getaddrinfo(*args, **kwds), loop=self.loop)
+
+ self.loop.getaddrinfo = getaddrinfo_task
+ self.loop.sock_connect = unittest.mock.Mock()
+ self.loop.sock_connect.side_effect = OSError('Err2')
+
+ coro = self.loop.create_connection(
+ MyProto, 'example.com', 80, family=socket.AF_INET,
+ local_addr=(None, 8080))
+ with self.assertRaises(OSError) as cm:
+ self.loop.run_until_complete(coro)
+
+ self.assertTrue(str(cm.exception).startswith('Multiple exceptions: '))
+ self.assertTrue(m_socket.socket.return_value.close.called)
+
+ def test_create_connection_no_local_addr(self):
+ @tasks.coroutine
+ def getaddrinfo(host, *args, **kw):
+ if host == 'example.com':
+ return [(2, 1, 6, '', ('107.6.106.82', 80)),
+ (2, 1, 6, '', ('107.6.106.82', 80))]
+ else:
+ return []
+
+ def getaddrinfo_task(*args, **kwds):
+ return tasks.Task(getaddrinfo(*args, **kwds), loop=self.loop)
+ self.loop.getaddrinfo = getaddrinfo_task
+
+ coro = self.loop.create_connection(
+ MyProto, 'example.com', 80, family=socket.AF_INET,
+ local_addr=(None, 8080))
+ self.assertRaises(
+ OSError, self.loop.run_until_complete, coro)
+
+ def test_create_connection_ssl_server_hostname_default(self):
+ self.loop.getaddrinfo = unittest.mock.Mock()
+
+ def mock_getaddrinfo(*args, **kwds):
+ f = futures.Future(loop=self.loop)
+ f.set_result([(socket.AF_INET, socket.SOCK_STREAM,
+ socket.SOL_TCP, '', ('1.2.3.4', 80))])
+ return f
+
+ self.loop.getaddrinfo.side_effect = mock_getaddrinfo
+ self.loop.sock_connect = unittest.mock.Mock()
+ self.loop.sock_connect.return_value = ()
+ self.loop._make_ssl_transport = unittest.mock.Mock()
+
+ class _SelectorTransportMock:
+ _sock = None
+
+ def close(self):
+ self._sock.close()
+
+ def mock_make_ssl_transport(sock, protocol, sslcontext, waiter,
+ **kwds):
+ waiter.set_result(None)
+ transport = _SelectorTransportMock()
+ transport._sock = sock
+ return transport
+
+ self.loop._make_ssl_transport.side_effect = mock_make_ssl_transport
+ ANY = unittest.mock.ANY
+ # First try the default server_hostname.
+ self.loop._make_ssl_transport.reset_mock()
+ coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True)
+ transport, _ = self.loop.run_until_complete(coro)
+ transport.close()
+ self.loop._make_ssl_transport.assert_called_with(
+ ANY, ANY, ANY, ANY,
+ server_side=False,
+ server_hostname='python.org')
+ # Next try an explicit server_hostname.
+ self.loop._make_ssl_transport.reset_mock()
+ coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True,
+ server_hostname='perl.com')
+ transport, _ = self.loop.run_until_complete(coro)
+ transport.close()
+ self.loop._make_ssl_transport.assert_called_with(
+ ANY, ANY, ANY, ANY,
+ server_side=False,
+ server_hostname='perl.com')
+ # Finally try an explicit empty server_hostname.
+ self.loop._make_ssl_transport.reset_mock()
+ coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True,
+ server_hostname='')
+ transport, _ = self.loop.run_until_complete(coro)
+ transport.close()
+ self.loop._make_ssl_transport.assert_called_with(ANY, ANY, ANY, ANY,
+ server_side=False,
+ server_hostname='')
+
+ def test_create_connection_no_ssl_server_hostname_errors(self):
+ # When not using ssl, server_hostname must be None.
+ coro = self.loop.create_connection(MyProto, 'python.org', 80,
+ server_hostname='')
+ self.assertRaises(ValueError, self.loop.run_until_complete, coro)
+ coro = self.loop.create_connection(MyProto, 'python.org', 80,
+ server_hostname='python.org')
+ self.assertRaises(ValueError, self.loop.run_until_complete, coro)
+
+ def test_create_connection_ssl_server_hostname_errors(self):
+ # When using ssl, server_hostname may be None if host is non-empty.
+ coro = self.loop.create_connection(MyProto, '', 80, ssl=True)
+ self.assertRaises(ValueError, self.loop.run_until_complete, coro)
+ coro = self.loop.create_connection(MyProto, None, 80, ssl=True)
+ self.assertRaises(ValueError, self.loop.run_until_complete, coro)
+ sock = socket.socket()
+ coro = self.loop.create_connection(MyProto, None, None,
+ ssl=True, sock=sock)
+ self.addCleanup(sock.close)
+ self.assertRaises(ValueError, self.loop.run_until_complete, coro)
+
+ def test_create_server_empty_host(self):
+ # if host is empty string use None instead
+ host = object()
+
+ @tasks.coroutine
+ def getaddrinfo(*args, **kw):
+ nonlocal host
+ host = args[0]
+ yield from []
+
+ def getaddrinfo_task(*args, **kwds):
+ return tasks.Task(getaddrinfo(*args, **kwds), loop=self.loop)
+
+ self.loop.getaddrinfo = getaddrinfo_task
+ fut = self.loop.create_server(MyProto, '', 0)
+ self.assertRaises(OSError, self.loop.run_until_complete, fut)
+ self.assertIsNone(host)
+
+ def test_create_server_host_port_sock(self):
+ fut = self.loop.create_server(
+ MyProto, '0.0.0.0', 0, sock=object())
+ self.assertRaises(ValueError, self.loop.run_until_complete, fut)
+
+ def test_create_server_no_host_port_sock(self):
+ fut = self.loop.create_server(MyProto)
+ self.assertRaises(ValueError, self.loop.run_until_complete, fut)
+
+ def test_create_server_no_getaddrinfo(self):
+ getaddrinfo = self.loop.getaddrinfo = unittest.mock.Mock()
+ getaddrinfo.return_value = []
+
+ f = self.loop.create_server(MyProto, '0.0.0.0', 0)
+ self.assertRaises(OSError, self.loop.run_until_complete, f)
+
+ @unittest.mock.patch('asyncio.base_events.socket')
+ def test_create_server_cant_bind(self, m_socket):
+
+ class Err(OSError):
+ strerror = 'error'
+
+ m_socket.getaddrinfo.return_value = [
+ (2, 1, 6, '', ('127.0.0.1', 10100))]
+ m_sock = m_socket.socket.return_value = unittest.mock.Mock()
+ m_sock.bind.side_effect = Err
+
+ fut = self.loop.create_server(MyProto, '0.0.0.0', 0)
+ self.assertRaises(OSError, self.loop.run_until_complete, fut)
+ self.assertTrue(m_sock.close.called)
+
+ @unittest.mock.patch('asyncio.base_events.socket')
+ def test_create_datagram_endpoint_no_addrinfo(self, m_socket):
+ m_socket.getaddrinfo.return_value = []
+
+ coro = self.loop.create_datagram_endpoint(
+ MyDatagramProto, local_addr=('localhost', 0))
+ self.assertRaises(
+ OSError, self.loop.run_until_complete, coro)
+
+ def test_create_datagram_endpoint_addr_error(self):
+ coro = self.loop.create_datagram_endpoint(
+ MyDatagramProto, local_addr='localhost')
+ self.assertRaises(
+ AssertionError, self.loop.run_until_complete, coro)
+ coro = self.loop.create_datagram_endpoint(
+ MyDatagramProto, local_addr=('localhost', 1, 2, 3))
+ self.assertRaises(
+ AssertionError, self.loop.run_until_complete, coro)
+
+ def test_create_datagram_endpoint_connect_err(self):
+ self.loop.sock_connect = unittest.mock.Mock()
+ self.loop.sock_connect.side_effect = OSError
+
+ coro = self.loop.create_datagram_endpoint(
+ protocols.DatagramProtocol, remote_addr=('127.0.0.1', 0))
+ self.assertRaises(
+ OSError, self.loop.run_until_complete, coro)
+
+ @unittest.mock.patch('asyncio.base_events.socket')
+ def test_create_datagram_endpoint_socket_err(self, m_socket):
+ m_socket.getaddrinfo = socket.getaddrinfo
+ m_socket.socket.side_effect = OSError
+
+ coro = self.loop.create_datagram_endpoint(
+ protocols.DatagramProtocol, family=socket.AF_INET)
+ self.assertRaises(
+ OSError, self.loop.run_until_complete, coro)
+
+ coro = self.loop.create_datagram_endpoint(
+ protocols.DatagramProtocol, local_addr=('127.0.0.1', 0))
+ self.assertRaises(
+ OSError, self.loop.run_until_complete, coro)
+
+ @unittest.skipUnless(IPV6_ENABLED, 'IPv6 not supported or enabled')
+ def test_create_datagram_endpoint_no_matching_family(self):
+ coro = self.loop.create_datagram_endpoint(
+ protocols.DatagramProtocol,
+ remote_addr=('127.0.0.1', 0), local_addr=('::1', 0))
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete, coro)
+
+ @unittest.mock.patch('asyncio.base_events.socket')
+ def test_create_datagram_endpoint_setblk_err(self, m_socket):
+ m_socket.socket.return_value.setblocking.side_effect = OSError
+
+ coro = self.loop.create_datagram_endpoint(
+ protocols.DatagramProtocol, family=socket.AF_INET)
+ self.assertRaises(
+ OSError, self.loop.run_until_complete, coro)
+ self.assertTrue(
+ m_socket.socket.return_value.close.called)
+
+ def test_create_datagram_endpoint_noaddr_nofamily(self):
+ coro = self.loop.create_datagram_endpoint(
+ protocols.DatagramProtocol)
+ self.assertRaises(ValueError, self.loop.run_until_complete, coro)
+
+ @unittest.mock.patch('asyncio.base_events.socket')
+ def test_create_datagram_endpoint_cant_bind(self, m_socket):
+ class Err(OSError):
+ pass
+
+ m_socket.AF_INET6 = socket.AF_INET6
+ m_socket.getaddrinfo = socket.getaddrinfo
+ m_sock = m_socket.socket.return_value = unittest.mock.Mock()
+ m_sock.bind.side_effect = Err
+
+ fut = self.loop.create_datagram_endpoint(
+ MyDatagramProto,
+ local_addr=('127.0.0.1', 0), family=socket.AF_INET)
+ self.assertRaises(Err, self.loop.run_until_complete, fut)
+ self.assertTrue(m_sock.close.called)
+
+ def test_accept_connection_retry(self):
+ sock = unittest.mock.Mock()
+ sock.accept.side_effect = BlockingIOError()
+
+ self.loop._accept_connection(MyProto, sock)
+ self.assertFalse(sock.close.called)
+
+ @unittest.mock.patch('asyncio.selector_events.logger')
+ def test_accept_connection_exception(self, m_log):
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ sock.accept.side_effect = OSError(errno.EMFILE, 'Too many open files')
+ self.loop.remove_reader = unittest.mock.Mock()
+ self.loop.call_later = unittest.mock.Mock()
+
+ self.loop._accept_connection(MyProto, sock)
+ self.assertTrue(m_log.exception.called)
+ self.assertFalse(sock.close.called)
+ self.loop.remove_reader.assert_called_with(10)
+ self.loop.call_later.assert_called_with(constants.ACCEPT_RETRY_DELAY,
+ # self.loop._start_serving
+ unittest.mock.ANY,
+ MyProto, sock, None, None)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py
new file mode 100644
index 0000000000..18411ecc24
--- /dev/null
+++ b/Lib/test/test_asyncio/test_events.py
@@ -0,0 +1,1672 @@
+"""Tests for events.py."""
+
+import functools
+import gc
+import io
+import os
+import signal
+import socket
+try:
+ import ssl
+except ImportError:
+ ssl = None
+import subprocess
+import sys
+import threading
+import time
+import errno
+import unittest
+import unittest.mock
+from test.support import find_unused_port, IPV6_ENABLED
+
+
+from asyncio import futures
+from asyncio import events
+from asyncio import transports
+from asyncio import protocols
+from asyncio import selector_events
+from asyncio import tasks
+from asyncio import test_utils
+from asyncio import locks
+
+
+class MyProto(protocols.Protocol):
+ done = None
+
+ def __init__(self, loop=None):
+ self.state = 'INITIAL'
+ self.nbytes = 0
+ if loop is not None:
+ self.done = futures.Future(loop=loop)
+
+ def connection_made(self, transport):
+ self.transport = transport
+ assert self.state == 'INITIAL', self.state
+ self.state = 'CONNECTED'
+ transport.write(b'GET / HTTP/1.0\r\nHost: example.com\r\n\r\n')
+
+ def data_received(self, data):
+ assert self.state == 'CONNECTED', self.state
+ self.nbytes += len(data)
+
+ def eof_received(self):
+ assert self.state == 'CONNECTED', self.state
+ self.state = 'EOF'
+
+ def connection_lost(self, exc):
+ assert self.state in ('CONNECTED', 'EOF'), self.state
+ self.state = 'CLOSED'
+ if self.done:
+ self.done.set_result(None)
+
+
+class MyDatagramProto(protocols.DatagramProtocol):
+ done = None
+
+ def __init__(self, loop=None):
+ self.state = 'INITIAL'
+ self.nbytes = 0
+ if loop is not None:
+ self.done = futures.Future(loop=loop)
+
+ def connection_made(self, transport):
+ self.transport = transport
+ assert self.state == 'INITIAL', self.state
+ self.state = 'INITIALIZED'
+
+ def datagram_received(self, data, addr):
+ assert self.state == 'INITIALIZED', self.state
+ self.nbytes += len(data)
+
+ def error_received(self, exc):
+ assert self.state == 'INITIALIZED', self.state
+
+ def connection_lost(self, exc):
+ assert self.state == 'INITIALIZED', self.state
+ self.state = 'CLOSED'
+ if self.done:
+ self.done.set_result(None)
+
+
+class MyReadPipeProto(protocols.Protocol):
+ done = None
+
+ def __init__(self, loop=None):
+ self.state = ['INITIAL']
+ self.nbytes = 0
+ self.transport = None
+ if loop is not None:
+ self.done = futures.Future(loop=loop)
+
+ def connection_made(self, transport):
+ self.transport = transport
+ assert self.state == ['INITIAL'], self.state
+ self.state.append('CONNECTED')
+
+ def data_received(self, data):
+ assert self.state == ['INITIAL', 'CONNECTED'], self.state
+ self.nbytes += len(data)
+
+ def eof_received(self):
+ assert self.state == ['INITIAL', 'CONNECTED'], self.state
+ self.state.append('EOF')
+
+ def connection_lost(self, exc):
+ assert self.state == ['INITIAL', 'CONNECTED', 'EOF'], self.state
+ self.state.append('CLOSED')
+ if self.done:
+ self.done.set_result(None)
+
+
+class MyWritePipeProto(protocols.BaseProtocol):
+ done = None
+
+ def __init__(self, loop=None):
+ self.state = 'INITIAL'
+ self.transport = None
+ if loop is not None:
+ self.done = futures.Future(loop=loop)
+
+ def connection_made(self, transport):
+ self.transport = transport
+ assert self.state == 'INITIAL', self.state
+ self.state = 'CONNECTED'
+
+ def connection_lost(self, exc):
+ assert self.state == 'CONNECTED', self.state
+ self.state = 'CLOSED'
+ if self.done:
+ self.done.set_result(None)
+
+
+class MySubprocessProtocol(protocols.SubprocessProtocol):
+
+ def __init__(self, loop):
+ self.state = 'INITIAL'
+ self.transport = None
+ self.connected = futures.Future(loop=loop)
+ self.completed = futures.Future(loop=loop)
+ self.disconnects = {fd: futures.Future(loop=loop) for fd in range(3)}
+ self.data = {1: b'', 2: b''}
+ self.returncode = None
+ self.got_data = {1: locks.Event(loop=loop),
+ 2: locks.Event(loop=loop)}
+
+ def connection_made(self, transport):
+ self.transport = transport
+ assert self.state == 'INITIAL', self.state
+ self.state = 'CONNECTED'
+ self.connected.set_result(None)
+
+ def connection_lost(self, exc):
+ assert self.state == 'CONNECTED', self.state
+ self.state = 'CLOSED'
+ self.completed.set_result(None)
+
+ def pipe_data_received(self, fd, data):
+ assert self.state == 'CONNECTED', self.state
+ self.data[fd] += data
+ self.got_data[fd].set()
+
+ def pipe_connection_lost(self, fd, exc):
+ assert self.state == 'CONNECTED', self.state
+ if exc:
+ self.disconnects[fd].set_exception(exc)
+ else:
+ self.disconnects[fd].set_result(exc)
+
+ def process_exited(self):
+ assert self.state == 'CONNECTED', self.state
+ self.returncode = self.transport.get_returncode()
+
+
+class EventLoopTestsMixin:
+
+ def setUp(self):
+ super().setUp()
+ self.loop = self.create_event_loop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ # just in case if we have transport close callbacks
+ test_utils.run_briefly(self.loop)
+
+ self.loop.close()
+ gc.collect()
+ super().tearDown()
+
+ def test_run_until_complete_nesting(self):
+ @tasks.coroutine
+ def coro1():
+ yield
+
+ @tasks.coroutine
+ def coro2():
+ self.assertTrue(self.loop.is_running())
+ self.loop.run_until_complete(coro1())
+
+ self.assertRaises(
+ RuntimeError, self.loop.run_until_complete, coro2())
+
+ # Note: because of the default Windows timing granularity of
+ # 15.6 msec, we use fairly long sleep times here (~100 msec).
+
+ def test_run_until_complete(self):
+ t0 = self.loop.time()
+ self.loop.run_until_complete(tasks.sleep(0.1, loop=self.loop))
+ t1 = self.loop.time()
+ self.assertTrue(0.08 <= t1-t0 <= 0.8, t1-t0)
+
+ def test_run_until_complete_stopped(self):
+ @tasks.coroutine
+ def cb():
+ self.loop.stop()
+ yield from tasks.sleep(0.1, loop=self.loop)
+ task = cb()
+ self.assertRaises(RuntimeError,
+ self.loop.run_until_complete, task)
+
+ def test_call_later(self):
+ results = []
+
+ def callback(arg):
+ results.append(arg)
+ self.loop.stop()
+
+ self.loop.call_later(0.1, callback, 'hello world')
+ t0 = time.monotonic()
+ self.loop.run_forever()
+ t1 = time.monotonic()
+ self.assertEqual(results, ['hello world'])
+ self.assertTrue(0.08 <= t1-t0 <= 0.8, t1-t0)
+
+ def test_call_soon(self):
+ results = []
+
+ def callback(arg1, arg2):
+ results.append((arg1, arg2))
+ self.loop.stop()
+
+ self.loop.call_soon(callback, 'hello', 'world')
+ self.loop.run_forever()
+ self.assertEqual(results, [('hello', 'world')])
+
+ def test_call_soon_threadsafe(self):
+ results = []
+ lock = threading.Lock()
+
+ def callback(arg):
+ results.append(arg)
+ if len(results) >= 2:
+ self.loop.stop()
+
+ def run_in_thread():
+ self.loop.call_soon_threadsafe(callback, 'hello')
+ lock.release()
+
+ lock.acquire()
+ t = threading.Thread(target=run_in_thread)
+ t.start()
+
+ with lock:
+ self.loop.call_soon(callback, 'world')
+ self.loop.run_forever()
+ t.join()
+ self.assertEqual(results, ['hello', 'world'])
+
+ def test_call_soon_threadsafe_same_thread(self):
+ results = []
+
+ def callback(arg):
+ results.append(arg)
+ if len(results) >= 2:
+ self.loop.stop()
+
+ self.loop.call_soon_threadsafe(callback, 'hello')
+ self.loop.call_soon(callback, 'world')
+ self.loop.run_forever()
+ self.assertEqual(results, ['hello', 'world'])
+
+ def test_run_in_executor(self):
+ def run(arg):
+ return (arg, threading.get_ident())
+ f2 = self.loop.run_in_executor(None, run, 'yo')
+ res, thread_id = self.loop.run_until_complete(f2)
+ self.assertEqual(res, 'yo')
+ self.assertNotEqual(thread_id, threading.get_ident())
+
+ def test_reader_callback(self):
+ r, w = test_utils.socketpair()
+ bytes_read = []
+
+ def reader():
+ try:
+ data = r.recv(1024)
+ except BlockingIOError:
+ # Spurious readiness notifications are possible
+ # at least on Linux -- see man select.
+ return
+ if data:
+ bytes_read.append(data)
+ else:
+ self.assertTrue(self.loop.remove_reader(r.fileno()))
+ r.close()
+
+ self.loop.add_reader(r.fileno(), reader)
+ self.loop.call_soon(w.send, b'abc')
+ test_utils.run_briefly(self.loop)
+ self.loop.call_soon(w.send, b'def')
+ test_utils.run_briefly(self.loop)
+ self.loop.call_soon(w.close)
+ self.loop.call_soon(self.loop.stop)
+ self.loop.run_forever()
+ self.assertEqual(b''.join(bytes_read), b'abcdef')
+
+ def test_writer_callback(self):
+ r, w = test_utils.socketpair()
+ w.setblocking(False)
+ self.loop.add_writer(w.fileno(), w.send, b'x'*(256*1024))
+ test_utils.run_briefly(self.loop)
+
+ def remove_writer():
+ self.assertTrue(self.loop.remove_writer(w.fileno()))
+
+ self.loop.call_soon(remove_writer)
+ self.loop.call_soon(self.loop.stop)
+ self.loop.run_forever()
+ w.close()
+ data = r.recv(256*1024)
+ r.close()
+ self.assertGreaterEqual(len(data), 200)
+
+ def test_sock_client_ops(self):
+ with test_utils.run_test_server() as httpd:
+ sock = socket.socket()
+ sock.setblocking(False)
+ self.loop.run_until_complete(
+ self.loop.sock_connect(sock, httpd.address))
+ self.loop.run_until_complete(
+ self.loop.sock_sendall(sock, b'GET / HTTP/1.0\r\n\r\n'))
+ data = self.loop.run_until_complete(
+ self.loop.sock_recv(sock, 1024))
+ # consume data
+ self.loop.run_until_complete(
+ self.loop.sock_recv(sock, 1024))
+ sock.close()
+
+ self.assertTrue(data.startswith(b'HTTP/1.0 200 OK'))
+
+ def test_sock_client_fail(self):
+ # Make sure that we will get an unused port
+ address = None
+ try:
+ s = socket.socket()
+ s.bind(('127.0.0.1', 0))
+ address = s.getsockname()
+ finally:
+ s.close()
+
+ sock = socket.socket()
+ sock.setblocking(False)
+ with self.assertRaises(ConnectionRefusedError):
+ self.loop.run_until_complete(
+ self.loop.sock_connect(sock, address))
+ sock.close()
+
+ def test_sock_accept(self):
+ listener = socket.socket()
+ listener.setblocking(False)
+ listener.bind(('127.0.0.1', 0))
+ listener.listen(1)
+ client = socket.socket()
+ client.connect(listener.getsockname())
+
+ f = self.loop.sock_accept(listener)
+ conn, addr = self.loop.run_until_complete(f)
+ self.assertEqual(conn.gettimeout(), 0)
+ self.assertEqual(addr, client.getsockname())
+ self.assertEqual(client.getpeername(), listener.getsockname())
+ client.close()
+ conn.close()
+ listener.close()
+
+ @unittest.skipUnless(hasattr(signal, 'SIGKILL'), 'No SIGKILL')
+ def test_add_signal_handler(self):
+ caught = 0
+
+ def my_handler():
+ nonlocal caught
+ caught += 1
+
+ # Check error behavior first.
+ self.assertRaises(
+ TypeError, self.loop.add_signal_handler, 'boom', my_handler)
+ self.assertRaises(
+ TypeError, self.loop.remove_signal_handler, 'boom')
+ self.assertRaises(
+ ValueError, self.loop.add_signal_handler, signal.NSIG+1,
+ my_handler)
+ self.assertRaises(
+ ValueError, self.loop.remove_signal_handler, signal.NSIG+1)
+ self.assertRaises(
+ ValueError, self.loop.add_signal_handler, 0, my_handler)
+ self.assertRaises(
+ ValueError, self.loop.remove_signal_handler, 0)
+ self.assertRaises(
+ ValueError, self.loop.add_signal_handler, -1, my_handler)
+ self.assertRaises(
+ ValueError, self.loop.remove_signal_handler, -1)
+ self.assertRaises(
+ RuntimeError, self.loop.add_signal_handler, signal.SIGKILL,
+ my_handler)
+ # Removing SIGKILL doesn't raise, since we don't call signal().
+ self.assertFalse(self.loop.remove_signal_handler(signal.SIGKILL))
+ # Now set a handler and handle it.
+ self.loop.add_signal_handler(signal.SIGINT, my_handler)
+ test_utils.run_briefly(self.loop)
+ os.kill(os.getpid(), signal.SIGINT)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(caught, 1)
+ # Removing it should restore the default handler.
+ self.assertTrue(self.loop.remove_signal_handler(signal.SIGINT))
+ self.assertEqual(signal.getsignal(signal.SIGINT),
+ signal.default_int_handler)
+ # Removing again returns False.
+ self.assertFalse(self.loop.remove_signal_handler(signal.SIGINT))
+
+ @unittest.skipUnless(hasattr(signal, 'SIGALRM'), 'No SIGALRM')
+ def test_signal_handling_while_selecting(self):
+ # Test with a signal actually arriving during a select() call.
+ caught = 0
+
+ def my_handler():
+ nonlocal caught
+ caught += 1
+ self.loop.stop()
+
+ self.loop.add_signal_handler(signal.SIGALRM, my_handler)
+
+ signal.setitimer(signal.ITIMER_REAL, 0.01, 0) # Send SIGALRM once.
+ self.loop.run_forever()
+ self.assertEqual(caught, 1)
+
+ @unittest.skipUnless(hasattr(signal, 'SIGALRM'), 'No SIGALRM')
+ def test_signal_handling_args(self):
+ some_args = (42,)
+ caught = 0
+
+ def my_handler(*args):
+ nonlocal caught
+ caught += 1
+ self.assertEqual(args, some_args)
+
+ self.loop.add_signal_handler(signal.SIGALRM, my_handler, *some_args)
+
+ signal.setitimer(signal.ITIMER_REAL, 0.1, 0) # Send SIGALRM once.
+ self.loop.call_later(0.5, self.loop.stop)
+ self.loop.run_forever()
+ self.assertEqual(caught, 1)
+
+ def test_create_connection(self):
+ with test_utils.run_test_server() as httpd:
+ f = self.loop.create_connection(
+ lambda: MyProto(loop=self.loop), *httpd.address)
+ tr, pr = self.loop.run_until_complete(f)
+ self.assertIsInstance(tr, transports.Transport)
+ self.assertIsInstance(pr, protocols.Protocol)
+ self.loop.run_until_complete(pr.done)
+ self.assertGreater(pr.nbytes, 0)
+ tr.close()
+
+ def test_create_connection_sock(self):
+ with test_utils.run_test_server() as httpd:
+ sock = None
+ infos = self.loop.run_until_complete(
+ self.loop.getaddrinfo(
+ *httpd.address, type=socket.SOCK_STREAM))
+ for family, type, proto, cname, address in infos:
+ try:
+ sock = socket.socket(family=family, type=type, proto=proto)
+ sock.setblocking(False)
+ self.loop.run_until_complete(
+ self.loop.sock_connect(sock, address))
+ except:
+ pass
+ else:
+ break
+ else:
+ assert False, 'Can not create socket.'
+
+ f = self.loop.create_connection(
+ lambda: MyProto(loop=self.loop), sock=sock)
+ tr, pr = self.loop.run_until_complete(f)
+ self.assertIsInstance(tr, transports.Transport)
+ self.assertIsInstance(pr, protocols.Protocol)
+ self.loop.run_until_complete(pr.done)
+ self.assertGreater(pr.nbytes, 0)
+ tr.close()
+
+ @unittest.skipIf(ssl is None, 'No ssl module')
+ def test_create_ssl_connection(self):
+ with test_utils.run_test_server(use_ssl=True) as httpd:
+ f = self.loop.create_connection(
+ lambda: MyProto(loop=self.loop), *httpd.address,
+ ssl=test_utils.dummy_ssl_context())
+ tr, pr = self.loop.run_until_complete(f)
+ self.assertIsInstance(tr, transports.Transport)
+ self.assertIsInstance(pr, protocols.Protocol)
+ self.assertTrue('ssl' in tr.__class__.__name__.lower())
+ self.assertIsNotNone(tr.get_extra_info('sockname'))
+ self.loop.run_until_complete(pr.done)
+ self.assertGreater(pr.nbytes, 0)
+ tr.close()
+
+ def test_create_connection_local_addr(self):
+ with test_utils.run_test_server() as httpd:
+ port = find_unused_port()
+ f = self.loop.create_connection(
+ lambda: MyProto(loop=self.loop),
+ *httpd.address, local_addr=(httpd.address[0], port))
+ tr, pr = self.loop.run_until_complete(f)
+ expected = pr.transport.get_extra_info('sockname')[1]
+ self.assertEqual(port, expected)
+ tr.close()
+
+ def test_create_connection_local_addr_in_use(self):
+ with test_utils.run_test_server() as httpd:
+ f = self.loop.create_connection(
+ lambda: MyProto(loop=self.loop),
+ *httpd.address, local_addr=httpd.address)
+ with self.assertRaises(OSError) as cm:
+ self.loop.run_until_complete(f)
+ self.assertEqual(cm.exception.errno, errno.EADDRINUSE)
+ self.assertIn(str(httpd.address), cm.exception.strerror)
+
+ def test_create_server(self):
+ proto = None
+
+ def factory():
+ nonlocal proto
+ proto = MyProto()
+ return proto
+
+ f = self.loop.create_server(factory, '0.0.0.0', 0)
+ server = self.loop.run_until_complete(f)
+ self.assertEqual(len(server.sockets), 1)
+ sock = server.sockets[0]
+ host, port = sock.getsockname()
+ self.assertEqual(host, '0.0.0.0')
+ client = socket.socket()
+ client.connect(('127.0.0.1', port))
+ client.sendall(b'xxx')
+ test_utils.run_briefly(self.loop)
+ test_utils.run_until(self.loop, lambda: proto is not None, 10)
+ self.assertIsInstance(proto, MyProto)
+ self.assertEqual('INITIAL', proto.state)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual('CONNECTED', proto.state)
+ test_utils.run_until(self.loop, lambda: proto.nbytes > 0,
+ timeout=10)
+ self.assertEqual(3, proto.nbytes)
+
+ # extra info is available
+ self.assertIsNotNone(proto.transport.get_extra_info('sockname'))
+ self.assertEqual('127.0.0.1',
+ proto.transport.get_extra_info('peername')[0])
+
+ # close connection
+ proto.transport.close()
+ test_utils.run_briefly(self.loop) # windows iocp
+
+ self.assertEqual('CLOSED', proto.state)
+
+ # the client socket must be closed after to avoid ECONNRESET upon
+ # recv()/send() on the serving socket
+ client.close()
+
+ # close server
+ server.close()
+
+ @unittest.skipIf(ssl is None, 'No ssl module')
+ def test_create_server_ssl(self):
+ proto = None
+
+ class ClientMyProto(MyProto):
+ def connection_made(self, transport):
+ self.transport = transport
+ assert self.state == 'INITIAL', self.state
+ self.state = 'CONNECTED'
+
+ def factory():
+ nonlocal proto
+ proto = MyProto(loop=self.loop)
+ return proto
+
+ here = os.path.dirname(__file__)
+ sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ sslcontext.load_cert_chain(
+ certfile=os.path.join(here, 'sample.crt'),
+ keyfile=os.path.join(here, 'sample.key'))
+
+ f = self.loop.create_server(
+ factory, '127.0.0.1', 0, ssl=sslcontext)
+
+ server = self.loop.run_until_complete(f)
+ sock = server.sockets[0]
+ host, port = sock.getsockname()
+ self.assertEqual(host, '127.0.0.1')
+
+ f_c = self.loop.create_connection(ClientMyProto, host, port,
+ ssl=test_utils.dummy_ssl_context())
+ client, pr = self.loop.run_until_complete(f_c)
+
+ client.write(b'xxx')
+ test_utils.run_briefly(self.loop)
+ self.assertIsInstance(proto, MyProto)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual('CONNECTED', proto.state)
+ test_utils.run_until(self.loop, lambda: proto.nbytes > 0,
+ timeout=10)
+ self.assertEqual(3, proto.nbytes)
+
+ # extra info is available
+ self.assertIsNotNone(proto.transport.get_extra_info('sockname'))
+ self.assertEqual('127.0.0.1',
+ proto.transport.get_extra_info('peername')[0])
+
+ # close connection
+ proto.transport.close()
+ self.loop.run_until_complete(proto.done)
+ self.assertEqual('CLOSED', proto.state)
+
+ # the client socket must be closed after to avoid ECONNRESET upon
+ # recv()/send() on the serving socket
+ client.close()
+
+ # stop serving
+ server.close()
+
+ def test_create_server_sock(self):
+ proto = futures.Future(loop=self.loop)
+
+ class TestMyProto(MyProto):
+ def connection_made(self, transport):
+ super().connection_made(transport)
+ proto.set_result(self)
+
+ sock_ob = socket.socket(type=socket.SOCK_STREAM)
+ sock_ob.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock_ob.bind(('0.0.0.0', 0))
+
+ f = self.loop.create_server(TestMyProto, sock=sock_ob)
+ server = self.loop.run_until_complete(f)
+ sock = server.sockets[0]
+ self.assertIs(sock, sock_ob)
+
+ host, port = sock.getsockname()
+ self.assertEqual(host, '0.0.0.0')
+ client = socket.socket()
+ client.connect(('127.0.0.1', port))
+ client.send(b'xxx')
+ client.close()
+ server.close()
+
+ def test_create_server_addr_in_use(self):
+ sock_ob = socket.socket(type=socket.SOCK_STREAM)
+ sock_ob.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock_ob.bind(('0.0.0.0', 0))
+
+ f = self.loop.create_server(MyProto, sock=sock_ob)
+ server = self.loop.run_until_complete(f)
+ sock = server.sockets[0]
+ host, port = sock.getsockname()
+
+ f = self.loop.create_server(MyProto, host=host, port=port)
+ with self.assertRaises(OSError) as cm:
+ self.loop.run_until_complete(f)
+ self.assertEqual(cm.exception.errno, errno.EADDRINUSE)
+
+ server.close()
+
+ @unittest.skipUnless(IPV6_ENABLED, 'IPv6 not supported or enabled')
+ def test_create_server_dual_stack(self):
+ f_proto = futures.Future(loop=self.loop)
+
+ class TestMyProto(MyProto):
+ def connection_made(self, transport):
+ super().connection_made(transport)
+ f_proto.set_result(self)
+
+ try_count = 0
+ while True:
+ try:
+ port = find_unused_port()
+ f = self.loop.create_server(TestMyProto, host=None, port=port)
+ server = self.loop.run_until_complete(f)
+ except OSError as ex:
+ if ex.errno == errno.EADDRINUSE:
+ try_count += 1
+ self.assertGreaterEqual(5, try_count)
+ continue
+ else:
+ raise
+ else:
+ break
+ client = socket.socket()
+ client.connect(('127.0.0.1', port))
+ client.send(b'xxx')
+ proto = self.loop.run_until_complete(f_proto)
+ proto.transport.close()
+ client.close()
+
+ f_proto = futures.Future(loop=self.loop)
+ client = socket.socket(socket.AF_INET6)
+ client.connect(('::1', port))
+ client.send(b'xxx')
+ proto = self.loop.run_until_complete(f_proto)
+ proto.transport.close()
+ client.close()
+
+ server.close()
+
+ def test_server_close(self):
+ f = self.loop.create_server(MyProto, '0.0.0.0', 0)
+ server = self.loop.run_until_complete(f)
+ sock = server.sockets[0]
+ host, port = sock.getsockname()
+
+ client = socket.socket()
+ client.connect(('127.0.0.1', port))
+ client.send(b'xxx')
+ client.close()
+
+ server.close()
+
+ client = socket.socket()
+ self.assertRaises(
+ ConnectionRefusedError, client.connect, ('127.0.0.1', port))
+ client.close()
+
+ def test_create_datagram_endpoint(self):
+ class TestMyDatagramProto(MyDatagramProto):
+ def __init__(inner_self):
+ super().__init__(loop=self.loop)
+
+ def datagram_received(self, data, addr):
+ super().datagram_received(data, addr)
+ self.transport.sendto(b'resp:'+data, addr)
+
+ coro = self.loop.create_datagram_endpoint(
+ TestMyDatagramProto, local_addr=('127.0.0.1', 0))
+ s_transport, server = self.loop.run_until_complete(coro)
+ host, port = s_transport.get_extra_info('sockname')
+
+ coro = self.loop.create_datagram_endpoint(
+ lambda: MyDatagramProto(loop=self.loop),
+ remote_addr=(host, port))
+ transport, client = self.loop.run_until_complete(coro)
+
+ self.assertEqual('INITIALIZED', client.state)
+ transport.sendto(b'xxx')
+ for _ in range(1000):
+ if server.nbytes:
+ break
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(3, server.nbytes)
+ for _ in range(1000):
+ if client.nbytes:
+ break
+ test_utils.run_briefly(self.loop)
+
+ # received
+ self.assertEqual(8, client.nbytes)
+
+ # extra info is available
+ self.assertIsNotNone(transport.get_extra_info('sockname'))
+
+ # close connection
+ transport.close()
+ self.loop.run_until_complete(client.done)
+ self.assertEqual('CLOSED', client.state)
+ server.transport.close()
+
+ def test_internal_fds(self):
+ loop = self.create_event_loop()
+ if not isinstance(loop, selector_events.BaseSelectorEventLoop):
+ return
+
+ self.assertEqual(1, loop._internal_fds)
+ loop.close()
+ self.assertEqual(0, loop._internal_fds)
+ self.assertIsNone(loop._csock)
+ self.assertIsNone(loop._ssock)
+
+ @unittest.skipUnless(sys.platform != 'win32',
+ "Don't support pipes for Windows")
+ def test_read_pipe(self):
+ proto = None
+
+ def factory():
+ nonlocal proto
+ proto = MyReadPipeProto(loop=self.loop)
+ return proto
+
+ rpipe, wpipe = os.pipe()
+ pipeobj = io.open(rpipe, 'rb', 1024)
+
+ @tasks.coroutine
+ def connect():
+ t, p = yield from self.loop.connect_read_pipe(factory, pipeobj)
+ self.assertIs(p, proto)
+ self.assertIs(t, proto.transport)
+ self.assertEqual(['INITIAL', 'CONNECTED'], proto.state)
+ self.assertEqual(0, proto.nbytes)
+
+ self.loop.run_until_complete(connect())
+
+ os.write(wpipe, b'1')
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(1, proto.nbytes)
+
+ os.write(wpipe, b'2345')
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(['INITIAL', 'CONNECTED'], proto.state)
+ self.assertEqual(5, proto.nbytes)
+
+ os.close(wpipe)
+ self.loop.run_until_complete(proto.done)
+ self.assertEqual(
+ ['INITIAL', 'CONNECTED', 'EOF', 'CLOSED'], proto.state)
+ # extra info is available
+ self.assertIsNotNone(proto.transport.get_extra_info('pipe'))
+
+ @unittest.skipUnless(sys.platform != 'win32',
+ "Don't support pipes for Windows")
+ def test_write_pipe(self):
+ proto = None
+ transport = None
+
+ def factory():
+ nonlocal proto
+ proto = MyWritePipeProto(loop=self.loop)
+ return proto
+
+ rpipe, wpipe = os.pipe()
+ pipeobj = io.open(wpipe, 'wb', 1024)
+
+ @tasks.coroutine
+ def connect():
+ nonlocal transport
+ t, p = yield from self.loop.connect_write_pipe(factory, pipeobj)
+ self.assertIs(p, proto)
+ self.assertIs(t, proto.transport)
+ self.assertEqual('CONNECTED', proto.state)
+ transport = t
+
+ self.loop.run_until_complete(connect())
+
+ transport.write(b'1')
+ test_utils.run_briefly(self.loop)
+ data = os.read(rpipe, 1024)
+ self.assertEqual(b'1', data)
+
+ transport.write(b'2345')
+ test_utils.run_briefly(self.loop)
+ data = os.read(rpipe, 1024)
+ self.assertEqual(b'2345', data)
+ self.assertEqual('CONNECTED', proto.state)
+
+ os.close(rpipe)
+
+ # extra info is available
+ self.assertIsNotNone(proto.transport.get_extra_info('pipe'))
+
+ # close connection
+ proto.transport.close()
+ self.loop.run_until_complete(proto.done)
+ self.assertEqual('CLOSED', proto.state)
+
+ @unittest.skipUnless(sys.platform != 'win32',
+ "Don't support pipes for Windows")
+ def test_write_pipe_disconnect_on_close(self):
+ proto = None
+ transport = None
+
+ def factory():
+ nonlocal proto
+ proto = MyWritePipeProto(loop=self.loop)
+ return proto
+
+ rsock, wsock = test_utils.socketpair()
+ pipeobj = io.open(wsock.detach(), 'wb', 1024)
+
+ @tasks.coroutine
+ def connect():
+ nonlocal transport
+ t, p = yield from self.loop.connect_write_pipe(factory,
+ pipeobj)
+ self.assertIs(p, proto)
+ self.assertIs(t, proto.transport)
+ self.assertEqual('CONNECTED', proto.state)
+ transport = t
+
+ self.loop.run_until_complete(connect())
+ self.assertEqual('CONNECTED', proto.state)
+
+ transport.write(b'1')
+ data = self.loop.run_until_complete(self.loop.sock_recv(rsock, 1024))
+ self.assertEqual(b'1', data)
+
+ rsock.close()
+
+ self.loop.run_until_complete(proto.done)
+ self.assertEqual('CLOSED', proto.state)
+
+ def test_prompt_cancellation(self):
+ r, w = test_utils.socketpair()
+ r.setblocking(False)
+ f = self.loop.sock_recv(r, 1)
+ ov = getattr(f, 'ov', None)
+ if ov is not None:
+ self.assertTrue(ov.pending)
+
+ @tasks.coroutine
+ def main():
+ try:
+ self.loop.call_soon(f.cancel)
+ yield from f
+ except futures.CancelledError:
+ res = 'cancelled'
+ else:
+ res = None
+ finally:
+ self.loop.stop()
+ return res
+
+ start = time.monotonic()
+ t = tasks.Task(main(), loop=self.loop)
+ self.loop.run_forever()
+ elapsed = time.monotonic() - start
+
+ self.assertLess(elapsed, 0.1)
+ self.assertEqual(t.result(), 'cancelled')
+ self.assertRaises(futures.CancelledError, f.result)
+ if ov is not None:
+ self.assertFalse(ov.pending)
+ self.loop._stop_serving(r)
+
+ r.close()
+ w.close()
+
+
+class SubprocessTestsMixin:
+
+ def check_terminated(self, returncode):
+ if sys.platform == 'win32':
+ self.assertIsInstance(returncode, int)
+ # expect 1 but sometimes get 0
+ else:
+ self.assertEqual(-signal.SIGTERM, returncode)
+
+ def check_killed(self, returncode):
+ if sys.platform == 'win32':
+ self.assertIsInstance(returncode, int)
+ # expect 1 but sometimes get 0
+ else:
+ self.assertEqual(-signal.SIGKILL, returncode)
+
+ def test_subprocess_exec(self):
+ proto = None
+ transp = None
+
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_exec(
+ functools.partial(MySubprocessProtocol, self.loop),
+ sys.executable, prog)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+ self.assertEqual('CONNECTED', proto.state)
+
+ stdin = transp.get_pipe_transport(0)
+ stdin.write(b'Python The Winner')
+ self.loop.run_until_complete(proto.got_data[1].wait())
+ transp.close()
+ self.loop.run_until_complete(proto.completed)
+ self.check_terminated(proto.returncode)
+ self.assertEqual(b'Python The Winner', proto.data[1])
+
+ def test_subprocess_interactive(self):
+ proto = None
+ transp = None
+
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_exec(
+ functools.partial(MySubprocessProtocol, self.loop),
+ sys.executable, prog)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+ self.assertEqual('CONNECTED', proto.state)
+
+ try:
+ stdin = transp.get_pipe_transport(0)
+ stdin.write(b'Python ')
+ self.loop.run_until_complete(proto.got_data[1].wait())
+ proto.got_data[1].clear()
+ self.assertEqual(b'Python ', proto.data[1])
+
+ stdin.write(b'The Winner')
+ self.loop.run_until_complete(proto.got_data[1].wait())
+ self.assertEqual(b'Python The Winner', proto.data[1])
+ finally:
+ transp.close()
+
+ self.loop.run_until_complete(proto.completed)
+ self.check_terminated(proto.returncode)
+
+ def test_subprocess_shell(self):
+ proto = None
+ transp = None
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_shell(
+ functools.partial(MySubprocessProtocol, self.loop),
+ 'echo Python')
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+
+ transp.get_pipe_transport(0).close()
+ self.loop.run_until_complete(proto.completed)
+ self.assertEqual(0, proto.returncode)
+ self.assertTrue(all(f.done() for f in proto.disconnects.values()))
+ self.assertEqual(proto.data[1].rstrip(b'\r\n'), b'Python')
+ self.assertEqual(proto.data[2], b'')
+
+ def test_subprocess_exitcode(self):
+ proto = None
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto
+ transp, proto = yield from self.loop.subprocess_shell(
+ functools.partial(MySubprocessProtocol, self.loop),
+ 'exit 7', stdin=None, stdout=None, stderr=None)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.completed)
+ self.assertEqual(7, proto.returncode)
+
+ def test_subprocess_close_after_finish(self):
+ proto = None
+ transp = None
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_shell(
+ functools.partial(MySubprocessProtocol, self.loop),
+ 'exit 7', stdin=None, stdout=None, stderr=None)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.assertIsNone(transp.get_pipe_transport(0))
+ self.assertIsNone(transp.get_pipe_transport(1))
+ self.assertIsNone(transp.get_pipe_transport(2))
+ self.loop.run_until_complete(proto.completed)
+ self.assertEqual(7, proto.returncode)
+ self.assertIsNone(transp.close())
+
+ def test_subprocess_kill(self):
+ proto = None
+ transp = None
+
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_exec(
+ functools.partial(MySubprocessProtocol, self.loop),
+ sys.executable, prog)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+
+ transp.kill()
+ self.loop.run_until_complete(proto.completed)
+ self.check_killed(proto.returncode)
+
+ def test_subprocess_terminate(self):
+ proto = None
+ transp = None
+
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_exec(
+ functools.partial(MySubprocessProtocol, self.loop),
+ sys.executable, prog)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+
+ transp.terminate()
+ self.loop.run_until_complete(proto.completed)
+ self.check_terminated(proto.returncode)
+
+ @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP")
+ def test_subprocess_send_signal(self):
+ proto = None
+ transp = None
+
+ prog = os.path.join(os.path.dirname(__file__), 'echo.py')
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_exec(
+ functools.partial(MySubprocessProtocol, self.loop),
+ sys.executable, prog)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+
+ transp.send_signal(signal.SIGHUP)
+ self.loop.run_until_complete(proto.completed)
+ self.assertEqual(-signal.SIGHUP, proto.returncode)
+
+ def test_subprocess_stderr(self):
+ proto = None
+ transp = None
+
+ prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_exec(
+ functools.partial(MySubprocessProtocol, self.loop),
+ sys.executable, prog)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+
+ stdin = transp.get_pipe_transport(0)
+ stdin.write(b'test')
+
+ self.loop.run_until_complete(proto.completed)
+
+ transp.close()
+ self.assertEqual(b'OUT:test', proto.data[1])
+ self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2])
+ self.assertEqual(0, proto.returncode)
+
+ def test_subprocess_stderr_redirect_to_stdout(self):
+ proto = None
+ transp = None
+
+ prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_exec(
+ functools.partial(MySubprocessProtocol, self.loop),
+ sys.executable, prog, stderr=subprocess.STDOUT)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+
+ stdin = transp.get_pipe_transport(0)
+ self.assertIsNotNone(transp.get_pipe_transport(1))
+ self.assertIsNone(transp.get_pipe_transport(2))
+
+ stdin.write(b'test')
+ self.loop.run_until_complete(proto.completed)
+ self.assertTrue(proto.data[1].startswith(b'OUT:testERR:test'),
+ proto.data[1])
+ self.assertEqual(b'', proto.data[2])
+
+ transp.close()
+ self.assertEqual(0, proto.returncode)
+
+ def test_subprocess_close_client_stream(self):
+ proto = None
+ transp = None
+
+ prog = os.path.join(os.path.dirname(__file__), 'echo3.py')
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto, transp
+ transp, proto = yield from self.loop.subprocess_exec(
+ functools.partial(MySubprocessProtocol, self.loop),
+ sys.executable, prog)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.connected)
+
+ stdin = transp.get_pipe_transport(0)
+ stdout = transp.get_pipe_transport(1)
+ stdin.write(b'test')
+ self.loop.run_until_complete(proto.got_data[1].wait())
+ self.assertEqual(b'OUT:test', proto.data[1])
+
+ stdout.close()
+ self.loop.run_until_complete(proto.disconnects[1])
+ stdin.write(b'xxx')
+ self.loop.run_until_complete(proto.got_data[2].wait())
+ if sys.platform != 'win32':
+ self.assertEqual(b'ERR:BrokenPipeError', proto.data[2])
+ else:
+ # After closing the read-end of a pipe, writing to the
+ # write-end using os.write() fails with errno==EINVAL and
+ # GetLastError()==ERROR_INVALID_NAME on Windows!?! (Using
+ # WriteFile() we get ERROR_BROKEN_PIPE as expected.)
+ self.assertEqual(b'ERR:OSError', proto.data[2])
+ transp.close()
+ self.loop.run_until_complete(proto.completed)
+ self.check_terminated(proto.returncode)
+
+ def test_subprocess_wait_no_same_group(self):
+ proto = None
+ transp = None
+
+ @tasks.coroutine
+ def connect():
+ nonlocal proto
+ # start the new process in a new session
+ transp, proto = yield from self.loop.subprocess_shell(
+ functools.partial(MySubprocessProtocol, self.loop),
+ 'exit 7', stdin=None, stdout=None, stderr=None,
+ start_new_session=True)
+ self.assertIsInstance(proto, MySubprocessProtocol)
+
+ self.loop.run_until_complete(connect())
+ self.loop.run_until_complete(proto.completed)
+ self.assertEqual(7, proto.returncode)
+
+
+if sys.platform == 'win32':
+ from asyncio import windows_events
+
+ class SelectEventLoopTests(EventLoopTestsMixin, unittest.TestCase):
+
+ def create_event_loop(self):
+ return windows_events.SelectorEventLoop()
+
+ class ProactorEventLoopTests(EventLoopTestsMixin,
+ SubprocessTestsMixin,
+ unittest.TestCase):
+
+ def create_event_loop(self):
+ return windows_events.ProactorEventLoop()
+
+ def test_create_ssl_connection(self):
+ raise unittest.SkipTest("IocpEventLoop imcompatible with SSL")
+
+ def test_create_server_ssl(self):
+ raise unittest.SkipTest("IocpEventLoop imcompatible with SSL")
+
+ def test_reader_callback(self):
+ raise unittest.SkipTest("IocpEventLoop does not have add_reader()")
+
+ def test_reader_callback_cancel(self):
+ raise unittest.SkipTest("IocpEventLoop does not have add_reader()")
+
+ def test_writer_callback(self):
+ raise unittest.SkipTest("IocpEventLoop does not have add_writer()")
+
+ def test_writer_callback_cancel(self):
+ raise unittest.SkipTest("IocpEventLoop does not have add_writer()")
+
+ def test_create_datagram_endpoint(self):
+ raise unittest.SkipTest(
+ "IocpEventLoop does not have create_datagram_endpoint()")
+else:
+ from asyncio import selectors
+ from asyncio import unix_events
+
+ class UnixEventLoopTestsMixin(EventLoopTestsMixin):
+ def setUp(self):
+ super().setUp()
+ watcher = unix_events.SafeChildWatcher()
+ watcher.attach_loop(self.loop)
+ events.set_child_watcher(watcher)
+
+ def tearDown(self):
+ events.set_child_watcher(None)
+ super().tearDown()
+
+ if hasattr(selectors, 'KqueueSelector'):
+ class KqueueEventLoopTests(UnixEventLoopTestsMixin,
+ SubprocessTestsMixin,
+ unittest.TestCase):
+
+ def create_event_loop(self):
+ return unix_events.SelectorEventLoop(
+ selectors.KqueueSelector())
+
+ if hasattr(selectors, 'EpollSelector'):
+ class EPollEventLoopTests(UnixEventLoopTestsMixin,
+ SubprocessTestsMixin,
+ unittest.TestCase):
+
+ def create_event_loop(self):
+ return unix_events.SelectorEventLoop(selectors.EpollSelector())
+
+ if hasattr(selectors, 'PollSelector'):
+ class PollEventLoopTests(UnixEventLoopTestsMixin,
+ SubprocessTestsMixin,
+ unittest.TestCase):
+
+ def create_event_loop(self):
+ return unix_events.SelectorEventLoop(selectors.PollSelector())
+
+ # Should always exist.
+ class SelectEventLoopTests(UnixEventLoopTestsMixin,
+ SubprocessTestsMixin,
+ unittest.TestCase):
+
+ def create_event_loop(self):
+ return unix_events.SelectorEventLoop(selectors.SelectSelector())
+
+
+class HandleTests(unittest.TestCase):
+
+ def test_handle(self):
+ def callback(*args):
+ return args
+
+ args = ()
+ h = events.Handle(callback, args)
+ self.assertIs(h._callback, callback)
+ self.assertIs(h._args, args)
+ self.assertFalse(h._cancelled)
+
+ r = repr(h)
+ self.assertTrue(r.startswith(
+ 'Handle('
+ '<function HandleTests.test_handle.<locals>.callback'))
+ self.assertTrue(r.endswith('())'))
+
+ h.cancel()
+ self.assertTrue(h._cancelled)
+
+ r = repr(h)
+ self.assertTrue(r.startswith(
+ 'Handle('
+ '<function HandleTests.test_handle.<locals>.callback'))
+ self.assertTrue(r.endswith('())<cancelled>'), r)
+
+ def test_make_handle(self):
+ def callback(*args):
+ return args
+ h1 = events.Handle(callback, ())
+ self.assertRaises(
+ AssertionError, events.make_handle, h1, ())
+
+ @unittest.mock.patch('asyncio.events.logger')
+ def test_callback_with_exception(self, log):
+ def callback():
+ raise ValueError()
+
+ h = events.Handle(callback, ())
+ h._run()
+ self.assertTrue(log.exception.called)
+
+
+class TimerTests(unittest.TestCase):
+
+ def test_hash(self):
+ when = time.monotonic()
+ h = events.TimerHandle(when, lambda: False, ())
+ self.assertEqual(hash(h), hash(when))
+
+ def test_timer(self):
+ def callback(*args):
+ return args
+
+ args = ()
+ when = time.monotonic()
+ h = events.TimerHandle(when, callback, args)
+ self.assertIs(h._callback, callback)
+ self.assertIs(h._args, args)
+ self.assertFalse(h._cancelled)
+
+ r = repr(h)
+ self.assertTrue(r.endswith('())'))
+
+ h.cancel()
+ self.assertTrue(h._cancelled)
+
+ r = repr(h)
+ self.assertTrue(r.endswith('())<cancelled>'), r)
+
+ self.assertRaises(AssertionError,
+ events.TimerHandle, None, callback, args)
+
+ def test_timer_comparison(self):
+ def callback(*args):
+ return args
+
+ when = time.monotonic()
+
+ h1 = events.TimerHandle(when, callback, ())
+ h2 = events.TimerHandle(when, callback, ())
+ # TODO: Use assertLess etc.
+ self.assertFalse(h1 < h2)
+ self.assertFalse(h2 < h1)
+ self.assertTrue(h1 <= h2)
+ self.assertTrue(h2 <= h1)
+ self.assertFalse(h1 > h2)
+ self.assertFalse(h2 > h1)
+ self.assertTrue(h1 >= h2)
+ self.assertTrue(h2 >= h1)
+ self.assertTrue(h1 == h2)
+ self.assertFalse(h1 != h2)
+
+ h2.cancel()
+ self.assertFalse(h1 == h2)
+
+ h1 = events.TimerHandle(when, callback, ())
+ h2 = events.TimerHandle(when + 10.0, callback, ())
+ self.assertTrue(h1 < h2)
+ self.assertFalse(h2 < h1)
+ self.assertTrue(h1 <= h2)
+ self.assertFalse(h2 <= h1)
+ self.assertFalse(h1 > h2)
+ self.assertTrue(h2 > h1)
+ self.assertFalse(h1 >= h2)
+ self.assertTrue(h2 >= h1)
+ self.assertFalse(h1 == h2)
+ self.assertTrue(h1 != h2)
+
+ h3 = events.Handle(callback, ())
+ self.assertIs(NotImplemented, h1.__eq__(h3))
+ self.assertIs(NotImplemented, h1.__ne__(h3))
+
+
+class AbstractEventLoopTests(unittest.TestCase):
+
+ def test_not_implemented(self):
+ f = unittest.mock.Mock()
+ loop = events.AbstractEventLoop()
+ self.assertRaises(
+ NotImplementedError, loop.run_forever)
+ self.assertRaises(
+ NotImplementedError, loop.run_until_complete, None)
+ self.assertRaises(
+ NotImplementedError, loop.stop)
+ self.assertRaises(
+ NotImplementedError, loop.is_running)
+ self.assertRaises(
+ NotImplementedError, loop.close)
+ self.assertRaises(
+ NotImplementedError, loop.call_later, None, None)
+ self.assertRaises(
+ NotImplementedError, loop.call_at, f, f)
+ self.assertRaises(
+ NotImplementedError, loop.call_soon, None)
+ self.assertRaises(
+ NotImplementedError, loop.time)
+ self.assertRaises(
+ NotImplementedError, loop.call_soon_threadsafe, None)
+ self.assertRaises(
+ NotImplementedError, loop.run_in_executor, f, f)
+ self.assertRaises(
+ NotImplementedError, loop.set_default_executor, f)
+ self.assertRaises(
+ NotImplementedError, loop.getaddrinfo, 'localhost', 8080)
+ self.assertRaises(
+ NotImplementedError, loop.getnameinfo, ('localhost', 8080))
+ self.assertRaises(
+ NotImplementedError, loop.create_connection, f)
+ self.assertRaises(
+ NotImplementedError, loop.create_server, f)
+ self.assertRaises(
+ NotImplementedError, loop.create_datagram_endpoint, f)
+ self.assertRaises(
+ NotImplementedError, loop.add_reader, 1, f)
+ self.assertRaises(
+ NotImplementedError, loop.remove_reader, 1)
+ self.assertRaises(
+ NotImplementedError, loop.add_writer, 1, f)
+ self.assertRaises(
+ NotImplementedError, loop.remove_writer, 1)
+ self.assertRaises(
+ NotImplementedError, loop.sock_recv, f, 10)
+ self.assertRaises(
+ NotImplementedError, loop.sock_sendall, f, 10)
+ self.assertRaises(
+ NotImplementedError, loop.sock_connect, f, f)
+ self.assertRaises(
+ NotImplementedError, loop.sock_accept, f)
+ self.assertRaises(
+ NotImplementedError, loop.add_signal_handler, 1, f)
+ self.assertRaises(
+ NotImplementedError, loop.remove_signal_handler, 1)
+ self.assertRaises(
+ NotImplementedError, loop.remove_signal_handler, 1)
+ self.assertRaises(
+ NotImplementedError, loop.connect_read_pipe, f,
+ unittest.mock.sentinel.pipe)
+ self.assertRaises(
+ NotImplementedError, loop.connect_write_pipe, f,
+ unittest.mock.sentinel.pipe)
+ self.assertRaises(
+ NotImplementedError, loop.subprocess_shell, f,
+ unittest.mock.sentinel)
+ self.assertRaises(
+ NotImplementedError, loop.subprocess_exec, f)
+
+
+class ProtocolsAbsTests(unittest.TestCase):
+
+ def test_empty(self):
+ f = unittest.mock.Mock()
+ p = protocols.Protocol()
+ self.assertIsNone(p.connection_made(f))
+ self.assertIsNone(p.connection_lost(f))
+ self.assertIsNone(p.data_received(f))
+ self.assertIsNone(p.eof_received())
+
+ dp = protocols.DatagramProtocol()
+ self.assertIsNone(dp.connection_made(f))
+ self.assertIsNone(dp.connection_lost(f))
+ self.assertIsNone(dp.error_received(f))
+ self.assertIsNone(dp.datagram_received(f, f))
+
+ sp = protocols.SubprocessProtocol()
+ self.assertIsNone(sp.connection_made(f))
+ self.assertIsNone(sp.connection_lost(f))
+ self.assertIsNone(sp.pipe_data_received(1, f))
+ self.assertIsNone(sp.pipe_connection_lost(1, f))
+ self.assertIsNone(sp.process_exited())
+
+
+class PolicyTests(unittest.TestCase):
+
+ def create_policy(self):
+ if sys.platform == "win32":
+ from asyncio import windows_events
+ return windows_events.DefaultEventLoopPolicy()
+ else:
+ from asyncio import unix_events
+ return unix_events.DefaultEventLoopPolicy()
+
+ def test_event_loop_policy(self):
+ policy = events.AbstractEventLoopPolicy()
+ self.assertRaises(NotImplementedError, policy.get_event_loop)
+ self.assertRaises(NotImplementedError, policy.set_event_loop, object())
+ self.assertRaises(NotImplementedError, policy.new_event_loop)
+ self.assertRaises(NotImplementedError, policy.get_child_watcher)
+ self.assertRaises(NotImplementedError, policy.set_child_watcher,
+ object())
+
+ def test_get_event_loop(self):
+ policy = self.create_policy()
+ self.assertIsNone(policy._local._loop)
+
+ loop = policy.get_event_loop()
+ self.assertIsInstance(loop, events.AbstractEventLoop)
+
+ self.assertIs(policy._local._loop, loop)
+ self.assertIs(loop, policy.get_event_loop())
+ loop.close()
+
+ def test_get_event_loop_calls_set_event_loop(self):
+ policy = self.create_policy()
+
+ with unittest.mock.patch.object(
+ policy, "set_event_loop",
+ wraps=policy.set_event_loop) as m_set_event_loop:
+
+ loop = policy.get_event_loop()
+
+ # policy._local._loop must be set through .set_event_loop()
+ # (the unix DefaultEventLoopPolicy needs this call to attach
+ # the child watcher correctly)
+ m_set_event_loop.assert_called_with(loop)
+
+ loop.close()
+
+ def test_get_event_loop_after_set_none(self):
+ policy = self.create_policy()
+ policy.set_event_loop(None)
+ self.assertRaises(AssertionError, policy.get_event_loop)
+
+ @unittest.mock.patch('asyncio.events.threading.current_thread')
+ def test_get_event_loop_thread(self, m_current_thread):
+
+ def f():
+ policy = self.create_policy()
+ self.assertRaises(AssertionError, policy.get_event_loop)
+
+ th = threading.Thread(target=f)
+ th.start()
+ th.join()
+
+ def test_new_event_loop(self):
+ policy = self.create_policy()
+
+ loop = policy.new_event_loop()
+ self.assertIsInstance(loop, events.AbstractEventLoop)
+ loop.close()
+
+ def test_set_event_loop(self):
+ policy = self.create_policy()
+ old_loop = policy.get_event_loop()
+
+ self.assertRaises(AssertionError, policy.set_event_loop, object())
+
+ loop = policy.new_event_loop()
+ policy.set_event_loop(loop)
+ self.assertIs(loop, policy.get_event_loop())
+ self.assertIsNot(old_loop, policy.get_event_loop())
+ loop.close()
+ old_loop.close()
+
+ def test_get_event_loop_policy(self):
+ policy = events.get_event_loop_policy()
+ self.assertIsInstance(policy, events.AbstractEventLoopPolicy)
+ self.assertIs(policy, events.get_event_loop_policy())
+
+ def test_set_event_loop_policy(self):
+ self.assertRaises(
+ AssertionError, events.set_event_loop_policy, object())
+
+ old_policy = events.get_event_loop_policy()
+
+ policy = self.create_policy()
+ events.set_event_loop_policy(policy)
+ self.assertIs(policy, events.get_event_loop_policy())
+ self.assertIsNot(policy, old_policy)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py
new file mode 100644
index 0000000000..e35fcf0708
--- /dev/null
+++ b/Lib/test/test_asyncio/test_futures.py
@@ -0,0 +1,347 @@
+"""Tests for futures.py."""
+
+import concurrent.futures
+import threading
+import unittest
+import unittest.mock
+
+from asyncio import events
+from asyncio import futures
+from asyncio import test_utils
+
+
+def _fakefunc(f):
+ return f
+
+
+class FutureTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+ def test_initial_state(self):
+ f = futures.Future(loop=self.loop)
+ self.assertFalse(f.cancelled())
+ self.assertFalse(f.done())
+ f.cancel()
+ self.assertTrue(f.cancelled())
+
+ def test_init_constructor_default_loop(self):
+ try:
+ events.set_event_loop(self.loop)
+ f = futures.Future()
+ self.assertIs(f._loop, self.loop)
+ finally:
+ events.set_event_loop(None)
+
+ def test_constructor_positional(self):
+ # Make sure Future does't accept a positional argument
+ self.assertRaises(TypeError, futures.Future, 42)
+
+ def test_cancel(self):
+ f = futures.Future(loop=self.loop)
+ self.assertTrue(f.cancel())
+ self.assertTrue(f.cancelled())
+ self.assertTrue(f.done())
+ self.assertRaises(futures.CancelledError, f.result)
+ self.assertRaises(futures.CancelledError, f.exception)
+ self.assertRaises(futures.InvalidStateError, f.set_result, None)
+ self.assertRaises(futures.InvalidStateError, f.set_exception, None)
+ self.assertFalse(f.cancel())
+
+ def test_result(self):
+ f = futures.Future(loop=self.loop)
+ self.assertRaises(futures.InvalidStateError, f.result)
+
+ f.set_result(42)
+ self.assertFalse(f.cancelled())
+ self.assertTrue(f.done())
+ self.assertEqual(f.result(), 42)
+ self.assertEqual(f.exception(), None)
+ self.assertRaises(futures.InvalidStateError, f.set_result, None)
+ self.assertRaises(futures.InvalidStateError, f.set_exception, None)
+ self.assertFalse(f.cancel())
+
+ def test_exception(self):
+ exc = RuntimeError()
+ f = futures.Future(loop=self.loop)
+ self.assertRaises(futures.InvalidStateError, f.exception)
+
+ f.set_exception(exc)
+ self.assertFalse(f.cancelled())
+ self.assertTrue(f.done())
+ self.assertRaises(RuntimeError, f.result)
+ self.assertEqual(f.exception(), exc)
+ self.assertRaises(futures.InvalidStateError, f.set_result, None)
+ self.assertRaises(futures.InvalidStateError, f.set_exception, None)
+ self.assertFalse(f.cancel())
+
+ def test_yield_from_twice(self):
+ f = futures.Future(loop=self.loop)
+
+ def fixture():
+ yield 'A'
+ x = yield from f
+ yield 'B', x
+ y = yield from f
+ yield 'C', y
+
+ g = fixture()
+ self.assertEqual(next(g), 'A') # yield 'A'.
+ self.assertEqual(next(g), f) # First yield from f.
+ f.set_result(42)
+ self.assertEqual(next(g), ('B', 42)) # yield 'B', x.
+ # The second "yield from f" does not yield f.
+ self.assertEqual(next(g), ('C', 42)) # yield 'C', y.
+
+ def test_repr(self):
+ f_pending = futures.Future(loop=self.loop)
+ self.assertEqual(repr(f_pending), 'Future<PENDING>')
+ f_pending.cancel()
+
+ f_cancelled = futures.Future(loop=self.loop)
+ f_cancelled.cancel()
+ self.assertEqual(repr(f_cancelled), 'Future<CANCELLED>')
+
+ f_result = futures.Future(loop=self.loop)
+ f_result.set_result(4)
+ self.assertEqual(repr(f_result), 'Future<result=4>')
+ self.assertEqual(f_result.result(), 4)
+
+ exc = RuntimeError()
+ f_exception = futures.Future(loop=self.loop)
+ f_exception.set_exception(exc)
+ self.assertEqual(repr(f_exception), 'Future<exception=RuntimeError()>')
+ self.assertIs(f_exception.exception(), exc)
+
+ f_few_callbacks = futures.Future(loop=self.loop)
+ f_few_callbacks.add_done_callback(_fakefunc)
+ self.assertIn('Future<PENDING, [<function _fakefunc',
+ repr(f_few_callbacks))
+ f_few_callbacks.cancel()
+
+ f_many_callbacks = futures.Future(loop=self.loop)
+ for i in range(20):
+ f_many_callbacks.add_done_callback(_fakefunc)
+ r = repr(f_many_callbacks)
+ self.assertIn('Future<PENDING, [<function _fakefunc', r)
+ self.assertIn('<18 more>', r)
+ f_many_callbacks.cancel()
+
+ def test_copy_state(self):
+ # Test the internal _copy_state method since it's being directly
+ # invoked in other modules.
+ f = futures.Future(loop=self.loop)
+ f.set_result(10)
+
+ newf = futures.Future(loop=self.loop)
+ newf._copy_state(f)
+ self.assertTrue(newf.done())
+ self.assertEqual(newf.result(), 10)
+
+ f_exception = futures.Future(loop=self.loop)
+ f_exception.set_exception(RuntimeError())
+
+ newf_exception = futures.Future(loop=self.loop)
+ newf_exception._copy_state(f_exception)
+ self.assertTrue(newf_exception.done())
+ self.assertRaises(RuntimeError, newf_exception.result)
+
+ f_cancelled = futures.Future(loop=self.loop)
+ f_cancelled.cancel()
+
+ newf_cancelled = futures.Future(loop=self.loop)
+ newf_cancelled._copy_state(f_cancelled)
+ self.assertTrue(newf_cancelled.cancelled())
+
+ def test_iter(self):
+ fut = futures.Future(loop=self.loop)
+
+ def coro():
+ yield from fut
+
+ def test():
+ arg1, arg2 = coro()
+
+ self.assertRaises(AssertionError, test)
+ fut.cancel()
+
+ @unittest.mock.patch('asyncio.futures.logger')
+ def test_tb_logger_abandoned(self, m_log):
+ fut = futures.Future(loop=self.loop)
+ del fut
+ self.assertFalse(m_log.error.called)
+
+ @unittest.mock.patch('asyncio.futures.logger')
+ def test_tb_logger_result_unretrieved(self, m_log):
+ fut = futures.Future(loop=self.loop)
+ fut.set_result(42)
+ del fut
+ self.assertFalse(m_log.error.called)
+
+ @unittest.mock.patch('asyncio.futures.logger')
+ def test_tb_logger_result_retrieved(self, m_log):
+ fut = futures.Future(loop=self.loop)
+ fut.set_result(42)
+ fut.result()
+ del fut
+ self.assertFalse(m_log.error.called)
+
+ @unittest.mock.patch('asyncio.futures.logger')
+ def test_tb_logger_exception_unretrieved(self, m_log):
+ fut = futures.Future(loop=self.loop)
+ fut.set_exception(RuntimeError('boom'))
+ del fut
+ test_utils.run_briefly(self.loop)
+ self.assertTrue(m_log.error.called)
+
+ @unittest.mock.patch('asyncio.futures.logger')
+ def test_tb_logger_exception_retrieved(self, m_log):
+ fut = futures.Future(loop=self.loop)
+ fut.set_exception(RuntimeError('boom'))
+ fut.exception()
+ del fut
+ self.assertFalse(m_log.error.called)
+
+ @unittest.mock.patch('asyncio.futures.logger')
+ def test_tb_logger_exception_result_retrieved(self, m_log):
+ fut = futures.Future(loop=self.loop)
+ fut.set_exception(RuntimeError('boom'))
+ self.assertRaises(RuntimeError, fut.result)
+ del fut
+ self.assertFalse(m_log.error.called)
+
+ def test_wrap_future(self):
+
+ def run(arg):
+ return (arg, threading.get_ident())
+ ex = concurrent.futures.ThreadPoolExecutor(1)
+ f1 = ex.submit(run, 'oi')
+ f2 = futures.wrap_future(f1, loop=self.loop)
+ res, ident = self.loop.run_until_complete(f2)
+ self.assertIsInstance(f2, futures.Future)
+ self.assertEqual(res, 'oi')
+ self.assertNotEqual(ident, threading.get_ident())
+
+ def test_wrap_future_future(self):
+ f1 = futures.Future(loop=self.loop)
+ f2 = futures.wrap_future(f1)
+ self.assertIs(f1, f2)
+
+ @unittest.mock.patch('asyncio.futures.events')
+ def test_wrap_future_use_global_loop(self, m_events):
+ def run(arg):
+ return (arg, threading.get_ident())
+ ex = concurrent.futures.ThreadPoolExecutor(1)
+ f1 = ex.submit(run, 'oi')
+ f2 = futures.wrap_future(f1)
+ self.assertIs(m_events.get_event_loop.return_value, f2._loop)
+
+ def test_wrap_future_cancel(self):
+ f1 = concurrent.futures.Future()
+ f2 = futures.wrap_future(f1, loop=self.loop)
+ f2.cancel()
+ test_utils.run_briefly(self.loop)
+ self.assertTrue(f1.cancelled())
+ self.assertTrue(f2.cancelled())
+
+ def test_wrap_future_cancel2(self):
+ f1 = concurrent.futures.Future()
+ f2 = futures.wrap_future(f1, loop=self.loop)
+ f1.set_result(42)
+ f2.cancel()
+ test_utils.run_briefly(self.loop)
+ self.assertFalse(f1.cancelled())
+ self.assertEqual(f1.result(), 42)
+ self.assertTrue(f2.cancelled())
+
+
+class FutureDoneCallbackTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+ def run_briefly(self):
+ test_utils.run_briefly(self.loop)
+
+ def _make_callback(self, bag, thing):
+ # Create a callback function that appends thing to bag.
+ def bag_appender(future):
+ bag.append(thing)
+ return bag_appender
+
+ def _new_future(self):
+ return futures.Future(loop=self.loop)
+
+ def test_callbacks_invoked_on_set_result(self):
+ bag = []
+ f = self._new_future()
+ f.add_done_callback(self._make_callback(bag, 42))
+ f.add_done_callback(self._make_callback(bag, 17))
+
+ self.assertEqual(bag, [])
+ f.set_result('foo')
+
+ self.run_briefly()
+
+ self.assertEqual(bag, [42, 17])
+ self.assertEqual(f.result(), 'foo')
+
+ def test_callbacks_invoked_on_set_exception(self):
+ bag = []
+ f = self._new_future()
+ f.add_done_callback(self._make_callback(bag, 100))
+
+ self.assertEqual(bag, [])
+ exc = RuntimeError()
+ f.set_exception(exc)
+
+ self.run_briefly()
+
+ self.assertEqual(bag, [100])
+ self.assertEqual(f.exception(), exc)
+
+ def test_remove_done_callback(self):
+ bag = []
+ f = self._new_future()
+ cb1 = self._make_callback(bag, 1)
+ cb2 = self._make_callback(bag, 2)
+ cb3 = self._make_callback(bag, 3)
+
+ # Add one cb1 and one cb2.
+ f.add_done_callback(cb1)
+ f.add_done_callback(cb2)
+
+ # One instance of cb2 removed. Now there's only one cb1.
+ self.assertEqual(f.remove_done_callback(cb2), 1)
+
+ # Never had any cb3 in there.
+ self.assertEqual(f.remove_done_callback(cb3), 0)
+
+ # After this there will be 6 instances of cb1 and one of cb2.
+ f.add_done_callback(cb2)
+ for i in range(5):
+ f.add_done_callback(cb1)
+
+ # Remove all instances of cb1. One cb2 remains.
+ self.assertEqual(f.remove_done_callback(cb1), 6)
+
+ self.assertEqual(bag, [])
+ f.set_result('foo')
+
+ self.run_briefly()
+
+ self.assertEqual(bag, [2])
+ self.assertEqual(f.result(), 'foo')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py
new file mode 100644
index 0000000000..df106e0d15
--- /dev/null
+++ b/Lib/test/test_asyncio/test_locks.py
@@ -0,0 +1,838 @@
+"""Tests for lock.py"""
+
+import unittest
+import unittest.mock
+import re
+
+from asyncio import events
+from asyncio import futures
+from asyncio import locks
+from asyncio import tasks
+from asyncio import test_utils
+
+
+STR_RGX_REPR = (
+ r'^<(?P<class>.*?) object at (?P<address>.*?)'
+ r'\[(?P<extras>'
+ r'(set|unset|locked|unlocked)(,value:\d)?(,waiters:\d+)?'
+ r')\]>\Z'
+)
+RGX_REPR = re.compile(STR_RGX_REPR)
+
+
+class LockTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+ def test_ctor_loop(self):
+ loop = unittest.mock.Mock()
+ lock = locks.Lock(loop=loop)
+ self.assertIs(lock._loop, loop)
+
+ lock = locks.Lock(loop=self.loop)
+ self.assertIs(lock._loop, self.loop)
+
+ def test_ctor_noloop(self):
+ try:
+ events.set_event_loop(self.loop)
+ lock = locks.Lock()
+ self.assertIs(lock._loop, self.loop)
+ finally:
+ events.set_event_loop(None)
+
+ def test_repr(self):
+ lock = locks.Lock(loop=self.loop)
+ self.assertTrue(repr(lock).endswith('[unlocked]>'))
+ self.assertTrue(RGX_REPR.match(repr(lock)))
+
+ @tasks.coroutine
+ def acquire_lock():
+ yield from lock
+
+ self.loop.run_until_complete(acquire_lock())
+ self.assertTrue(repr(lock).endswith('[locked]>'))
+ self.assertTrue(RGX_REPR.match(repr(lock)))
+
+ def test_lock(self):
+ lock = locks.Lock(loop=self.loop)
+
+ @tasks.coroutine
+ def acquire_lock():
+ return (yield from lock)
+
+ res = self.loop.run_until_complete(acquire_lock())
+
+ self.assertTrue(res)
+ self.assertTrue(lock.locked())
+
+ lock.release()
+ self.assertFalse(lock.locked())
+
+ def test_acquire(self):
+ lock = locks.Lock(loop=self.loop)
+ result = []
+
+ self.assertTrue(self.loop.run_until_complete(lock.acquire()))
+
+ @tasks.coroutine
+ def c1(result):
+ if (yield from lock.acquire()):
+ result.append(1)
+ return True
+
+ @tasks.coroutine
+ def c2(result):
+ if (yield from lock.acquire()):
+ result.append(2)
+ return True
+
+ @tasks.coroutine
+ def c3(result):
+ if (yield from lock.acquire()):
+ result.append(3)
+ return True
+
+ t1 = tasks.Task(c1(result), loop=self.loop)
+ t2 = tasks.Task(c2(result), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+
+ lock.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1], result)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1], result)
+
+ t3 = tasks.Task(c3(result), loop=self.loop)
+
+ lock.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1, 2], result)
+
+ lock.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1, 2, 3], result)
+
+ self.assertTrue(t1.done())
+ self.assertTrue(t1.result())
+ self.assertTrue(t2.done())
+ self.assertTrue(t2.result())
+ self.assertTrue(t3.done())
+ self.assertTrue(t3.result())
+
+ def test_acquire_cancel(self):
+ lock = locks.Lock(loop=self.loop)
+ self.assertTrue(self.loop.run_until_complete(lock.acquire()))
+
+ task = tasks.Task(lock.acquire(), loop=self.loop)
+ self.loop.call_soon(task.cancel)
+ self.assertRaises(
+ futures.CancelledError,
+ self.loop.run_until_complete, task)
+ self.assertFalse(lock._waiters)
+
+ def test_cancel_race(self):
+ # Several tasks:
+ # - A acquires the lock
+ # - B is blocked in aqcuire()
+ # - C is blocked in aqcuire()
+ #
+ # Now, concurrently:
+ # - B is cancelled
+ # - A releases the lock
+ #
+ # If B's waiter is marked cancelled but not yet removed from
+ # _waiters, A's release() call will crash when trying to set
+ # B's waiter; instead, it should move on to C's waiter.
+
+ # Setup: A has the lock, b and c are waiting.
+ lock = locks.Lock(loop=self.loop)
+
+ @tasks.coroutine
+ def lockit(name, blocker):
+ yield from lock.acquire()
+ try:
+ if blocker is not None:
+ yield from blocker
+ finally:
+ lock.release()
+
+ fa = futures.Future(loop=self.loop)
+ ta = tasks.Task(lockit('A', fa), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertTrue(lock.locked())
+ tb = tasks.Task(lockit('B', None), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(len(lock._waiters), 1)
+ tc = tasks.Task(lockit('C', None), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(len(lock._waiters), 2)
+
+ # Create the race and check.
+ # Without the fix this failed at the last assert.
+ fa.set_result(None)
+ tb.cancel()
+ self.assertTrue(lock._waiters[0].cancelled())
+ test_utils.run_briefly(self.loop)
+ self.assertFalse(lock.locked())
+ self.assertTrue(ta.done())
+ self.assertTrue(tb.cancelled())
+ self.assertTrue(tc.done())
+
+ def test_release_not_acquired(self):
+ lock = locks.Lock(loop=self.loop)
+
+ self.assertRaises(RuntimeError, lock.release)
+
+ def test_release_no_waiters(self):
+ lock = locks.Lock(loop=self.loop)
+ self.loop.run_until_complete(lock.acquire())
+ self.assertTrue(lock.locked())
+
+ lock.release()
+ self.assertFalse(lock.locked())
+
+ def test_context_manager(self):
+ lock = locks.Lock(loop=self.loop)
+
+ @tasks.coroutine
+ def acquire_lock():
+ return (yield from lock)
+
+ with self.loop.run_until_complete(acquire_lock()):
+ self.assertTrue(lock.locked())
+
+ self.assertFalse(lock.locked())
+
+ def test_context_manager_no_yield(self):
+ lock = locks.Lock(loop=self.loop)
+
+ try:
+ with lock:
+ self.fail('RuntimeError is not raised in with expression')
+ except RuntimeError as err:
+ self.assertEqual(
+ str(err),
+ '"yield from" should be used as context manager expression')
+
+
+class EventTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+ def test_ctor_loop(self):
+ loop = unittest.mock.Mock()
+ ev = locks.Event(loop=loop)
+ self.assertIs(ev._loop, loop)
+
+ ev = locks.Event(loop=self.loop)
+ self.assertIs(ev._loop, self.loop)
+
+ def test_ctor_noloop(self):
+ try:
+ events.set_event_loop(self.loop)
+ ev = locks.Event()
+ self.assertIs(ev._loop, self.loop)
+ finally:
+ events.set_event_loop(None)
+
+ def test_repr(self):
+ ev = locks.Event(loop=self.loop)
+ self.assertTrue(repr(ev).endswith('[unset]>'))
+ match = RGX_REPR.match(repr(ev))
+ self.assertEqual(match.group('extras'), 'unset')
+
+ ev.set()
+ self.assertTrue(repr(ev).endswith('[set]>'))
+ self.assertTrue(RGX_REPR.match(repr(ev)))
+
+ ev._waiters.append(unittest.mock.Mock())
+ self.assertTrue('waiters:1' in repr(ev))
+ self.assertTrue(RGX_REPR.match(repr(ev)))
+
+ def test_wait(self):
+ ev = locks.Event(loop=self.loop)
+ self.assertFalse(ev.is_set())
+
+ result = []
+
+ @tasks.coroutine
+ def c1(result):
+ if (yield from ev.wait()):
+ result.append(1)
+
+ @tasks.coroutine
+ def c2(result):
+ if (yield from ev.wait()):
+ result.append(2)
+
+ @tasks.coroutine
+ def c3(result):
+ if (yield from ev.wait()):
+ result.append(3)
+
+ t1 = tasks.Task(c1(result), loop=self.loop)
+ t2 = tasks.Task(c2(result), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+
+ t3 = tasks.Task(c3(result), loop=self.loop)
+
+ ev.set()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([3, 1, 2], result)
+
+ self.assertTrue(t1.done())
+ self.assertIsNone(t1.result())
+ self.assertTrue(t2.done())
+ self.assertIsNone(t2.result())
+ self.assertTrue(t3.done())
+ self.assertIsNone(t3.result())
+
+ def test_wait_on_set(self):
+ ev = locks.Event(loop=self.loop)
+ ev.set()
+
+ res = self.loop.run_until_complete(ev.wait())
+ self.assertTrue(res)
+
+ def test_wait_cancel(self):
+ ev = locks.Event(loop=self.loop)
+
+ wait = tasks.Task(ev.wait(), loop=self.loop)
+ self.loop.call_soon(wait.cancel)
+ self.assertRaises(
+ futures.CancelledError,
+ self.loop.run_until_complete, wait)
+ self.assertFalse(ev._waiters)
+
+ def test_clear(self):
+ ev = locks.Event(loop=self.loop)
+ self.assertFalse(ev.is_set())
+
+ ev.set()
+ self.assertTrue(ev.is_set())
+
+ ev.clear()
+ self.assertFalse(ev.is_set())
+
+ def test_clear_with_waiters(self):
+ ev = locks.Event(loop=self.loop)
+ result = []
+
+ @tasks.coroutine
+ def c1(result):
+ if (yield from ev.wait()):
+ result.append(1)
+ return True
+
+ t = tasks.Task(c1(result), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+
+ ev.set()
+ ev.clear()
+ self.assertFalse(ev.is_set())
+
+ ev.set()
+ ev.set()
+ self.assertEqual(1, len(ev._waiters))
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1], result)
+ self.assertEqual(0, len(ev._waiters))
+
+ self.assertTrue(t.done())
+ self.assertTrue(t.result())
+
+
+class ConditionTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+ def test_ctor_loop(self):
+ loop = unittest.mock.Mock()
+ cond = locks.Condition(loop=loop)
+ self.assertIs(cond._loop, loop)
+
+ cond = locks.Condition(loop=self.loop)
+ self.assertIs(cond._loop, self.loop)
+
+ def test_ctor_noloop(self):
+ try:
+ events.set_event_loop(self.loop)
+ cond = locks.Condition()
+ self.assertIs(cond._loop, self.loop)
+ finally:
+ events.set_event_loop(None)
+
+ def test_wait(self):
+ cond = locks.Condition(loop=self.loop)
+ result = []
+
+ @tasks.coroutine
+ def c1(result):
+ yield from cond.acquire()
+ if (yield from cond.wait()):
+ result.append(1)
+ return True
+
+ @tasks.coroutine
+ def c2(result):
+ yield from cond.acquire()
+ if (yield from cond.wait()):
+ result.append(2)
+ return True
+
+ @tasks.coroutine
+ def c3(result):
+ yield from cond.acquire()
+ if (yield from cond.wait()):
+ result.append(3)
+ return True
+
+ t1 = tasks.Task(c1(result), loop=self.loop)
+ t2 = tasks.Task(c2(result), loop=self.loop)
+ t3 = tasks.Task(c3(result), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+ self.assertFalse(cond.locked())
+
+ self.assertTrue(self.loop.run_until_complete(cond.acquire()))
+ cond.notify()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+ self.assertTrue(cond.locked())
+
+ cond.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1], result)
+ self.assertTrue(cond.locked())
+
+ cond.notify(2)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1], result)
+ self.assertTrue(cond.locked())
+
+ cond.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1, 2], result)
+ self.assertTrue(cond.locked())
+
+ cond.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1, 2, 3], result)
+ self.assertTrue(cond.locked())
+
+ self.assertTrue(t1.done())
+ self.assertTrue(t1.result())
+ self.assertTrue(t2.done())
+ self.assertTrue(t2.result())
+ self.assertTrue(t3.done())
+ self.assertTrue(t3.result())
+
+ def test_wait_cancel(self):
+ cond = locks.Condition(loop=self.loop)
+ self.loop.run_until_complete(cond.acquire())
+
+ wait = tasks.Task(cond.wait(), loop=self.loop)
+ self.loop.call_soon(wait.cancel)
+ self.assertRaises(
+ futures.CancelledError,
+ self.loop.run_until_complete, wait)
+ self.assertFalse(cond._waiters)
+ self.assertTrue(cond.locked())
+
+ def test_wait_unacquired(self):
+ cond = locks.Condition(loop=self.loop)
+ self.assertRaises(
+ RuntimeError,
+ self.loop.run_until_complete, cond.wait())
+
+ def test_wait_for(self):
+ cond = locks.Condition(loop=self.loop)
+ presult = False
+
+ def predicate():
+ return presult
+
+ result = []
+
+ @tasks.coroutine
+ def c1(result):
+ yield from cond.acquire()
+ if (yield from cond.wait_for(predicate)):
+ result.append(1)
+ cond.release()
+ return True
+
+ t = tasks.Task(c1(result), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+
+ self.loop.run_until_complete(cond.acquire())
+ cond.notify()
+ cond.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+
+ presult = True
+ self.loop.run_until_complete(cond.acquire())
+ cond.notify()
+ cond.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1], result)
+
+ self.assertTrue(t.done())
+ self.assertTrue(t.result())
+
+ def test_wait_for_unacquired(self):
+ cond = locks.Condition(loop=self.loop)
+
+ # predicate can return true immediately
+ res = self.loop.run_until_complete(cond.wait_for(lambda: [1, 2, 3]))
+ self.assertEqual([1, 2, 3], res)
+
+ self.assertRaises(
+ RuntimeError,
+ self.loop.run_until_complete,
+ cond.wait_for(lambda: False))
+
+ def test_notify(self):
+ cond = locks.Condition(loop=self.loop)
+ result = []
+
+ @tasks.coroutine
+ def c1(result):
+ yield from cond.acquire()
+ if (yield from cond.wait()):
+ result.append(1)
+ cond.release()
+ return True
+
+ @tasks.coroutine
+ def c2(result):
+ yield from cond.acquire()
+ if (yield from cond.wait()):
+ result.append(2)
+ cond.release()
+ return True
+
+ @tasks.coroutine
+ def c3(result):
+ yield from cond.acquire()
+ if (yield from cond.wait()):
+ result.append(3)
+ cond.release()
+ return True
+
+ t1 = tasks.Task(c1(result), loop=self.loop)
+ t2 = tasks.Task(c2(result), loop=self.loop)
+ t3 = tasks.Task(c3(result), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+
+ self.loop.run_until_complete(cond.acquire())
+ cond.notify(1)
+ cond.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1], result)
+
+ self.loop.run_until_complete(cond.acquire())
+ cond.notify(1)
+ cond.notify(2048)
+ cond.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1, 2, 3], result)
+
+ self.assertTrue(t1.done())
+ self.assertTrue(t1.result())
+ self.assertTrue(t2.done())
+ self.assertTrue(t2.result())
+ self.assertTrue(t3.done())
+ self.assertTrue(t3.result())
+
+ def test_notify_all(self):
+ cond = locks.Condition(loop=self.loop)
+
+ result = []
+
+ @tasks.coroutine
+ def c1(result):
+ yield from cond.acquire()
+ if (yield from cond.wait()):
+ result.append(1)
+ cond.release()
+ return True
+
+ @tasks.coroutine
+ def c2(result):
+ yield from cond.acquire()
+ if (yield from cond.wait()):
+ result.append(2)
+ cond.release()
+ return True
+
+ t1 = tasks.Task(c1(result), loop=self.loop)
+ t2 = tasks.Task(c2(result), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([], result)
+
+ self.loop.run_until_complete(cond.acquire())
+ cond.notify_all()
+ cond.release()
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1, 2], result)
+
+ self.assertTrue(t1.done())
+ self.assertTrue(t1.result())
+ self.assertTrue(t2.done())
+ self.assertTrue(t2.result())
+
+ def test_notify_unacquired(self):
+ cond = locks.Condition(loop=self.loop)
+ self.assertRaises(RuntimeError, cond.notify)
+
+ def test_notify_all_unacquired(self):
+ cond = locks.Condition(loop=self.loop)
+ self.assertRaises(RuntimeError, cond.notify_all)
+
+ def test_repr(self):
+ cond = locks.Condition(loop=self.loop)
+ self.assertTrue('unlocked' in repr(cond))
+ self.assertTrue(RGX_REPR.match(repr(cond)))
+
+ self.loop.run_until_complete(cond.acquire())
+ self.assertTrue('locked' in repr(cond))
+
+ cond._waiters.append(unittest.mock.Mock())
+ self.assertTrue('waiters:1' in repr(cond))
+ self.assertTrue(RGX_REPR.match(repr(cond)))
+
+ cond._waiters.append(unittest.mock.Mock())
+ self.assertTrue('waiters:2' in repr(cond))
+ self.assertTrue(RGX_REPR.match(repr(cond)))
+
+ def test_context_manager(self):
+ cond = locks.Condition(loop=self.loop)
+
+ @tasks.coroutine
+ def acquire_cond():
+ return (yield from cond)
+
+ with self.loop.run_until_complete(acquire_cond()):
+ self.assertTrue(cond.locked())
+
+ self.assertFalse(cond.locked())
+
+ def test_context_manager_no_yield(self):
+ cond = locks.Condition(loop=self.loop)
+
+ try:
+ with cond:
+ self.fail('RuntimeError is not raised in with expression')
+ except RuntimeError as err:
+ self.assertEqual(
+ str(err),
+ '"yield from" should be used as context manager expression')
+
+
+class SemaphoreTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+ def test_ctor_loop(self):
+ loop = unittest.mock.Mock()
+ sem = locks.Semaphore(loop=loop)
+ self.assertIs(sem._loop, loop)
+
+ sem = locks.Semaphore(loop=self.loop)
+ self.assertIs(sem._loop, self.loop)
+
+ def test_ctor_noloop(self):
+ try:
+ events.set_event_loop(self.loop)
+ sem = locks.Semaphore()
+ self.assertIs(sem._loop, self.loop)
+ finally:
+ events.set_event_loop(None)
+
+ def test_initial_value_zero(self):
+ sem = locks.Semaphore(0, loop=self.loop)
+ self.assertTrue(sem.locked())
+
+ def test_repr(self):
+ sem = locks.Semaphore(loop=self.loop)
+ self.assertTrue(repr(sem).endswith('[unlocked,value:1]>'))
+ self.assertTrue(RGX_REPR.match(repr(sem)))
+
+ self.loop.run_until_complete(sem.acquire())
+ self.assertTrue(repr(sem).endswith('[locked]>'))
+ self.assertTrue('waiters' not in repr(sem))
+ self.assertTrue(RGX_REPR.match(repr(sem)))
+
+ sem._waiters.append(unittest.mock.Mock())
+ self.assertTrue('waiters:1' in repr(sem))
+ self.assertTrue(RGX_REPR.match(repr(sem)))
+
+ sem._waiters.append(unittest.mock.Mock())
+ self.assertTrue('waiters:2' in repr(sem))
+ self.assertTrue(RGX_REPR.match(repr(sem)))
+
+ def test_semaphore(self):
+ sem = locks.Semaphore(loop=self.loop)
+ self.assertEqual(1, sem._value)
+
+ @tasks.coroutine
+ def acquire_lock():
+ return (yield from sem)
+
+ res = self.loop.run_until_complete(acquire_lock())
+
+ self.assertTrue(res)
+ self.assertTrue(sem.locked())
+ self.assertEqual(0, sem._value)
+
+ sem.release()
+ self.assertFalse(sem.locked())
+ self.assertEqual(1, sem._value)
+
+ def test_semaphore_value(self):
+ self.assertRaises(ValueError, locks.Semaphore, -1)
+
+ def test_acquire(self):
+ sem = locks.Semaphore(3, loop=self.loop)
+ result = []
+
+ self.assertTrue(self.loop.run_until_complete(sem.acquire()))
+ self.assertTrue(self.loop.run_until_complete(sem.acquire()))
+ self.assertFalse(sem.locked())
+
+ @tasks.coroutine
+ def c1(result):
+ yield from sem.acquire()
+ result.append(1)
+ return True
+
+ @tasks.coroutine
+ def c2(result):
+ yield from sem.acquire()
+ result.append(2)
+ return True
+
+ @tasks.coroutine
+ def c3(result):
+ yield from sem.acquire()
+ result.append(3)
+ return True
+
+ @tasks.coroutine
+ def c4(result):
+ yield from sem.acquire()
+ result.append(4)
+ return True
+
+ t1 = tasks.Task(c1(result), loop=self.loop)
+ t2 = tasks.Task(c2(result), loop=self.loop)
+ t3 = tasks.Task(c3(result), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual([1], result)
+ self.assertTrue(sem.locked())
+ self.assertEqual(2, len(sem._waiters))
+ self.assertEqual(0, sem._value)
+
+ t4 = tasks.Task(c4(result), loop=self.loop)
+
+ sem.release()
+ sem.release()
+ self.assertEqual(2, sem._value)
+
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(0, sem._value)
+ self.assertEqual([1, 2, 3], result)
+ self.assertTrue(sem.locked())
+ self.assertEqual(1, len(sem._waiters))
+ self.assertEqual(0, sem._value)
+
+ self.assertTrue(t1.done())
+ self.assertTrue(t1.result())
+ self.assertTrue(t2.done())
+ self.assertTrue(t2.result())
+ self.assertTrue(t3.done())
+ self.assertTrue(t3.result())
+ self.assertFalse(t4.done())
+
+ # cleanup locked semaphore
+ sem.release()
+
+ def test_acquire_cancel(self):
+ sem = locks.Semaphore(loop=self.loop)
+ self.loop.run_until_complete(sem.acquire())
+
+ acquire = tasks.Task(sem.acquire(), loop=self.loop)
+ self.loop.call_soon(acquire.cancel)
+ self.assertRaises(
+ futures.CancelledError,
+ self.loop.run_until_complete, acquire)
+ self.assertFalse(sem._waiters)
+
+ def test_release_not_acquired(self):
+ sem = locks.BoundedSemaphore(loop=self.loop)
+
+ self.assertRaises(ValueError, sem.release)
+
+ def test_release_no_waiters(self):
+ sem = locks.Semaphore(loop=self.loop)
+ self.loop.run_until_complete(sem.acquire())
+ self.assertTrue(sem.locked())
+
+ sem.release()
+ self.assertFalse(sem.locked())
+
+ def test_context_manager(self):
+ sem = locks.Semaphore(2, loop=self.loop)
+
+ @tasks.coroutine
+ def acquire_lock():
+ return (yield from sem)
+
+ with self.loop.run_until_complete(acquire_lock()):
+ self.assertFalse(sem.locked())
+ self.assertEqual(1, sem._value)
+
+ with self.loop.run_until_complete(acquire_lock()):
+ self.assertTrue(sem.locked())
+
+ self.assertEqual(2, sem._value)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_proactor_events.py b/Lib/test/test_asyncio/test_proactor_events.py
new file mode 100644
index 0000000000..5a2a51c42e
--- /dev/null
+++ b/Lib/test/test_asyncio/test_proactor_events.py
@@ -0,0 +1,484 @@
+"""Tests for proactor_events.py"""
+
+import socket
+import unittest
+import unittest.mock
+
+import asyncio
+from asyncio.proactor_events import BaseProactorEventLoop
+from asyncio.proactor_events import _ProactorSocketTransport
+from asyncio.proactor_events import _ProactorWritePipeTransport
+from asyncio.proactor_events import _ProactorDuplexPipeTransport
+from asyncio import test_utils
+
+
+class ProactorSocketTransportTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ self.proactor = unittest.mock.Mock()
+ self.loop._proactor = self.proactor
+ self.protocol = test_utils.make_test_protocol(asyncio.Protocol)
+ self.sock = unittest.mock.Mock(socket.socket)
+
+ def test_ctor(self):
+ fut = asyncio.Future(loop=self.loop)
+ tr = _ProactorSocketTransport(
+ self.loop, self.sock, self.protocol, fut)
+ test_utils.run_briefly(self.loop)
+ self.assertIsNone(fut.result())
+ self.protocol.connection_made(tr)
+ self.proactor.recv.assert_called_with(self.sock, 4096)
+
+ def test_loop_reading(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._loop_reading()
+ self.loop._proactor.recv.assert_called_with(self.sock, 4096)
+ self.assertFalse(self.protocol.data_received.called)
+ self.assertFalse(self.protocol.eof_received.called)
+
+ def test_loop_reading_data(self):
+ res = asyncio.Future(loop=self.loop)
+ res.set_result(b'data')
+
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+
+ tr._read_fut = res
+ tr._loop_reading(res)
+ self.loop._proactor.recv.assert_called_with(self.sock, 4096)
+ self.protocol.data_received.assert_called_with(b'data')
+
+ def test_loop_reading_no_data(self):
+ res = asyncio.Future(loop=self.loop)
+ res.set_result(b'')
+
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+
+ self.assertRaises(AssertionError, tr._loop_reading, res)
+
+ tr.close = unittest.mock.Mock()
+ tr._read_fut = res
+ tr._loop_reading(res)
+ self.assertFalse(self.loop._proactor.recv.called)
+ self.assertTrue(self.protocol.eof_received.called)
+ self.assertTrue(tr.close.called)
+
+ def test_loop_reading_aborted(self):
+ err = self.loop._proactor.recv.side_effect = ConnectionAbortedError()
+
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._fatal_error = unittest.mock.Mock()
+ tr._loop_reading()
+ tr._fatal_error.assert_called_with(err)
+
+ def test_loop_reading_aborted_closing(self):
+ self.loop._proactor.recv.side_effect = ConnectionAbortedError()
+
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._closing = True
+ tr._fatal_error = unittest.mock.Mock()
+ tr._loop_reading()
+ self.assertFalse(tr._fatal_error.called)
+
+ def test_loop_reading_aborted_is_fatal(self):
+ self.loop._proactor.recv.side_effect = ConnectionAbortedError()
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._closing = False
+ tr._fatal_error = unittest.mock.Mock()
+ tr._loop_reading()
+ self.assertTrue(tr._fatal_error.called)
+
+ def test_loop_reading_conn_reset_lost(self):
+ err = self.loop._proactor.recv.side_effect = ConnectionResetError()
+
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._closing = False
+ tr._fatal_error = unittest.mock.Mock()
+ tr._force_close = unittest.mock.Mock()
+ tr._loop_reading()
+ self.assertFalse(tr._fatal_error.called)
+ tr._force_close.assert_called_with(err)
+
+ def test_loop_reading_exception(self):
+ err = self.loop._proactor.recv.side_effect = (OSError())
+
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._fatal_error = unittest.mock.Mock()
+ tr._loop_reading()
+ tr._fatal_error.assert_called_with(err)
+
+ def test_write(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._loop_writing = unittest.mock.Mock()
+ tr.write(b'data')
+ self.assertEqual(tr._buffer, [b'data'])
+ self.assertTrue(tr._loop_writing.called)
+
+ def test_write_no_data(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr.write(b'')
+ self.assertFalse(tr._buffer)
+
+ def test_write_more(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._write_fut = unittest.mock.Mock()
+ tr._loop_writing = unittest.mock.Mock()
+ tr.write(b'data')
+ self.assertEqual(tr._buffer, [b'data'])
+ self.assertFalse(tr._loop_writing.called)
+
+ def test_loop_writing(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._buffer = [b'da', b'ta']
+ tr._loop_writing()
+ self.loop._proactor.send.assert_called_with(self.sock, b'data')
+ self.loop._proactor.send.return_value.add_done_callback.\
+ assert_called_with(tr._loop_writing)
+
+ @unittest.mock.patch('asyncio.proactor_events.logger')
+ def test_loop_writing_err(self, m_log):
+ err = self.loop._proactor.send.side_effect = OSError()
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._fatal_error = unittest.mock.Mock()
+ tr._buffer = [b'da', b'ta']
+ tr._loop_writing()
+ tr._fatal_error.assert_called_with(err)
+ tr._conn_lost = 1
+
+ tr.write(b'data')
+ tr.write(b'data')
+ tr.write(b'data')
+ tr.write(b'data')
+ tr.write(b'data')
+ self.assertEqual(tr._buffer, [])
+ m_log.warning.assert_called_with('socket.send() raised exception.')
+
+ def test_loop_writing_stop(self):
+ fut = asyncio.Future(loop=self.loop)
+ fut.set_result(b'data')
+
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._write_fut = fut
+ tr._loop_writing(fut)
+ self.assertIsNone(tr._write_fut)
+
+ def test_loop_writing_closing(self):
+ fut = asyncio.Future(loop=self.loop)
+ fut.set_result(1)
+
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._write_fut = fut
+ tr.close()
+ tr._loop_writing(fut)
+ self.assertIsNone(tr._write_fut)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(None)
+
+ def test_abort(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._force_close = unittest.mock.Mock()
+ tr.abort()
+ tr._force_close.assert_called_with(None)
+
+ def test_close(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr.close()
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(None)
+ self.assertTrue(tr._closing)
+ self.assertEqual(tr._conn_lost, 1)
+
+ self.protocol.connection_lost.reset_mock()
+ tr.close()
+ test_utils.run_briefly(self.loop)
+ self.assertFalse(self.protocol.connection_lost.called)
+
+ def test_close_write_fut(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._write_fut = unittest.mock.Mock()
+ tr.close()
+ test_utils.run_briefly(self.loop)
+ self.assertFalse(self.protocol.connection_lost.called)
+
+ def test_close_buffer(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._buffer = [b'data']
+ tr.close()
+ test_utils.run_briefly(self.loop)
+ self.assertFalse(self.protocol.connection_lost.called)
+
+ @unittest.mock.patch('asyncio.proactor_events.logger')
+ def test_fatal_error(self, m_logging):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._force_close = unittest.mock.Mock()
+ tr._fatal_error(None)
+ self.assertTrue(tr._force_close.called)
+ self.assertTrue(m_logging.exception.called)
+
+ def test_force_close(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._buffer = [b'data']
+ read_fut = tr._read_fut = unittest.mock.Mock()
+ write_fut = tr._write_fut = unittest.mock.Mock()
+ tr._force_close(None)
+
+ read_fut.cancel.assert_called_with()
+ write_fut.cancel.assert_called_with()
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(None)
+ self.assertEqual([], tr._buffer)
+ self.assertEqual(tr._conn_lost, 1)
+
+ def test_force_close_idempotent(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._closing = True
+ tr._force_close(None)
+ test_utils.run_briefly(self.loop)
+ self.assertFalse(self.protocol.connection_lost.called)
+
+ def test_fatal_error_2(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._buffer = [b'data']
+ tr._force_close(None)
+
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(None)
+ self.assertEqual([], tr._buffer)
+
+ def test_call_connection_lost(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ tr._call_connection_lost(None)
+ self.assertTrue(self.protocol.connection_lost.called)
+ self.assertTrue(self.sock.close.called)
+
+ def test_write_eof(self):
+ tr = _ProactorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ self.assertTrue(tr.can_write_eof())
+ tr.write_eof()
+ self.sock.shutdown.assert_called_with(socket.SHUT_WR)
+ tr.write_eof()
+ self.assertEqual(self.sock.shutdown.call_count, 1)
+ tr.close()
+
+ def test_write_eof_buffer(self):
+ tr = _ProactorSocketTransport(self.loop, self.sock, self.protocol)
+ f = asyncio.Future(loop=self.loop)
+ tr._loop._proactor.send.return_value = f
+ tr.write(b'data')
+ tr.write_eof()
+ self.assertTrue(tr._eof_written)
+ self.assertFalse(self.sock.shutdown.called)
+ tr._loop._proactor.send.assert_called_with(self.sock, b'data')
+ f.set_result(4)
+ self.loop._run_once()
+ self.sock.shutdown.assert_called_with(socket.SHUT_WR)
+ tr.close()
+
+ def test_write_eof_write_pipe(self):
+ tr = _ProactorWritePipeTransport(
+ self.loop, self.sock, self.protocol)
+ self.assertTrue(tr.can_write_eof())
+ tr.write_eof()
+ self.assertTrue(tr._closing)
+ self.loop._run_once()
+ self.assertTrue(self.sock.close.called)
+ tr.close()
+
+ def test_write_eof_buffer_write_pipe(self):
+ tr = _ProactorWritePipeTransport(self.loop, self.sock, self.protocol)
+ f = asyncio.Future(loop=self.loop)
+ tr._loop._proactor.send.return_value = f
+ tr.write(b'data')
+ tr.write_eof()
+ self.assertTrue(tr._closing)
+ self.assertFalse(self.sock.shutdown.called)
+ tr._loop._proactor.send.assert_called_with(self.sock, b'data')
+ f.set_result(4)
+ self.loop._run_once()
+ self.loop._run_once()
+ self.assertTrue(self.sock.close.called)
+ tr.close()
+
+ def test_write_eof_duplex_pipe(self):
+ tr = _ProactorDuplexPipeTransport(
+ self.loop, self.sock, self.protocol)
+ self.assertFalse(tr.can_write_eof())
+ with self.assertRaises(NotImplementedError):
+ tr.write_eof()
+ tr.close()
+
+ def test_pause_resume_reading(self):
+ tr = _ProactorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ futures = []
+ for msg in [b'data1', b'data2', b'data3', b'data4', b'']:
+ f = asyncio.Future(loop=self.loop)
+ f.set_result(msg)
+ futures.append(f)
+ self.loop._proactor.recv.side_effect = futures
+ self.loop._run_once()
+ self.assertFalse(tr._paused)
+ self.loop._run_once()
+ self.protocol.data_received.assert_called_with(b'data1')
+ self.loop._run_once()
+ self.protocol.data_received.assert_called_with(b'data2')
+ tr.pause_reading()
+ self.assertTrue(tr._paused)
+ for i in range(10):
+ self.loop._run_once()
+ self.protocol.data_received.assert_called_with(b'data2')
+ tr.resume_reading()
+ self.assertFalse(tr._paused)
+ self.loop._run_once()
+ self.protocol.data_received.assert_called_with(b'data3')
+ self.loop._run_once()
+ self.protocol.data_received.assert_called_with(b'data4')
+ tr.close()
+
+
+class BaseProactorEventLoopTests(unittest.TestCase):
+
+ def setUp(self):
+ self.sock = unittest.mock.Mock(socket.socket)
+ self.proactor = unittest.mock.Mock()
+
+ self.ssock, self.csock = unittest.mock.Mock(), unittest.mock.Mock()
+
+ class EventLoop(BaseProactorEventLoop):
+ def _socketpair(s):
+ return (self.ssock, self.csock)
+
+ self.loop = EventLoop(self.proactor)
+
+ @unittest.mock.patch.object(BaseProactorEventLoop, 'call_soon')
+ @unittest.mock.patch.object(BaseProactorEventLoop, '_socketpair')
+ def test_ctor(self, socketpair, call_soon):
+ ssock, csock = socketpair.return_value = (
+ unittest.mock.Mock(), unittest.mock.Mock())
+ loop = BaseProactorEventLoop(self.proactor)
+ self.assertIs(loop._ssock, ssock)
+ self.assertIs(loop._csock, csock)
+ self.assertEqual(loop._internal_fds, 1)
+ call_soon.assert_called_with(loop._loop_self_reading)
+
+ def test_close_self_pipe(self):
+ self.loop._close_self_pipe()
+ self.assertEqual(self.loop._internal_fds, 0)
+ self.assertTrue(self.ssock.close.called)
+ self.assertTrue(self.csock.close.called)
+ self.assertIsNone(self.loop._ssock)
+ self.assertIsNone(self.loop._csock)
+
+ def test_close(self):
+ self.loop._close_self_pipe = unittest.mock.Mock()
+ self.loop.close()
+ self.assertTrue(self.loop._close_self_pipe.called)
+ self.assertTrue(self.proactor.close.called)
+ self.assertIsNone(self.loop._proactor)
+
+ self.loop._close_self_pipe.reset_mock()
+ self.loop.close()
+ self.assertFalse(self.loop._close_self_pipe.called)
+
+ def test_sock_recv(self):
+ self.loop.sock_recv(self.sock, 1024)
+ self.proactor.recv.assert_called_with(self.sock, 1024)
+
+ def test_sock_sendall(self):
+ self.loop.sock_sendall(self.sock, b'data')
+ self.proactor.send.assert_called_with(self.sock, b'data')
+
+ def test_sock_connect(self):
+ self.loop.sock_connect(self.sock, 123)
+ self.proactor.connect.assert_called_with(self.sock, 123)
+
+ def test_sock_accept(self):
+ self.loop.sock_accept(self.sock)
+ self.proactor.accept.assert_called_with(self.sock)
+
+ def test_socketpair(self):
+ self.assertRaises(
+ NotImplementedError, BaseProactorEventLoop, self.proactor)
+
+ def test_make_socket_transport(self):
+ tr = self.loop._make_socket_transport(self.sock, unittest.mock.Mock())
+ self.assertIsInstance(tr, _ProactorSocketTransport)
+
+ def test_loop_self_reading(self):
+ self.loop._loop_self_reading()
+ self.proactor.recv.assert_called_with(self.ssock, 4096)
+ self.proactor.recv.return_value.add_done_callback.assert_called_with(
+ self.loop._loop_self_reading)
+
+ def test_loop_self_reading_fut(self):
+ fut = unittest.mock.Mock()
+ self.loop._loop_self_reading(fut)
+ self.assertTrue(fut.result.called)
+ self.proactor.recv.assert_called_with(self.ssock, 4096)
+ self.proactor.recv.return_value.add_done_callback.assert_called_with(
+ self.loop._loop_self_reading)
+
+ def test_loop_self_reading_exception(self):
+ self.loop.close = unittest.mock.Mock()
+ self.proactor.recv.side_effect = OSError()
+ self.assertRaises(OSError, self.loop._loop_self_reading)
+ self.assertTrue(self.loop.close.called)
+
+ def test_write_to_self(self):
+ self.loop._write_to_self()
+ self.csock.send.assert_called_with(b'x')
+
+ def test_process_events(self):
+ self.loop._process_events([])
+
+ @unittest.mock.patch('asyncio.proactor_events.logger')
+ def test_create_server(self, m_log):
+ pf = unittest.mock.Mock()
+ call_soon = self.loop.call_soon = unittest.mock.Mock()
+
+ self.loop._start_serving(pf, self.sock)
+ self.assertTrue(call_soon.called)
+
+ # callback
+ loop = call_soon.call_args[0][0]
+ loop()
+ self.proactor.accept.assert_called_with(self.sock)
+
+ # conn
+ fut = unittest.mock.Mock()
+ fut.result.return_value = (unittest.mock.Mock(), unittest.mock.Mock())
+
+ make_tr = self.loop._make_socket_transport = unittest.mock.Mock()
+ loop(fut)
+ self.assertTrue(fut.result.called)
+ self.assertTrue(make_tr.called)
+
+ # exception
+ fut.result.side_effect = OSError()
+ loop(fut)
+ self.assertTrue(self.sock.close.called)
+ self.assertTrue(m_log.exception.called)
+
+ def test_create_server_cancel(self):
+ pf = unittest.mock.Mock()
+ call_soon = self.loop.call_soon = unittest.mock.Mock()
+
+ self.loop._start_serving(pf, self.sock)
+ loop = call_soon.call_args[0][0]
+
+ # cancelled
+ fut = asyncio.Future(loop=self.loop)
+ fut.cancel()
+ loop(fut)
+ self.assertTrue(self.sock.close.called)
+
+ def test_stop_serving(self):
+ sock = unittest.mock.Mock()
+ self.loop._stop_serving(sock)
+ self.assertTrue(sock.close.called)
+ self.proactor._stop_serving.assert_called_with(sock)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_queues.py b/Lib/test/test_asyncio/test_queues.py
new file mode 100644
index 0000000000..8af4ee7f9b
--- /dev/null
+++ b/Lib/test/test_asyncio/test_queues.py
@@ -0,0 +1,470 @@
+"""Tests for queues.py"""
+
+import unittest
+import unittest.mock
+
+from asyncio import events
+from asyncio import futures
+from asyncio import locks
+from asyncio import queues
+from asyncio import tasks
+from asyncio import test_utils
+
+
+class _QueueTestBase(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+
+class QueueBasicTests(_QueueTestBase):
+
+ def _test_repr_or_str(self, fn, expect_id):
+ """Test Queue's repr or str.
+
+ fn is repr or str. expect_id is True if we expect the Queue's id to
+ appear in fn(Queue()).
+ """
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0.1
+ self.assertAlmostEqual(0.2, when)
+ yield 0.1
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ q = queues.Queue(loop=loop)
+ self.assertTrue(fn(q).startswith('<Queue'), fn(q))
+ id_is_present = hex(id(q)) in fn(q)
+ self.assertEqual(expect_id, id_is_present)
+
+ @tasks.coroutine
+ def add_getter():
+ q = queues.Queue(loop=loop)
+ # Start a task that waits to get.
+ tasks.Task(q.get(), loop=loop)
+ # Let it start waiting.
+ yield from tasks.sleep(0.1, loop=loop)
+ self.assertTrue('_getters[1]' in fn(q))
+ # resume q.get coroutine to finish generator
+ q.put_nowait(0)
+
+ loop.run_until_complete(add_getter())
+
+ @tasks.coroutine
+ def add_putter():
+ q = queues.Queue(maxsize=1, loop=loop)
+ q.put_nowait(1)
+ # Start a task that waits to put.
+ tasks.Task(q.put(2), loop=loop)
+ # Let it start waiting.
+ yield from tasks.sleep(0.1, loop=loop)
+ self.assertTrue('_putters[1]' in fn(q))
+ # resume q.put coroutine to finish generator
+ q.get_nowait()
+
+ loop.run_until_complete(add_putter())
+
+ q = queues.Queue(loop=loop)
+ q.put_nowait(1)
+ self.assertTrue('_queue=[1]' in fn(q))
+
+ def test_ctor_loop(self):
+ loop = unittest.mock.Mock()
+ q = queues.Queue(loop=loop)
+ self.assertIs(q._loop, loop)
+
+ q = queues.Queue(loop=self.loop)
+ self.assertIs(q._loop, self.loop)
+
+ def test_ctor_noloop(self):
+ try:
+ events.set_event_loop(self.loop)
+ q = queues.Queue()
+ self.assertIs(q._loop, self.loop)
+ finally:
+ events.set_event_loop(None)
+
+ def test_repr(self):
+ self._test_repr_or_str(repr, True)
+
+ def test_str(self):
+ self._test_repr_or_str(str, False)
+
+ def test_empty(self):
+ q = queues.Queue(loop=self.loop)
+ self.assertTrue(q.empty())
+ q.put_nowait(1)
+ self.assertFalse(q.empty())
+ self.assertEqual(1, q.get_nowait())
+ self.assertTrue(q.empty())
+
+ def test_full(self):
+ q = queues.Queue(loop=self.loop)
+ self.assertFalse(q.full())
+
+ q = queues.Queue(maxsize=1, loop=self.loop)
+ q.put_nowait(1)
+ self.assertTrue(q.full())
+
+ def test_order(self):
+ q = queues.Queue(loop=self.loop)
+ for i in [1, 3, 2]:
+ q.put_nowait(i)
+
+ items = [q.get_nowait() for _ in range(3)]
+ self.assertEqual([1, 3, 2], items)
+
+ def test_maxsize(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.01, when)
+ when = yield 0.01
+ self.assertAlmostEqual(0.02, when)
+ yield 0.01
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ q = queues.Queue(maxsize=2, loop=loop)
+ self.assertEqual(2, q.maxsize)
+ have_been_put = []
+
+ @tasks.coroutine
+ def putter():
+ for i in range(3):
+ yield from q.put(i)
+ have_been_put.append(i)
+ return True
+
+ @tasks.coroutine
+ def test():
+ t = tasks.Task(putter(), loop=loop)
+ yield from tasks.sleep(0.01, loop=loop)
+
+ # The putter is blocked after putting two items.
+ self.assertEqual([0, 1], have_been_put)
+ self.assertEqual(0, q.get_nowait())
+
+ # Let the putter resume and put last item.
+ yield from tasks.sleep(0.01, loop=loop)
+ self.assertEqual([0, 1, 2], have_been_put)
+ self.assertEqual(1, q.get_nowait())
+ self.assertEqual(2, q.get_nowait())
+
+ self.assertTrue(t.done())
+ self.assertTrue(t.result())
+
+ loop.run_until_complete(test())
+ self.assertAlmostEqual(0.02, loop.time())
+
+
+class QueueGetTests(_QueueTestBase):
+
+ def test_blocking_get(self):
+ q = queues.Queue(loop=self.loop)
+ q.put_nowait(1)
+
+ @tasks.coroutine
+ def queue_get():
+ return (yield from q.get())
+
+ res = self.loop.run_until_complete(queue_get())
+ self.assertEqual(1, res)
+
+ def test_get_with_putters(self):
+ q = queues.Queue(1, loop=self.loop)
+ q.put_nowait(1)
+
+ waiter = futures.Future(loop=self.loop)
+ q._putters.append((2, waiter))
+
+ res = self.loop.run_until_complete(q.get())
+ self.assertEqual(1, res)
+ self.assertTrue(waiter.done())
+ self.assertIsNone(waiter.result())
+
+ def test_blocking_get_wait(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.01, when)
+ yield 0.01
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ q = queues.Queue(loop=loop)
+ started = locks.Event(loop=loop)
+ finished = False
+
+ @tasks.coroutine
+ def queue_get():
+ nonlocal finished
+ started.set()
+ res = yield from q.get()
+ finished = True
+ return res
+
+ @tasks.coroutine
+ def queue_put():
+ loop.call_later(0.01, q.put_nowait, 1)
+ queue_get_task = tasks.Task(queue_get(), loop=loop)
+ yield from started.wait()
+ self.assertFalse(finished)
+ res = yield from queue_get_task
+ self.assertTrue(finished)
+ return res
+
+ res = loop.run_until_complete(queue_put())
+ self.assertEqual(1, res)
+ self.assertAlmostEqual(0.01, loop.time())
+
+ def test_nonblocking_get(self):
+ q = queues.Queue(loop=self.loop)
+ q.put_nowait(1)
+ self.assertEqual(1, q.get_nowait())
+
+ def test_nonblocking_get_exception(self):
+ q = queues.Queue(loop=self.loop)
+ self.assertRaises(queues.Empty, q.get_nowait)
+
+ def test_get_cancelled(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.01, when)
+ when = yield 0.01
+ self.assertAlmostEqual(0.061, when)
+ yield 0.05
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ q = queues.Queue(loop=loop)
+
+ @tasks.coroutine
+ def queue_get():
+ return (yield from tasks.wait_for(q.get(), 0.051, loop=loop))
+
+ @tasks.coroutine
+ def test():
+ get_task = tasks.Task(queue_get(), loop=loop)
+ yield from tasks.sleep(0.01, loop=loop) # let the task start
+ q.put_nowait(1)
+ return (yield from get_task)
+
+ self.assertEqual(1, loop.run_until_complete(test()))
+ self.assertAlmostEqual(0.06, loop.time())
+
+ def test_get_cancelled_race(self):
+ q = queues.Queue(loop=self.loop)
+
+ t1 = tasks.Task(q.get(), loop=self.loop)
+ t2 = tasks.Task(q.get(), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ t1.cancel()
+ test_utils.run_briefly(self.loop)
+ self.assertTrue(t1.done())
+ q.put_nowait('a')
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(t2.result(), 'a')
+
+ def test_get_with_waiting_putters(self):
+ q = queues.Queue(loop=self.loop, maxsize=1)
+ tasks.Task(q.put('a'), loop=self.loop)
+ tasks.Task(q.put('b'), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(self.loop.run_until_complete(q.get()), 'a')
+ self.assertEqual(self.loop.run_until_complete(q.get()), 'b')
+
+
+class QueuePutTests(_QueueTestBase):
+
+ def test_blocking_put(self):
+ q = queues.Queue(loop=self.loop)
+
+ @tasks.coroutine
+ def queue_put():
+ # No maxsize, won't block.
+ yield from q.put(1)
+
+ self.loop.run_until_complete(queue_put())
+
+ def test_blocking_put_wait(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.01, when)
+ yield 0.01
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ q = queues.Queue(maxsize=1, loop=loop)
+ started = locks.Event(loop=loop)
+ finished = False
+
+ @tasks.coroutine
+ def queue_put():
+ nonlocal finished
+ started.set()
+ yield from q.put(1)
+ yield from q.put(2)
+ finished = True
+
+ @tasks.coroutine
+ def queue_get():
+ loop.call_later(0.01, q.get_nowait)
+ queue_put_task = tasks.Task(queue_put(), loop=loop)
+ yield from started.wait()
+ self.assertFalse(finished)
+ yield from queue_put_task
+ self.assertTrue(finished)
+
+ loop.run_until_complete(queue_get())
+ self.assertAlmostEqual(0.01, loop.time())
+
+ def test_nonblocking_put(self):
+ q = queues.Queue(loop=self.loop)
+ q.put_nowait(1)
+ self.assertEqual(1, q.get_nowait())
+
+ def test_nonblocking_put_exception(self):
+ q = queues.Queue(maxsize=1, loop=self.loop)
+ q.put_nowait(1)
+ self.assertRaises(queues.Full, q.put_nowait, 2)
+
+ def test_put_cancelled(self):
+ q = queues.Queue(loop=self.loop)
+
+ @tasks.coroutine
+ def queue_put():
+ yield from q.put(1)
+ return True
+
+ @tasks.coroutine
+ def test():
+ return (yield from q.get())
+
+ t = tasks.Task(queue_put(), loop=self.loop)
+ self.assertEqual(1, self.loop.run_until_complete(test()))
+ self.assertTrue(t.done())
+ self.assertTrue(t.result())
+
+ def test_put_cancelled_race(self):
+ q = queues.Queue(loop=self.loop, maxsize=1)
+
+ tasks.Task(q.put('a'), loop=self.loop)
+ tasks.Task(q.put('c'), loop=self.loop)
+ t = tasks.Task(q.put('b'), loop=self.loop)
+
+ test_utils.run_briefly(self.loop)
+ t.cancel()
+ test_utils.run_briefly(self.loop)
+ self.assertTrue(t.done())
+ self.assertEqual(q.get_nowait(), 'a')
+ self.assertEqual(q.get_nowait(), 'c')
+
+ def test_put_with_waiting_getters(self):
+ q = queues.Queue(loop=self.loop)
+ t = tasks.Task(q.get(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.loop.run_until_complete(q.put('a'))
+ self.assertEqual(self.loop.run_until_complete(t), 'a')
+
+
+class LifoQueueTests(_QueueTestBase):
+
+ def test_order(self):
+ q = queues.LifoQueue(loop=self.loop)
+ for i in [1, 3, 2]:
+ q.put_nowait(i)
+
+ items = [q.get_nowait() for _ in range(3)]
+ self.assertEqual([2, 3, 1], items)
+
+
+class PriorityQueueTests(_QueueTestBase):
+
+ def test_order(self):
+ q = queues.PriorityQueue(loop=self.loop)
+ for i in [1, 3, 2]:
+ q.put_nowait(i)
+
+ items = [q.get_nowait() for _ in range(3)]
+ self.assertEqual([1, 2, 3], items)
+
+
+class JoinableQueueTests(_QueueTestBase):
+
+ def test_task_done_underflow(self):
+ q = queues.JoinableQueue(loop=self.loop)
+ self.assertRaises(ValueError, q.task_done)
+
+ def test_task_done(self):
+ q = queues.JoinableQueue(loop=self.loop)
+ for i in range(100):
+ q.put_nowait(i)
+
+ accumulator = 0
+
+ # Two workers get items from the queue and call task_done after each.
+ # Join the queue and assert all items have been processed.
+ running = True
+
+ @tasks.coroutine
+ def worker():
+ nonlocal accumulator
+
+ while running:
+ item = yield from q.get()
+ accumulator += item
+ q.task_done()
+
+ @tasks.coroutine
+ def test():
+ for _ in range(2):
+ tasks.Task(worker(), loop=self.loop)
+
+ yield from q.join()
+
+ self.loop.run_until_complete(test())
+ self.assertEqual(sum(range(100)), accumulator)
+
+ # close running generators
+ running = False
+ for i in range(2):
+ q.put_nowait(0)
+
+ def test_join_empty_queue(self):
+ q = queues.JoinableQueue(loop=self.loop)
+
+ # Test that a queue join()s successfully, and before anything else
+ # (done twice for insurance).
+
+ @tasks.coroutine
+ def join():
+ yield from q.join()
+ yield from q.join()
+
+ self.loop.run_until_complete(join())
+
+ def test_format(self):
+ q = queues.JoinableQueue(loop=self.loop)
+ self.assertEqual(q._format(), 'maxsize=0')
+
+ q._unfinished_tasks = 2
+ self.assertEqual(q._format(), 'maxsize=0 tasks=2')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py
new file mode 100644
index 0000000000..38aa766998
--- /dev/null
+++ b/Lib/test/test_asyncio/test_selector_events.py
@@ -0,0 +1,1663 @@
+"""Tests for selector_events.py"""
+
+import collections
+import errno
+import gc
+import pprint
+import socket
+import sys
+import unittest
+import unittest.mock
+try:
+ import ssl
+except ImportError:
+ ssl = None
+
+from asyncio import futures
+from asyncio import selectors
+from asyncio import test_utils
+from asyncio.protocols import DatagramProtocol, Protocol
+from asyncio.selector_events import BaseSelectorEventLoop
+from asyncio.selector_events import _SelectorTransport
+from asyncio.selector_events import _SelectorSslTransport
+from asyncio.selector_events import _SelectorSocketTransport
+from asyncio.selector_events import _SelectorDatagramTransport
+
+
+class TestBaseSelectorEventLoop(BaseSelectorEventLoop):
+
+ def _make_self_pipe(self):
+ self._ssock = unittest.mock.Mock()
+ self._csock = unittest.mock.Mock()
+ self._internal_fds += 1
+
+
+def list_to_buffer(l=()):
+ return bytearray().join(l)
+
+
+class BaseSelectorEventLoopTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = TestBaseSelectorEventLoop(unittest.mock.Mock())
+
+ def test_make_socket_transport(self):
+ m = unittest.mock.Mock()
+ self.loop.add_reader = unittest.mock.Mock()
+ self.assertIsInstance(
+ self.loop._make_socket_transport(m, m), _SelectorSocketTransport)
+
+ @unittest.skipIf(ssl is None, 'No ssl module')
+ def test_make_ssl_transport(self):
+ m = unittest.mock.Mock()
+ self.loop.add_reader = unittest.mock.Mock()
+ self.loop.add_writer = unittest.mock.Mock()
+ self.loop.remove_reader = unittest.mock.Mock()
+ self.loop.remove_writer = unittest.mock.Mock()
+ self.assertIsInstance(
+ self.loop._make_ssl_transport(m, m, m, m), _SelectorSslTransport)
+
+ @unittest.mock.patch('asyncio.selector_events.ssl', None)
+ def test_make_ssl_transport_without_ssl_error(self):
+ m = unittest.mock.Mock()
+ self.loop.add_reader = unittest.mock.Mock()
+ self.loop.add_writer = unittest.mock.Mock()
+ self.loop.remove_reader = unittest.mock.Mock()
+ self.loop.remove_writer = unittest.mock.Mock()
+ with self.assertRaises(RuntimeError):
+ self.loop._make_ssl_transport(m, m, m, m)
+
+ def test_close(self):
+ ssock = self.loop._ssock
+ ssock.fileno.return_value = 7
+ csock = self.loop._csock
+ csock.fileno.return_value = 1
+ remove_reader = self.loop.remove_reader = unittest.mock.Mock()
+
+ self.loop._selector.close()
+ self.loop._selector = selector = unittest.mock.Mock()
+ self.loop.close()
+ self.assertIsNone(self.loop._selector)
+ self.assertIsNone(self.loop._csock)
+ self.assertIsNone(self.loop._ssock)
+ selector.close.assert_called_with()
+ ssock.close.assert_called_with()
+ csock.close.assert_called_with()
+ remove_reader.assert_called_with(7)
+
+ self.loop.close()
+ self.loop.close()
+
+ def test_close_no_selector(self):
+ ssock = self.loop._ssock
+ csock = self.loop._csock
+ remove_reader = self.loop.remove_reader = unittest.mock.Mock()
+
+ self.loop._selector.close()
+ self.loop._selector = None
+ self.loop.close()
+ self.assertIsNone(self.loop._selector)
+ self.assertFalse(ssock.close.called)
+ self.assertFalse(csock.close.called)
+ self.assertFalse(remove_reader.called)
+
+ def test_socketpair(self):
+ self.assertRaises(NotImplementedError, self.loop._socketpair)
+
+ def test_read_from_self_tryagain(self):
+ self.loop._ssock.recv.side_effect = BlockingIOError
+ self.assertIsNone(self.loop._read_from_self())
+
+ def test_read_from_self_exception(self):
+ self.loop._ssock.recv.side_effect = OSError
+ self.assertRaises(OSError, self.loop._read_from_self)
+
+ def test_write_to_self_tryagain(self):
+ self.loop._csock.send.side_effect = BlockingIOError
+ self.assertIsNone(self.loop._write_to_self())
+
+ def test_write_to_self_exception(self):
+ self.loop._csock.send.side_effect = OSError()
+ self.assertRaises(OSError, self.loop._write_to_self)
+
+ def test_sock_recv(self):
+ sock = unittest.mock.Mock()
+ self.loop._sock_recv = unittest.mock.Mock()
+
+ f = self.loop.sock_recv(sock, 1024)
+ self.assertIsInstance(f, futures.Future)
+ self.loop._sock_recv.assert_called_with(f, False, sock, 1024)
+
+ def test__sock_recv_canceled_fut(self):
+ sock = unittest.mock.Mock()
+
+ f = futures.Future(loop=self.loop)
+ f.cancel()
+
+ self.loop._sock_recv(f, False, sock, 1024)
+ self.assertFalse(sock.recv.called)
+
+ def test__sock_recv_unregister(self):
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+
+ f = futures.Future(loop=self.loop)
+ f.cancel()
+
+ self.loop.remove_reader = unittest.mock.Mock()
+ self.loop._sock_recv(f, True, sock, 1024)
+ self.assertEqual((10,), self.loop.remove_reader.call_args[0])
+
+ def test__sock_recv_tryagain(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ sock.recv.side_effect = BlockingIOError
+
+ self.loop.add_reader = unittest.mock.Mock()
+ self.loop._sock_recv(f, False, sock, 1024)
+ self.assertEqual((10, self.loop._sock_recv, f, True, sock, 1024),
+ self.loop.add_reader.call_args[0])
+
+ def test__sock_recv_exception(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ err = sock.recv.side_effect = OSError()
+
+ self.loop._sock_recv(f, False, sock, 1024)
+ self.assertIs(err, f.exception())
+
+ def test_sock_sendall(self):
+ sock = unittest.mock.Mock()
+ self.loop._sock_sendall = unittest.mock.Mock()
+
+ f = self.loop.sock_sendall(sock, b'data')
+ self.assertIsInstance(f, futures.Future)
+ self.assertEqual(
+ (f, False, sock, b'data'),
+ self.loop._sock_sendall.call_args[0])
+
+ def test_sock_sendall_nodata(self):
+ sock = unittest.mock.Mock()
+ self.loop._sock_sendall = unittest.mock.Mock()
+
+ f = self.loop.sock_sendall(sock, b'')
+ self.assertIsInstance(f, futures.Future)
+ self.assertTrue(f.done())
+ self.assertIsNone(f.result())
+ self.assertFalse(self.loop._sock_sendall.called)
+
+ def test__sock_sendall_canceled_fut(self):
+ sock = unittest.mock.Mock()
+
+ f = futures.Future(loop=self.loop)
+ f.cancel()
+
+ self.loop._sock_sendall(f, False, sock, b'data')
+ self.assertFalse(sock.send.called)
+
+ def test__sock_sendall_unregister(self):
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+
+ f = futures.Future(loop=self.loop)
+ f.cancel()
+
+ self.loop.remove_writer = unittest.mock.Mock()
+ self.loop._sock_sendall(f, True, sock, b'data')
+ self.assertEqual((10,), self.loop.remove_writer.call_args[0])
+
+ def test__sock_sendall_tryagain(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ sock.send.side_effect = BlockingIOError
+
+ self.loop.add_writer = unittest.mock.Mock()
+ self.loop._sock_sendall(f, False, sock, b'data')
+ self.assertEqual(
+ (10, self.loop._sock_sendall, f, True, sock, b'data'),
+ self.loop.add_writer.call_args[0])
+
+ def test__sock_sendall_interrupted(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ sock.send.side_effect = InterruptedError
+
+ self.loop.add_writer = unittest.mock.Mock()
+ self.loop._sock_sendall(f, False, sock, b'data')
+ self.assertEqual(
+ (10, self.loop._sock_sendall, f, True, sock, b'data'),
+ self.loop.add_writer.call_args[0])
+
+ def test__sock_sendall_exception(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ err = sock.send.side_effect = OSError()
+
+ self.loop._sock_sendall(f, False, sock, b'data')
+ self.assertIs(f.exception(), err)
+
+ def test__sock_sendall(self):
+ sock = unittest.mock.Mock()
+
+ f = futures.Future(loop=self.loop)
+ sock.fileno.return_value = 10
+ sock.send.return_value = 4
+
+ self.loop._sock_sendall(f, False, sock, b'data')
+ self.assertTrue(f.done())
+ self.assertIsNone(f.result())
+
+ def test__sock_sendall_partial(self):
+ sock = unittest.mock.Mock()
+
+ f = futures.Future(loop=self.loop)
+ sock.fileno.return_value = 10
+ sock.send.return_value = 2
+
+ self.loop.add_writer = unittest.mock.Mock()
+ self.loop._sock_sendall(f, False, sock, b'data')
+ self.assertFalse(f.done())
+ self.assertEqual(
+ (10, self.loop._sock_sendall, f, True, sock, b'ta'),
+ self.loop.add_writer.call_args[0])
+
+ def test__sock_sendall_none(self):
+ sock = unittest.mock.Mock()
+
+ f = futures.Future(loop=self.loop)
+ sock.fileno.return_value = 10
+ sock.send.return_value = 0
+
+ self.loop.add_writer = unittest.mock.Mock()
+ self.loop._sock_sendall(f, False, sock, b'data')
+ self.assertFalse(f.done())
+ self.assertEqual(
+ (10, self.loop._sock_sendall, f, True, sock, b'data'),
+ self.loop.add_writer.call_args[0])
+
+ def test_sock_connect(self):
+ sock = unittest.mock.Mock()
+ self.loop._sock_connect = unittest.mock.Mock()
+
+ f = self.loop.sock_connect(sock, ('127.0.0.1', 8080))
+ self.assertIsInstance(f, futures.Future)
+ self.assertEqual(
+ (f, False, sock, ('127.0.0.1', 8080)),
+ self.loop._sock_connect.call_args[0])
+
+ def test__sock_connect(self):
+ f = futures.Future(loop=self.loop)
+
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+
+ self.loop._sock_connect(f, False, sock, ('127.0.0.1', 8080))
+ self.assertTrue(f.done())
+ self.assertIsNone(f.result())
+ self.assertTrue(sock.connect.called)
+
+ def test__sock_connect_canceled_fut(self):
+ sock = unittest.mock.Mock()
+
+ f = futures.Future(loop=self.loop)
+ f.cancel()
+
+ self.loop._sock_connect(f, False, sock, ('127.0.0.1', 8080))
+ self.assertFalse(sock.connect.called)
+
+ def test__sock_connect_unregister(self):
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+
+ f = futures.Future(loop=self.loop)
+ f.cancel()
+
+ self.loop.remove_writer = unittest.mock.Mock()
+ self.loop._sock_connect(f, True, sock, ('127.0.0.1', 8080))
+ self.assertEqual((10,), self.loop.remove_writer.call_args[0])
+
+ def test__sock_connect_tryagain(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ sock.getsockopt.return_value = errno.EAGAIN
+
+ self.loop.add_writer = unittest.mock.Mock()
+ self.loop.remove_writer = unittest.mock.Mock()
+
+ self.loop._sock_connect(f, True, sock, ('127.0.0.1', 8080))
+ self.assertEqual(
+ (10, self.loop._sock_connect, f,
+ True, sock, ('127.0.0.1', 8080)),
+ self.loop.add_writer.call_args[0])
+
+ def test__sock_connect_exception(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ sock.getsockopt.return_value = errno.ENOTCONN
+
+ self.loop.remove_writer = unittest.mock.Mock()
+ self.loop._sock_connect(f, True, sock, ('127.0.0.1', 8080))
+ self.assertIsInstance(f.exception(), OSError)
+
+ def test_sock_accept(self):
+ sock = unittest.mock.Mock()
+ self.loop._sock_accept = unittest.mock.Mock()
+
+ f = self.loop.sock_accept(sock)
+ self.assertIsInstance(f, futures.Future)
+ self.assertEqual(
+ (f, False, sock), self.loop._sock_accept.call_args[0])
+
+ def test__sock_accept(self):
+ f = futures.Future(loop=self.loop)
+
+ conn = unittest.mock.Mock()
+
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ sock.accept.return_value = conn, ('127.0.0.1', 1000)
+
+ self.loop._sock_accept(f, False, sock)
+ self.assertTrue(f.done())
+ self.assertEqual((conn, ('127.0.0.1', 1000)), f.result())
+ self.assertEqual((False,), conn.setblocking.call_args[0])
+
+ def test__sock_accept_canceled_fut(self):
+ sock = unittest.mock.Mock()
+
+ f = futures.Future(loop=self.loop)
+ f.cancel()
+
+ self.loop._sock_accept(f, False, sock)
+ self.assertFalse(sock.accept.called)
+
+ def test__sock_accept_unregister(self):
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+
+ f = futures.Future(loop=self.loop)
+ f.cancel()
+
+ self.loop.remove_reader = unittest.mock.Mock()
+ self.loop._sock_accept(f, True, sock)
+ self.assertEqual((10,), self.loop.remove_reader.call_args[0])
+
+ def test__sock_accept_tryagain(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ sock.accept.side_effect = BlockingIOError
+
+ self.loop.add_reader = unittest.mock.Mock()
+ self.loop._sock_accept(f, False, sock)
+ self.assertEqual(
+ (10, self.loop._sock_accept, f, True, sock),
+ self.loop.add_reader.call_args[0])
+
+ def test__sock_accept_exception(self):
+ f = futures.Future(loop=self.loop)
+ sock = unittest.mock.Mock()
+ sock.fileno.return_value = 10
+ err = sock.accept.side_effect = OSError()
+
+ self.loop._sock_accept(f, False, sock)
+ self.assertIs(err, f.exception())
+
+ def test_add_reader(self):
+ self.loop._selector.get_key.side_effect = KeyError
+ cb = lambda: True
+ self.loop.add_reader(1, cb)
+
+ self.assertTrue(self.loop._selector.register.called)
+ fd, mask, (r, w) = self.loop._selector.register.call_args[0]
+ self.assertEqual(1, fd)
+ self.assertEqual(selectors.EVENT_READ, mask)
+ self.assertEqual(cb, r._callback)
+ self.assertIsNone(w)
+
+ def test_add_reader_existing(self):
+ reader = unittest.mock.Mock()
+ writer = unittest.mock.Mock()
+ self.loop._selector.get_key.return_value = selectors.SelectorKey(
+ 1, 1, selectors.EVENT_WRITE, (reader, writer))
+ cb = lambda: True
+ self.loop.add_reader(1, cb)
+
+ self.assertTrue(reader.cancel.called)
+ self.assertFalse(self.loop._selector.register.called)
+ self.assertTrue(self.loop._selector.modify.called)
+ fd, mask, (r, w) = self.loop._selector.modify.call_args[0]
+ self.assertEqual(1, fd)
+ self.assertEqual(selectors.EVENT_WRITE | selectors.EVENT_READ, mask)
+ self.assertEqual(cb, r._callback)
+ self.assertEqual(writer, w)
+
+ def test_add_reader_existing_writer(self):
+ writer = unittest.mock.Mock()
+ self.loop._selector.get_key.return_value = selectors.SelectorKey(
+ 1, 1, selectors.EVENT_WRITE, (None, writer))
+ cb = lambda: True
+ self.loop.add_reader(1, cb)
+
+ self.assertFalse(self.loop._selector.register.called)
+ self.assertTrue(self.loop._selector.modify.called)
+ fd, mask, (r, w) = self.loop._selector.modify.call_args[0]
+ self.assertEqual(1, fd)
+ self.assertEqual(selectors.EVENT_WRITE | selectors.EVENT_READ, mask)
+ self.assertEqual(cb, r._callback)
+ self.assertEqual(writer, w)
+
+ def test_remove_reader(self):
+ self.loop._selector.get_key.return_value = selectors.SelectorKey(
+ 1, 1, selectors.EVENT_READ, (None, None))
+ self.assertFalse(self.loop.remove_reader(1))
+
+ self.assertTrue(self.loop._selector.unregister.called)
+
+ def test_remove_reader_read_write(self):
+ reader = unittest.mock.Mock()
+ writer = unittest.mock.Mock()
+ self.loop._selector.get_key.return_value = selectors.SelectorKey(
+ 1, 1, selectors.EVENT_READ | selectors.EVENT_WRITE,
+ (reader, writer))
+ self.assertTrue(
+ self.loop.remove_reader(1))
+
+ self.assertFalse(self.loop._selector.unregister.called)
+ self.assertEqual(
+ (1, selectors.EVENT_WRITE, (None, writer)),
+ self.loop._selector.modify.call_args[0])
+
+ def test_remove_reader_unknown(self):
+ self.loop._selector.get_key.side_effect = KeyError
+ self.assertFalse(
+ self.loop.remove_reader(1))
+
+ def test_add_writer(self):
+ self.loop._selector.get_key.side_effect = KeyError
+ cb = lambda: True
+ self.loop.add_writer(1, cb)
+
+ self.assertTrue(self.loop._selector.register.called)
+ fd, mask, (r, w) = self.loop._selector.register.call_args[0]
+ self.assertEqual(1, fd)
+ self.assertEqual(selectors.EVENT_WRITE, mask)
+ self.assertIsNone(r)
+ self.assertEqual(cb, w._callback)
+
+ def test_add_writer_existing(self):
+ reader = unittest.mock.Mock()
+ writer = unittest.mock.Mock()
+ self.loop._selector.get_key.return_value = selectors.SelectorKey(
+ 1, 1, selectors.EVENT_READ, (reader, writer))
+ cb = lambda: True
+ self.loop.add_writer(1, cb)
+
+ self.assertTrue(writer.cancel.called)
+ self.assertFalse(self.loop._selector.register.called)
+ self.assertTrue(self.loop._selector.modify.called)
+ fd, mask, (r, w) = self.loop._selector.modify.call_args[0]
+ self.assertEqual(1, fd)
+ self.assertEqual(selectors.EVENT_WRITE | selectors.EVENT_READ, mask)
+ self.assertEqual(reader, r)
+ self.assertEqual(cb, w._callback)
+
+ def test_remove_writer(self):
+ self.loop._selector.get_key.return_value = selectors.SelectorKey(
+ 1, 1, selectors.EVENT_WRITE, (None, None))
+ self.assertFalse(self.loop.remove_writer(1))
+
+ self.assertTrue(self.loop._selector.unregister.called)
+
+ def test_remove_writer_read_write(self):
+ reader = unittest.mock.Mock()
+ writer = unittest.mock.Mock()
+ self.loop._selector.get_key.return_value = selectors.SelectorKey(
+ 1, 1, selectors.EVENT_READ | selectors.EVENT_WRITE,
+ (reader, writer))
+ self.assertTrue(
+ self.loop.remove_writer(1))
+
+ self.assertFalse(self.loop._selector.unregister.called)
+ self.assertEqual(
+ (1, selectors.EVENT_READ, (reader, None)),
+ self.loop._selector.modify.call_args[0])
+
+ def test_remove_writer_unknown(self):
+ self.loop._selector.get_key.side_effect = KeyError
+ self.assertFalse(
+ self.loop.remove_writer(1))
+
+ def test_process_events_read(self):
+ reader = unittest.mock.Mock()
+ reader._cancelled = False
+
+ self.loop._add_callback = unittest.mock.Mock()
+ self.loop._process_events(
+ [(selectors.SelectorKey(
+ 1, 1, selectors.EVENT_READ, (reader, None)),
+ selectors.EVENT_READ)])
+ self.assertTrue(self.loop._add_callback.called)
+ self.loop._add_callback.assert_called_with(reader)
+
+ def test_process_events_read_cancelled(self):
+ reader = unittest.mock.Mock()
+ reader.cancelled = True
+
+ self.loop.remove_reader = unittest.mock.Mock()
+ self.loop._process_events(
+ [(selectors.SelectorKey(
+ 1, 1, selectors.EVENT_READ, (reader, None)),
+ selectors.EVENT_READ)])
+ self.loop.remove_reader.assert_called_with(1)
+
+ def test_process_events_write(self):
+ writer = unittest.mock.Mock()
+ writer._cancelled = False
+
+ self.loop._add_callback = unittest.mock.Mock()
+ self.loop._process_events(
+ [(selectors.SelectorKey(1, 1, selectors.EVENT_WRITE,
+ (None, writer)),
+ selectors.EVENT_WRITE)])
+ self.loop._add_callback.assert_called_with(writer)
+
+ def test_process_events_write_cancelled(self):
+ writer = unittest.mock.Mock()
+ writer.cancelled = True
+ self.loop.remove_writer = unittest.mock.Mock()
+
+ self.loop._process_events(
+ [(selectors.SelectorKey(1, 1, selectors.EVENT_WRITE,
+ (None, writer)),
+ selectors.EVENT_WRITE)])
+ self.loop.remove_writer.assert_called_with(1)
+
+
+class SelectorTransportTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ self.protocol = test_utils.make_test_protocol(Protocol)
+ self.sock = unittest.mock.Mock(socket.socket)
+ self.sock.fileno.return_value = 7
+
+ def test_ctor(self):
+ tr = _SelectorTransport(self.loop, self.sock, self.protocol, None)
+ self.assertIs(tr._loop, self.loop)
+ self.assertIs(tr._sock, self.sock)
+ self.assertIs(tr._sock_fd, 7)
+
+ def test_abort(self):
+ tr = _SelectorTransport(self.loop, self.sock, self.protocol, None)
+ tr._force_close = unittest.mock.Mock()
+
+ tr.abort()
+ tr._force_close.assert_called_with(None)
+
+ def test_close(self):
+ tr = _SelectorTransport(self.loop, self.sock, self.protocol, None)
+ tr.close()
+
+ self.assertTrue(tr._closing)
+ self.assertEqual(1, self.loop.remove_reader_count[7])
+ self.protocol.connection_lost(None)
+ self.assertEqual(tr._conn_lost, 1)
+
+ tr.close()
+ self.assertEqual(tr._conn_lost, 1)
+ self.assertEqual(1, self.loop.remove_reader_count[7])
+
+ def test_close_write_buffer(self):
+ tr = _SelectorTransport(self.loop, self.sock, self.protocol, None)
+ tr._buffer.extend(b'data')
+ tr.close()
+
+ self.assertFalse(self.loop.readers)
+ test_utils.run_briefly(self.loop)
+ self.assertFalse(self.protocol.connection_lost.called)
+
+ def test_force_close(self):
+ tr = _SelectorTransport(self.loop, self.sock, self.protocol, None)
+ tr._buffer.extend(b'1')
+ self.loop.add_reader(7, unittest.mock.sentinel)
+ self.loop.add_writer(7, unittest.mock.sentinel)
+ tr._force_close(None)
+
+ self.assertTrue(tr._closing)
+ self.assertEqual(tr._buffer, list_to_buffer())
+ self.assertFalse(self.loop.readers)
+ self.assertFalse(self.loop.writers)
+
+ # second close should not remove reader
+ tr._force_close(None)
+ self.assertFalse(self.loop.readers)
+ self.assertEqual(1, self.loop.remove_reader_count[7])
+
+ @unittest.mock.patch('asyncio.log.logger.exception')
+ def test_fatal_error(self, m_exc):
+ exc = OSError()
+ tr = _SelectorTransport(self.loop, self.sock, self.protocol, None)
+ tr._force_close = unittest.mock.Mock()
+ tr._fatal_error(exc)
+
+ m_exc.assert_called_with('Fatal error for %s', tr)
+ tr._force_close.assert_called_with(exc)
+
+ def test_connection_lost(self):
+ exc = OSError()
+ tr = _SelectorTransport(self.loop, self.sock, self.protocol, None)
+ tr._call_connection_lost(exc)
+
+ self.protocol.connection_lost.assert_called_with(exc)
+ self.sock.close.assert_called_with()
+ self.assertIsNone(tr._sock)
+
+ self.assertIsNone(tr._protocol)
+ self.assertEqual(2, sys.getrefcount(self.protocol),
+ pprint.pformat(gc.get_referrers(self.protocol)))
+ self.assertIsNone(tr._loop)
+ self.assertEqual(2, sys.getrefcount(self.loop),
+ pprint.pformat(gc.get_referrers(self.loop)))
+
+
+class SelectorSocketTransportTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ self.protocol = test_utils.make_test_protocol(Protocol)
+ self.sock = unittest.mock.Mock(socket.socket)
+ self.sock_fd = self.sock.fileno.return_value = 7
+
+ def test_ctor(self):
+ tr = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ self.loop.assert_reader(7, tr._read_ready)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_made.assert_called_with(tr)
+
+ def test_ctor_with_waiter(self):
+ fut = futures.Future(loop=self.loop)
+
+ _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol, fut)
+ test_utils.run_briefly(self.loop)
+ self.assertIsNone(fut.result())
+
+ def test_pause_resume_reading(self):
+ tr = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ self.assertFalse(tr._paused)
+ self.loop.assert_reader(7, tr._read_ready)
+ tr.pause_reading()
+ self.assertTrue(tr._paused)
+ self.assertFalse(7 in self.loop.readers)
+ tr.resume_reading()
+ self.assertFalse(tr._paused)
+ self.loop.assert_reader(7, tr._read_ready)
+
+ def test_read_ready(self):
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+
+ self.sock.recv.return_value = b'data'
+ transport._read_ready()
+
+ self.protocol.data_received.assert_called_with(b'data')
+
+ def test_read_ready_eof(self):
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.close = unittest.mock.Mock()
+
+ self.sock.recv.return_value = b''
+ transport._read_ready()
+
+ self.protocol.eof_received.assert_called_with()
+ transport.close.assert_called_with()
+
+ def test_read_ready_eof_keep_open(self):
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.close = unittest.mock.Mock()
+
+ self.sock.recv.return_value = b''
+ self.protocol.eof_received.return_value = True
+ transport._read_ready()
+
+ self.protocol.eof_received.assert_called_with()
+ self.assertFalse(transport.close.called)
+
+ @unittest.mock.patch('logging.exception')
+ def test_read_ready_tryagain(self, m_exc):
+ self.sock.recv.side_effect = BlockingIOError
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport._read_ready()
+
+ self.assertFalse(transport._fatal_error.called)
+
+ @unittest.mock.patch('logging.exception')
+ def test_read_ready_tryagain_interrupted(self, m_exc):
+ self.sock.recv.side_effect = InterruptedError
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport._read_ready()
+
+ self.assertFalse(transport._fatal_error.called)
+
+ @unittest.mock.patch('logging.exception')
+ def test_read_ready_conn_reset(self, m_exc):
+ err = self.sock.recv.side_effect = ConnectionResetError()
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._force_close = unittest.mock.Mock()
+ transport._read_ready()
+ transport._force_close.assert_called_with(err)
+
+ @unittest.mock.patch('logging.exception')
+ def test_read_ready_err(self, m_exc):
+ err = self.sock.recv.side_effect = OSError()
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport._read_ready()
+
+ transport._fatal_error.assert_called_with(err)
+
+ def test_write(self):
+ data = b'data'
+ self.sock.send.return_value = len(data)
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.write(data)
+ self.sock.send.assert_called_with(data)
+
+ def test_write_bytearray(self):
+ data = bytearray(b'data')
+ self.sock.send.return_value = len(data)
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.write(data)
+ self.sock.send.assert_called_with(data)
+ self.assertEqual(data, bytearray(b'data')) # Hasn't been mutated.
+
+ def test_write_memoryview(self):
+ data = memoryview(b'data')
+ self.sock.send.return_value = len(data)
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.write(data)
+ self.sock.send.assert_called_with(data)
+
+ def test_write_no_data(self):
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.extend(b'data')
+ transport.write(b'')
+ self.assertFalse(self.sock.send.called)
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ def test_write_buffer(self):
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.extend(b'data1')
+ transport.write(b'data2')
+ self.assertFalse(self.sock.send.called)
+ self.assertEqual(list_to_buffer([b'data1', b'data2']),
+ transport._buffer)
+
+ def test_write_partial(self):
+ data = b'data'
+ self.sock.send.return_value = 2
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.write(data)
+
+ self.loop.assert_writer(7, transport._write_ready)
+ self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
+
+ def test_write_partial_bytearray(self):
+ data = bytearray(b'data')
+ self.sock.send.return_value = 2
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.write(data)
+
+ self.loop.assert_writer(7, transport._write_ready)
+ self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
+ self.assertEqual(data, bytearray(b'data')) # Hasn't been mutated.
+
+ def test_write_partial_memoryview(self):
+ data = memoryview(b'data')
+ self.sock.send.return_value = 2
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.write(data)
+
+ self.loop.assert_writer(7, transport._write_ready)
+ self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
+
+ def test_write_partial_none(self):
+ data = b'data'
+ self.sock.send.return_value = 0
+ self.sock.fileno.return_value = 7
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.write(data)
+
+ self.loop.assert_writer(7, transport._write_ready)
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ def test_write_tryagain(self):
+ self.sock.send.side_effect = BlockingIOError
+
+ data = b'data'
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.write(data)
+
+ self.loop.assert_writer(7, transport._write_ready)
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ @unittest.mock.patch('asyncio.selector_events.logger')
+ def test_write_exception(self, m_log):
+ err = self.sock.send.side_effect = OSError()
+
+ data = b'data'
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport.write(data)
+ transport._fatal_error.assert_called_with(err)
+ transport._conn_lost = 1
+
+ self.sock.reset_mock()
+ transport.write(data)
+ self.assertFalse(self.sock.send.called)
+ self.assertEqual(transport._conn_lost, 2)
+ transport.write(data)
+ transport.write(data)
+ transport.write(data)
+ transport.write(data)
+ m_log.warning.assert_called_with('socket.send() raised exception.')
+
+ def test_write_str(self):
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ self.assertRaises(TypeError, transport.write, 'str')
+
+ def test_write_closing(self):
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.close()
+ self.assertEqual(transport._conn_lost, 1)
+ transport.write(b'data')
+ self.assertEqual(transport._conn_lost, 2)
+
+ def test_write_ready(self):
+ data = b'data'
+ self.sock.send.return_value = len(data)
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.extend(data)
+ self.loop.add_writer(7, transport._write_ready)
+ transport._write_ready()
+ self.assertTrue(self.sock.send.called)
+ self.assertFalse(self.loop.writers)
+
+ def test_write_ready_closing(self):
+ data = b'data'
+ self.sock.send.return_value = len(data)
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._closing = True
+ transport._buffer.extend(data)
+ self.loop.add_writer(7, transport._write_ready)
+ transport._write_ready()
+ self.assertTrue(self.sock.send.called)
+ self.assertFalse(self.loop.writers)
+ self.sock.close.assert_called_with()
+ self.protocol.connection_lost.assert_called_with(None)
+
+ def test_write_ready_no_data(self):
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ # This is an internal error.
+ self.assertRaises(AssertionError, transport._write_ready)
+
+ def test_write_ready_partial(self):
+ data = b'data'
+ self.sock.send.return_value = 2
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.extend(data)
+ self.loop.add_writer(7, transport._write_ready)
+ transport._write_ready()
+ self.loop.assert_writer(7, transport._write_ready)
+ self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
+
+ def test_write_ready_partial_none(self):
+ data = b'data'
+ self.sock.send.return_value = 0
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.extend(data)
+ self.loop.add_writer(7, transport._write_ready)
+ transport._write_ready()
+ self.loop.assert_writer(7, transport._write_ready)
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ def test_write_ready_tryagain(self):
+ self.sock.send.side_effect = BlockingIOError
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer = list_to_buffer([b'data1', b'data2'])
+ self.loop.add_writer(7, transport._write_ready)
+ transport._write_ready()
+
+ self.loop.assert_writer(7, transport._write_ready)
+ self.assertEqual(list_to_buffer([b'data1data2']), transport._buffer)
+
+ def test_write_ready_exception(self):
+ err = self.sock.send.side_effect = OSError()
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport._buffer.extend(b'data')
+ transport._write_ready()
+ transport._fatal_error.assert_called_with(err)
+
+ @unittest.mock.patch('asyncio.selector_events.logger')
+ def test_write_ready_exception_and_close(self, m_log):
+ self.sock.send.side_effect = OSError()
+ remove_writer = self.loop.remove_writer = unittest.mock.Mock()
+
+ transport = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ transport.close()
+ transport._buffer.extend(b'data')
+ transport._write_ready()
+ remove_writer.assert_called_with(self.sock_fd)
+
+ def test_write_eof(self):
+ tr = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ self.assertTrue(tr.can_write_eof())
+ tr.write_eof()
+ self.sock.shutdown.assert_called_with(socket.SHUT_WR)
+ tr.write_eof()
+ self.assertEqual(self.sock.shutdown.call_count, 1)
+ tr.close()
+
+ def test_write_eof_buffer(self):
+ tr = _SelectorSocketTransport(
+ self.loop, self.sock, self.protocol)
+ self.sock.send.side_effect = BlockingIOError
+ tr.write(b'data')
+ tr.write_eof()
+ self.assertEqual(tr._buffer, list_to_buffer([b'data']))
+ self.assertTrue(tr._eof)
+ self.assertFalse(self.sock.shutdown.called)
+ self.sock.send.side_effect = lambda _: 4
+ tr._write_ready()
+ self.assertTrue(self.sock.send.called)
+ self.sock.shutdown.assert_called_with(socket.SHUT_WR)
+ tr.close()
+
+
+@unittest.skipIf(ssl is None, 'No ssl module')
+class SelectorSslTransportTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ self.protocol = test_utils.make_test_protocol(Protocol)
+ self.sock = unittest.mock.Mock(socket.socket)
+ self.sock.fileno.return_value = 7
+ self.sslsock = unittest.mock.Mock()
+ self.sslsock.fileno.return_value = 1
+ self.sslcontext = unittest.mock.Mock()
+ self.sslcontext.wrap_socket.return_value = self.sslsock
+
+ def _make_one(self, create_waiter=None):
+ transport = _SelectorSslTransport(
+ self.loop, self.sock, self.protocol, self.sslcontext)
+ self.sock.reset_mock()
+ self.sslsock.reset_mock()
+ self.sslcontext.reset_mock()
+ self.loop.reset_counters()
+ return transport
+
+ def test_on_handshake(self):
+ waiter = futures.Future(loop=self.loop)
+ tr = _SelectorSslTransport(
+ self.loop, self.sock, self.protocol, self.sslcontext,
+ waiter=waiter)
+ self.assertTrue(self.sslsock.do_handshake.called)
+ self.loop.assert_reader(1, tr._read_ready)
+ test_utils.run_briefly(self.loop)
+ self.assertIsNone(waiter.result())
+
+ def test_on_handshake_reader_retry(self):
+ self.sslsock.do_handshake.side_effect = ssl.SSLWantReadError
+ transport = _SelectorSslTransport(
+ self.loop, self.sock, self.protocol, self.sslcontext)
+ transport._on_handshake()
+ self.loop.assert_reader(1, transport._on_handshake)
+
+ def test_on_handshake_writer_retry(self):
+ self.sslsock.do_handshake.side_effect = ssl.SSLWantWriteError
+ transport = _SelectorSslTransport(
+ self.loop, self.sock, self.protocol, self.sslcontext)
+ transport._on_handshake()
+ self.loop.assert_writer(1, transport._on_handshake)
+
+ def test_on_handshake_exc(self):
+ exc = ValueError()
+ self.sslsock.do_handshake.side_effect = exc
+ transport = _SelectorSslTransport(
+ self.loop, self.sock, self.protocol, self.sslcontext)
+ transport._waiter = futures.Future(loop=self.loop)
+ transport._on_handshake()
+ self.assertTrue(self.sslsock.close.called)
+ self.assertTrue(transport._waiter.done())
+ self.assertIs(exc, transport._waiter.exception())
+
+ def test_on_handshake_base_exc(self):
+ transport = _SelectorSslTransport(
+ self.loop, self.sock, self.protocol, self.sslcontext)
+ transport._waiter = futures.Future(loop=self.loop)
+ exc = BaseException()
+ self.sslsock.do_handshake.side_effect = exc
+ self.assertRaises(BaseException, transport._on_handshake)
+ self.assertTrue(self.sslsock.close.called)
+ self.assertTrue(transport._waiter.done())
+ self.assertIs(exc, transport._waiter.exception())
+
+ def test_pause_resume_reading(self):
+ tr = self._make_one()
+ self.assertFalse(tr._paused)
+ self.loop.assert_reader(1, tr._read_ready)
+ tr.pause_reading()
+ self.assertTrue(tr._paused)
+ self.assertFalse(1 in self.loop.readers)
+ tr.resume_reading()
+ self.assertFalse(tr._paused)
+ self.loop.assert_reader(1, tr._read_ready)
+
+ def test_write(self):
+ transport = self._make_one()
+ transport.write(b'data')
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ def test_write_bytearray(self):
+ transport = self._make_one()
+ data = bytearray(b'data')
+ transport.write(data)
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+ self.assertEqual(data, bytearray(b'data')) # Hasn't been mutated.
+ self.assertIsNot(data, transport._buffer) # Hasn't been incorporated.
+
+ def test_write_memoryview(self):
+ transport = self._make_one()
+ data = memoryview(b'data')
+ transport.write(data)
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ def test_write_no_data(self):
+ transport = self._make_one()
+ transport._buffer.extend(b'data')
+ transport.write(b'')
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ def test_write_str(self):
+ transport = self._make_one()
+ self.assertRaises(TypeError, transport.write, 'str')
+
+ def test_write_closing(self):
+ transport = self._make_one()
+ transport.close()
+ self.assertEqual(transport._conn_lost, 1)
+ transport.write(b'data')
+ self.assertEqual(transport._conn_lost, 2)
+
+ @unittest.mock.patch('asyncio.selector_events.logger')
+ def test_write_exception(self, m_log):
+ transport = self._make_one()
+ transport._conn_lost = 1
+ transport.write(b'data')
+ self.assertEqual(transport._buffer, list_to_buffer())
+ transport.write(b'data')
+ transport.write(b'data')
+ transport.write(b'data')
+ transport.write(b'data')
+ m_log.warning.assert_called_with('socket.send() raised exception.')
+
+ def test_read_ready_recv(self):
+ self.sslsock.recv.return_value = b'data'
+ transport = self._make_one()
+ transport._read_ready()
+ self.assertTrue(self.sslsock.recv.called)
+ self.assertEqual((b'data',), self.protocol.data_received.call_args[0])
+
+ def test_read_ready_write_wants_read(self):
+ self.loop.add_writer = unittest.mock.Mock()
+ self.sslsock.recv.side_effect = BlockingIOError
+ transport = self._make_one()
+ transport._write_wants_read = True
+ transport._write_ready = unittest.mock.Mock()
+ transport._buffer.extend(b'data')
+ transport._read_ready()
+
+ self.assertFalse(transport._write_wants_read)
+ transport._write_ready.assert_called_with()
+ self.loop.add_writer.assert_called_with(
+ transport._sock_fd, transport._write_ready)
+
+ def test_read_ready_recv_eof(self):
+ self.sslsock.recv.return_value = b''
+ transport = self._make_one()
+ transport.close = unittest.mock.Mock()
+ transport._read_ready()
+ transport.close.assert_called_with()
+ self.protocol.eof_received.assert_called_with()
+
+ def test_read_ready_recv_conn_reset(self):
+ err = self.sslsock.recv.side_effect = ConnectionResetError()
+ transport = self._make_one()
+ transport._force_close = unittest.mock.Mock()
+ transport._read_ready()
+ transport._force_close.assert_called_with(err)
+
+ def test_read_ready_recv_retry(self):
+ self.sslsock.recv.side_effect = ssl.SSLWantReadError
+ transport = self._make_one()
+ transport._read_ready()
+ self.assertTrue(self.sslsock.recv.called)
+ self.assertFalse(self.protocol.data_received.called)
+
+ self.sslsock.recv.side_effect = BlockingIOError
+ transport._read_ready()
+ self.assertFalse(self.protocol.data_received.called)
+
+ self.sslsock.recv.side_effect = InterruptedError
+ transport._read_ready()
+ self.assertFalse(self.protocol.data_received.called)
+
+ def test_read_ready_recv_write(self):
+ self.loop.remove_reader = unittest.mock.Mock()
+ self.loop.add_writer = unittest.mock.Mock()
+ self.sslsock.recv.side_effect = ssl.SSLWantWriteError
+ transport = self._make_one()
+ transport._read_ready()
+ self.assertFalse(self.protocol.data_received.called)
+ self.assertTrue(transport._read_wants_write)
+
+ self.loop.remove_reader.assert_called_with(transport._sock_fd)
+ self.loop.add_writer.assert_called_with(
+ transport._sock_fd, transport._write_ready)
+
+ def test_read_ready_recv_exc(self):
+ err = self.sslsock.recv.side_effect = OSError()
+ transport = self._make_one()
+ transport._fatal_error = unittest.mock.Mock()
+ transport._read_ready()
+ transport._fatal_error.assert_called_with(err)
+
+ def test_write_ready_send(self):
+ self.sslsock.send.return_value = 4
+ transport = self._make_one()
+ transport._buffer = list_to_buffer([b'data'])
+ transport._write_ready()
+ self.assertEqual(list_to_buffer(), transport._buffer)
+ self.assertTrue(self.sslsock.send.called)
+
+ def test_write_ready_send_none(self):
+ self.sslsock.send.return_value = 0
+ transport = self._make_one()
+ transport._buffer = list_to_buffer([b'data1', b'data2'])
+ transport._write_ready()
+ self.assertTrue(self.sslsock.send.called)
+ self.assertEqual(list_to_buffer([b'data1data2']), transport._buffer)
+
+ def test_write_ready_send_partial(self):
+ self.sslsock.send.return_value = 2
+ transport = self._make_one()
+ transport._buffer = list_to_buffer([b'data1', b'data2'])
+ transport._write_ready()
+ self.assertTrue(self.sslsock.send.called)
+ self.assertEqual(list_to_buffer([b'ta1data2']), transport._buffer)
+
+ def test_write_ready_send_closing_partial(self):
+ self.sslsock.send.return_value = 2
+ transport = self._make_one()
+ transport._buffer = list_to_buffer([b'data1', b'data2'])
+ transport._write_ready()
+ self.assertTrue(self.sslsock.send.called)
+ self.assertFalse(self.sslsock.close.called)
+
+ def test_write_ready_send_closing(self):
+ self.sslsock.send.return_value = 4
+ transport = self._make_one()
+ transport.close()
+ transport._buffer = list_to_buffer([b'data'])
+ transport._write_ready()
+ self.assertFalse(self.loop.writers)
+ self.protocol.connection_lost.assert_called_with(None)
+
+ def test_write_ready_send_closing_empty_buffer(self):
+ self.sslsock.send.return_value = 4
+ transport = self._make_one()
+ transport.close()
+ transport._buffer = list_to_buffer()
+ transport._write_ready()
+ self.assertFalse(self.loop.writers)
+ self.protocol.connection_lost.assert_called_with(None)
+
+ def test_write_ready_send_retry(self):
+ transport = self._make_one()
+ transport._buffer = list_to_buffer([b'data'])
+
+ self.sslsock.send.side_effect = ssl.SSLWantWriteError
+ transport._write_ready()
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ self.sslsock.send.side_effect = BlockingIOError()
+ transport._write_ready()
+ self.assertEqual(list_to_buffer([b'data']), transport._buffer)
+
+ def test_write_ready_send_read(self):
+ transport = self._make_one()
+ transport._buffer = list_to_buffer([b'data'])
+
+ self.loop.remove_writer = unittest.mock.Mock()
+ self.sslsock.send.side_effect = ssl.SSLWantReadError
+ transport._write_ready()
+ self.assertFalse(self.protocol.data_received.called)
+ self.assertTrue(transport._write_wants_read)
+ self.loop.remove_writer.assert_called_with(transport._sock_fd)
+
+ def test_write_ready_send_exc(self):
+ err = self.sslsock.send.side_effect = OSError()
+
+ transport = self._make_one()
+ transport._buffer = list_to_buffer([b'data'])
+ transport._fatal_error = unittest.mock.Mock()
+ transport._write_ready()
+ transport._fatal_error.assert_called_with(err)
+ self.assertEqual(list_to_buffer(), transport._buffer)
+
+ def test_write_ready_read_wants_write(self):
+ self.loop.add_reader = unittest.mock.Mock()
+ self.sslsock.send.side_effect = BlockingIOError
+ transport = self._make_one()
+ transport._read_wants_write = True
+ transport._read_ready = unittest.mock.Mock()
+ transport._write_ready()
+
+ self.assertFalse(transport._read_wants_write)
+ transport._read_ready.assert_called_with()
+ self.loop.add_reader.assert_called_with(
+ transport._sock_fd, transport._read_ready)
+
+ def test_write_eof(self):
+ tr = self._make_one()
+ self.assertFalse(tr.can_write_eof())
+ self.assertRaises(NotImplementedError, tr.write_eof)
+
+ def test_close(self):
+ tr = self._make_one()
+ tr.close()
+
+ self.assertTrue(tr._closing)
+ self.assertEqual(1, self.loop.remove_reader_count[1])
+ self.assertEqual(tr._conn_lost, 1)
+
+ tr.close()
+ self.assertEqual(tr._conn_lost, 1)
+ self.assertEqual(1, self.loop.remove_reader_count[1])
+
+ @unittest.skipIf(ssl is None or not ssl.HAS_SNI, 'No SNI support')
+ def test_server_hostname(self):
+ _SelectorSslTransport(
+ self.loop, self.sock, self.protocol, self.sslcontext,
+ server_hostname='localhost')
+ self.sslcontext.wrap_socket.assert_called_with(
+ self.sock, do_handshake_on_connect=False, server_side=False,
+ server_hostname='localhost')
+
+
+class SelectorSslWithoutSslTransportTests(unittest.TestCase):
+
+ @unittest.mock.patch('asyncio.selector_events.ssl', None)
+ def test_ssl_transport_requires_ssl_module(self):
+ Mock = unittest.mock.Mock
+ with self.assertRaises(RuntimeError):
+ transport = _SelectorSslTransport(Mock(), Mock(), Mock(), Mock())
+
+
+class SelectorDatagramTransportTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ self.protocol = test_utils.make_test_protocol(DatagramProtocol)
+ self.sock = unittest.mock.Mock(spec_set=socket.socket)
+ self.sock.fileno.return_value = 7
+
+ def test_read_ready(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+
+ self.sock.recvfrom.return_value = (b'data', ('0.0.0.0', 1234))
+ transport._read_ready()
+
+ self.protocol.datagram_received.assert_called_with(
+ b'data', ('0.0.0.0', 1234))
+
+ def test_read_ready_tryagain(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+
+ self.sock.recvfrom.side_effect = BlockingIOError
+ transport._fatal_error = unittest.mock.Mock()
+ transport._read_ready()
+
+ self.assertFalse(transport._fatal_error.called)
+
+ def test_read_ready_err(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+
+ err = self.sock.recvfrom.side_effect = RuntimeError()
+ transport._fatal_error = unittest.mock.Mock()
+ transport._read_ready()
+
+ transport._fatal_error.assert_called_with(err)
+
+ def test_read_ready_oserr(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+
+ err = self.sock.recvfrom.side_effect = OSError()
+ transport._fatal_error = unittest.mock.Mock()
+ transport._read_ready()
+
+ self.assertFalse(transport._fatal_error.called)
+ self.protocol.error_received.assert_called_with(err)
+
+ def test_sendto(self):
+ data = b'data'
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport.sendto(data, ('0.0.0.0', 1234))
+ self.assertTrue(self.sock.sendto.called)
+ self.assertEqual(
+ self.sock.sendto.call_args[0], (data, ('0.0.0.0', 1234)))
+
+ def test_sendto_bytearray(self):
+ data = bytearray(b'data')
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport.sendto(data, ('0.0.0.0', 1234))
+ self.assertTrue(self.sock.sendto.called)
+ self.assertEqual(
+ self.sock.sendto.call_args[0], (data, ('0.0.0.0', 1234)))
+
+ def test_sendto_memoryview(self):
+ data = memoryview(b'data')
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport.sendto(data, ('0.0.0.0', 1234))
+ self.assertTrue(self.sock.sendto.called)
+ self.assertEqual(
+ self.sock.sendto.call_args[0], (data, ('0.0.0.0', 1234)))
+
+ def test_sendto_no_data(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.append((b'data', ('0.0.0.0', 12345)))
+ transport.sendto(b'', ())
+ self.assertFalse(self.sock.sendto.called)
+ self.assertEqual(
+ [(b'data', ('0.0.0.0', 12345))], list(transport._buffer))
+
+ def test_sendto_buffer(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.append((b'data1', ('0.0.0.0', 12345)))
+ transport.sendto(b'data2', ('0.0.0.0', 12345))
+ self.assertFalse(self.sock.sendto.called)
+ self.assertEqual(
+ [(b'data1', ('0.0.0.0', 12345)),
+ (b'data2', ('0.0.0.0', 12345))],
+ list(transport._buffer))
+
+ def test_sendto_buffer_bytearray(self):
+ data2 = bytearray(b'data2')
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.append((b'data1', ('0.0.0.0', 12345)))
+ transport.sendto(data2, ('0.0.0.0', 12345))
+ self.assertFalse(self.sock.sendto.called)
+ self.assertEqual(
+ [(b'data1', ('0.0.0.0', 12345)),
+ (b'data2', ('0.0.0.0', 12345))],
+ list(transport._buffer))
+ self.assertIsInstance(transport._buffer[1][0], bytes)
+
+ def test_sendto_buffer_memoryview(self):
+ data2 = memoryview(b'data2')
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.append((b'data1', ('0.0.0.0', 12345)))
+ transport.sendto(data2, ('0.0.0.0', 12345))
+ self.assertFalse(self.sock.sendto.called)
+ self.assertEqual(
+ [(b'data1', ('0.0.0.0', 12345)),
+ (b'data2', ('0.0.0.0', 12345))],
+ list(transport._buffer))
+ self.assertIsInstance(transport._buffer[1][0], bytes)
+
+ def test_sendto_tryagain(self):
+ data = b'data'
+
+ self.sock.sendto.side_effect = BlockingIOError
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport.sendto(data, ('0.0.0.0', 12345))
+
+ self.loop.assert_writer(7, transport._sendto_ready)
+ self.assertEqual(
+ [(b'data', ('0.0.0.0', 12345))], list(transport._buffer))
+
+ @unittest.mock.patch('asyncio.selector_events.logger')
+ def test_sendto_exception(self, m_log):
+ data = b'data'
+ err = self.sock.sendto.side_effect = RuntimeError()
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport.sendto(data, ())
+
+ self.assertTrue(transport._fatal_error.called)
+ transport._fatal_error.assert_called_with(err)
+ transport._conn_lost = 1
+
+ transport._address = ('123',)
+ transport.sendto(data)
+ transport.sendto(data)
+ transport.sendto(data)
+ transport.sendto(data)
+ transport.sendto(data)
+ m_log.warning.assert_called_with('socket.send() raised exception.')
+
+ def test_sendto_error_received(self):
+ data = b'data'
+
+ self.sock.sendto.side_effect = ConnectionRefusedError
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport.sendto(data, ())
+
+ self.assertEqual(transport._conn_lost, 0)
+ self.assertFalse(transport._fatal_error.called)
+
+ def test_sendto_error_received_connected(self):
+ data = b'data'
+
+ self.sock.send.side_effect = ConnectionRefusedError
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol, ('0.0.0.0', 1))
+ transport._fatal_error = unittest.mock.Mock()
+ transport.sendto(data)
+
+ self.assertFalse(transport._fatal_error.called)
+ self.assertTrue(self.protocol.error_received.called)
+
+ def test_sendto_str(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ self.assertRaises(TypeError, transport.sendto, 'str', ())
+
+ def test_sendto_connected_addr(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol, ('0.0.0.0', 1))
+ self.assertRaises(
+ ValueError, transport.sendto, b'str', ('0.0.0.0', 2))
+
+ def test_sendto_closing(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol, address=(1,))
+ transport.close()
+ self.assertEqual(transport._conn_lost, 1)
+ transport.sendto(b'data', (1,))
+ self.assertEqual(transport._conn_lost, 2)
+
+ def test_sendto_ready(self):
+ data = b'data'
+ self.sock.sendto.return_value = len(data)
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.append((data, ('0.0.0.0', 12345)))
+ self.loop.add_writer(7, transport._sendto_ready)
+ transport._sendto_ready()
+ self.assertTrue(self.sock.sendto.called)
+ self.assertEqual(
+ self.sock.sendto.call_args[0], (data, ('0.0.0.0', 12345)))
+ self.assertFalse(self.loop.writers)
+
+ def test_sendto_ready_closing(self):
+ data = b'data'
+ self.sock.send.return_value = len(data)
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._closing = True
+ transport._buffer.append((data, ()))
+ self.loop.add_writer(7, transport._sendto_ready)
+ transport._sendto_ready()
+ self.sock.sendto.assert_called_with(data, ())
+ self.assertFalse(self.loop.writers)
+ self.sock.close.assert_called_with()
+ self.protocol.connection_lost.assert_called_with(None)
+
+ def test_sendto_ready_no_data(self):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ self.loop.add_writer(7, transport._sendto_ready)
+ transport._sendto_ready()
+ self.assertFalse(self.sock.sendto.called)
+ self.assertFalse(self.loop.writers)
+
+ def test_sendto_ready_tryagain(self):
+ self.sock.sendto.side_effect = BlockingIOError
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._buffer.extend([(b'data1', ()), (b'data2', ())])
+ self.loop.add_writer(7, transport._sendto_ready)
+ transport._sendto_ready()
+
+ self.loop.assert_writer(7, transport._sendto_ready)
+ self.assertEqual(
+ [(b'data1', ()), (b'data2', ())],
+ list(transport._buffer))
+
+ def test_sendto_ready_exception(self):
+ err = self.sock.sendto.side_effect = RuntimeError()
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport._buffer.append((b'data', ()))
+ transport._sendto_ready()
+
+ transport._fatal_error.assert_called_with(err)
+
+ def test_sendto_ready_error_received(self):
+ self.sock.sendto.side_effect = ConnectionRefusedError
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol)
+ transport._fatal_error = unittest.mock.Mock()
+ transport._buffer.append((b'data', ()))
+ transport._sendto_ready()
+
+ self.assertFalse(transport._fatal_error.called)
+
+ def test_sendto_ready_error_received_connection(self):
+ self.sock.send.side_effect = ConnectionRefusedError
+
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol, ('0.0.0.0', 1))
+ transport._fatal_error = unittest.mock.Mock()
+ transport._buffer.append((b'data', ()))
+ transport._sendto_ready()
+
+ self.assertFalse(transport._fatal_error.called)
+ self.assertTrue(self.protocol.error_received.called)
+
+ @unittest.mock.patch('asyncio.log.logger.exception')
+ def test_fatal_error_connected(self, m_exc):
+ transport = _SelectorDatagramTransport(
+ self.loop, self.sock, self.protocol, ('0.0.0.0', 1))
+ err = ConnectionRefusedError()
+ transport._fatal_error(err)
+ self.assertFalse(self.protocol.error_received.called)
+ m_exc.assert_called_with('Fatal error for %s', transport)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py
new file mode 100644
index 0000000000..5516c15873
--- /dev/null
+++ b/Lib/test/test_asyncio/test_streams.py
@@ -0,0 +1,430 @@
+"""Tests for streams.py."""
+
+import gc
+import unittest
+import unittest.mock
+try:
+ import ssl
+except ImportError:
+ ssl = None
+
+from asyncio import events
+from asyncio import streams
+from asyncio import tasks
+from asyncio import test_utils
+
+
+class StreamReaderTests(unittest.TestCase):
+
+ DATA = b'line1\nline2\nline3\n'
+
+ def setUp(self):
+ self.loop = events.new_event_loop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ # just in case if we have transport close callbacks
+ test_utils.run_briefly(self.loop)
+
+ self.loop.close()
+ gc.collect()
+
+ @unittest.mock.patch('asyncio.streams.events')
+ def test_ctor_global_loop(self, m_events):
+ stream = streams.StreamReader()
+ self.assertIs(stream._loop, m_events.get_event_loop.return_value)
+
+ def test_open_connection(self):
+ with test_utils.run_test_server() as httpd:
+ f = streams.open_connection(*httpd.address, loop=self.loop)
+ reader, writer = self.loop.run_until_complete(f)
+ writer.write(b'GET / HTTP/1.0\r\n\r\n')
+ f = reader.readline()
+ data = self.loop.run_until_complete(f)
+ self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
+ f = reader.read()
+ data = self.loop.run_until_complete(f)
+ self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
+
+ writer.close()
+
+ @unittest.skipIf(ssl is None, 'No ssl module')
+ def test_open_connection_no_loop_ssl(self):
+ with test_utils.run_test_server(use_ssl=True) as httpd:
+ try:
+ events.set_event_loop(self.loop)
+ f = streams.open_connection(*httpd.address,
+ ssl=test_utils.dummy_ssl_context())
+ reader, writer = self.loop.run_until_complete(f)
+ finally:
+ events.set_event_loop(None)
+ writer.write(b'GET / HTTP/1.0\r\n\r\n')
+ f = reader.read()
+ data = self.loop.run_until_complete(f)
+ self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
+
+ writer.close()
+
+ def test_open_connection_error(self):
+ with test_utils.run_test_server() as httpd:
+ f = streams.open_connection(*httpd.address, loop=self.loop)
+ reader, writer = self.loop.run_until_complete(f)
+ writer._protocol.connection_lost(ZeroDivisionError())
+ f = reader.read()
+ with self.assertRaises(ZeroDivisionError):
+ self.loop.run_until_complete(f)
+
+ writer.close()
+ test_utils.run_briefly(self.loop)
+
+ def test_feed_empty_data(self):
+ stream = streams.StreamReader(loop=self.loop)
+
+ stream.feed_data(b'')
+ self.assertEqual(0, stream._byte_count)
+
+ def test_feed_data_byte_count(self):
+ stream = streams.StreamReader(loop=self.loop)
+
+ stream.feed_data(self.DATA)
+ self.assertEqual(len(self.DATA), stream._byte_count)
+
+ def test_read_zero(self):
+ # Read zero bytes.
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(self.DATA)
+
+ data = self.loop.run_until_complete(stream.read(0))
+ self.assertEqual(b'', data)
+ self.assertEqual(len(self.DATA), stream._byte_count)
+
+ def test_read(self):
+ # Read bytes.
+ stream = streams.StreamReader(loop=self.loop)
+ read_task = tasks.Task(stream.read(30), loop=self.loop)
+
+ def cb():
+ stream.feed_data(self.DATA)
+ self.loop.call_soon(cb)
+
+ data = self.loop.run_until_complete(read_task)
+ self.assertEqual(self.DATA, data)
+ self.assertFalse(stream._byte_count)
+
+ def test_read_line_breaks(self):
+ # Read bytes without line breaks.
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(b'line1')
+ stream.feed_data(b'line2')
+
+ data = self.loop.run_until_complete(stream.read(5))
+
+ self.assertEqual(b'line1', data)
+ self.assertEqual(5, stream._byte_count)
+
+ def test_read_eof(self):
+ # Read bytes, stop at eof.
+ stream = streams.StreamReader(loop=self.loop)
+ read_task = tasks.Task(stream.read(1024), loop=self.loop)
+
+ def cb():
+ stream.feed_eof()
+ self.loop.call_soon(cb)
+
+ data = self.loop.run_until_complete(read_task)
+ self.assertEqual(b'', data)
+ self.assertFalse(stream._byte_count)
+
+ def test_read_until_eof(self):
+ # Read all bytes until eof.
+ stream = streams.StreamReader(loop=self.loop)
+ read_task = tasks.Task(stream.read(-1), loop=self.loop)
+
+ def cb():
+ stream.feed_data(b'chunk1\n')
+ stream.feed_data(b'chunk2')
+ stream.feed_eof()
+ self.loop.call_soon(cb)
+
+ data = self.loop.run_until_complete(read_task)
+
+ self.assertEqual(b'chunk1\nchunk2', data)
+ self.assertFalse(stream._byte_count)
+
+ def test_read_exception(self):
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(b'line\n')
+
+ data = self.loop.run_until_complete(stream.read(2))
+ self.assertEqual(b'li', data)
+
+ stream.set_exception(ValueError())
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete, stream.read(2))
+
+ def test_readline(self):
+ # Read one line.
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(b'chunk1 ')
+ read_task = tasks.Task(stream.readline(), loop=self.loop)
+
+ def cb():
+ stream.feed_data(b'chunk2 ')
+ stream.feed_data(b'chunk3 ')
+ stream.feed_data(b'\n chunk4')
+ self.loop.call_soon(cb)
+
+ line = self.loop.run_until_complete(read_task)
+ self.assertEqual(b'chunk1 chunk2 chunk3 \n', line)
+ self.assertEqual(len(b'\n chunk4')-1, stream._byte_count)
+
+ def test_readline_limit_with_existing_data(self):
+ stream = streams.StreamReader(3, loop=self.loop)
+ stream.feed_data(b'li')
+ stream.feed_data(b'ne1\nline2\n')
+
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete, stream.readline())
+ self.assertEqual([b'line2\n'], list(stream._buffer))
+
+ stream = streams.StreamReader(3, loop=self.loop)
+ stream.feed_data(b'li')
+ stream.feed_data(b'ne1')
+ stream.feed_data(b'li')
+
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete, stream.readline())
+ self.assertEqual([b'li'], list(stream._buffer))
+ self.assertEqual(2, stream._byte_count)
+
+ def test_readline_limit(self):
+ stream = streams.StreamReader(7, loop=self.loop)
+
+ def cb():
+ stream.feed_data(b'chunk1')
+ stream.feed_data(b'chunk2')
+ stream.feed_data(b'chunk3\n')
+ stream.feed_eof()
+ self.loop.call_soon(cb)
+
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete, stream.readline())
+ self.assertEqual([b'chunk3\n'], list(stream._buffer))
+ self.assertEqual(7, stream._byte_count)
+
+ def test_readline_line_byte_count(self):
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(self.DATA[:6])
+ stream.feed_data(self.DATA[6:])
+
+ line = self.loop.run_until_complete(stream.readline())
+
+ self.assertEqual(b'line1\n', line)
+ self.assertEqual(len(self.DATA) - len(b'line1\n'), stream._byte_count)
+
+ def test_readline_eof(self):
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(b'some data')
+ stream.feed_eof()
+
+ line = self.loop.run_until_complete(stream.readline())
+ self.assertEqual(b'some data', line)
+
+ def test_readline_empty_eof(self):
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_eof()
+
+ line = self.loop.run_until_complete(stream.readline())
+ self.assertEqual(b'', line)
+
+ def test_readline_read_byte_count(self):
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(self.DATA)
+
+ self.loop.run_until_complete(stream.readline())
+
+ data = self.loop.run_until_complete(stream.read(7))
+
+ self.assertEqual(b'line2\nl', data)
+ self.assertEqual(
+ len(self.DATA) - len(b'line1\n') - len(b'line2\nl'),
+ stream._byte_count)
+
+ def test_readline_exception(self):
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(b'line\n')
+
+ data = self.loop.run_until_complete(stream.readline())
+ self.assertEqual(b'line\n', data)
+
+ stream.set_exception(ValueError())
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete, stream.readline())
+
+ def test_readexactly_zero_or_less(self):
+ # Read exact number of bytes (zero or less).
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(self.DATA)
+
+ data = self.loop.run_until_complete(stream.readexactly(0))
+ self.assertEqual(b'', data)
+ self.assertEqual(len(self.DATA), stream._byte_count)
+
+ data = self.loop.run_until_complete(stream.readexactly(-1))
+ self.assertEqual(b'', data)
+ self.assertEqual(len(self.DATA), stream._byte_count)
+
+ def test_readexactly(self):
+ # Read exact number of bytes.
+ stream = streams.StreamReader(loop=self.loop)
+
+ n = 2 * len(self.DATA)
+ read_task = tasks.Task(stream.readexactly(n), loop=self.loop)
+
+ def cb():
+ stream.feed_data(self.DATA)
+ stream.feed_data(self.DATA)
+ stream.feed_data(self.DATA)
+ self.loop.call_soon(cb)
+
+ data = self.loop.run_until_complete(read_task)
+ self.assertEqual(self.DATA + self.DATA, data)
+ self.assertEqual(len(self.DATA), stream._byte_count)
+
+ def test_readexactly_eof(self):
+ # Read exact number of bytes (eof).
+ stream = streams.StreamReader(loop=self.loop)
+ n = 2 * len(self.DATA)
+ read_task = tasks.Task(stream.readexactly(n), loop=self.loop)
+
+ def cb():
+ stream.feed_data(self.DATA)
+ stream.feed_eof()
+ self.loop.call_soon(cb)
+
+ data = self.loop.run_until_complete(read_task)
+ self.assertEqual(self.DATA, data)
+ self.assertFalse(stream._byte_count)
+
+ def test_readexactly_exception(self):
+ stream = streams.StreamReader(loop=self.loop)
+ stream.feed_data(b'line\n')
+
+ data = self.loop.run_until_complete(stream.readexactly(2))
+ self.assertEqual(b'li', data)
+
+ stream.set_exception(ValueError())
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete, stream.readexactly(2))
+
+ def test_exception(self):
+ stream = streams.StreamReader(loop=self.loop)
+ self.assertIsNone(stream.exception())
+
+ exc = ValueError()
+ stream.set_exception(exc)
+ self.assertIs(stream.exception(), exc)
+
+ def test_exception_waiter(self):
+ stream = streams.StreamReader(loop=self.loop)
+
+ @tasks.coroutine
+ def set_err():
+ stream.set_exception(ValueError())
+
+ @tasks.coroutine
+ def readline():
+ yield from stream.readline()
+
+ t1 = tasks.Task(stream.readline(), loop=self.loop)
+ t2 = tasks.Task(set_err(), loop=self.loop)
+
+ self.loop.run_until_complete(tasks.wait([t1, t2], loop=self.loop))
+
+ self.assertRaises(ValueError, t1.result)
+
+ def test_exception_cancel(self):
+ stream = streams.StreamReader(loop=self.loop)
+
+ @tasks.coroutine
+ def read_a_line():
+ yield from stream.readline()
+
+ t = tasks.Task(read_a_line(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ t.cancel()
+ test_utils.run_briefly(self.loop)
+ # The following line fails if set_exception() isn't careful.
+ stream.set_exception(RuntimeError('message'))
+ test_utils.run_briefly(self.loop)
+ self.assertIs(stream._waiter, None)
+
+ def test_start_server(self):
+
+ class MyServer:
+
+ def __init__(self, loop):
+ self.server = None
+ self.loop = loop
+
+ @tasks.coroutine
+ def handle_client(self, client_reader, client_writer):
+ data = yield from client_reader.readline()
+ client_writer.write(data)
+
+ def start(self):
+ self.server = self.loop.run_until_complete(
+ streams.start_server(self.handle_client,
+ '127.0.0.1', 12345,
+ loop=self.loop))
+
+ def handle_client_callback(self, client_reader, client_writer):
+ task = tasks.Task(client_reader.readline(), loop=self.loop)
+
+ def done(task):
+ client_writer.write(task.result())
+
+ task.add_done_callback(done)
+
+ def start_callback(self):
+ self.server = self.loop.run_until_complete(
+ streams.start_server(self.handle_client_callback,
+ '127.0.0.1', 12345,
+ loop=self.loop))
+
+ def stop(self):
+ if self.server is not None:
+ self.server.close()
+ self.loop.run_until_complete(self.server.wait_closed())
+ self.server = None
+
+ @tasks.coroutine
+ def client():
+ reader, writer = yield from streams.open_connection(
+ '127.0.0.1', 12345, loop=self.loop)
+ # send a line
+ writer.write(b"hello world!\n")
+ # read it back
+ msgback = yield from reader.readline()
+ writer.close()
+ return msgback
+
+ # test the server variant with a coroutine as client handler
+ server = MyServer(self.loop)
+ server.start()
+ msg = self.loop.run_until_complete(tasks.Task(client(),
+ loop=self.loop))
+ server.stop()
+ self.assertEqual(msg, b"hello world!\n")
+
+ # test the server variant with a callback as client handler
+ server = MyServer(self.loop)
+ server.start_callback()
+ msg = self.loop.run_until_complete(tasks.Task(client(),
+ loop=self.loop))
+ server.stop()
+ self.assertEqual(msg, b"hello world!\n")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
new file mode 100644
index 0000000000..8f0d081554
--- /dev/null
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -0,0 +1,1518 @@
+"""Tests for tasks.py."""
+
+import gc
+import unittest
+import unittest.mock
+from unittest.mock import Mock
+
+from asyncio import events
+from asyncio import futures
+from asyncio import tasks
+from asyncio import test_utils
+
+
+class Dummy:
+
+ def __repr__(self):
+ return 'Dummy()'
+
+ def __call__(self, *args):
+ pass
+
+
+class TaskTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+ gc.collect()
+
+ def test_task_class(self):
+ @tasks.coroutine
+ def notmuch():
+ return 'ok'
+ t = tasks.Task(notmuch(), loop=self.loop)
+ self.loop.run_until_complete(t)
+ self.assertTrue(t.done())
+ self.assertEqual(t.result(), 'ok')
+ self.assertIs(t._loop, self.loop)
+
+ loop = events.new_event_loop()
+ t = tasks.Task(notmuch(), loop=loop)
+ self.assertIs(t._loop, loop)
+ loop.close()
+
+ def test_async_coroutine(self):
+ @tasks.coroutine
+ def notmuch():
+ return 'ok'
+ t = tasks.async(notmuch(), loop=self.loop)
+ self.loop.run_until_complete(t)
+ self.assertTrue(t.done())
+ self.assertEqual(t.result(), 'ok')
+ self.assertIs(t._loop, self.loop)
+
+ loop = events.new_event_loop()
+ t = tasks.async(notmuch(), loop=loop)
+ self.assertIs(t._loop, loop)
+ loop.close()
+
+ def test_async_future(self):
+ f_orig = futures.Future(loop=self.loop)
+ f_orig.set_result('ko')
+
+ f = tasks.async(f_orig)
+ self.loop.run_until_complete(f)
+ self.assertTrue(f.done())
+ self.assertEqual(f.result(), 'ko')
+ self.assertIs(f, f_orig)
+
+ loop = events.new_event_loop()
+
+ with self.assertRaises(ValueError):
+ f = tasks.async(f_orig, loop=loop)
+
+ loop.close()
+
+ f = tasks.async(f_orig, loop=self.loop)
+ self.assertIs(f, f_orig)
+
+ def test_async_task(self):
+ @tasks.coroutine
+ def notmuch():
+ return 'ok'
+ t_orig = tasks.Task(notmuch(), loop=self.loop)
+ t = tasks.async(t_orig)
+ self.loop.run_until_complete(t)
+ self.assertTrue(t.done())
+ self.assertEqual(t.result(), 'ok')
+ self.assertIs(t, t_orig)
+
+ loop = events.new_event_loop()
+
+ with self.assertRaises(ValueError):
+ t = tasks.async(t_orig, loop=loop)
+
+ loop.close()
+
+ t = tasks.async(t_orig, loop=self.loop)
+ self.assertIs(t, t_orig)
+
+ def test_async_neither(self):
+ with self.assertRaises(TypeError):
+ tasks.async('ok')
+
+ def test_task_repr(self):
+ @tasks.coroutine
+ def notmuch():
+ yield from []
+ return 'abc'
+
+ t = tasks.Task(notmuch(), loop=self.loop)
+ t.add_done_callback(Dummy())
+ self.assertEqual(repr(t), 'Task(<notmuch>)<PENDING, [Dummy()]>')
+ t.cancel() # Does not take immediate effect!
+ self.assertEqual(repr(t), 'Task(<notmuch>)<CANCELLING, [Dummy()]>')
+ self.assertRaises(futures.CancelledError,
+ self.loop.run_until_complete, t)
+ self.assertEqual(repr(t), 'Task(<notmuch>)<CANCELLED>')
+ t = tasks.Task(notmuch(), loop=self.loop)
+ self.loop.run_until_complete(t)
+ self.assertEqual(repr(t), "Task(<notmuch>)<result='abc'>")
+
+ def test_task_repr_custom(self):
+ @tasks.coroutine
+ def coro():
+ pass
+
+ class T(futures.Future):
+ def __repr__(self):
+ return 'T[]'
+
+ class MyTask(tasks.Task, T):
+ def __repr__(self):
+ return super().__repr__()
+
+ gen = coro()
+ t = MyTask(gen, loop=self.loop)
+ self.assertEqual(repr(t), 'T[](<coro>)')
+ gen.close()
+
+ def test_task_basics(self):
+ @tasks.coroutine
+ def outer():
+ a = yield from inner1()
+ b = yield from inner2()
+ return a+b
+
+ @tasks.coroutine
+ def inner1():
+ return 42
+
+ @tasks.coroutine
+ def inner2():
+ return 1000
+
+ t = outer()
+ self.assertEqual(self.loop.run_until_complete(t), 1042)
+
+ def test_cancel(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(10.0, when)
+ yield 0
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ @tasks.coroutine
+ def task():
+ yield from tasks.sleep(10.0, loop=loop)
+ return 12
+
+ t = tasks.Task(task(), loop=loop)
+ loop.call_soon(t.cancel)
+ with self.assertRaises(futures.CancelledError):
+ loop.run_until_complete(t)
+ self.assertTrue(t.done())
+ self.assertTrue(t.cancelled())
+ self.assertFalse(t.cancel())
+
+ def test_cancel_yield(self):
+ @tasks.coroutine
+ def task():
+ yield
+ yield
+ return 12
+
+ t = tasks.Task(task(), loop=self.loop)
+ test_utils.run_briefly(self.loop) # start coro
+ t.cancel()
+ self.assertRaises(
+ futures.CancelledError, self.loop.run_until_complete, t)
+ self.assertTrue(t.done())
+ self.assertTrue(t.cancelled())
+ self.assertFalse(t.cancel())
+
+ def test_cancel_inner_future(self):
+ f = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def task():
+ yield from f
+ return 12
+
+ t = tasks.Task(task(), loop=self.loop)
+ test_utils.run_briefly(self.loop) # start task
+ f.cancel()
+ with self.assertRaises(futures.CancelledError):
+ self.loop.run_until_complete(t)
+ self.assertTrue(f.cancelled())
+ self.assertTrue(t.cancelled())
+
+ def test_cancel_both_task_and_inner_future(self):
+ f = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def task():
+ yield from f
+ return 12
+
+ t = tasks.Task(task(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+
+ f.cancel()
+ t.cancel()
+
+ with self.assertRaises(futures.CancelledError):
+ self.loop.run_until_complete(t)
+
+ self.assertTrue(t.done())
+ self.assertTrue(f.cancelled())
+ self.assertTrue(t.cancelled())
+
+ def test_cancel_task_catching(self):
+ fut1 = futures.Future(loop=self.loop)
+ fut2 = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def task():
+ yield from fut1
+ try:
+ yield from fut2
+ except futures.CancelledError:
+ return 42
+
+ t = tasks.Task(task(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertIs(t._fut_waiter, fut1) # White-box test.
+ fut1.set_result(None)
+ test_utils.run_briefly(self.loop)
+ self.assertIs(t._fut_waiter, fut2) # White-box test.
+ t.cancel()
+ self.assertTrue(fut2.cancelled())
+ res = self.loop.run_until_complete(t)
+ self.assertEqual(res, 42)
+ self.assertFalse(t.cancelled())
+
+ def test_cancel_task_ignoring(self):
+ fut1 = futures.Future(loop=self.loop)
+ fut2 = futures.Future(loop=self.loop)
+ fut3 = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def task():
+ yield from fut1
+ try:
+ yield from fut2
+ except futures.CancelledError:
+ pass
+ res = yield from fut3
+ return res
+
+ t = tasks.Task(task(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertIs(t._fut_waiter, fut1) # White-box test.
+ fut1.set_result(None)
+ test_utils.run_briefly(self.loop)
+ self.assertIs(t._fut_waiter, fut2) # White-box test.
+ t.cancel()
+ self.assertTrue(fut2.cancelled())
+ test_utils.run_briefly(self.loop)
+ self.assertIs(t._fut_waiter, fut3) # White-box test.
+ fut3.set_result(42)
+ res = self.loop.run_until_complete(t)
+ self.assertEqual(res, 42)
+ self.assertFalse(fut3.cancelled())
+ self.assertFalse(t.cancelled())
+
+ def test_cancel_current_task(self):
+ loop = events.new_event_loop()
+ self.addCleanup(loop.close)
+
+ @tasks.coroutine
+ def task():
+ t.cancel()
+ self.assertTrue(t._must_cancel) # White-box test.
+ # The sleep should be cancelled immediately.
+ yield from tasks.sleep(100, loop=loop)
+ return 12
+
+ t = tasks.Task(task(), loop=loop)
+ self.assertRaises(
+ futures.CancelledError, loop.run_until_complete, t)
+ self.assertTrue(t.done())
+ self.assertFalse(t._must_cancel) # White-box test.
+ self.assertFalse(t.cancel())
+
+ def test_stop_while_run_in_complete(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0.1
+ self.assertAlmostEqual(0.2, when)
+ when = yield 0.1
+ self.assertAlmostEqual(0.3, when)
+ yield 0.1
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ x = 0
+ waiters = []
+
+ @tasks.coroutine
+ def task():
+ nonlocal x
+ while x < 10:
+ waiters.append(tasks.sleep(0.1, loop=loop))
+ yield from waiters[-1]
+ x += 1
+ if x == 2:
+ loop.stop()
+
+ t = tasks.Task(task(), loop=loop)
+ self.assertRaises(
+ RuntimeError, loop.run_until_complete, t)
+ self.assertFalse(t.done())
+ self.assertEqual(x, 2)
+ self.assertAlmostEqual(0.3, loop.time())
+
+ # close generators
+ for w in waiters:
+ w.close()
+
+ def test_wait_for(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.2, when)
+ when = yield 0
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0.1
+ self.assertAlmostEqual(0.4, when)
+ yield 0.1
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ @tasks.coroutine
+ def foo():
+ yield from tasks.sleep(0.2, loop=loop)
+ return 'done'
+
+ fut = tasks.Task(foo(), loop=loop)
+
+ with self.assertRaises(futures.TimeoutError):
+ loop.run_until_complete(tasks.wait_for(fut, 0.1, loop=loop))
+
+ self.assertFalse(fut.done())
+ self.assertAlmostEqual(0.1, loop.time())
+
+ # wait for result
+ res = loop.run_until_complete(
+ tasks.wait_for(fut, 0.3, loop=loop))
+ self.assertEqual(res, 'done')
+ self.assertAlmostEqual(0.2, loop.time())
+
+ def test_wait_for_with_global_loop(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.2, when)
+ when = yield 0
+ self.assertAlmostEqual(0.01, when)
+ yield 0.01
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ @tasks.coroutine
+ def foo():
+ yield from tasks.sleep(0.2, loop=loop)
+ return 'done'
+
+ events.set_event_loop(loop)
+ try:
+ fut = tasks.Task(foo(), loop=loop)
+ with self.assertRaises(futures.TimeoutError):
+ loop.run_until_complete(tasks.wait_for(fut, 0.01))
+ finally:
+ events.set_event_loop(None)
+
+ self.assertAlmostEqual(0.01, loop.time())
+ self.assertFalse(fut.done())
+
+ # move forward to close generator
+ loop.advance_time(10)
+ loop.run_until_complete(fut)
+
+ def test_wait(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0
+ self.assertAlmostEqual(0.15, when)
+ yield 0.15
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.Task(tasks.sleep(0.1, loop=loop), loop=loop)
+ b = tasks.Task(tasks.sleep(0.15, loop=loop), loop=loop)
+
+ @tasks.coroutine
+ def foo():
+ done, pending = yield from tasks.wait([b, a], loop=loop)
+ self.assertEqual(done, set([a, b]))
+ self.assertEqual(pending, set())
+ return 42
+
+ res = loop.run_until_complete(tasks.Task(foo(), loop=loop))
+ self.assertEqual(res, 42)
+ self.assertAlmostEqual(0.15, loop.time())
+
+ # Doing it again should take no time and exercise a different path.
+ res = loop.run_until_complete(tasks.Task(foo(), loop=loop))
+ self.assertAlmostEqual(0.15, loop.time())
+ self.assertEqual(res, 42)
+
+ def test_wait_with_global_loop(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.01, when)
+ when = yield 0
+ self.assertAlmostEqual(0.015, when)
+ yield 0.015
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.Task(tasks.sleep(0.01, loop=loop), loop=loop)
+ b = tasks.Task(tasks.sleep(0.015, loop=loop), loop=loop)
+
+ @tasks.coroutine
+ def foo():
+ done, pending = yield from tasks.wait([b, a])
+ self.assertEqual(done, set([a, b]))
+ self.assertEqual(pending, set())
+ return 42
+
+ events.set_event_loop(loop)
+ try:
+ res = loop.run_until_complete(
+ tasks.Task(foo(), loop=loop))
+ finally:
+ events.set_event_loop(None)
+
+ self.assertEqual(res, 42)
+
+ def test_wait_errors(self):
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete,
+ tasks.wait(set(), loop=self.loop))
+
+ self.assertRaises(
+ ValueError, self.loop.run_until_complete,
+ tasks.wait([tasks.sleep(10.0, loop=self.loop)],
+ return_when=-1, loop=self.loop))
+
+ def test_wait_first_completed(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(10.0, when)
+ when = yield 0
+ self.assertAlmostEqual(0.1, when)
+ yield 0.1
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.Task(tasks.sleep(10.0, loop=loop), loop=loop)
+ b = tasks.Task(tasks.sleep(0.1, loop=loop), loop=loop)
+ task = tasks.Task(
+ tasks.wait([b, a], return_when=tasks.FIRST_COMPLETED,
+ loop=loop),
+ loop=loop)
+
+ done, pending = loop.run_until_complete(task)
+ self.assertEqual({b}, done)
+ self.assertEqual({a}, pending)
+ self.assertFalse(a.done())
+ self.assertTrue(b.done())
+ self.assertIsNone(b.result())
+ self.assertAlmostEqual(0.1, loop.time())
+
+ # move forward to close generator
+ loop.advance_time(10)
+ loop.run_until_complete(tasks.wait([a, b], loop=loop))
+
+ def test_wait_really_done(self):
+ # there is possibility that some tasks in the pending list
+ # became done but their callbacks haven't all been called yet
+
+ @tasks.coroutine
+ def coro1():
+ yield
+
+ @tasks.coroutine
+ def coro2():
+ yield
+ yield
+
+ a = tasks.Task(coro1(), loop=self.loop)
+ b = tasks.Task(coro2(), loop=self.loop)
+ task = tasks.Task(
+ tasks.wait([b, a], return_when=tasks.FIRST_COMPLETED,
+ loop=self.loop),
+ loop=self.loop)
+
+ done, pending = self.loop.run_until_complete(task)
+ self.assertEqual({a, b}, done)
+ self.assertTrue(a.done())
+ self.assertIsNone(a.result())
+ self.assertTrue(b.done())
+ self.assertIsNone(b.result())
+
+ def test_wait_first_exception(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(10.0, when)
+ yield 0
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ # first_exception, task already has exception
+ a = tasks.Task(tasks.sleep(10.0, loop=loop), loop=loop)
+
+ @tasks.coroutine
+ def exc():
+ raise ZeroDivisionError('err')
+
+ b = tasks.Task(exc(), loop=loop)
+ task = tasks.Task(
+ tasks.wait([b, a], return_when=tasks.FIRST_EXCEPTION,
+ loop=loop),
+ loop=loop)
+
+ done, pending = loop.run_until_complete(task)
+ self.assertEqual({b}, done)
+ self.assertEqual({a}, pending)
+ self.assertAlmostEqual(0, loop.time())
+
+ # move forward to close generator
+ loop.advance_time(10)
+ loop.run_until_complete(tasks.wait([a, b], loop=loop))
+
+ def test_wait_first_exception_in_wait(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(10.0, when)
+ when = yield 0
+ self.assertAlmostEqual(0.01, when)
+ yield 0.01
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ # first_exception, exception during waiting
+ a = tasks.Task(tasks.sleep(10.0, loop=loop), loop=loop)
+
+ @tasks.coroutine
+ def exc():
+ yield from tasks.sleep(0.01, loop=loop)
+ raise ZeroDivisionError('err')
+
+ b = tasks.Task(exc(), loop=loop)
+ task = tasks.wait([b, a], return_when=tasks.FIRST_EXCEPTION,
+ loop=loop)
+
+ done, pending = loop.run_until_complete(task)
+ self.assertEqual({b}, done)
+ self.assertEqual({a}, pending)
+ self.assertAlmostEqual(0.01, loop.time())
+
+ # move forward to close generator
+ loop.advance_time(10)
+ loop.run_until_complete(tasks.wait([a, b], loop=loop))
+
+ def test_wait_with_exception(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0
+ self.assertAlmostEqual(0.15, when)
+ yield 0.15
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.Task(tasks.sleep(0.1, loop=loop), loop=loop)
+
+ @tasks.coroutine
+ def sleeper():
+ yield from tasks.sleep(0.15, loop=loop)
+ raise ZeroDivisionError('really')
+
+ b = tasks.Task(sleeper(), loop=loop)
+
+ @tasks.coroutine
+ def foo():
+ done, pending = yield from tasks.wait([b, a], loop=loop)
+ self.assertEqual(len(done), 2)
+ self.assertEqual(pending, set())
+ errors = set(f for f in done if f.exception() is not None)
+ self.assertEqual(len(errors), 1)
+
+ loop.run_until_complete(tasks.Task(foo(), loop=loop))
+ self.assertAlmostEqual(0.15, loop.time())
+
+ loop.run_until_complete(tasks.Task(foo(), loop=loop))
+ self.assertAlmostEqual(0.15, loop.time())
+
+ def test_wait_with_timeout(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0
+ self.assertAlmostEqual(0.15, when)
+ when = yield 0
+ self.assertAlmostEqual(0.11, when)
+ yield 0.11
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.Task(tasks.sleep(0.1, loop=loop), loop=loop)
+ b = tasks.Task(tasks.sleep(0.15, loop=loop), loop=loop)
+
+ @tasks.coroutine
+ def foo():
+ done, pending = yield from tasks.wait([b, a], timeout=0.11,
+ loop=loop)
+ self.assertEqual(done, set([a]))
+ self.assertEqual(pending, set([b]))
+
+ loop.run_until_complete(tasks.Task(foo(), loop=loop))
+ self.assertAlmostEqual(0.11, loop.time())
+
+ # move forward to close generator
+ loop.advance_time(10)
+ loop.run_until_complete(tasks.wait([a, b], loop=loop))
+
+ def test_wait_concurrent_complete(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0
+ self.assertAlmostEqual(0.15, when)
+ when = yield 0
+ self.assertAlmostEqual(0.1, when)
+ yield 0.1
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.Task(tasks.sleep(0.1, loop=loop), loop=loop)
+ b = tasks.Task(tasks.sleep(0.15, loop=loop), loop=loop)
+
+ done, pending = loop.run_until_complete(
+ tasks.wait([b, a], timeout=0.1, loop=loop))
+
+ self.assertEqual(done, set([a]))
+ self.assertEqual(pending, set([b]))
+ self.assertAlmostEqual(0.1, loop.time())
+
+ # move forward to close generator
+ loop.advance_time(10)
+ loop.run_until_complete(tasks.wait([a, b], loop=loop))
+
+ def test_as_completed(self):
+
+ def gen():
+ yield 0
+ yield 0
+ yield 0.01
+ yield 0
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+ completed = set()
+ time_shifted = False
+
+ @tasks.coroutine
+ def sleeper(dt, x):
+ nonlocal time_shifted
+ yield from tasks.sleep(dt, loop=loop)
+ completed.add(x)
+ if not time_shifted and 'a' in completed and 'b' in completed:
+ time_shifted = True
+ loop.advance_time(0.14)
+ return x
+
+ a = sleeper(0.01, 'a')
+ b = sleeper(0.01, 'b')
+ c = sleeper(0.15, 'c')
+
+ @tasks.coroutine
+ def foo():
+ values = []
+ for f in tasks.as_completed([b, c, a], loop=loop):
+ values.append((yield from f))
+ return values
+
+ res = loop.run_until_complete(tasks.Task(foo(), loop=loop))
+ self.assertAlmostEqual(0.15, loop.time())
+ self.assertTrue('a' in res[:2])
+ self.assertTrue('b' in res[:2])
+ self.assertEqual(res[2], 'c')
+
+ # Doing it again should take no time and exercise a different path.
+ res = loop.run_until_complete(tasks.Task(foo(), loop=loop))
+ self.assertAlmostEqual(0.15, loop.time())
+
+ def test_as_completed_with_timeout(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.12, when)
+ when = yield 0
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0
+ self.assertAlmostEqual(0.15, when)
+ when = yield 0.1
+ self.assertAlmostEqual(0.12, when)
+ yield 0.02
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.sleep(0.1, 'a', loop=loop)
+ b = tasks.sleep(0.15, 'b', loop=loop)
+
+ @tasks.coroutine
+ def foo():
+ values = []
+ for f in tasks.as_completed([a, b], timeout=0.12, loop=loop):
+ try:
+ v = yield from f
+ values.append((1, v))
+ except futures.TimeoutError as exc:
+ values.append((2, exc))
+ return values
+
+ res = loop.run_until_complete(tasks.Task(foo(), loop=loop))
+ self.assertEqual(len(res), 2, res)
+ self.assertEqual(res[0], (1, 'a'))
+ self.assertEqual(res[1][0], 2)
+ self.assertIsInstance(res[1][1], futures.TimeoutError)
+ self.assertAlmostEqual(0.12, loop.time())
+
+ # move forward to close generator
+ loop.advance_time(10)
+ loop.run_until_complete(tasks.wait([a, b], loop=loop))
+
+ def test_as_completed_reverse_wait(self):
+
+ def gen():
+ yield 0
+ yield 0.05
+ yield 0
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.sleep(0.05, 'a', loop=loop)
+ b = tasks.sleep(0.10, 'b', loop=loop)
+ fs = {a, b}
+ futs = list(tasks.as_completed(fs, loop=loop))
+ self.assertEqual(len(futs), 2)
+
+ x = loop.run_until_complete(futs[1])
+ self.assertEqual(x, 'a')
+ self.assertAlmostEqual(0.05, loop.time())
+ loop.advance_time(0.05)
+ y = loop.run_until_complete(futs[0])
+ self.assertEqual(y, 'b')
+ self.assertAlmostEqual(0.10, loop.time())
+
+ def test_as_completed_concurrent(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.05, when)
+ when = yield 0
+ self.assertAlmostEqual(0.05, when)
+ yield 0.05
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ a = tasks.sleep(0.05, 'a', loop=loop)
+ b = tasks.sleep(0.05, 'b', loop=loop)
+ fs = {a, b}
+ futs = list(tasks.as_completed(fs, loop=loop))
+ self.assertEqual(len(futs), 2)
+ waiter = tasks.wait(futs, loop=loop)
+ done, pending = loop.run_until_complete(waiter)
+ self.assertEqual(set(f.result() for f in done), {'a', 'b'})
+
+ def test_sleep(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.05, when)
+ when = yield 0.05
+ self.assertAlmostEqual(0.1, when)
+ yield 0.05
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ @tasks.coroutine
+ def sleeper(dt, arg):
+ yield from tasks.sleep(dt/2, loop=loop)
+ res = yield from tasks.sleep(dt/2, arg, loop=loop)
+ return res
+
+ t = tasks.Task(sleeper(0.1, 'yeah'), loop=loop)
+ loop.run_until_complete(t)
+ self.assertTrue(t.done())
+ self.assertEqual(t.result(), 'yeah')
+ self.assertAlmostEqual(0.1, loop.time())
+
+ def test_sleep_cancel(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(10.0, when)
+ yield 0
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ t = tasks.Task(tasks.sleep(10.0, 'yeah', loop=loop),
+ loop=loop)
+
+ handle = None
+ orig_call_later = loop.call_later
+
+ def call_later(self, delay, callback, *args):
+ nonlocal handle
+ handle = orig_call_later(self, delay, callback, *args)
+ return handle
+
+ loop.call_later = call_later
+ test_utils.run_briefly(loop)
+
+ self.assertFalse(handle._cancelled)
+
+ t.cancel()
+ test_utils.run_briefly(loop)
+ self.assertTrue(handle._cancelled)
+
+ def test_task_cancel_sleeping_task(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(0.1, when)
+ when = yield 0
+ self.assertAlmostEqual(5000, when)
+ yield 0.1
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ sleepfut = None
+
+ @tasks.coroutine
+ def sleep(dt):
+ nonlocal sleepfut
+ sleepfut = tasks.sleep(dt, loop=loop)
+ yield from sleepfut
+
+ @tasks.coroutine
+ def doit():
+ sleeper = tasks.Task(sleep(5000), loop=loop)
+ loop.call_later(0.1, sleeper.cancel)
+ try:
+ yield from sleeper
+ except futures.CancelledError:
+ return 'cancelled'
+ else:
+ return 'slept in'
+
+ doer = doit()
+ self.assertEqual(loop.run_until_complete(doer), 'cancelled')
+ self.assertAlmostEqual(0.1, loop.time())
+
+ def test_task_cancel_waiter_future(self):
+ fut = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def coro():
+ yield from fut
+
+ task = tasks.Task(coro(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertIs(task._fut_waiter, fut)
+
+ task.cancel()
+ test_utils.run_briefly(self.loop)
+ self.assertRaises(
+ futures.CancelledError, self.loop.run_until_complete, task)
+ self.assertIsNone(task._fut_waiter)
+ self.assertTrue(fut.cancelled())
+
+ def test_step_in_completed_task(self):
+ @tasks.coroutine
+ def notmuch():
+ return 'ko'
+
+ gen = notmuch()
+ task = tasks.Task(gen, loop=self.loop)
+ task.set_result('ok')
+
+ self.assertRaises(AssertionError, task._step)
+ gen.close()
+
+ def test_step_result(self):
+ @tasks.coroutine
+ def notmuch():
+ yield None
+ yield 1
+ return 'ko'
+
+ self.assertRaises(
+ RuntimeError, self.loop.run_until_complete, notmuch())
+
+ def test_step_result_future(self):
+ # If coroutine returns future, task waits on this future.
+
+ class Fut(futures.Future):
+ def __init__(self, *args, **kwds):
+ self.cb_added = False
+ super().__init__(*args, **kwds)
+
+ def add_done_callback(self, fn):
+ self.cb_added = True
+ super().add_done_callback(fn)
+
+ fut = Fut(loop=self.loop)
+ result = None
+
+ @tasks.coroutine
+ def wait_for_future():
+ nonlocal result
+ result = yield from fut
+
+ t = tasks.Task(wait_for_future(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ self.assertTrue(fut.cb_added)
+
+ res = object()
+ fut.set_result(res)
+ test_utils.run_briefly(self.loop)
+ self.assertIs(res, result)
+ self.assertTrue(t.done())
+ self.assertIsNone(t.result())
+
+ def test_step_with_baseexception(self):
+ @tasks.coroutine
+ def notmutch():
+ raise BaseException()
+
+ task = tasks.Task(notmutch(), loop=self.loop)
+ self.assertRaises(BaseException, task._step)
+
+ self.assertTrue(task.done())
+ self.assertIsInstance(task.exception(), BaseException)
+
+ def test_baseexception_during_cancel(self):
+
+ def gen():
+ when = yield
+ self.assertAlmostEqual(10.0, when)
+ yield 0
+
+ loop = test_utils.TestLoop(gen)
+ self.addCleanup(loop.close)
+
+ @tasks.coroutine
+ def sleeper():
+ yield from tasks.sleep(10, loop=loop)
+
+ base_exc = BaseException()
+
+ @tasks.coroutine
+ def notmutch():
+ try:
+ yield from sleeper()
+ except futures.CancelledError:
+ raise base_exc
+
+ task = tasks.Task(notmutch(), loop=loop)
+ test_utils.run_briefly(loop)
+
+ task.cancel()
+ self.assertFalse(task.done())
+
+ self.assertRaises(BaseException, test_utils.run_briefly, loop)
+
+ self.assertTrue(task.done())
+ self.assertFalse(task.cancelled())
+ self.assertIs(task.exception(), base_exc)
+
+ def test_iscoroutinefunction(self):
+ def fn():
+ pass
+
+ self.assertFalse(tasks.iscoroutinefunction(fn))
+
+ def fn1():
+ yield
+ self.assertFalse(tasks.iscoroutinefunction(fn1))
+
+ @tasks.coroutine
+ def fn2():
+ yield
+ self.assertTrue(tasks.iscoroutinefunction(fn2))
+
+ def test_yield_vs_yield_from(self):
+ fut = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def wait_for_future():
+ yield fut
+
+ task = wait_for_future()
+ with self.assertRaises(RuntimeError):
+ self.loop.run_until_complete(task)
+
+ self.assertFalse(fut.done())
+
+ def test_yield_vs_yield_from_generator(self):
+ @tasks.coroutine
+ def coro():
+ yield
+
+ @tasks.coroutine
+ def wait_for_future():
+ gen = coro()
+ try:
+ yield gen
+ finally:
+ gen.close()
+
+ task = wait_for_future()
+ self.assertRaises(
+ RuntimeError,
+ self.loop.run_until_complete, task)
+
+ def test_coroutine_non_gen_function(self):
+ @tasks.coroutine
+ def func():
+ return 'test'
+
+ self.assertTrue(tasks.iscoroutinefunction(func))
+
+ coro = func()
+ self.assertTrue(tasks.iscoroutine(coro))
+
+ res = self.loop.run_until_complete(coro)
+ self.assertEqual(res, 'test')
+
+ def test_coroutine_non_gen_function_return_future(self):
+ fut = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def func():
+ return fut
+
+ @tasks.coroutine
+ def coro():
+ fut.set_result('test')
+
+ t1 = tasks.Task(func(), loop=self.loop)
+ t2 = tasks.Task(coro(), loop=self.loop)
+ res = self.loop.run_until_complete(t1)
+ self.assertEqual(res, 'test')
+ self.assertIsNone(t2.result())
+
+ # Some thorough tests for cancellation propagation through
+ # coroutines, tasks and wait().
+
+ def test_yield_future_passes_cancel(self):
+ # Cancelling outer() cancels inner() cancels waiter.
+ proof = 0
+ waiter = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def inner():
+ nonlocal proof
+ try:
+ yield from waiter
+ except futures.CancelledError:
+ proof += 1
+ raise
+ else:
+ self.fail('got past sleep() in inner()')
+
+ @tasks.coroutine
+ def outer():
+ nonlocal proof
+ try:
+ yield from inner()
+ except futures.CancelledError:
+ proof += 100 # Expect this path.
+ else:
+ proof += 10
+
+ f = tasks.async(outer(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ f.cancel()
+ self.loop.run_until_complete(f)
+ self.assertEqual(proof, 101)
+ self.assertTrue(waiter.cancelled())
+
+ def test_yield_wait_does_not_shield_cancel(self):
+ # Cancelling outer() makes wait() return early, leaves inner()
+ # running.
+ proof = 0
+ waiter = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def inner():
+ nonlocal proof
+ yield from waiter
+ proof += 1
+
+ @tasks.coroutine
+ def outer():
+ nonlocal proof
+ d, p = yield from tasks.wait([inner()], loop=self.loop)
+ proof += 100
+
+ f = tasks.async(outer(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ f.cancel()
+ self.assertRaises(
+ futures.CancelledError, self.loop.run_until_complete, f)
+ waiter.set_result(None)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(proof, 1)
+
+ def test_shield_result(self):
+ inner = futures.Future(loop=self.loop)
+ outer = tasks.shield(inner)
+ inner.set_result(42)
+ res = self.loop.run_until_complete(outer)
+ self.assertEqual(res, 42)
+
+ def test_shield_exception(self):
+ inner = futures.Future(loop=self.loop)
+ outer = tasks.shield(inner)
+ test_utils.run_briefly(self.loop)
+ exc = RuntimeError('expected')
+ inner.set_exception(exc)
+ test_utils.run_briefly(self.loop)
+ self.assertIs(outer.exception(), exc)
+
+ def test_shield_cancel(self):
+ inner = futures.Future(loop=self.loop)
+ outer = tasks.shield(inner)
+ test_utils.run_briefly(self.loop)
+ inner.cancel()
+ test_utils.run_briefly(self.loop)
+ self.assertTrue(outer.cancelled())
+
+ def test_shield_shortcut(self):
+ fut = futures.Future(loop=self.loop)
+ fut.set_result(42)
+ res = self.loop.run_until_complete(tasks.shield(fut))
+ self.assertEqual(res, 42)
+
+ def test_shield_effect(self):
+ # Cancelling outer() does not affect inner().
+ proof = 0
+ waiter = futures.Future(loop=self.loop)
+
+ @tasks.coroutine
+ def inner():
+ nonlocal proof
+ yield from waiter
+ proof += 1
+
+ @tasks.coroutine
+ def outer():
+ nonlocal proof
+ yield from tasks.shield(inner(), loop=self.loop)
+ proof += 100
+
+ f = tasks.async(outer(), loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ f.cancel()
+ with self.assertRaises(futures.CancelledError):
+ self.loop.run_until_complete(f)
+ waiter.set_result(None)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(proof, 1)
+
+ def test_shield_gather(self):
+ child1 = futures.Future(loop=self.loop)
+ child2 = futures.Future(loop=self.loop)
+ parent = tasks.gather(child1, child2, loop=self.loop)
+ outer = tasks.shield(parent, loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ outer.cancel()
+ test_utils.run_briefly(self.loop)
+ self.assertTrue(outer.cancelled())
+ child1.set_result(1)
+ child2.set_result(2)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(parent.result(), [1, 2])
+
+ def test_gather_shield(self):
+ child1 = futures.Future(loop=self.loop)
+ child2 = futures.Future(loop=self.loop)
+ inner1 = tasks.shield(child1, loop=self.loop)
+ inner2 = tasks.shield(child2, loop=self.loop)
+ parent = tasks.gather(inner1, inner2, loop=self.loop)
+ test_utils.run_briefly(self.loop)
+ parent.cancel()
+ # This should cancel inner1 and inner2 but bot child1 and child2.
+ test_utils.run_briefly(self.loop)
+ self.assertIsInstance(parent.exception(), futures.CancelledError)
+ self.assertTrue(inner1.cancelled())
+ self.assertTrue(inner2.cancelled())
+ child1.set_result(1)
+ child2.set_result(2)
+ test_utils.run_briefly(self.loop)
+
+
+class GatherTestsBase:
+
+ def setUp(self):
+ self.one_loop = test_utils.TestLoop()
+ self.other_loop = test_utils.TestLoop()
+
+ def tearDown(self):
+ self.one_loop.close()
+ self.other_loop.close()
+
+ def _run_loop(self, loop):
+ while loop._ready:
+ test_utils.run_briefly(loop)
+
+ def _check_success(self, **kwargs):
+ a, b, c = [futures.Future(loop=self.one_loop) for i in range(3)]
+ fut = tasks.gather(*self.wrap_futures(a, b, c), **kwargs)
+ cb = Mock()
+ fut.add_done_callback(cb)
+ b.set_result(1)
+ a.set_result(2)
+ self._run_loop(self.one_loop)
+ self.assertEqual(cb.called, False)
+ self.assertFalse(fut.done())
+ c.set_result(3)
+ self._run_loop(self.one_loop)
+ cb.assert_called_once_with(fut)
+ self.assertEqual(fut.result(), [2, 1, 3])
+
+ def test_success(self):
+ self._check_success()
+ self._check_success(return_exceptions=False)
+
+ def test_result_exception_success(self):
+ self._check_success(return_exceptions=True)
+
+ def test_one_exception(self):
+ a, b, c, d, e = [futures.Future(loop=self.one_loop) for i in range(5)]
+ fut = tasks.gather(*self.wrap_futures(a, b, c, d, e))
+ cb = Mock()
+ fut.add_done_callback(cb)
+ exc = ZeroDivisionError()
+ a.set_result(1)
+ b.set_exception(exc)
+ self._run_loop(self.one_loop)
+ self.assertTrue(fut.done())
+ cb.assert_called_once_with(fut)
+ self.assertIs(fut.exception(), exc)
+ # Does nothing
+ c.set_result(3)
+ d.cancel()
+ e.set_exception(RuntimeError())
+
+ def test_return_exceptions(self):
+ a, b, c, d = [futures.Future(loop=self.one_loop) for i in range(4)]
+ fut = tasks.gather(*self.wrap_futures(a, b, c, d),
+ return_exceptions=True)
+ cb = Mock()
+ fut.add_done_callback(cb)
+ exc = ZeroDivisionError()
+ exc2 = RuntimeError()
+ b.set_result(1)
+ c.set_exception(exc)
+ a.set_result(3)
+ self._run_loop(self.one_loop)
+ self.assertFalse(fut.done())
+ d.set_exception(exc2)
+ self._run_loop(self.one_loop)
+ self.assertTrue(fut.done())
+ cb.assert_called_once_with(fut)
+ self.assertEqual(fut.result(), [3, 1, exc, exc2])
+
+
+class FutureGatherTests(GatherTestsBase, unittest.TestCase):
+
+ def wrap_futures(self, *futures):
+ return futures
+
+ def _check_empty_sequence(self, seq_or_iter):
+ events.set_event_loop(self.one_loop)
+ self.addCleanup(events.set_event_loop, None)
+ fut = tasks.gather(*seq_or_iter)
+ self.assertIsInstance(fut, futures.Future)
+ self.assertIs(fut._loop, self.one_loop)
+ self._run_loop(self.one_loop)
+ self.assertTrue(fut.done())
+ self.assertEqual(fut.result(), [])
+ fut = tasks.gather(*seq_or_iter, loop=self.other_loop)
+ self.assertIs(fut._loop, self.other_loop)
+
+ def test_constructor_empty_sequence(self):
+ self._check_empty_sequence([])
+ self._check_empty_sequence(())
+ self._check_empty_sequence(set())
+ self._check_empty_sequence(iter(""))
+
+ def test_constructor_heterogenous_futures(self):
+ fut1 = futures.Future(loop=self.one_loop)
+ fut2 = futures.Future(loop=self.other_loop)
+ with self.assertRaises(ValueError):
+ tasks.gather(fut1, fut2)
+ with self.assertRaises(ValueError):
+ tasks.gather(fut1, loop=self.other_loop)
+
+ def test_constructor_homogenous_futures(self):
+ children = [futures.Future(loop=self.other_loop) for i in range(3)]
+ fut = tasks.gather(*children)
+ self.assertIs(fut._loop, self.other_loop)
+ self._run_loop(self.other_loop)
+ self.assertFalse(fut.done())
+ fut = tasks.gather(*children, loop=self.other_loop)
+ self.assertIs(fut._loop, self.other_loop)
+ self._run_loop(self.other_loop)
+ self.assertFalse(fut.done())
+
+ def test_one_cancellation(self):
+ a, b, c, d, e = [futures.Future(loop=self.one_loop) for i in range(5)]
+ fut = tasks.gather(a, b, c, d, e)
+ cb = Mock()
+ fut.add_done_callback(cb)
+ a.set_result(1)
+ b.cancel()
+ self._run_loop(self.one_loop)
+ self.assertTrue(fut.done())
+ cb.assert_called_once_with(fut)
+ self.assertFalse(fut.cancelled())
+ self.assertIsInstance(fut.exception(), futures.CancelledError)
+ # Does nothing
+ c.set_result(3)
+ d.cancel()
+ e.set_exception(RuntimeError())
+
+ def test_result_exception_one_cancellation(self):
+ a, b, c, d, e, f = [futures.Future(loop=self.one_loop)
+ for i in range(6)]
+ fut = tasks.gather(a, b, c, d, e, f, return_exceptions=True)
+ cb = Mock()
+ fut.add_done_callback(cb)
+ a.set_result(1)
+ zde = ZeroDivisionError()
+ b.set_exception(zde)
+ c.cancel()
+ self._run_loop(self.one_loop)
+ self.assertFalse(fut.done())
+ d.set_result(3)
+ e.cancel()
+ rte = RuntimeError()
+ f.set_exception(rte)
+ res = self.one_loop.run_until_complete(fut)
+ self.assertIsInstance(res[2], futures.CancelledError)
+ self.assertIsInstance(res[4], futures.CancelledError)
+ res[2] = res[4] = None
+ self.assertEqual(res, [1, zde, None, 3, None, rte])
+ cb.assert_called_once_with(fut)
+
+
+class CoroutineGatherTests(GatherTestsBase, unittest.TestCase):
+
+ def setUp(self):
+ super().setUp()
+ events.set_event_loop(self.one_loop)
+
+ def tearDown(self):
+ events.set_event_loop(None)
+ super().tearDown()
+
+ def wrap_futures(self, *futures):
+ coros = []
+ for fut in futures:
+ @tasks.coroutine
+ def coro(fut=fut):
+ return (yield from fut)
+ coros.append(coro())
+ return coros
+
+ def test_constructor_loop_selection(self):
+ @tasks.coroutine
+ def coro():
+ return 'abc'
+ gen1 = coro()
+ gen2 = coro()
+ fut = tasks.gather(gen1, gen2)
+ self.assertIs(fut._loop, self.one_loop)
+ gen1.close()
+ gen2.close()
+ gen3 = coro()
+ gen4 = coro()
+ fut = tasks.gather(gen3, gen4, loop=self.other_loop)
+ self.assertIs(fut._loop, self.other_loop)
+ gen3.close()
+ gen4.close()
+
+ def test_cancellation_broadcast(self):
+ # Cancelling outer() cancels all children.
+ proof = 0
+ waiter = futures.Future(loop=self.one_loop)
+
+ @tasks.coroutine
+ def inner():
+ nonlocal proof
+ yield from waiter
+ proof += 1
+
+ child1 = tasks.async(inner(), loop=self.one_loop)
+ child2 = tasks.async(inner(), loop=self.one_loop)
+ gatherer = None
+
+ @tasks.coroutine
+ def outer():
+ nonlocal proof, gatherer
+ gatherer = tasks.gather(child1, child2, loop=self.one_loop)
+ yield from gatherer
+ proof += 100
+
+ f = tasks.async(outer(), loop=self.one_loop)
+ test_utils.run_briefly(self.one_loop)
+ self.assertTrue(f.cancel())
+ with self.assertRaises(futures.CancelledError):
+ self.one_loop.run_until_complete(f)
+ self.assertFalse(gatherer.cancel())
+ self.assertTrue(waiter.cancelled())
+ self.assertTrue(child1.cancelled())
+ self.assertTrue(child2.cancelled())
+ test_utils.run_briefly(self.one_loop)
+ self.assertEqual(proof, 0)
+
+ def test_exception_marking(self):
+ # Test for the first line marked "Mark exception retrieved."
+
+ @tasks.coroutine
+ def inner(f):
+ yield from f
+ raise RuntimeError('should not be ignored')
+
+ a = futures.Future(loop=self.one_loop)
+ b = futures.Future(loop=self.one_loop)
+
+ @tasks.coroutine
+ def outer():
+ yield from tasks.gather(inner(a), inner(b), loop=self.one_loop)
+
+ f = tasks.async(outer(), loop=self.one_loop)
+ test_utils.run_briefly(self.one_loop)
+ a.set_result(None)
+ test_utils.run_briefly(self.one_loop)
+ b.set_result(None)
+ test_utils.run_briefly(self.one_loop)
+ self.assertIsInstance(f.exception(), RuntimeError)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_transports.py b/Lib/test/test_asyncio/test_transports.py
new file mode 100644
index 0000000000..f96445c19c
--- /dev/null
+++ b/Lib/test/test_asyncio/test_transports.py
@@ -0,0 +1,59 @@
+"""Tests for transports.py."""
+
+import unittest
+import unittest.mock
+
+from asyncio import transports
+
+
+class TransportTests(unittest.TestCase):
+
+ def test_ctor_extra_is_none(self):
+ transport = transports.Transport()
+ self.assertEqual(transport._extra, {})
+
+ def test_get_extra_info(self):
+ transport = transports.Transport({'extra': 'info'})
+ self.assertEqual('info', transport.get_extra_info('extra'))
+ self.assertIsNone(transport.get_extra_info('unknown'))
+
+ default = object()
+ self.assertIs(default, transport.get_extra_info('unknown', default))
+
+ def test_writelines(self):
+ transport = transports.Transport()
+ transport.write = unittest.mock.Mock()
+
+ transport.writelines(['line1', 'line2', 'line3'])
+ self.assertEqual(3, transport.write.call_count)
+
+ def test_not_implemented(self):
+ transport = transports.Transport()
+
+ self.assertRaises(NotImplementedError, transport.write, 'data')
+ self.assertRaises(NotImplementedError, transport.write_eof)
+ self.assertRaises(NotImplementedError, transport.can_write_eof)
+ self.assertRaises(NotImplementedError, transport.pause_reading)
+ self.assertRaises(NotImplementedError, transport.resume_reading)
+ self.assertRaises(NotImplementedError, transport.close)
+ self.assertRaises(NotImplementedError, transport.abort)
+
+ def test_dgram_not_implemented(self):
+ transport = transports.DatagramTransport()
+
+ self.assertRaises(NotImplementedError, transport.sendto, 'data')
+ self.assertRaises(NotImplementedError, transport.abort)
+
+ def test_subprocess_transport_not_implemented(self):
+ transport = transports.SubprocessTransport()
+
+ self.assertRaises(NotImplementedError, transport.get_pid)
+ self.assertRaises(NotImplementedError, transport.get_returncode)
+ self.assertRaises(NotImplementedError, transport.get_pipe_transport, 1)
+ self.assertRaises(NotImplementedError, transport.send_signal, 1)
+ self.assertRaises(NotImplementedError, transport.terminate)
+ self.assertRaises(NotImplementedError, transport.kill)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py
new file mode 100644
index 0000000000..98cf407959
--- /dev/null
+++ b/Lib/test/test_asyncio/test_unix_events.py
@@ -0,0 +1,1488 @@
+"""Tests for unix_events.py."""
+
+import collections
+import gc
+import errno
+import io
+import os
+import pprint
+import signal
+import stat
+import sys
+import threading
+import unittest
+import unittest.mock
+
+if sys.platform == 'win32':
+ raise unittest.SkipTest('UNIX only')
+
+
+from asyncio import events
+from asyncio import futures
+from asyncio import protocols
+from asyncio import test_utils
+from asyncio import unix_events
+
+
+@unittest.skipUnless(signal, 'Signals are not supported')
+class SelectorEventLoopTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = unix_events.SelectorEventLoop()
+ events.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+
+ def test_check_signal(self):
+ self.assertRaises(
+ TypeError, self.loop._check_signal, '1')
+ self.assertRaises(
+ ValueError, self.loop._check_signal, signal.NSIG + 1)
+
+ def test_handle_signal_no_handler(self):
+ self.loop._handle_signal(signal.NSIG + 1, ())
+
+ def test_handle_signal_cancelled_handler(self):
+ h = events.Handle(unittest.mock.Mock(), ())
+ h.cancel()
+ self.loop._signal_handlers[signal.NSIG + 1] = h
+ self.loop.remove_signal_handler = unittest.mock.Mock()
+ self.loop._handle_signal(signal.NSIG + 1, ())
+ self.loop.remove_signal_handler.assert_called_with(signal.NSIG + 1)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ def test_add_signal_handler_setup_error(self, m_signal):
+ m_signal.NSIG = signal.NSIG
+ m_signal.set_wakeup_fd.side_effect = ValueError
+
+ self.assertRaises(
+ RuntimeError,
+ self.loop.add_signal_handler,
+ signal.SIGINT, lambda: True)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ def test_add_signal_handler(self, m_signal):
+ m_signal.NSIG = signal.NSIG
+
+ cb = lambda: True
+ self.loop.add_signal_handler(signal.SIGHUP, cb)
+ h = self.loop._signal_handlers.get(signal.SIGHUP)
+ self.assertIsInstance(h, events.Handle)
+ self.assertEqual(h._callback, cb)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ def test_add_signal_handler_install_error(self, m_signal):
+ m_signal.NSIG = signal.NSIG
+
+ def set_wakeup_fd(fd):
+ if fd == -1:
+ raise ValueError()
+ m_signal.set_wakeup_fd = set_wakeup_fd
+
+ class Err(OSError):
+ errno = errno.EFAULT
+ m_signal.signal.side_effect = Err
+
+ self.assertRaises(
+ Err,
+ self.loop.add_signal_handler,
+ signal.SIGINT, lambda: True)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ @unittest.mock.patch('asyncio.unix_events.logger')
+ def test_add_signal_handler_install_error2(self, m_logging, m_signal):
+ m_signal.NSIG = signal.NSIG
+
+ class Err(OSError):
+ errno = errno.EINVAL
+ m_signal.signal.side_effect = Err
+
+ self.loop._signal_handlers[signal.SIGHUP] = lambda: True
+ self.assertRaises(
+ RuntimeError,
+ self.loop.add_signal_handler,
+ signal.SIGINT, lambda: True)
+ self.assertFalse(m_logging.info.called)
+ self.assertEqual(1, m_signal.set_wakeup_fd.call_count)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ @unittest.mock.patch('asyncio.unix_events.logger')
+ def test_add_signal_handler_install_error3(self, m_logging, m_signal):
+ class Err(OSError):
+ errno = errno.EINVAL
+ m_signal.signal.side_effect = Err
+ m_signal.NSIG = signal.NSIG
+
+ self.assertRaises(
+ RuntimeError,
+ self.loop.add_signal_handler,
+ signal.SIGINT, lambda: True)
+ self.assertFalse(m_logging.info.called)
+ self.assertEqual(2, m_signal.set_wakeup_fd.call_count)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ def test_remove_signal_handler(self, m_signal):
+ m_signal.NSIG = signal.NSIG
+
+ self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
+
+ self.assertTrue(
+ self.loop.remove_signal_handler(signal.SIGHUP))
+ self.assertTrue(m_signal.set_wakeup_fd.called)
+ self.assertTrue(m_signal.signal.called)
+ self.assertEqual(
+ (signal.SIGHUP, m_signal.SIG_DFL), m_signal.signal.call_args[0])
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ def test_remove_signal_handler_2(self, m_signal):
+ m_signal.NSIG = signal.NSIG
+ m_signal.SIGINT = signal.SIGINT
+
+ self.loop.add_signal_handler(signal.SIGINT, lambda: True)
+ self.loop._signal_handlers[signal.SIGHUP] = object()
+ m_signal.set_wakeup_fd.reset_mock()
+
+ self.assertTrue(
+ self.loop.remove_signal_handler(signal.SIGINT))
+ self.assertFalse(m_signal.set_wakeup_fd.called)
+ self.assertTrue(m_signal.signal.called)
+ self.assertEqual(
+ (signal.SIGINT, m_signal.default_int_handler),
+ m_signal.signal.call_args[0])
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ @unittest.mock.patch('asyncio.unix_events.logger')
+ def test_remove_signal_handler_cleanup_error(self, m_logging, m_signal):
+ m_signal.NSIG = signal.NSIG
+ self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
+
+ m_signal.set_wakeup_fd.side_effect = ValueError
+
+ self.loop.remove_signal_handler(signal.SIGHUP)
+ self.assertTrue(m_logging.info)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ def test_remove_signal_handler_error(self, m_signal):
+ m_signal.NSIG = signal.NSIG
+ self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
+
+ m_signal.signal.side_effect = OSError
+
+ self.assertRaises(
+ OSError, self.loop.remove_signal_handler, signal.SIGHUP)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ def test_remove_signal_handler_error2(self, m_signal):
+ m_signal.NSIG = signal.NSIG
+ self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
+
+ class Err(OSError):
+ errno = errno.EINVAL
+ m_signal.signal.side_effect = Err
+
+ self.assertRaises(
+ RuntimeError, self.loop.remove_signal_handler, signal.SIGHUP)
+
+ @unittest.mock.patch('asyncio.unix_events.signal')
+ def test_close(self, m_signal):
+ m_signal.NSIG = signal.NSIG
+
+ self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
+ self.loop.add_signal_handler(signal.SIGCHLD, lambda: True)
+
+ self.assertEqual(len(self.loop._signal_handlers), 2)
+
+ m_signal.set_wakeup_fd.reset_mock()
+
+ self.loop.close()
+
+ self.assertEqual(len(self.loop._signal_handlers), 0)
+ m_signal.set_wakeup_fd.assert_called_once_with(-1)
+
+
+class UnixReadPipeTransportTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ self.protocol = test_utils.make_test_protocol(protocols.Protocol)
+ self.pipe = unittest.mock.Mock(spec_set=io.RawIOBase)
+ self.pipe.fileno.return_value = 5
+
+ fcntl_patcher = unittest.mock.patch('fcntl.fcntl')
+ fcntl_patcher.start()
+ self.addCleanup(fcntl_patcher.stop)
+
+ fstat_patcher = unittest.mock.patch('os.fstat')
+ m_fstat = fstat_patcher.start()
+ st = unittest.mock.Mock()
+ st.st_mode = stat.S_IFIFO
+ m_fstat.return_value = st
+ self.addCleanup(fstat_patcher.stop)
+
+ def test_ctor(self):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+ self.loop.assert_reader(5, tr._read_ready)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_made.assert_called_with(tr)
+
+ def test_ctor_with_waiter(self):
+ fut = futures.Future(loop=self.loop)
+ unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol, fut)
+ test_utils.run_briefly(self.loop)
+ self.assertIsNone(fut.result())
+
+ @unittest.mock.patch('os.read')
+ def test__read_ready(self, m_read):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+ m_read.return_value = b'data'
+ tr._read_ready()
+
+ m_read.assert_called_with(5, tr.max_size)
+ self.protocol.data_received.assert_called_with(b'data')
+
+ @unittest.mock.patch('os.read')
+ def test__read_ready_eof(self, m_read):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+ m_read.return_value = b''
+ tr._read_ready()
+
+ m_read.assert_called_with(5, tr.max_size)
+ self.assertFalse(self.loop.readers)
+ test_utils.run_briefly(self.loop)
+ self.protocol.eof_received.assert_called_with()
+ self.protocol.connection_lost.assert_called_with(None)
+
+ @unittest.mock.patch('os.read')
+ def test__read_ready_blocked(self, m_read):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+ m_read.side_effect = BlockingIOError
+ tr._read_ready()
+
+ m_read.assert_called_with(5, tr.max_size)
+ test_utils.run_briefly(self.loop)
+ self.assertFalse(self.protocol.data_received.called)
+
+ @unittest.mock.patch('asyncio.log.logger.exception')
+ @unittest.mock.patch('os.read')
+ def test__read_ready_error(self, m_read, m_logexc):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+ err = OSError()
+ m_read.side_effect = err
+ tr._close = unittest.mock.Mock()
+ tr._read_ready()
+
+ m_read.assert_called_with(5, tr.max_size)
+ tr._close.assert_called_with(err)
+ m_logexc.assert_called_with('Fatal error for %s', tr)
+
+ @unittest.mock.patch('os.read')
+ def test_pause_reading(self, m_read):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ m = unittest.mock.Mock()
+ self.loop.add_reader(5, m)
+ tr.pause_reading()
+ self.assertFalse(self.loop.readers)
+
+ @unittest.mock.patch('os.read')
+ def test_resume_reading(self, m_read):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ tr.resume_reading()
+ self.loop.assert_reader(5, tr._read_ready)
+
+ @unittest.mock.patch('os.read')
+ def test_close(self, m_read):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ tr._close = unittest.mock.Mock()
+ tr.close()
+ tr._close.assert_called_with(None)
+
+ @unittest.mock.patch('os.read')
+ def test_close_already_closing(self, m_read):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ tr._closing = True
+ tr._close = unittest.mock.Mock()
+ tr.close()
+ self.assertFalse(tr._close.called)
+
+ @unittest.mock.patch('os.read')
+ def test__close(self, m_read):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ err = object()
+ tr._close(err)
+ self.assertTrue(tr._closing)
+ self.assertFalse(self.loop.readers)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(err)
+
+ def test__call_connection_lost(self):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ err = None
+ tr._call_connection_lost(err)
+ self.protocol.connection_lost.assert_called_with(err)
+ self.pipe.close.assert_called_with()
+
+ self.assertIsNone(tr._protocol)
+ self.assertEqual(2, sys.getrefcount(self.protocol),
+ pprint.pformat(gc.get_referrers(self.protocol)))
+ self.assertIsNone(tr._loop)
+ self.assertEqual(2, sys.getrefcount(self.loop),
+ pprint.pformat(gc.get_referrers(self.loop)))
+
+ def test__call_connection_lost_with_err(self):
+ tr = unix_events._UnixReadPipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ err = OSError()
+ tr._call_connection_lost(err)
+ self.protocol.connection_lost.assert_called_with(err)
+ self.pipe.close.assert_called_with()
+
+ self.assertIsNone(tr._protocol)
+ self.assertEqual(2, sys.getrefcount(self.protocol),
+ pprint.pformat(gc.get_referrers(self.protocol)))
+ self.assertIsNone(tr._loop)
+ self.assertEqual(2, sys.getrefcount(self.loop),
+ pprint.pformat(gc.get_referrers(self.loop)))
+
+
+class UnixWritePipeTransportTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ self.protocol = test_utils.make_test_protocol(protocols.BaseProtocol)
+ self.pipe = unittest.mock.Mock(spec_set=io.RawIOBase)
+ self.pipe.fileno.return_value = 5
+
+ fcntl_patcher = unittest.mock.patch('fcntl.fcntl')
+ fcntl_patcher.start()
+ self.addCleanup(fcntl_patcher.stop)
+
+ fstat_patcher = unittest.mock.patch('os.fstat')
+ m_fstat = fstat_patcher.start()
+ st = unittest.mock.Mock()
+ st.st_mode = stat.S_IFSOCK
+ m_fstat.return_value = st
+ self.addCleanup(fstat_patcher.stop)
+
+ def test_ctor(self):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+ self.loop.assert_reader(5, tr._read_ready)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_made.assert_called_with(tr)
+
+ def test_ctor_with_waiter(self):
+ fut = futures.Future(loop=self.loop)
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol, fut)
+ self.loop.assert_reader(5, tr._read_ready)
+ test_utils.run_briefly(self.loop)
+ self.assertEqual(None, fut.result())
+
+ def test_can_write_eof(self):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+ self.assertTrue(tr.can_write_eof())
+
+ @unittest.mock.patch('os.write')
+ def test_write(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ m_write.return_value = 4
+ tr.write(b'data')
+ m_write.assert_called_with(5, b'data')
+ self.assertFalse(self.loop.writers)
+ self.assertEqual([], tr._buffer)
+
+ @unittest.mock.patch('os.write')
+ def test_write_no_data(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ tr.write(b'')
+ self.assertFalse(m_write.called)
+ self.assertFalse(self.loop.writers)
+ self.assertEqual([], tr._buffer)
+
+ @unittest.mock.patch('os.write')
+ def test_write_partial(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ m_write.return_value = 2
+ tr.write(b'data')
+ m_write.assert_called_with(5, b'data')
+ self.loop.assert_writer(5, tr._write_ready)
+ self.assertEqual([b'ta'], tr._buffer)
+
+ @unittest.mock.patch('os.write')
+ def test_write_buffer(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ self.loop.add_writer(5, tr._write_ready)
+ tr._buffer = [b'previous']
+ tr.write(b'data')
+ self.assertFalse(m_write.called)
+ self.loop.assert_writer(5, tr._write_ready)
+ self.assertEqual([b'previous', b'data'], tr._buffer)
+
+ @unittest.mock.patch('os.write')
+ def test_write_again(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ m_write.side_effect = BlockingIOError()
+ tr.write(b'data')
+ m_write.assert_called_with(5, b'data')
+ self.loop.assert_writer(5, tr._write_ready)
+ self.assertEqual([b'data'], tr._buffer)
+
+ @unittest.mock.patch('asyncio.unix_events.logger')
+ @unittest.mock.patch('os.write')
+ def test_write_err(self, m_write, m_log):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ err = OSError()
+ m_write.side_effect = err
+ tr._fatal_error = unittest.mock.Mock()
+ tr.write(b'data')
+ m_write.assert_called_with(5, b'data')
+ self.assertFalse(self.loop.writers)
+ self.assertEqual([], tr._buffer)
+ tr._fatal_error.assert_called_with(err)
+ self.assertEqual(1, tr._conn_lost)
+
+ tr.write(b'data')
+ self.assertEqual(2, tr._conn_lost)
+ tr.write(b'data')
+ tr.write(b'data')
+ tr.write(b'data')
+ tr.write(b'data')
+ # This is a bit overspecified. :-(
+ m_log.warning.assert_called_with(
+ 'pipe closed by peer or os.write(pipe, data) raised exception.')
+
+ @unittest.mock.patch('os.write')
+ def test_write_close(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+ tr._read_ready() # pipe was closed by peer
+
+ tr.write(b'data')
+ self.assertEqual(tr._conn_lost, 1)
+ tr.write(b'data')
+ self.assertEqual(tr._conn_lost, 2)
+
+ def test__read_ready(self):
+ tr = unix_events._UnixWritePipeTransport(self.loop, self.pipe,
+ self.protocol)
+ tr._read_ready()
+ self.assertFalse(self.loop.readers)
+ self.assertFalse(self.loop.writers)
+ self.assertTrue(tr._closing)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(None)
+
+ @unittest.mock.patch('os.write')
+ def test__write_ready(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+ self.loop.add_writer(5, tr._write_ready)
+ tr._buffer = [b'da', b'ta']
+ m_write.return_value = 4
+ tr._write_ready()
+ m_write.assert_called_with(5, b'data')
+ self.assertFalse(self.loop.writers)
+ self.assertEqual([], tr._buffer)
+
+ @unittest.mock.patch('os.write')
+ def test__write_ready_partial(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ self.loop.add_writer(5, tr._write_ready)
+ tr._buffer = [b'da', b'ta']
+ m_write.return_value = 3
+ tr._write_ready()
+ m_write.assert_called_with(5, b'data')
+ self.loop.assert_writer(5, tr._write_ready)
+ self.assertEqual([b'a'], tr._buffer)
+
+ @unittest.mock.patch('os.write')
+ def test__write_ready_again(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ self.loop.add_writer(5, tr._write_ready)
+ tr._buffer = [b'da', b'ta']
+ m_write.side_effect = BlockingIOError()
+ tr._write_ready()
+ m_write.assert_called_with(5, b'data')
+ self.loop.assert_writer(5, tr._write_ready)
+ self.assertEqual([b'data'], tr._buffer)
+
+ @unittest.mock.patch('os.write')
+ def test__write_ready_empty(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ self.loop.add_writer(5, tr._write_ready)
+ tr._buffer = [b'da', b'ta']
+ m_write.return_value = 0
+ tr._write_ready()
+ m_write.assert_called_with(5, b'data')
+ self.loop.assert_writer(5, tr._write_ready)
+ self.assertEqual([b'data'], tr._buffer)
+
+ @unittest.mock.patch('asyncio.log.logger.exception')
+ @unittest.mock.patch('os.write')
+ def test__write_ready_err(self, m_write, m_logexc):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ self.loop.add_writer(5, tr._write_ready)
+ tr._buffer = [b'da', b'ta']
+ m_write.side_effect = err = OSError()
+ tr._write_ready()
+ m_write.assert_called_with(5, b'data')
+ self.assertFalse(self.loop.writers)
+ self.assertFalse(self.loop.readers)
+ self.assertEqual([], tr._buffer)
+ self.assertTrue(tr._closing)
+ m_logexc.assert_called_with('Fatal error for %s', tr)
+ self.assertEqual(1, tr._conn_lost)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(err)
+
+ @unittest.mock.patch('os.write')
+ def test__write_ready_closing(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ self.loop.add_writer(5, tr._write_ready)
+ tr._closing = True
+ tr._buffer = [b'da', b'ta']
+ m_write.return_value = 4
+ tr._write_ready()
+ m_write.assert_called_with(5, b'data')
+ self.assertFalse(self.loop.writers)
+ self.assertFalse(self.loop.readers)
+ self.assertEqual([], tr._buffer)
+ self.protocol.connection_lost.assert_called_with(None)
+ self.pipe.close.assert_called_with()
+
+ @unittest.mock.patch('os.write')
+ def test_abort(self, m_write):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ self.loop.add_writer(5, tr._write_ready)
+ self.loop.add_reader(5, tr._read_ready)
+ tr._buffer = [b'da', b'ta']
+ tr.abort()
+ self.assertFalse(m_write.called)
+ self.assertFalse(self.loop.readers)
+ self.assertFalse(self.loop.writers)
+ self.assertEqual([], tr._buffer)
+ self.assertTrue(tr._closing)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(None)
+
+ def test__call_connection_lost(self):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ err = None
+ tr._call_connection_lost(err)
+ self.protocol.connection_lost.assert_called_with(err)
+ self.pipe.close.assert_called_with()
+
+ self.assertIsNone(tr._protocol)
+ self.assertEqual(2, sys.getrefcount(self.protocol),
+ pprint.pformat(gc.get_referrers(self.protocol)))
+ self.assertIsNone(tr._loop)
+ self.assertEqual(2, sys.getrefcount(self.loop),
+ pprint.pformat(gc.get_referrers(self.loop)))
+
+ def test__call_connection_lost_with_err(self):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ err = OSError()
+ tr._call_connection_lost(err)
+ self.protocol.connection_lost.assert_called_with(err)
+ self.pipe.close.assert_called_with()
+
+ self.assertIsNone(tr._protocol)
+ self.assertEqual(2, sys.getrefcount(self.protocol),
+ pprint.pformat(gc.get_referrers(self.protocol)))
+ self.assertIsNone(tr._loop)
+ self.assertEqual(2, sys.getrefcount(self.loop),
+ pprint.pformat(gc.get_referrers(self.loop)))
+
+ def test_close(self):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ tr.write_eof = unittest.mock.Mock()
+ tr.close()
+ tr.write_eof.assert_called_with()
+
+ def test_close_closing(self):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ tr.write_eof = unittest.mock.Mock()
+ tr._closing = True
+ tr.close()
+ self.assertFalse(tr.write_eof.called)
+
+ def test_write_eof(self):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+
+ tr.write_eof()
+ self.assertTrue(tr._closing)
+ self.assertFalse(self.loop.readers)
+ test_utils.run_briefly(self.loop)
+ self.protocol.connection_lost.assert_called_with(None)
+
+ def test_write_eof_pending(self):
+ tr = unix_events._UnixWritePipeTransport(
+ self.loop, self.pipe, self.protocol)
+ tr._buffer = [b'data']
+ tr.write_eof()
+ self.assertTrue(tr._closing)
+ self.assertFalse(self.protocol.connection_lost.called)
+
+
+class AbstractChildWatcherTests(unittest.TestCase):
+
+ def test_not_implemented(self):
+ f = unittest.mock.Mock()
+ watcher = unix_events.AbstractChildWatcher()
+ self.assertRaises(
+ NotImplementedError, watcher.add_child_handler, f, f)
+ self.assertRaises(
+ NotImplementedError, watcher.remove_child_handler, f)
+ self.assertRaises(
+ NotImplementedError, watcher.attach_loop, f)
+ self.assertRaises(
+ NotImplementedError, watcher.close)
+ self.assertRaises(
+ NotImplementedError, watcher.__enter__)
+ self.assertRaises(
+ NotImplementedError, watcher.__exit__, f, f, f)
+
+
+class BaseChildWatcherTests(unittest.TestCase):
+
+ def test_not_implemented(self):
+ f = unittest.mock.Mock()
+ watcher = unix_events.BaseChildWatcher()
+ self.assertRaises(
+ NotImplementedError, watcher._do_waitpid, f)
+
+
+WaitPidMocks = collections.namedtuple("WaitPidMocks",
+ ("waitpid",
+ "WIFEXITED",
+ "WIFSIGNALED",
+ "WEXITSTATUS",
+ "WTERMSIG",
+ ))
+
+
+class ChildWatcherTestsMixin:
+
+ ignore_warnings = unittest.mock.patch.object(unix_events.logger, "warning")
+
+ def setUp(self):
+ self.loop = test_utils.TestLoop()
+ self.running = False
+ self.zombies = {}
+
+ with unittest.mock.patch.object(
+ self.loop, "add_signal_handler") as self.m_add_signal_handler:
+ self.watcher = self.create_watcher()
+ self.watcher.attach_loop(self.loop)
+
+ def waitpid(self, pid, flags):
+ if isinstance(self.watcher, unix_events.SafeChildWatcher) or pid != -1:
+ self.assertGreater(pid, 0)
+ try:
+ if pid < 0:
+ return self.zombies.popitem()
+ else:
+ return pid, self.zombies.pop(pid)
+ except KeyError:
+ pass
+ if self.running:
+ return 0, 0
+ else:
+ raise ChildProcessError()
+
+ def add_zombie(self, pid, returncode):
+ self.zombies[pid] = returncode + 32768
+
+ def WIFEXITED(self, status):
+ return status >= 32768
+
+ def WIFSIGNALED(self, status):
+ return 32700 < status < 32768
+
+ def WEXITSTATUS(self, status):
+ self.assertTrue(self.WIFEXITED(status))
+ return status - 32768
+
+ def WTERMSIG(self, status):
+ self.assertTrue(self.WIFSIGNALED(status))
+ return 32768 - status
+
+ def test_create_watcher(self):
+ self.m_add_signal_handler.assert_called_once_with(
+ signal.SIGCHLD, self.watcher._sig_chld)
+
+ def waitpid_mocks(func):
+ def wrapped_func(self):
+ def patch(target, wrapper):
+ return unittest.mock.patch(target, wraps=wrapper,
+ new_callable=unittest.mock.Mock)
+
+ with patch('os.WTERMSIG', self.WTERMSIG) as m_WTERMSIG, \
+ patch('os.WEXITSTATUS', self.WEXITSTATUS) as m_WEXITSTATUS, \
+ patch('os.WIFSIGNALED', self.WIFSIGNALED) as m_WIFSIGNALED, \
+ patch('os.WIFEXITED', self.WIFEXITED) as m_WIFEXITED, \
+ patch('os.waitpid', self.waitpid) as m_waitpid:
+ func(self, WaitPidMocks(m_waitpid,
+ m_WIFEXITED, m_WIFSIGNALED,
+ m_WEXITSTATUS, m_WTERMSIG,
+ ))
+ return wrapped_func
+
+ @waitpid_mocks
+ def test_sigchld(self, m):
+ # register a child
+ callback = unittest.mock.Mock()
+
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(42, callback, 9, 10, 14)
+
+ self.assertFalse(callback.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # child is running
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # child terminates (returncode 12)
+ self.running = False
+ self.add_zombie(42, 12)
+ self.watcher._sig_chld()
+
+ self.assertTrue(m.WIFEXITED.called)
+ self.assertTrue(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+ callback.assert_called_once_with(42, 12, 9, 10, 14)
+
+ m.WIFSIGNALED.reset_mock()
+ m.WIFEXITED.reset_mock()
+ m.WEXITSTATUS.reset_mock()
+ callback.reset_mock()
+
+ # ensure that the child is effectively reaped
+ self.add_zombie(42, 13)
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ m.WIFSIGNALED.reset_mock()
+ m.WIFEXITED.reset_mock()
+ m.WEXITSTATUS.reset_mock()
+
+ # sigchld called again
+ self.zombies.clear()
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ @waitpid_mocks
+ def test_sigchld_two_children(self, m):
+ callback1 = unittest.mock.Mock()
+ callback2 = unittest.mock.Mock()
+
+ # register child 1
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(43, callback1, 7, 8)
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # register child 2
+ with self.watcher:
+ self.watcher.add_child_handler(44, callback2, 147, 18)
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # childen are running
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # child 1 terminates (signal 3)
+ self.add_zombie(43, -3)
+ self.watcher._sig_chld()
+
+ callback1.assert_called_once_with(43, -3, 7, 8)
+ self.assertFalse(callback2.called)
+ self.assertTrue(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertTrue(m.WTERMSIG.called)
+
+ m.WIFSIGNALED.reset_mock()
+ m.WIFEXITED.reset_mock()
+ m.WTERMSIG.reset_mock()
+ callback1.reset_mock()
+
+ # child 2 still running
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # child 2 terminates (code 108)
+ self.add_zombie(44, 108)
+ self.running = False
+ self.watcher._sig_chld()
+
+ callback2.assert_called_once_with(44, 108, 147, 18)
+ self.assertFalse(callback1.called)
+ self.assertTrue(m.WIFEXITED.called)
+ self.assertTrue(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ m.WIFSIGNALED.reset_mock()
+ m.WIFEXITED.reset_mock()
+ m.WEXITSTATUS.reset_mock()
+ callback2.reset_mock()
+
+ # ensure that the children are effectively reaped
+ self.add_zombie(43, 14)
+ self.add_zombie(44, 15)
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ m.WIFSIGNALED.reset_mock()
+ m.WIFEXITED.reset_mock()
+ m.WEXITSTATUS.reset_mock()
+
+ # sigchld called again
+ self.zombies.clear()
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ @waitpid_mocks
+ def test_sigchld_two_children_terminating_together(self, m):
+ callback1 = unittest.mock.Mock()
+ callback2 = unittest.mock.Mock()
+
+ # register child 1
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(45, callback1, 17, 8)
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # register child 2
+ with self.watcher:
+ self.watcher.add_child_handler(46, callback2, 1147, 18)
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # childen are running
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # child 1 terminates (code 78)
+ # child 2 terminates (signal 5)
+ self.add_zombie(45, 78)
+ self.add_zombie(46, -5)
+ self.running = False
+ self.watcher._sig_chld()
+
+ callback1.assert_called_once_with(45, 78, 17, 8)
+ callback2.assert_called_once_with(46, -5, 1147, 18)
+ self.assertTrue(m.WIFSIGNALED.called)
+ self.assertTrue(m.WIFEXITED.called)
+ self.assertTrue(m.WEXITSTATUS.called)
+ self.assertTrue(m.WTERMSIG.called)
+
+ m.WIFSIGNALED.reset_mock()
+ m.WIFEXITED.reset_mock()
+ m.WTERMSIG.reset_mock()
+ m.WEXITSTATUS.reset_mock()
+ callback1.reset_mock()
+ callback2.reset_mock()
+
+ # ensure that the children are effectively reaped
+ self.add_zombie(45, 14)
+ self.add_zombie(46, 15)
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ @waitpid_mocks
+ def test_sigchld_race_condition(self, m):
+ # register a child
+ callback = unittest.mock.Mock()
+
+ with self.watcher:
+ # child terminates before being registered
+ self.add_zombie(50, 4)
+ self.watcher._sig_chld()
+
+ self.watcher.add_child_handler(50, callback, 1, 12)
+
+ callback.assert_called_once_with(50, 4, 1, 12)
+ callback.reset_mock()
+
+ # ensure that the child is effectively reaped
+ self.add_zombie(50, -1)
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback.called)
+
+ @waitpid_mocks
+ def test_sigchld_replace_handler(self, m):
+ callback1 = unittest.mock.Mock()
+ callback2 = unittest.mock.Mock()
+
+ # register a child
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(51, callback1, 19)
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # register the same child again
+ with self.watcher:
+ self.watcher.add_child_handler(51, callback2, 21)
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # child terminates (signal 8)
+ self.running = False
+ self.add_zombie(51, -8)
+ self.watcher._sig_chld()
+
+ callback2.assert_called_once_with(51, -8, 21)
+ self.assertFalse(callback1.called)
+ self.assertTrue(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertTrue(m.WTERMSIG.called)
+
+ m.WIFSIGNALED.reset_mock()
+ m.WIFEXITED.reset_mock()
+ m.WTERMSIG.reset_mock()
+ callback2.reset_mock()
+
+ # ensure that the child is effectively reaped
+ self.add_zombie(51, 13)
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ @waitpid_mocks
+ def test_sigchld_remove_handler(self, m):
+ callback = unittest.mock.Mock()
+
+ # register a child
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(52, callback, 1984)
+
+ self.assertFalse(callback.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # unregister the child
+ self.watcher.remove_child_handler(52)
+
+ self.assertFalse(callback.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # child terminates (code 99)
+ self.running = False
+ self.add_zombie(52, 99)
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback.called)
+
+ @waitpid_mocks
+ def test_sigchld_unknown_status(self, m):
+ callback = unittest.mock.Mock()
+
+ # register a child
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(53, callback, -19)
+
+ self.assertFalse(callback.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # terminate with unknown status
+ self.zombies[53] = 1178
+ self.running = False
+ self.watcher._sig_chld()
+
+ callback.assert_called_once_with(53, 1178, -19)
+ self.assertTrue(m.WIFEXITED.called)
+ self.assertTrue(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ callback.reset_mock()
+ m.WIFEXITED.reset_mock()
+ m.WIFSIGNALED.reset_mock()
+
+ # ensure that the child is effectively reaped
+ self.add_zombie(53, 101)
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback.called)
+
+ @waitpid_mocks
+ def test_remove_child_handler(self, m):
+ callback1 = unittest.mock.Mock()
+ callback2 = unittest.mock.Mock()
+ callback3 = unittest.mock.Mock()
+
+ # register children
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(54, callback1, 1)
+ self.watcher.add_child_handler(55, callback2, 2)
+ self.watcher.add_child_handler(56, callback3, 3)
+
+ # remove child handler 1
+ self.assertTrue(self.watcher.remove_child_handler(54))
+
+ # remove child handler 2 multiple times
+ self.assertTrue(self.watcher.remove_child_handler(55))
+ self.assertFalse(self.watcher.remove_child_handler(55))
+ self.assertFalse(self.watcher.remove_child_handler(55))
+
+ # all children terminate
+ self.add_zombie(54, 0)
+ self.add_zombie(55, 1)
+ self.add_zombie(56, 2)
+ self.running = False
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ callback3.assert_called_once_with(56, 2, 3)
+
+ @waitpid_mocks
+ def test_sigchld_unhandled_exception(self, m):
+ callback = unittest.mock.Mock()
+
+ # register a child
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(57, callback)
+
+ # raise an exception
+ m.waitpid.side_effect = ValueError
+
+ with unittest.mock.patch.object(unix_events.logger,
+ "exception") as m_exception:
+
+ self.assertEqual(self.watcher._sig_chld(), None)
+ self.assertTrue(m_exception.called)
+
+ @waitpid_mocks
+ def test_sigchld_child_reaped_elsewhere(self, m):
+ # register a child
+ callback = unittest.mock.Mock()
+
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(58, callback)
+
+ self.assertFalse(callback.called)
+ self.assertFalse(m.WIFEXITED.called)
+ self.assertFalse(m.WIFSIGNALED.called)
+ self.assertFalse(m.WEXITSTATUS.called)
+ self.assertFalse(m.WTERMSIG.called)
+
+ # child terminates
+ self.running = False
+ self.add_zombie(58, 4)
+
+ # waitpid is called elsewhere
+ os.waitpid(58, os.WNOHANG)
+
+ m.waitpid.reset_mock()
+
+ # sigchld
+ with self.ignore_warnings:
+ self.watcher._sig_chld()
+
+ callback.assert_called(m.waitpid)
+ if isinstance(self.watcher, unix_events.FastChildWatcher):
+ # here the FastChildWatche enters a deadlock
+ # (there is no way to prevent it)
+ self.assertFalse(callback.called)
+ else:
+ callback.assert_called_once_with(58, 255)
+
+ @waitpid_mocks
+ def test_sigchld_unknown_pid_during_registration(self, m):
+ # register two children
+ callback1 = unittest.mock.Mock()
+ callback2 = unittest.mock.Mock()
+
+ with self.ignore_warnings, self.watcher:
+ self.running = True
+ # child 1 terminates
+ self.add_zombie(591, 7)
+ # an unknown child terminates
+ self.add_zombie(593, 17)
+
+ self.watcher._sig_chld()
+
+ self.watcher.add_child_handler(591, callback1)
+ self.watcher.add_child_handler(592, callback2)
+
+ callback1.assert_called_once_with(591, 7)
+ self.assertFalse(callback2.called)
+
+ @waitpid_mocks
+ def test_set_loop(self, m):
+ # register a child
+ callback = unittest.mock.Mock()
+
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(60, callback)
+
+ # attach a new loop
+ old_loop = self.loop
+ self.loop = test_utils.TestLoop()
+
+ with unittest.mock.patch.object(
+ old_loop,
+ "remove_signal_handler") as m_old_remove_signal_handler, \
+ unittest.mock.patch.object(
+ self.loop,
+ "add_signal_handler") as m_new_add_signal_handler:
+
+ self.watcher.attach_loop(self.loop)
+
+ m_old_remove_signal_handler.assert_called_once_with(
+ signal.SIGCHLD)
+ m_new_add_signal_handler.assert_called_once_with(
+ signal.SIGCHLD, self.watcher._sig_chld)
+
+ # child terminates
+ self.running = False
+ self.add_zombie(60, 9)
+ self.watcher._sig_chld()
+
+ callback.assert_called_once_with(60, 9)
+
+ @waitpid_mocks
+ def test_set_loop_race_condition(self, m):
+ # register 3 children
+ callback1 = unittest.mock.Mock()
+ callback2 = unittest.mock.Mock()
+ callback3 = unittest.mock.Mock()
+
+ with self.watcher:
+ self.running = True
+ self.watcher.add_child_handler(61, callback1)
+ self.watcher.add_child_handler(62, callback2)
+ self.watcher.add_child_handler(622, callback3)
+
+ # detach the loop
+ old_loop = self.loop
+ self.loop = None
+
+ with unittest.mock.patch.object(
+ old_loop, "remove_signal_handler") as m_remove_signal_handler:
+
+ self.watcher.attach_loop(None)
+
+ m_remove_signal_handler.assert_called_once_with(
+ signal.SIGCHLD)
+
+ # child 1 & 2 terminate
+ self.add_zombie(61, 11)
+ self.add_zombie(62, -5)
+
+ # SIGCHLD was not catched
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ self.assertFalse(callback3.called)
+
+ # attach a new loop
+ self.loop = test_utils.TestLoop()
+
+ with unittest.mock.patch.object(
+ self.loop, "add_signal_handler") as m_add_signal_handler:
+
+ self.watcher.attach_loop(self.loop)
+
+ m_add_signal_handler.assert_called_once_with(
+ signal.SIGCHLD, self.watcher._sig_chld)
+ callback1.assert_called_once_with(61, 11) # race condition!
+ callback2.assert_called_once_with(62, -5) # race condition!
+ self.assertFalse(callback3.called)
+
+ callback1.reset_mock()
+ callback2.reset_mock()
+
+ # child 3 terminates
+ self.running = False
+ self.add_zombie(622, 19)
+ self.watcher._sig_chld()
+
+ self.assertFalse(callback1.called)
+ self.assertFalse(callback2.called)
+ callback3.assert_called_once_with(622, 19)
+
+ @waitpid_mocks
+ def test_close(self, m):
+ # register two children
+ callback1 = unittest.mock.Mock()
+ callback2 = unittest.mock.Mock()
+
+ with self.watcher:
+ self.running = True
+ # child 1 terminates
+ self.add_zombie(63, 9)
+ # other child terminates
+ self.add_zombie(65, 18)
+ self.watcher._sig_chld()
+
+ self.watcher.add_child_handler(63, callback1)
+ self.watcher.add_child_handler(64, callback1)
+
+ self.assertEqual(len(self.watcher._callbacks), 1)
+ if isinstance(self.watcher, unix_events.FastChildWatcher):
+ self.assertEqual(len(self.watcher._zombies), 1)
+
+ with unittest.mock.patch.object(
+ self.loop,
+ "remove_signal_handler") as m_remove_signal_handler:
+
+ self.watcher.close()
+
+ m_remove_signal_handler.assert_called_once_with(
+ signal.SIGCHLD)
+ self.assertFalse(self.watcher._callbacks)
+ if isinstance(self.watcher, unix_events.FastChildWatcher):
+ self.assertFalse(self.watcher._zombies)
+
+
+class SafeChildWatcherTests (ChildWatcherTestsMixin, unittest.TestCase):
+ def create_watcher(self):
+ return unix_events.SafeChildWatcher()
+
+
+class FastChildWatcherTests (ChildWatcherTestsMixin, unittest.TestCase):
+ def create_watcher(self):
+ return unix_events.FastChildWatcher()
+
+
+class PolicyTests(unittest.TestCase):
+
+ def create_policy(self):
+ return unix_events.DefaultEventLoopPolicy()
+
+ def test_get_child_watcher(self):
+ policy = self.create_policy()
+ self.assertIsNone(policy._watcher)
+
+ watcher = policy.get_child_watcher()
+ self.assertIsInstance(watcher, unix_events.SafeChildWatcher)
+
+ self.assertIs(policy._watcher, watcher)
+
+ self.assertIs(watcher, policy.get_child_watcher())
+ self.assertIsNone(watcher._loop)
+
+ def test_get_child_watcher_after_set(self):
+ policy = self.create_policy()
+ watcher = unix_events.FastChildWatcher()
+
+ policy.set_child_watcher(watcher)
+ self.assertIs(policy._watcher, watcher)
+ self.assertIs(watcher, policy.get_child_watcher())
+
+ def test_get_child_watcher_with_mainloop_existing(self):
+ policy = self.create_policy()
+ loop = policy.get_event_loop()
+
+ self.assertIsNone(policy._watcher)
+ watcher = policy.get_child_watcher()
+
+ self.assertIsInstance(watcher, unix_events.SafeChildWatcher)
+ self.assertIs(watcher._loop, loop)
+
+ loop.close()
+
+ def test_get_child_watcher_thread(self):
+
+ def f():
+ policy.set_event_loop(policy.new_event_loop())
+
+ self.assertIsInstance(policy.get_event_loop(),
+ events.AbstractEventLoop)
+ watcher = policy.get_child_watcher()
+
+ self.assertIsInstance(watcher, unix_events.SafeChildWatcher)
+ self.assertIsNone(watcher._loop)
+
+ policy.get_event_loop().close()
+
+ policy = self.create_policy()
+
+ th = threading.Thread(target=f)
+ th.start()
+ th.join()
+
+ def test_child_watcher_replace_mainloop_existing(self):
+ policy = self.create_policy()
+ loop = policy.get_event_loop()
+
+ watcher = policy.get_child_watcher()
+
+ self.assertIs(watcher._loop, loop)
+
+ new_loop = policy.new_event_loop()
+ policy.set_event_loop(new_loop)
+
+ self.assertIs(watcher._loop, new_loop)
+
+ policy.set_event_loop(None)
+
+ self.assertIs(watcher._loop, None)
+
+ loop.close()
+ new_loop.close()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_windows_events.py b/Lib/test/test_asyncio/test_windows_events.py
new file mode 100644
index 0000000000..7ba33dacc4
--- /dev/null
+++ b/Lib/test/test_asyncio/test_windows_events.py
@@ -0,0 +1,139 @@
+import os
+import sys
+import unittest
+
+if sys.platform != 'win32':
+ raise unittest.SkipTest('Windows only')
+
+import _winapi
+
+import asyncio
+
+from asyncio import windows_events
+from asyncio import futures
+from asyncio import protocols
+from asyncio import streams
+from asyncio import transports
+from asyncio import test_utils
+from asyncio import _overlapped
+
+
+class UpperProto(protocols.Protocol):
+ def __init__(self):
+ self.buf = []
+
+ def connection_made(self, trans):
+ self.trans = trans
+
+ def data_received(self, data):
+ self.buf.append(data)
+ if b'\n' in data:
+ self.trans.write(b''.join(self.buf).upper())
+ self.trans.close()
+
+
+class ProactorTests(unittest.TestCase):
+
+ def setUp(self):
+ self.loop = windows_events.ProactorEventLoop()
+ asyncio.set_event_loop(None)
+
+ def tearDown(self):
+ self.loop.close()
+ self.loop = None
+
+ def test_close(self):
+ a, b = self.loop._socketpair()
+ trans = self.loop._make_socket_transport(a, protocols.Protocol())
+ f = asyncio.async(self.loop.sock_recv(b, 100))
+ trans.close()
+ self.loop.run_until_complete(f)
+ self.assertEqual(f.result(), b'')
+
+ def test_double_bind(self):
+ ADDRESS = r'\\.\pipe\test_double_bind-%s' % os.getpid()
+ server1 = windows_events.PipeServer(ADDRESS)
+ with self.assertRaises(PermissionError):
+ server2 = windows_events.PipeServer(ADDRESS)
+ server1.close()
+
+ def test_pipe(self):
+ res = self.loop.run_until_complete(self._test_pipe())
+ self.assertEqual(res, 'done')
+
+ def _test_pipe(self):
+ ADDRESS = r'\\.\pipe\_test_pipe-%s' % os.getpid()
+
+ with self.assertRaises(FileNotFoundError):
+ yield from self.loop.create_pipe_connection(
+ protocols.Protocol, ADDRESS)
+
+ [server] = yield from self.loop.start_serving_pipe(
+ UpperProto, ADDRESS)
+ self.assertIsInstance(server, windows_events.PipeServer)
+
+ clients = []
+ for i in range(5):
+ stream_reader = streams.StreamReader(loop=self.loop)
+ protocol = streams.StreamReaderProtocol(stream_reader)
+ trans, proto = yield from self.loop.create_pipe_connection(
+ lambda: protocol, ADDRESS)
+ self.assertIsInstance(trans, transports.Transport)
+ self.assertEqual(protocol, proto)
+ clients.append((stream_reader, trans))
+
+ for i, (r, w) in enumerate(clients):
+ w.write('lower-{}\n'.format(i).encode())
+
+ for i, (r, w) in enumerate(clients):
+ response = yield from r.readline()
+ self.assertEqual(response, 'LOWER-{}\n'.format(i).encode())
+ w.close()
+
+ server.close()
+
+ with self.assertRaises(FileNotFoundError):
+ yield from self.loop.create_pipe_connection(
+ protocols.Protocol, ADDRESS)
+
+ return 'done'
+
+ def test_wait_for_handle(self):
+ event = _overlapped.CreateEvent(None, True, False, None)
+ self.addCleanup(_winapi.CloseHandle, event)
+
+ # Wait for unset event with 0.2s timeout;
+ # result should be False at timeout
+ f = self.loop._proactor.wait_for_handle(event, 0.2)
+ start = self.loop.time()
+ self.loop.run_until_complete(f)
+ elapsed = self.loop.time() - start
+ self.assertFalse(f.result())
+ self.assertTrue(0.18 < elapsed < 0.5, elapsed)
+
+ _overlapped.SetEvent(event)
+
+ # Wait for for set event;
+ # result should be True immediately
+ f = self.loop._proactor.wait_for_handle(event, 10)
+ start = self.loop.time()
+ self.loop.run_until_complete(f)
+ elapsed = self.loop.time() - start
+ self.assertTrue(f.result())
+ self.assertTrue(0 <= elapsed < 0.02, elapsed)
+
+ _overlapped.ResetEvent(event)
+
+ # Wait for unset event with a cancelled future;
+ # CancelledError should be raised immediately
+ f = self.loop._proactor.wait_for_handle(event, 10)
+ f.cancel()
+ start = self.loop.time()
+ with self.assertRaises(futures.CancelledError):
+ self.loop.run_until_complete(f)
+ elapsed = self.loop.time() - start
+ self.assertTrue(0 <= elapsed < 0.02, elapsed)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/test_windows_utils.py b/Lib/test/test_asyncio/test_windows_utils.py
new file mode 100644
index 0000000000..fa9d66c021
--- /dev/null
+++ b/Lib/test/test_asyncio/test_windows_utils.py
@@ -0,0 +1,141 @@
+"""Tests for window_utils"""
+
+import sys
+import test.support
+import unittest
+import unittest.mock
+
+if sys.platform != 'win32':
+ raise unittest.SkipTest('Windows only')
+
+import _winapi
+
+from asyncio import windows_utils
+from asyncio import _overlapped
+
+
+class WinsocketpairTests(unittest.TestCase):
+
+ def test_winsocketpair(self):
+ ssock, csock = windows_utils.socketpair()
+
+ csock.send(b'xxx')
+ self.assertEqual(b'xxx', ssock.recv(1024))
+
+ csock.close()
+ ssock.close()
+
+ @unittest.mock.patch('asyncio.windows_utils.socket')
+ def test_winsocketpair_exc(self, m_socket):
+ m_socket.socket.return_value.getsockname.return_value = ('', 12345)
+ m_socket.socket.return_value.accept.return_value = object(), object()
+ m_socket.socket.return_value.connect.side_effect = OSError()
+
+ self.assertRaises(OSError, windows_utils.socketpair)
+
+
+class PipeTests(unittest.TestCase):
+
+ def test_pipe_overlapped(self):
+ h1, h2 = windows_utils.pipe(overlapped=(True, True))
+ try:
+ ov1 = _overlapped.Overlapped()
+ self.assertFalse(ov1.pending)
+ self.assertEqual(ov1.error, 0)
+
+ ov1.ReadFile(h1, 100)
+ self.assertTrue(ov1.pending)
+ self.assertEqual(ov1.error, _winapi.ERROR_IO_PENDING)
+ ERROR_IO_INCOMPLETE = 996
+ try:
+ ov1.getresult()
+ except OSError as e:
+ self.assertEqual(e.winerror, ERROR_IO_INCOMPLETE)
+ else:
+ raise RuntimeError('expected ERROR_IO_INCOMPLETE')
+
+ ov2 = _overlapped.Overlapped()
+ self.assertFalse(ov2.pending)
+ self.assertEqual(ov2.error, 0)
+
+ ov2.WriteFile(h2, b"hello")
+ self.assertIn(ov2.error, {0, _winapi.ERROR_IO_PENDING})
+
+ res = _winapi.WaitForMultipleObjects([ov2.event], False, 100)
+ self.assertEqual(res, _winapi.WAIT_OBJECT_0)
+
+ self.assertFalse(ov1.pending)
+ self.assertEqual(ov1.error, ERROR_IO_INCOMPLETE)
+ self.assertFalse(ov2.pending)
+ self.assertIn(ov2.error, {0, _winapi.ERROR_IO_PENDING})
+ self.assertEqual(ov1.getresult(), b"hello")
+ finally:
+ _winapi.CloseHandle(h1)
+ _winapi.CloseHandle(h2)
+
+ def test_pipe_handle(self):
+ h, _ = windows_utils.pipe(overlapped=(True, True))
+ _winapi.CloseHandle(_)
+ p = windows_utils.PipeHandle(h)
+ self.assertEqual(p.fileno(), h)
+ self.assertEqual(p.handle, h)
+
+ # check garbage collection of p closes handle
+ del p
+ test.support.gc_collect()
+ try:
+ _winapi.CloseHandle(h)
+ except OSError as e:
+ self.assertEqual(e.winerror, 6) # ERROR_INVALID_HANDLE
+ else:
+ raise RuntimeError('expected ERROR_INVALID_HANDLE')
+
+
+class PopenTests(unittest.TestCase):
+
+ def test_popen(self):
+ command = r"""if 1:
+ import sys
+ s = sys.stdin.readline()
+ sys.stdout.write(s.upper())
+ sys.stderr.write('stderr')
+ """
+ msg = b"blah\n"
+
+ p = windows_utils.Popen([sys.executable, '-c', command],
+ stdin=windows_utils.PIPE,
+ stdout=windows_utils.PIPE,
+ stderr=windows_utils.PIPE)
+
+ for f in [p.stdin, p.stdout, p.stderr]:
+ self.assertIsInstance(f, windows_utils.PipeHandle)
+
+ ovin = _overlapped.Overlapped()
+ ovout = _overlapped.Overlapped()
+ overr = _overlapped.Overlapped()
+
+ ovin.WriteFile(p.stdin.handle, msg)
+ ovout.ReadFile(p.stdout.handle, 100)
+ overr.ReadFile(p.stderr.handle, 100)
+
+ events = [ovin.event, ovout.event, overr.event]
+ # Super-long timeout for slow buildbots.
+ res = _winapi.WaitForMultipleObjects(events, True, 10000)
+ self.assertEqual(res, _winapi.WAIT_OBJECT_0)
+ self.assertFalse(ovout.pending)
+ self.assertFalse(overr.pending)
+ self.assertFalse(ovin.pending)
+
+ self.assertEqual(ovin.getresult(), len(msg))
+ out = ovout.getresult().rstrip()
+ err = overr.getresult().rstrip()
+
+ self.assertGreater(len(out), 0)
+ self.assertGreater(len(err), 0)
+ # allow for partial reads...
+ self.assertTrue(msg.upper().rstrip().startswith(out))
+ self.assertTrue(b"stderr".startswith(err))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_asyncio/tests.txt b/Lib/test/test_asyncio/tests.txt
new file mode 100644
index 0000000000..30609cd5fe
--- /dev/null
+++ b/Lib/test/test_asyncio/tests.txt
@@ -0,0 +1,13 @@
+test_asyncio.test_base_events
+test_asyncio.test_events
+test_asyncio.test_futures
+test_asyncio.test_locks
+test_asyncio.test_proactor_events
+test_asyncio.test_queues
+test_asyncio.test_selector_events
+test_asyncio.test_streams
+test_asyncio.test_tasks
+test_asyncio.test_transports
+test_asyncio.test_unix_events
+test_asyncio.test_windows_events
+test_asyncio.test_windows_utils
diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py
index 5d0632ef26..084d247295 100644
--- a/Lib/test/test_asyncore.py
+++ b/Lib/test/test_asyncore.py
@@ -19,6 +19,7 @@ try:
except ImportError:
threading = None
+TIMEOUT = 3
HAS_UNIX_SOCKETS = hasattr(socket, 'AF_UNIX')
class dummysocket:
@@ -395,7 +396,10 @@ class DispatcherWithSendTests(unittest.TestCase):
self.assertEqual(cap.getvalue(), data*2)
finally:
- t.join()
+ t.join(timeout=TIMEOUT)
+ if t.is_alive():
+ self.fail("join() timed out")
+
class DispatcherWithSendTests_UsePoll(DispatcherWithSendTests):
@@ -740,7 +744,12 @@ class BaseTestAPI:
s.create_socket(self.family)
self.assertEqual(s.socket.family, self.family)
SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0)
- self.assertEqual(s.socket.type, socket.SOCK_STREAM | SOCK_NONBLOCK)
+ sock_type = socket.SOCK_STREAM | SOCK_NONBLOCK
+ if hasattr(socket, 'SOCK_CLOEXEC'):
+ self.assertIn(s.socket.type,
+ (sock_type | socket.SOCK_CLOEXEC, sock_type))
+ else:
+ self.assertEqual(s.socket.type, sock_type)
def test_bind(self):
if HAS_UNIX_SOCKETS and self.family == socket.AF_UNIX:
@@ -754,7 +763,7 @@ class BaseTestAPI:
s2 = asyncore.dispatcher()
s2.create_socket(self.family)
# EADDRINUSE indicates the socket was correctly bound
- self.assertRaises(socket.error, s2.bind, (self.addr[0], port))
+ self.assertRaises(OSError, s2.bind, (self.addr[0], port))
def test_set_reuse_addr(self):
if HAS_UNIX_SOCKETS and self.family == socket.AF_UNIX:
@@ -762,7 +771,7 @@ class BaseTestAPI:
sock = socket.socket(self.family)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- except socket.error:
+ except OSError:
unittest.skip("SO_REUSEADDR not supported on this platform")
else:
# if SO_REUSEADDR succeeded for sock we expect asyncore
@@ -787,7 +796,11 @@ class BaseTestAPI:
t = threading.Thread(target=lambda: asyncore.loop(timeout=0.1,
count=500))
t.start()
- self.addCleanup(t.join)
+ def cleanup():
+ t.join(timeout=TIMEOUT)
+ if t.is_alive():
+ self.fail("join() timed out")
+ self.addCleanup(cleanup)
s = socket.socket(self.family, socket.SOCK_STREAM)
s.settimeout(.2)
@@ -795,7 +808,7 @@ class BaseTestAPI:
struct.pack('ii', 1, 0))
try:
s.connect(server.address)
- except socket.error:
+ except OSError:
pass
finally:
s.close()
diff --git a/Lib/test/test_atexit.py b/Lib/test/test_atexit.py
index 30c3b4a07b..84644f17a8 100644
--- a/Lib/test/test_atexit.py
+++ b/Lib/test/test_atexit.py
@@ -2,6 +2,7 @@ import sys
import unittest
import io
import atexit
+import _testcapi
from test import support
### helpers
@@ -23,7 +24,9 @@ def raise1():
def raise2():
raise SystemError
-class TestCase(unittest.TestCase):
+
+class GeneralTest(unittest.TestCase):
+
def setUp(self):
self.save_stdout = sys.stdout
self.save_stderr = sys.stderr
@@ -141,8 +144,43 @@ class TestCase(unittest.TestCase):
self.assertEqual(l, [5])
+class SubinterpreterTest(unittest.TestCase):
+
+ def test_callbacks_leak(self):
+ # This test shows a leak in refleak mode if atexit doesn't
+ # take care to free callbacks in its per-subinterpreter module
+ # state.
+ n = atexit._ncallbacks()
+ code = r"""if 1:
+ import atexit
+ def f():
+ pass
+ atexit.register(f)
+ del atexit
+ """
+ ret = support.run_in_subinterp(code)
+ self.assertEqual(ret, 0)
+ self.assertEqual(atexit._ncallbacks(), n)
+
+ def test_callbacks_leak_refcycle(self):
+ # Similar to the above, but with a refcycle through the atexit
+ # module.
+ n = atexit._ncallbacks()
+ code = r"""if 1:
+ import atexit
+ def f():
+ pass
+ atexit.register(f)
+ atexit.__atexit = atexit
+ """
+ ret = support.run_in_subinterp(code)
+ self.assertEqual(ret, 0)
+ self.assertEqual(atexit._ncallbacks(), n)
+
+
def test_main():
- support.run_unittest(TestCase)
+ support.run_unittest(__name__)
+
if __name__ == "__main__":
test_main()
diff --git a/Lib/test/test_audioop.py b/Lib/test/test_audioop.py
index a92cf874bd..d5075450e6 100644
--- a/Lib/test/test_audioop.py
+++ b/Lib/test/test_audioop.py
@@ -5,13 +5,18 @@ import unittest
def pack(width, data):
return b''.join(v.to_bytes(width, sys.byteorder, signed=True) for v in data)
-packs = {w: (lambda *data, width=w: pack(width, data)) for w in (1, 2, 4)}
-maxvalues = {w: (1 << (8 * w - 1)) - 1 for w in (1, 2, 4)}
-minvalues = {w: -1 << (8 * w - 1) for w in (1, 2, 4)}
+def unpack(width, data):
+ return [int.from_bytes(data[i: i + width], sys.byteorder, signed=True)
+ for i in range(0, len(data), width)]
+
+packs = {w: (lambda *data, width=w: pack(width, data)) for w in (1, 2, 3, 4)}
+maxvalues = {w: (1 << (8 * w - 1)) - 1 for w in (1, 2, 3, 4)}
+minvalues = {w: -1 << (8 * w - 1) for w in (1, 2, 3, 4)}
datas = {
1: b'\x00\x12\x45\xbb\x7f\x80\xff',
2: packs[2](0, 0x1234, 0x4567, -0x4567, 0x7fff, -0x8000, -1),
+ 3: packs[3](0, 0x123456, 0x456789, -0x456789, 0x7fffff, -0x800000, -1),
4: packs[4](0, 0x12345678, 0x456789ab, -0x456789ab,
0x7fffffff, -0x80000000, -1),
}
@@ -19,6 +24,7 @@ datas = {
INVALID_DATA = [
(b'abc', 0),
(b'abc', 2),
+ (b'ab', 3),
(b'abc', 4),
]
@@ -26,8 +32,10 @@ INVALID_DATA = [
class TestAudioop(unittest.TestCase):
def test_max(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.max(b'', w), 0)
+ self.assertEqual(audioop.max(bytearray(), w), 0)
+ self.assertEqual(audioop.max(memoryview(b''), w), 0)
p = packs[w]
self.assertEqual(audioop.max(p(5), w), 5)
self.assertEqual(audioop.max(p(5, -8, -1), w), 8)
@@ -36,9 +44,13 @@ class TestAudioop(unittest.TestCase):
self.assertEqual(audioop.max(datas[w], w), -minvalues[w])
def test_minmax(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.minmax(b'', w),
(0x7fffffff, -0x80000000))
+ self.assertEqual(audioop.minmax(bytearray(), w),
+ (0x7fffffff, -0x80000000))
+ self.assertEqual(audioop.minmax(memoryview(b''), w),
+ (0x7fffffff, -0x80000000))
p = packs[w]
self.assertEqual(audioop.minmax(p(5), w), (5, 5))
self.assertEqual(audioop.minmax(p(5, -8, -1), w), (-8, 5))
@@ -50,16 +62,20 @@ class TestAudioop(unittest.TestCase):
(minvalues[w], maxvalues[w]))
def test_maxpp(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.maxpp(b'', w), 0)
+ self.assertEqual(audioop.maxpp(bytearray(), w), 0)
+ self.assertEqual(audioop.maxpp(memoryview(b''), w), 0)
self.assertEqual(audioop.maxpp(packs[w](*range(100)), w), 0)
self.assertEqual(audioop.maxpp(packs[w](9, 10, 5, 5, 0, 1), w), 10)
self.assertEqual(audioop.maxpp(datas[w], w),
maxvalues[w] - minvalues[w])
def test_avg(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.avg(b'', w), 0)
+ self.assertEqual(audioop.avg(bytearray(), w), 0)
+ self.assertEqual(audioop.avg(memoryview(b''), w), 0)
p = packs[w]
self.assertEqual(audioop.avg(p(5), w), 5)
self .assertEqual(audioop.avg(p(5, 8), w), 6)
@@ -74,17 +90,22 @@ class TestAudioop(unittest.TestCase):
-0x60000000)
def test_avgpp(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.avgpp(b'', w), 0)
+ self.assertEqual(audioop.avgpp(bytearray(), w), 0)
+ self.assertEqual(audioop.avgpp(memoryview(b''), w), 0)
self.assertEqual(audioop.avgpp(packs[w](*range(100)), w), 0)
self.assertEqual(audioop.avgpp(packs[w](9, 10, 5, 5, 0, 1), w), 10)
self.assertEqual(audioop.avgpp(datas[1], 1), 196)
self.assertEqual(audioop.avgpp(datas[2], 2), 50534)
+ self.assertEqual(audioop.avgpp(datas[3], 3), 12937096)
self.assertEqual(audioop.avgpp(datas[4], 4), 3311897002)
def test_rms(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.rms(b'', w), 0)
+ self.assertEqual(audioop.rms(bytearray(), w), 0)
+ self.assertEqual(audioop.rms(memoryview(b''), w), 0)
p = packs[w]
self.assertEqual(audioop.rms(p(*range(100)), w), 57)
self.assertAlmostEqual(audioop.rms(p(maxvalues[w]) * 5, w),
@@ -93,11 +114,14 @@ class TestAudioop(unittest.TestCase):
-minvalues[w], delta=1)
self.assertEqual(audioop.rms(datas[1], 1), 77)
self.assertEqual(audioop.rms(datas[2], 2), 20001)
+ self.assertEqual(audioop.rms(datas[3], 3), 5120523)
self.assertEqual(audioop.rms(datas[4], 4), 1310854152)
def test_cross(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.cross(b'', w), -1)
+ self.assertEqual(audioop.cross(bytearray(), w), -1)
+ self.assertEqual(audioop.cross(memoryview(b''), w), -1)
p = packs[w]
self.assertEqual(audioop.cross(p(0, 1, 2), w), 0)
self.assertEqual(audioop.cross(p(1, 2, -3, -4), w), 1)
@@ -106,22 +130,29 @@ class TestAudioop(unittest.TestCase):
self.assertEqual(audioop.cross(p(minvalues[w], maxvalues[w]), w), 1)
def test_add(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.add(b'', b'', w), b'')
+ self.assertEqual(audioop.add(bytearray(), bytearray(), w), b'')
+ self.assertEqual(audioop.add(memoryview(b''), memoryview(b''), w), b'')
self.assertEqual(audioop.add(datas[w], b'\0' * len(datas[w]), w),
datas[w])
self.assertEqual(audioop.add(datas[1], datas[1], 1),
b'\x00\x24\x7f\x80\x7f\x80\xfe')
self.assertEqual(audioop.add(datas[2], datas[2], 2),
packs[2](0, 0x2468, 0x7fff, -0x8000, 0x7fff, -0x8000, -2))
+ self.assertEqual(audioop.add(datas[3], datas[3], 3),
+ packs[3](0, 0x2468ac, 0x7fffff, -0x800000,
+ 0x7fffff, -0x800000, -2))
self.assertEqual(audioop.add(datas[4], datas[4], 4),
packs[4](0, 0x2468acf0, 0x7fffffff, -0x80000000,
0x7fffffff, -0x80000000, -2))
def test_bias(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
for bias in 0, 1, -1, 127, -128, 0x7fffffff, -0x80000000:
self.assertEqual(audioop.bias(b'', w, bias), b'')
+ self.assertEqual(audioop.bias(bytearray(), w, bias), b'')
+ self.assertEqual(audioop.bias(memoryview(b''), w, bias), b'')
self.assertEqual(audioop.bias(datas[1], 1, 1),
b'\x01\x13\x46\xbc\x80\x81\x00')
self.assertEqual(audioop.bias(datas[1], 1, -1),
@@ -138,6 +169,17 @@ class TestAudioop(unittest.TestCase):
packs[2](-1, 0x1233, 0x4566, -0x4568, 0x7ffe, 0x7fff, -2))
self.assertEqual(audioop.bias(datas[2], 2, -0x80000000),
datas[2])
+ self.assertEqual(audioop.bias(datas[3], 3, 1),
+ packs[3](1, 0x123457, 0x45678a, -0x456788,
+ -0x800000, -0x7fffff, 0))
+ self.assertEqual(audioop.bias(datas[3], 3, -1),
+ packs[3](-1, 0x123455, 0x456788, -0x45678a,
+ 0x7ffffe, 0x7fffff, -2))
+ self.assertEqual(audioop.bias(datas[3], 3, 0x7fffffff),
+ packs[3](-1, 0x123455, 0x456788, -0x45678a,
+ 0x7ffffe, 0x7fffff, -2))
+ self.assertEqual(audioop.bias(datas[3], 3, -0x80000000),
+ datas[3])
self.assertEqual(audioop.bias(datas[4], 4, 1),
packs[4](1, 0x12345679, 0x456789ac, -0x456789aa,
-0x80000000, -0x7fffffff, 0))
@@ -152,99 +194,141 @@ class TestAudioop(unittest.TestCase):
-1, 0, 0x7fffffff))
def test_lin2lin(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.lin2lin(datas[w], w, w), datas[w])
+ self.assertEqual(audioop.lin2lin(bytearray(datas[w]), w, w),
+ datas[w])
+ self.assertEqual(audioop.lin2lin(memoryview(datas[w]), w, w),
+ datas[w])
self.assertEqual(audioop.lin2lin(datas[1], 1, 2),
packs[2](0, 0x1200, 0x4500, -0x4500, 0x7f00, -0x8000, -0x100))
+ self.assertEqual(audioop.lin2lin(datas[1], 1, 3),
+ packs[3](0, 0x120000, 0x450000, -0x450000,
+ 0x7f0000, -0x800000, -0x10000))
self.assertEqual(audioop.lin2lin(datas[1], 1, 4),
packs[4](0, 0x12000000, 0x45000000, -0x45000000,
0x7f000000, -0x80000000, -0x1000000))
self.assertEqual(audioop.lin2lin(datas[2], 2, 1),
b'\x00\x12\x45\xba\x7f\x80\xff')
+ self.assertEqual(audioop.lin2lin(datas[2], 2, 3),
+ packs[3](0, 0x123400, 0x456700, -0x456700,
+ 0x7fff00, -0x800000, -0x100))
self.assertEqual(audioop.lin2lin(datas[2], 2, 4),
packs[4](0, 0x12340000, 0x45670000, -0x45670000,
0x7fff0000, -0x80000000, -0x10000))
+ self.assertEqual(audioop.lin2lin(datas[3], 3, 1),
+ b'\x00\x12\x45\xba\x7f\x80\xff')
+ self.assertEqual(audioop.lin2lin(datas[3], 3, 2),
+ packs[2](0, 0x1234, 0x4567, -0x4568, 0x7fff, -0x8000, -1))
+ self.assertEqual(audioop.lin2lin(datas[3], 3, 4),
+ packs[4](0, 0x12345600, 0x45678900, -0x45678900,
+ 0x7fffff00, -0x80000000, -0x100))
self.assertEqual(audioop.lin2lin(datas[4], 4, 1),
b'\x00\x12\x45\xba\x7f\x80\xff')
self.assertEqual(audioop.lin2lin(datas[4], 4, 2),
packs[2](0, 0x1234, 0x4567, -0x4568, 0x7fff, -0x8000, -1))
+ self.assertEqual(audioop.lin2lin(datas[4], 4, 3),
+ packs[3](0, 0x123456, 0x456789, -0x45678a,
+ 0x7fffff, -0x800000, -1))
def test_adpcm2lin(self):
self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 1, None),
(b'\x00\x00\x00\xff\x00\xff', (-179, 40)))
+ self.assertEqual(audioop.adpcm2lin(bytearray(b'\x07\x7f\x7f'), 1, None),
+ (b'\x00\x00\x00\xff\x00\xff', (-179, 40)))
+ self.assertEqual(audioop.adpcm2lin(memoryview(b'\x07\x7f\x7f'), 1, None),
+ (b'\x00\x00\x00\xff\x00\xff', (-179, 40)))
self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 2, None),
(packs[2](0, 0xb, 0x29, -0x16, 0x72, -0xb3), (-179, 40)))
+ self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 3, None),
+ (packs[3](0, 0xb00, 0x2900, -0x1600, 0x7200,
+ -0xb300), (-179, 40)))
self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 4, None),
(packs[4](0, 0xb0000, 0x290000, -0x160000, 0x720000,
-0xb30000), (-179, 40)))
# Very cursory test
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.adpcm2lin(b'\0' * 5, w, None),
(b'\0' * w * 10, (0, 0)))
def test_lin2adpcm(self):
self.assertEqual(audioop.lin2adpcm(datas[1], 1, None),
(b'\x07\x7f\x7f', (-221, 39)))
- self.assertEqual(audioop.lin2adpcm(datas[2], 2, None),
- (b'\x07\x7f\x7f', (31, 39)))
- self.assertEqual(audioop.lin2adpcm(datas[4], 4, None),
- (b'\x07\x7f\x7f', (31, 39)))
+ self.assertEqual(audioop.lin2adpcm(bytearray(datas[1]), 1, None),
+ (b'\x07\x7f\x7f', (-221, 39)))
+ self.assertEqual(audioop.lin2adpcm(memoryview(datas[1]), 1, None),
+ (b'\x07\x7f\x7f', (-221, 39)))
+ for w in 2, 3, 4:
+ self.assertEqual(audioop.lin2adpcm(datas[w], w, None),
+ (b'\x07\x7f\x7f', (31, 39)))
# Very cursory test
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.lin2adpcm(b'\0' * w * 10, w, None),
(b'\0' * 5, (0, 0)))
def test_lin2alaw(self):
self.assertEqual(audioop.lin2alaw(datas[1], 1),
b'\xd5\x87\xa4\x24\xaa\x2a\x5a')
- self.assertEqual(audioop.lin2alaw(datas[2], 2),
- b'\xd5\x87\xa4\x24\xaa\x2a\x55')
- self.assertEqual(audioop.lin2alaw(datas[4], 4),
- b'\xd5\x87\xa4\x24\xaa\x2a\x55')
+ self.assertEqual(audioop.lin2alaw(bytearray(datas[1]), 1),
+ b'\xd5\x87\xa4\x24\xaa\x2a\x5a')
+ self.assertEqual(audioop.lin2alaw(memoryview(datas[1]), 1),
+ b'\xd5\x87\xa4\x24\xaa\x2a\x5a')
+ for w in 2, 3, 4:
+ self.assertEqual(audioop.lin2alaw(datas[w], w),
+ b'\xd5\x87\xa4\x24\xaa\x2a\x55')
def test_alaw2lin(self):
encoded = b'\x00\x03\x24\x2a\x51\x54\x55\x58\x6b\x71\x7f'\
b'\x80\x83\xa4\xaa\xd1\xd4\xd5\xd8\xeb\xf1\xff'
src = [-688, -720, -2240, -4032, -9, -3, -1, -27, -244, -82, -106,
688, 720, 2240, 4032, 9, 3, 1, 27, 244, 82, 106]
- for w in 1, 2, 4:
- self.assertEqual(audioop.alaw2lin(encoded, w),
- packs[w](*(x << (w * 8) >> 13 for x in src)))
+ for w in 1, 2, 3, 4:
+ decoded = packs[w](*(x << (w * 8) >> 13 for x in src))
+ self.assertEqual(audioop.alaw2lin(encoded, w), decoded)
+ self.assertEqual(audioop.alaw2lin(bytearray(encoded), w), decoded)
+ self.assertEqual(audioop.alaw2lin(memoryview(encoded), w), decoded)
encoded = bytes(range(256))
- for w in 2, 4:
+ for w in 2, 3, 4:
decoded = audioop.alaw2lin(encoded, w)
self.assertEqual(audioop.lin2alaw(decoded, w), encoded)
def test_lin2ulaw(self):
self.assertEqual(audioop.lin2ulaw(datas[1], 1),
b'\xff\xad\x8e\x0e\x80\x00\x67')
- self.assertEqual(audioop.lin2ulaw(datas[2], 2),
- b'\xff\xad\x8e\x0e\x80\x00\x7e')
- self.assertEqual(audioop.lin2ulaw(datas[4], 4),
- b'\xff\xad\x8e\x0e\x80\x00\x7e')
+ self.assertEqual(audioop.lin2ulaw(bytearray(datas[1]), 1),
+ b'\xff\xad\x8e\x0e\x80\x00\x67')
+ self.assertEqual(audioop.lin2ulaw(memoryview(datas[1]), 1),
+ b'\xff\xad\x8e\x0e\x80\x00\x67')
+ for w in 2, 3, 4:
+ self.assertEqual(audioop.lin2ulaw(datas[w], w),
+ b'\xff\xad\x8e\x0e\x80\x00\x7e')
def test_ulaw2lin(self):
encoded = b'\x00\x0e\x28\x3f\x57\x6a\x76\x7c\x7e\x7f'\
b'\x80\x8e\xa8\xbf\xd7\xea\xf6\xfc\xfe\xff'
src = [-8031, -4447, -1471, -495, -163, -53, -18, -6, -2, 0,
8031, 4447, 1471, 495, 163, 53, 18, 6, 2, 0]
- for w in 1, 2, 4:
- self.assertEqual(audioop.ulaw2lin(encoded, w),
- packs[w](*(x << (w * 8) >> 14 for x in src)))
+ for w in 1, 2, 3, 4:
+ decoded = packs[w](*(x << (w * 8) >> 14 for x in src))
+ self.assertEqual(audioop.ulaw2lin(encoded, w), decoded)
+ self.assertEqual(audioop.ulaw2lin(bytearray(encoded), w), decoded)
+ self.assertEqual(audioop.ulaw2lin(memoryview(encoded), w), decoded)
# Current u-law implementation has two codes fo 0: 0x7f and 0xff.
encoded = bytes(range(127)) + bytes(range(128, 256))
- for w in 2, 4:
+ for w in 2, 3, 4:
decoded = audioop.ulaw2lin(encoded, w)
self.assertEqual(audioop.lin2ulaw(decoded, w), encoded)
def test_mul(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.mul(b'', w, 2), b'')
+ self.assertEqual(audioop.mul(bytearray(), w, 2), b'')
+ self.assertEqual(audioop.mul(memoryview(b''), w, 2), b'')
self.assertEqual(audioop.mul(datas[w], w, 0),
b'\0' * len(datas[w]))
self.assertEqual(audioop.mul(datas[w], w, 1),
@@ -253,14 +337,21 @@ class TestAudioop(unittest.TestCase):
b'\x00\x24\x7f\x80\x7f\x80\xfe')
self.assertEqual(audioop.mul(datas[2], 2, 2),
packs[2](0, 0x2468, 0x7fff, -0x8000, 0x7fff, -0x8000, -2))
+ self.assertEqual(audioop.mul(datas[3], 3, 2),
+ packs[3](0, 0x2468ac, 0x7fffff, -0x800000,
+ 0x7fffff, -0x800000, -2))
self.assertEqual(audioop.mul(datas[4], 4, 2),
packs[4](0, 0x2468acf0, 0x7fffffff, -0x80000000,
0x7fffffff, -0x80000000, -2))
def test_ratecv(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.ratecv(b'', w, 1, 8000, 8000, None),
(b'', (-1, ((0, 0),))))
+ self.assertEqual(audioop.ratecv(bytearray(), w, 1, 8000, 8000, None),
+ (b'', (-1, ((0, 0),))))
+ self.assertEqual(audioop.ratecv(memoryview(b''), w, 1, 8000, 8000, None),
+ (b'', (-1, ((0, 0),))))
self.assertEqual(audioop.ratecv(b'', w, 5, 8000, 8000, None),
(b'', (-1, ((0, 0),) * 5)))
self.assertEqual(audioop.ratecv(b'', w, 1, 8000, 16000, None),
@@ -272,7 +363,7 @@ class TestAudioop(unittest.TestCase):
d2, state = audioop.ratecv(b'\x00\x01\x02', 1, 1, 8000, 16000, state)
self.assertEqual(d1 + d2, b'\000\000\001\001\002\001\000\000\001\001\002')
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
d0, state0 = audioop.ratecv(datas[w], w, 1, 8000, 16000, None)
d, state = b'', None
for i in range(0, len(datas[w]), w):
@@ -283,13 +374,15 @@ class TestAudioop(unittest.TestCase):
self.assertEqual(state, state0)
def test_reverse(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
self.assertEqual(audioop.reverse(b'', w), b'')
+ self.assertEqual(audioop.reverse(bytearray(), w), b'')
+ self.assertEqual(audioop.reverse(memoryview(b''), w), b'')
self.assertEqual(audioop.reverse(packs[w](0, 1, 2), w),
packs[w](2, 1, 0))
def test_tomono(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
data1 = datas[w]
data2 = bytearray(2 * len(data1))
for k in range(w):
@@ -299,9 +392,13 @@ class TestAudioop(unittest.TestCase):
for k in range(w):
data2[k+w::2*w] = data1[k::w]
self.assertEqual(audioop.tomono(data2, w, 0.5, 0.5), data1)
+ self.assertEqual(audioop.tomono(bytearray(data2), w, 0.5, 0.5),
+ data1)
+ self.assertEqual(audioop.tomono(memoryview(data2), w, 0.5, 0.5),
+ data1)
def test_tostereo(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
data1 = datas[w]
data2 = bytearray(2 * len(data1))
for k in range(w):
@@ -311,14 +408,25 @@ class TestAudioop(unittest.TestCase):
for k in range(w):
data2[k+w::2*w] = data1[k::w]
self.assertEqual(audioop.tostereo(data1, w, 1, 1), data2)
+ self.assertEqual(audioop.tostereo(bytearray(data1), w, 1, 1), data2)
+ self.assertEqual(audioop.tostereo(memoryview(data1), w, 1, 1),
+ data2)
def test_findfactor(self):
self.assertEqual(audioop.findfactor(datas[2], datas[2]), 1.0)
+ self.assertEqual(audioop.findfactor(bytearray(datas[2]),
+ bytearray(datas[2])), 1.0)
+ self.assertEqual(audioop.findfactor(memoryview(datas[2]),
+ memoryview(datas[2])), 1.0)
self.assertEqual(audioop.findfactor(b'\0' * len(datas[2]), datas[2]),
0.0)
def test_findfit(self):
self.assertEqual(audioop.findfit(datas[2], datas[2]), (0, 1.0))
+ self.assertEqual(audioop.findfit(bytearray(datas[2]),
+ bytearray(datas[2])), (0, 1.0))
+ self.assertEqual(audioop.findfit(memoryview(datas[2]),
+ memoryview(datas[2])), (0, 1.0))
self.assertEqual(audioop.findfit(datas[2], packs[2](1, 2, 0)),
(1, 8038.8))
self.assertEqual(audioop.findfit(datas[2][:-2] * 5 + datas[2], datas[2]),
@@ -326,16 +434,37 @@ class TestAudioop(unittest.TestCase):
def test_findmax(self):
self.assertEqual(audioop.findmax(datas[2], 1), 5)
+ self.assertEqual(audioop.findmax(bytearray(datas[2]), 1), 5)
+ self.assertEqual(audioop.findmax(memoryview(datas[2]), 1), 5)
def test_getsample(self):
- for w in 1, 2, 4:
+ for w in 1, 2, 3, 4:
data = packs[w](0, 1, -1, maxvalues[w], minvalues[w])
self.assertEqual(audioop.getsample(data, w, 0), 0)
+ self.assertEqual(audioop.getsample(bytearray(data), w, 0), 0)
+ self.assertEqual(audioop.getsample(memoryview(data), w, 0), 0)
self.assertEqual(audioop.getsample(data, w, 1), 1)
self.assertEqual(audioop.getsample(data, w, 2), -1)
self.assertEqual(audioop.getsample(data, w, 3), maxvalues[w])
self.assertEqual(audioop.getsample(data, w, 4), minvalues[w])
+ def test_byteswap(self):
+ swapped_datas = {
+ 1: datas[1],
+ 2: packs[2](0, 0x3412, 0x6745, -0x6646, -0x81, 0x80, -1),
+ 3: packs[3](0, 0x563412, -0x7698bb, 0x7798ba, -0x81, 0x80, -1),
+ 4: packs[4](0, 0x78563412, -0x547698bb, 0x557698ba,
+ -0x81, 0x80, -1),
+ }
+ for w in 1, 2, 3, 4:
+ self.assertEqual(audioop.byteswap(b'', w), b'')
+ self.assertEqual(audioop.byteswap(datas[w], w), swapped_datas[w])
+ self.assertEqual(audioop.byteswap(swapped_datas[w], w), datas[w])
+ self.assertEqual(audioop.byteswap(bytearray(datas[w]), w),
+ swapped_datas[w])
+ self.assertEqual(audioop.byteswap(memoryview(datas[w]), w),
+ swapped_datas[w])
+
def test_negativelen(self):
# from issue 3306, previously it segfaulted
self.assertRaises(audioop.error,
@@ -365,10 +494,33 @@ class TestAudioop(unittest.TestCase):
self.assertRaises(audioop.error, audioop.lin2alaw, data, size)
self.assertRaises(audioop.error, audioop.lin2adpcm, data, size, state)
+ def test_string(self):
+ data = 'abcd'
+ size = 2
+ self.assertRaises(TypeError, audioop.getsample, data, size, 0)
+ self.assertRaises(TypeError, audioop.max, data, size)
+ self.assertRaises(TypeError, audioop.minmax, data, size)
+ self.assertRaises(TypeError, audioop.avg, data, size)
+ self.assertRaises(TypeError, audioop.rms, data, size)
+ self.assertRaises(TypeError, audioop.avgpp, data, size)
+ self.assertRaises(TypeError, audioop.maxpp, data, size)
+ self.assertRaises(TypeError, audioop.cross, data, size)
+ self.assertRaises(TypeError, audioop.mul, data, size, 1.0)
+ self.assertRaises(TypeError, audioop.tomono, data, size, 0.5, 0.5)
+ self.assertRaises(TypeError, audioop.tostereo, data, size, 0.5, 0.5)
+ self.assertRaises(TypeError, audioop.add, data, data, size)
+ self.assertRaises(TypeError, audioop.bias, data, size, 0)
+ self.assertRaises(TypeError, audioop.reverse, data, size)
+ self.assertRaises(TypeError, audioop.lin2lin, data, size, size)
+ self.assertRaises(TypeError, audioop.ratecv, data, size, 1, 1, 1, None)
+ self.assertRaises(TypeError, audioop.lin2ulaw, data, size)
+ self.assertRaises(TypeError, audioop.lin2alaw, data, size)
+ self.assertRaises(TypeError, audioop.lin2adpcm, data, size, None)
+
def test_wrongsize(self):
data = b'abcdefgh'
state = None
- for size in (-1, 0, 3, 5, 1024):
+ for size in (-1, 0, 5, 1024):
self.assertRaises(audioop.error, audioop.ulaw2lin, data, size)
self.assertRaises(audioop.error, audioop.alaw2lin, data, size)
self.assertRaises(audioop.error, audioop.adpcm2lin, data, size, state)
diff --git a/Lib/test/test_base64.py b/Lib/test/test_base64.py
index 13695de67e..b9738ddaf5 100644
--- a/Lib/test/test_base64.py
+++ b/Lib/test/test_base64.py
@@ -5,10 +5,21 @@ import binascii
import os
import sys
import subprocess
-
+import struct
+from array import array
class LegacyBase64TestCase(unittest.TestCase):
+
+ # Legacy API is not as permissive as the modern API
+ def check_type_errors(self, f):
+ self.assertRaises(TypeError, f, "")
+ self.assertRaises(TypeError, f, [])
+ multidimensional = memoryview(b"1234").cast('B', (2, 2))
+ self.assertRaises(TypeError, f, multidimensional)
+ int_data = memoryview(b"1234").cast('I')
+ self.assertRaises(TypeError, f, int_data)
+
def test_encodebytes(self):
eq = self.assertEqual
eq(base64.encodebytes(b"www.python.org"), b"d3d3LnB5dGhvbi5vcmc=\n")
@@ -24,7 +35,9 @@ class LegacyBase64TestCase(unittest.TestCase):
b"Y3ODkhQCMwXiYqKCk7Ojw+LC4gW117fQ==\n")
# Non-bytes
eq(base64.encodebytes(bytearray(b'abc')), b'YWJj\n')
- self.assertRaises(TypeError, base64.encodebytes, "")
+ eq(base64.encodebytes(memoryview(b'abc')), b'YWJj\n')
+ eq(base64.encodebytes(array('B', b'abc')), b'YWJj\n')
+ self.check_type_errors(base64.encodebytes)
def test_decodebytes(self):
eq = self.assertEqual
@@ -41,7 +54,9 @@ class LegacyBase64TestCase(unittest.TestCase):
eq(base64.decodebytes(b''), b'')
# Non-bytes
eq(base64.decodebytes(bytearray(b'YWJj\n')), b'abc')
- self.assertRaises(TypeError, base64.decodebytes, "")
+ eq(base64.decodebytes(memoryview(b'YWJj\n')), b'abc')
+ eq(base64.decodebytes(array('B', b'YWJj\n')), b'abc')
+ self.check_type_errors(base64.decodebytes)
def test_encode(self):
eq = self.assertEqual
@@ -73,6 +88,42 @@ class LegacyBase64TestCase(unittest.TestCase):
class BaseXYTestCase(unittest.TestCase):
+
+ # Modern API completely ignores exported dimension and format data and
+ # treats any buffer as a stream of bytes
+ def check_encode_type_errors(self, f):
+ self.assertRaises(TypeError, f, "")
+ self.assertRaises(TypeError, f, [])
+
+ def check_decode_type_errors(self, f):
+ self.assertRaises(TypeError, f, [])
+
+ def check_other_types(self, f, bytes_data, expected):
+ eq = self.assertEqual
+ b = bytearray(bytes_data)
+ eq(f(b), expected)
+ # The bytearray wasn't mutated
+ eq(b, bytes_data)
+ eq(f(memoryview(bytes_data)), expected)
+ eq(f(array('B', bytes_data)), expected)
+ # XXX why is b64encode hardcoded here?
+ self.check_nonbyte_element_format(base64.b64encode, bytes_data)
+ self.check_multidimensional(base64.b64encode, bytes_data)
+
+ def check_multidimensional(self, f, data):
+ padding = b"\x00" if len(data) % 2 else b""
+ bytes_data = data + padding # Make sure cast works
+ shape = (len(bytes_data) // 2, 2)
+ multidimensional = memoryview(bytes_data).cast('B', shape)
+ self.assertEqual(f(multidimensional), f(bytes_data))
+
+ def check_nonbyte_element_format(self, f, data):
+ padding = b"\x00" * ((4 - len(data)) % 4)
+ bytes_data = data + padding # Make sure cast works
+ int_data = memoryview(bytes_data).cast('I')
+ self.assertEqual(f(int_data), f(bytes_data))
+
+
def test_b64encode(self):
eq = self.assertEqual
# Test default alphabet
@@ -90,13 +141,16 @@ class BaseXYTestCase(unittest.TestCase):
b"Y3ODkhQCMwXiYqKCk7Ojw+LC4gW117fQ==")
# Test with arbitrary alternative characters
eq(base64.b64encode(b'\xd3V\xbeo\xf7\x1d', altchars=b'*$'), b'01a*b$cd')
- # Non-bytes
- eq(base64.b64encode(bytearray(b'abcd')), b'YWJjZA==')
eq(base64.b64encode(b'\xd3V\xbeo\xf7\x1d', altchars=bytearray(b'*$')),
b'01a*b$cd')
- # Check if passing a str object raises an error
- self.assertRaises(TypeError, base64.b64encode, "")
- self.assertRaises(TypeError, base64.b64encode, b"", altchars="")
+ eq(base64.b64encode(b'\xd3V\xbeo\xf7\x1d', altchars=memoryview(b'*$')),
+ b'01a*b$cd')
+ eq(base64.b64encode(b'\xd3V\xbeo\xf7\x1d', altchars=array('B', b'*$')),
+ b'01a*b$cd')
+ # Non-bytes
+ self.check_other_types(base64.b64encode, b'abcd', b'YWJjZA==')
+ self.check_encode_type_errors(base64.b64encode)
+ self.assertRaises(TypeError, base64.b64encode, b"", altchars="*$")
# Test standard alphabet
eq(base64.standard_b64encode(b"www.python.org"), b"d3d3LnB5dGhvbi5vcmc=")
eq(base64.standard_b64encode(b"a"), b"YQ==")
@@ -110,15 +164,15 @@ class BaseXYTestCase(unittest.TestCase):
b"RUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NT"
b"Y3ODkhQCMwXiYqKCk7Ojw+LC4gW117fQ==")
# Non-bytes
- eq(base64.standard_b64encode(bytearray(b'abcd')), b'YWJjZA==')
- # Check if passing a str object raises an error
- self.assertRaises(TypeError, base64.standard_b64encode, "")
+ self.check_other_types(base64.standard_b64encode,
+ b'abcd', b'YWJjZA==')
+ self.check_encode_type_errors(base64.standard_b64encode)
# Test with 'URL safe' alternative characters
eq(base64.urlsafe_b64encode(b'\xd3V\xbeo\xf7\x1d'), b'01a-b_cd')
# Non-bytes
- eq(base64.urlsafe_b64encode(bytearray(b'\xd3V\xbeo\xf7\x1d')), b'01a-b_cd')
- # Check if passing a str object raises an error
- self.assertRaises(TypeError, base64.urlsafe_b64encode, "")
+ self.check_other_types(base64.urlsafe_b64encode,
+ b'\xd3V\xbeo\xf7\x1d', b'01a-b_cd')
+ self.check_encode_type_errors(base64.urlsafe_b64encode)
def test_b64decode(self):
eq = self.assertEqual
@@ -141,7 +195,8 @@ class BaseXYTestCase(unittest.TestCase):
eq(base64.b64decode(data), res)
eq(base64.b64decode(data.decode('ascii')), res)
# Non-bytes
- eq(base64.b64decode(bytearray(b"YWJj")), b"abc")
+ self.check_other_types(base64.b64decode, b"YWJj", b"abc")
+ self.check_decode_type_errors(base64.b64decode)
# Test with arbitrary alternative characters
tests_altchars = {(b'01a*b$cd', b'*$'): b'\xd3V\xbeo\xf7\x1d',
@@ -160,7 +215,8 @@ class BaseXYTestCase(unittest.TestCase):
eq(base64.standard_b64decode(data), res)
eq(base64.standard_b64decode(data.decode('ascii')), res)
# Non-bytes
- eq(base64.standard_b64decode(bytearray(b"YWJj")), b"abc")
+ self.check_other_types(base64.standard_b64decode, b"YWJj", b"abc")
+ self.check_decode_type_errors(base64.standard_b64decode)
# Test with 'URL safe' alternative characters
tests_urlsafe = {b'01a-b_cd': b'\xd3V\xbeo\xf7\x1d',
@@ -170,7 +226,9 @@ class BaseXYTestCase(unittest.TestCase):
eq(base64.urlsafe_b64decode(data), res)
eq(base64.urlsafe_b64decode(data.decode('ascii')), res)
# Non-bytes
- eq(base64.urlsafe_b64decode(bytearray(b'01a-b_cd')), b'\xd3V\xbeo\xf7\x1d')
+ self.check_other_types(base64.urlsafe_b64decode, b'01a-b_cd',
+ b'\xd3V\xbeo\xf7\x1d')
+ self.check_decode_type_errors(base64.urlsafe_b64decode)
def test_b64decode_padding_error(self):
self.assertRaises(binascii.Error, base64.b64decode, b'abc')
@@ -205,8 +263,8 @@ class BaseXYTestCase(unittest.TestCase):
eq(base64.b32encode(b'abcd'), b'MFRGGZA=')
eq(base64.b32encode(b'abcde'), b'MFRGGZDF')
# Non-bytes
- eq(base64.b32encode(bytearray(b'abcd')), b'MFRGGZA=')
- self.assertRaises(TypeError, base64.b32encode, "")
+ self.check_other_types(base64.b32encode, b'abcd', b'MFRGGZA=')
+ self.check_encode_type_errors(base64.b32encode)
def test_b32decode(self):
eq = self.assertEqual
@@ -222,7 +280,8 @@ class BaseXYTestCase(unittest.TestCase):
eq(base64.b32decode(data), res)
eq(base64.b32decode(data.decode('ascii')), res)
# Non-bytes
- eq(base64.b32decode(bytearray(b'MFRGG===')), b'abc')
+ self.check_other_types(base64.b32decode, b'MFRGG===', b"abc")
+ self.check_decode_type_errors(base64.b32decode)
def test_b32decode_casefold(self):
eq = self.assertEqual
@@ -277,8 +336,9 @@ class BaseXYTestCase(unittest.TestCase):
eq(base64.b16encode(b'\x01\x02\xab\xcd\xef'), b'0102ABCDEF')
eq(base64.b16encode(b'\x00'), b'00')
# Non-bytes
- eq(base64.b16encode(bytearray(b'\x01\x02\xab\xcd\xef')), b'0102ABCDEF')
- self.assertRaises(TypeError, base64.b16encode, "")
+ self.check_other_types(base64.b16encode, b'\x01\x02\xab\xcd\xef',
+ b'0102ABCDEF')
+ self.check_encode_type_errors(base64.b16encode)
def test_b16decode(self):
eq = self.assertEqual
@@ -293,14 +353,268 @@ class BaseXYTestCase(unittest.TestCase):
eq(base64.b16decode(b'0102abcdef', True), b'\x01\x02\xab\xcd\xef')
eq(base64.b16decode('0102abcdef', True), b'\x01\x02\xab\xcd\xef')
# Non-bytes
- eq(base64.b16decode(bytearray(b"0102ABCDEF")), b'\x01\x02\xab\xcd\xef')
+ self.check_other_types(base64.b16decode, b"0102ABCDEF",
+ b'\x01\x02\xab\xcd\xef')
+ self.check_decode_type_errors(base64.b16decode)
+ eq(base64.b16decode(bytearray(b"0102abcdef"), True),
+ b'\x01\x02\xab\xcd\xef')
+ eq(base64.b16decode(memoryview(b"0102abcdef"), True),
+ b'\x01\x02\xab\xcd\xef')
+ eq(base64.b16decode(array('B', b"0102abcdef"), True),
+ b'\x01\x02\xab\xcd\xef')
+
+ def test_a85encode(self):
+ eq = self.assertEqual
+
+ tests = {
+ b'': b'',
+ b"www.python.org": b'GB\\6`E-ZP=Df.1GEb>',
+ bytes(range(255)): b"""!!*-'"9eu7#RLhG$k3[W&.oNg'GVB"(`=52*$$"""
+ b"""(B+<_pR,UFcb-n-Vr/1iJ-0JP==1c70M3&s#]4?Ykm5X@_(6q'R884cE"""
+ b"""H9MJ8X:f1+h<)lt#=BSg3>[:ZC?t!MSA7]@cBPD3sCi+'.E,fo>FEMbN"""
+ b"""G^4U^I!pHnJ:W<)KS>/9Ll%"IN/`jYOHG]iPa.Q$R$jD4S=Q7DTV8*TU"""
+ b"""nsrdW2ZetXKAY/Yd(L?['d?O\\@K2_]Y2%o^qmn*`5Ta:aN;TJbg"GZd"""
+ b"""*^:jeCE.%f\\,!5gtgiEi8N\\UjQ5OekiqBum-X60nF?)@o_%qPq"ad`"""
+ b"""r;HT""",
+ b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ b"0123456789!@#0^&*();:<>,. []{}":
+ b'@:E_WAS,RgBkhF"D/O92EH6,BF`qtRH$VbC6UX@47n?3D92&&T'
+ b":Jand;cHat='/U/0JP==1c70M3&r-I,;<FN.OZ`-3]oSW/g+A(H[P",
+ b"no padding..": b'DJpY:@:Wn_DJ(RS',
+ b"zero compression\0\0\0\0": b'H=_,8+Cf>,E,oN2F(oQ1z',
+ b"zero compression\0\0\0": b'H=_,8+Cf>,E,oN2F(oQ1!!!!',
+ b"Boundary:\0\0\0\0": b'6>q!aA79M(3WK-[!!',
+ b"Space compr: ": b';fH/TAKYK$D/aMV+<VdL',
+ b'\xff': b'rr',
+ b'\xff'*2: b's8N',
+ b'\xff'*3: b's8W*',
+ b'\xff'*4: b's8W-!',
+ }
+
+ for data, res in tests.items():
+ eq(base64.a85encode(data), res, data)
+ eq(base64.a85encode(data, adobe=False), res, data)
+ eq(base64.a85encode(data, adobe=True), b'<~' + res + b'~>', data)
+
+ self.check_other_types(base64.a85encode, b"www.python.org",
+ b'GB\\6`E-ZP=Df.1GEb>')
+
+ self.assertRaises(TypeError, base64.a85encode, "")
+
+ eq(base64.a85encode(b"www.python.org", wrapcol=7, adobe=False),
+ b'GB\\6`E-\nZP=Df.1\nGEb>')
+ eq(base64.a85encode(b"\0\0\0\0www.python.org", wrapcol=7, adobe=False),
+ b'zGB\\6`E\n-ZP=Df.\n1GEb>')
+ eq(base64.a85encode(b"www.python.org", wrapcol=7, adobe=True),
+ b'<~GB\\6`\nE-ZP=Df\n.1GEb>\n~>')
+
+ eq(base64.a85encode(b' '*8, foldspaces=True, adobe=False), b'yy')
+ eq(base64.a85encode(b' '*7, foldspaces=True, adobe=False), b'y+<Vd')
+ eq(base64.a85encode(b' '*6, foldspaces=True, adobe=False), b'y+<U')
+ eq(base64.a85encode(b' '*5, foldspaces=True, adobe=False), b'y+9')
+
+ def test_b85encode(self):
+ eq = self.assertEqual
+
+ tests = {
+ b'': b'',
+ b'www.python.org': b'cXxL#aCvlSZ*DGca%T',
+ bytes(range(255)): b"""009C61O)~M2nh-c3=Iws5D^j+6crX17#SKH9337X"""
+ b"""AR!_nBqb&%C@Cr{EG;fCFflSSG&MFiI5|2yJUu=?KtV!7L`6nNNJ&ad"""
+ b"""OifNtP*GA-R8>}2SXo+ITwPvYU}0ioWMyV&XlZI|Y;A6DaB*^Tbai%j"""
+ b"""czJqze0_d@fPsR8goTEOh>41ejE#<ukdcy;l$Dm3n3<ZJoSmMZprN9p"""
+ b"""q@|{(sHv)}tgWuEu(7hUw6(UkxVgH!yuH4^z`?@9#Kp$P$jQpf%+1cv"""
+ b"""(9zP<)YaD4*xB0K+}+;a;Njxq<mKk)=;`X~?CtLF@bU8V^!4`l`1$(#"""
+ b"""{Qdp""",
+ b"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"""
+ b"""0123456789!@#0^&*();:<>,. []{}""":
+ b"""VPa!sWoBn+X=-b1ZEkOHadLBXb#`}nd3r%YLqtVJM@UIZOH55pPf$@("""
+ b"""Q&d$}S6EqEFflSSG&MFiI5{CeBQRbjDkv#CIy^osE+AW7dwl""",
+ b'no padding..': b'Zf_uPVPs@!Zf7no',
+ b'zero compression\x00\x00\x00\x00': b'dS!BNAY*TBaB^jHb7^mG00000',
+ b'zero compression\x00\x00\x00': b'dS!BNAY*TBaB^jHb7^mG0000',
+ b"""Boundary:\x00\x00\x00\x00""": b"""LT`0$WMOi7IsgCw00""",
+ b'Space compr: ': b'Q*dEpWgug3ZE$irARr(h',
+ b'\xff': b'{{',
+ b'\xff'*2: b'|Nj',
+ b'\xff'*3: b'|Ns9',
+ b'\xff'*4: b'|NsC0',
+ }
+
+ for data, res in tests.items():
+ eq(base64.b85encode(data), res)
+
+ self.check_other_types(base64.b85encode, b"www.python.org",
+ b'cXxL#aCvlSZ*DGca%T')
+
+ def test_a85decode(self):
+ eq = self.assertEqual
+
+ tests = {
+ b'': b'',
+ b'GB\\6`E-ZP=Df.1GEb>': b'www.python.org',
+ b"""! ! * -'"\n\t\t9eu\r\n7# RL\vhG$k3[W&.oNg'GVB"(`=52*$$"""
+ b"""(B+<_pR,UFcb-n-Vr/1iJ-0JP==1c70M3&s#]4?Ykm5X@_(6q'R884cE"""
+ b"""H9MJ8X:f1+h<)lt#=BSg3>[:ZC?t!MSA7]@cBPD3sCi+'.E,fo>FEMbN"""
+ b"""G^4U^I!pHnJ:W<)KS>/9Ll%"IN/`jYOHG]iPa.Q$R$jD4S=Q7DTV8*TU"""
+ b"""nsrdW2ZetXKAY/Yd(L?['d?O\\@K2_]Y2%o^qmn*`5Ta:aN;TJbg"GZd"""
+ b"""*^:jeCE.%f\\,!5gtgiEi8N\\UjQ5OekiqBum-X60nF?)@o_%qPq"ad`"""
+ b"""r;HT""": bytes(range(255)),
+ b"""@:E_WAS,RgBkhF"D/O92EH6,BF`qtRH$VbC6UX@47n?3D92&&T:Jand;c"""
+ b"""Hat='/U/0JP==1c70M3&r-I,;<FN.OZ`-3]oSW/g+A(H[P""":
+ b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234'
+ b'56789!@#0^&*();:<>,. []{}',
+ b'DJpY:@:Wn_DJ(RS': b'no padding..',
+ b'H=_,8+Cf>,E,oN2F(oQ1z': b'zero compression\x00\x00\x00\x00',
+ b'H=_,8+Cf>,E,oN2F(oQ1!!!!': b'zero compression\x00\x00\x00',
+ b'6>q!aA79M(3WK-[!!': b"Boundary:\x00\x00\x00\x00",
+ b';fH/TAKYK$D/aMV+<VdL': b'Space compr: ',
+ b'rr': b'\xff',
+ b's8N': b'\xff'*2,
+ b's8W*': b'\xff'*3,
+ b's8W-!': b'\xff'*4,
+ }
+
+ for data, res in tests.items():
+ eq(base64.a85decode(data), res, data)
+ eq(base64.a85decode(data, adobe=False), res, data)
+ eq(base64.a85decode(data.decode("ascii"), adobe=False), res, data)
+ eq(base64.a85decode(b'<~' + data + b'~>', adobe=True), res, data)
+ eq(base64.a85decode('<~%s~>' % data.decode("ascii"), adobe=True),
+ res, data)
+
+ eq(base64.a85decode(b'yy', foldspaces=True, adobe=False), b' '*8)
+ eq(base64.a85decode(b'y+<Vd', foldspaces=True, adobe=False), b' '*7)
+ eq(base64.a85decode(b'y+<U', foldspaces=True, adobe=False), b' '*6)
+ eq(base64.a85decode(b'y+9', foldspaces=True, adobe=False), b' '*5)
+
+ self.check_other_types(base64.a85decode, b'GB\\6`E-ZP=Df.1GEb>',
+ b"www.python.org")
+
+ def test_b85decode(self):
+ eq = self.assertEqual
+
+ tests = {
+ b'': b'',
+ b'cXxL#aCvlSZ*DGca%T': b'www.python.org',
+ b"""009C61O)~M2nh-c3=Iws5D^j+6crX17#SKH9337X"""
+ b"""AR!_nBqb&%C@Cr{EG;fCFflSSG&MFiI5|2yJUu=?KtV!7L`6nNNJ&ad"""
+ b"""OifNtP*GA-R8>}2SXo+ITwPvYU}0ioWMyV&XlZI|Y;A6DaB*^Tbai%j"""
+ b"""czJqze0_d@fPsR8goTEOh>41ejE#<ukdcy;l$Dm3n3<ZJoSmMZprN9p"""
+ b"""q@|{(sHv)}tgWuEu(7hUw6(UkxVgH!yuH4^z`?@9#Kp$P$jQpf%+1cv"""
+ b"""(9zP<)YaD4*xB0K+}+;a;Njxq<mKk)=;`X~?CtLF@bU8V^!4`l`1$(#"""
+ b"""{Qdp""": bytes(range(255)),
+ b"""VPa!sWoBn+X=-b1ZEkOHadLBXb#`}nd3r%YLqtVJM@UIZOH55pPf$@("""
+ b"""Q&d$}S6EqEFflSSG&MFiI5{CeBQRbjDkv#CIy^osE+AW7dwl""":
+ b"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"""
+ b"""0123456789!@#0^&*();:<>,. []{}""",
+ b'Zf_uPVPs@!Zf7no': b'no padding..',
+ b'dS!BNAY*TBaB^jHb7^mG00000': b'zero compression\x00\x00\x00\x00',
+ b'dS!BNAY*TBaB^jHb7^mG0000': b'zero compression\x00\x00\x00',
+ b"""LT`0$WMOi7IsgCw00""": b"""Boundary:\x00\x00\x00\x00""",
+ b'Q*dEpWgug3ZE$irARr(h': b'Space compr: ',
+ b'{{': b'\xff',
+ b'|Nj': b'\xff'*2,
+ b'|Ns9': b'\xff'*3,
+ b'|NsC0': b'\xff'*4,
+ }
+
+ for data, res in tests.items():
+ eq(base64.b85decode(data), res)
+ eq(base64.b85decode(data.decode("ascii")), res)
+
+ self.check_other_types(base64.b85decode, b'cXxL#aCvlSZ*DGca%T',
+ b"www.python.org")
+
+ def test_a85_padding(self):
+ eq = self.assertEqual
+
+ eq(base64.a85encode(b"x", pad=True), b'GQ7^D')
+ eq(base64.a85encode(b"xx", pad=True), b"G^'2g")
+ eq(base64.a85encode(b"xxx", pad=True), b'G^+H5')
+ eq(base64.a85encode(b"xxxx", pad=True), b'G^+IX')
+ eq(base64.a85encode(b"xxxxx", pad=True), b'G^+IXGQ7^D')
+
+ eq(base64.a85decode(b'GQ7^D'), b"x\x00\x00\x00")
+ eq(base64.a85decode(b"G^'2g"), b"xx\x00\x00")
+ eq(base64.a85decode(b'G^+H5'), b"xxx\x00")
+ eq(base64.a85decode(b'G^+IX'), b"xxxx")
+ eq(base64.a85decode(b'G^+IXGQ7^D'), b"xxxxx\x00\x00\x00")
+
+ def test_b85_padding(self):
+ eq = self.assertEqual
+
+ eq(base64.b85encode(b"x", pad=True), b'cmMzZ')
+ eq(base64.b85encode(b"xx", pad=True), b'cz6H+')
+ eq(base64.b85encode(b"xxx", pad=True), b'czAdK')
+ eq(base64.b85encode(b"xxxx", pad=True), b'czAet')
+ eq(base64.b85encode(b"xxxxx", pad=True), b'czAetcmMzZ')
+
+ eq(base64.b85decode(b'cmMzZ'), b"x\x00\x00\x00")
+ eq(base64.b85decode(b'cz6H+'), b"xx\x00\x00")
+ eq(base64.b85decode(b'czAdK'), b"xxx\x00")
+ eq(base64.b85decode(b'czAet'), b"xxxx")
+ eq(base64.b85decode(b'czAetcmMzZ'), b"xxxxx\x00\x00\x00")
+
+ def test_a85decode_errors(self):
+ illegal = (set(range(32)) | set(range(118, 256))) - set(b' \t\n\r\v')
+ for c in illegal:
+ with self.assertRaises(ValueError, msg=bytes([c])):
+ base64.a85decode(b'!!!!' + bytes([c]))
+ with self.assertRaises(ValueError, msg=bytes([c])):
+ base64.a85decode(b'!!!!' + bytes([c]), adobe=False)
+ with self.assertRaises(ValueError, msg=bytes([c])):
+ base64.a85decode(b'<~!!!!' + bytes([c]) + b'~>', adobe=True)
+
+ self.assertRaises(ValueError, base64.a85decode,
+ b"malformed", adobe=True)
+ self.assertRaises(ValueError, base64.a85decode,
+ b"<~still malformed", adobe=True)
+ self.assertRaises(ValueError, base64.a85decode,
+ b"also malformed~>", adobe=True)
+
+ # With adobe=False (the default), Adobe framing markers are disallowed
+ self.assertRaises(ValueError, base64.a85decode,
+ b"<~~>")
+ self.assertRaises(ValueError, base64.a85decode,
+ b"<~~>", adobe=False)
+ base64.a85decode(b"<~~>", adobe=True) # sanity check
+
+ self.assertRaises(ValueError, base64.a85decode,
+ b"abcx", adobe=False)
+ self.assertRaises(ValueError, base64.a85decode,
+ b"abcdey", adobe=False)
+ self.assertRaises(ValueError, base64.a85decode,
+ b"a b\nc", adobe=False, ignorechars=b"")
+
+ self.assertRaises(ValueError, base64.a85decode, b's', adobe=False)
+ self.assertRaises(ValueError, base64.a85decode, b's8', adobe=False)
+ self.assertRaises(ValueError, base64.a85decode, b's8W', adobe=False)
+ self.assertRaises(ValueError, base64.a85decode, b's8W-', adobe=False)
+ self.assertRaises(ValueError, base64.a85decode, b's8W-"', adobe=False)
+
+ def test_b85decode_errors(self):
+ illegal = list(range(33)) + \
+ list(b'"\',./:[\\]') + \
+ list(range(128, 256))
+ for c in illegal:
+ with self.assertRaises(ValueError, msg=bytes([c])):
+ base64.b85decode(b'0000' + bytes([c]))
+
+ self.assertRaises(ValueError, base64.b85decode, b'|')
+ self.assertRaises(ValueError, base64.b85decode, b'|N')
+ self.assertRaises(ValueError, base64.b85decode, b'|Ns')
+ self.assertRaises(ValueError, base64.b85decode, b'|NsC')
+ self.assertRaises(ValueError, base64.b85decode, b'|NsC1')
def test_decode_nonascii_str(self):
decode_funcs = (base64.b64decode,
base64.standard_b64decode,
base64.urlsafe_b64decode,
base64.b32decode,
- base64.b16decode)
+ base64.b16decode,
+ base64.b85decode,
+ base64.a85decode)
for f in decode_funcs:
self.assertRaises(ValueError, f, 'with non-ascii \xcb')
diff --git a/Lib/test/test_bisect.py b/Lib/test/test_bisect.py
index 7b9bd19fcb..580a963f62 100644
--- a/Lib/test/test_bisect.py
+++ b/Lib/test/test_bisect.py
@@ -7,7 +7,7 @@ py_bisect = support.import_fresh_module('bisect', blocked=['_bisect'])
c_bisect = support.import_fresh_module('bisect', fresh=['_bisect'])
class Range(object):
- """A trivial range()-like object without any integer width limitations."""
+ """A trivial range()-like object that has an insert() method."""
def __init__(self, start, stop):
self.start = start
self.stop = stop
@@ -120,10 +120,10 @@ class TestBisect:
def test_negative_lo(self):
# Issue 3301
mod = self.module
- self.assertRaises(ValueError, mod.bisect_left, [1, 2, 3], 5, -1, 3),
- self.assertRaises(ValueError, mod.bisect_right, [1, 2, 3], 5, -1, 3),
- self.assertRaises(ValueError, mod.insort_left, [1, 2, 3], 5, -1, 3),
- self.assertRaises(ValueError, mod.insort_right, [1, 2, 3], 5, -1, 3),
+ self.assertRaises(ValueError, mod.bisect_left, [1, 2, 3], 5, -1, 3)
+ self.assertRaises(ValueError, mod.bisect_right, [1, 2, 3], 5, -1, 3)
+ self.assertRaises(ValueError, mod.insort_left, [1, 2, 3], 5, -1, 3)
+ self.assertRaises(ValueError, mod.insort_right, [1, 2, 3], 5, -1, 3)
def test_large_range(self):
# Issue 13496
diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py
index 04e3f61e89..1667847a9d 100644
--- a/Lib/test/test_buffer.py
+++ b/Lib/test/test_buffer.py
@@ -4290,9 +4290,5 @@ class TestBufferProtocol(unittest.TestCase):
self.assertRaises(BufferError, memoryview, x)
-def test_main():
- support.run_unittest(TestBufferProtocol)
-
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index c342a4329f..68430660f0 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -464,6 +464,11 @@ class BuiltinTest(unittest.TestCase):
self.assertRaises(TypeError, eval, ())
self.assertRaises(SyntaxError, eval, bom[:2] + b'a')
+ class X:
+ def __getitem__(self, key):
+ raise ValueError
+ self.assertRaises(ValueError, eval, "foo", {}, X())
+
def test_general_eval(self):
# Tests that general mappings can be used for the locals argument
@@ -579,7 +584,10 @@ class BuiltinTest(unittest.TestCase):
raise frozendict_error("frozendict is readonly")
# read-only builtins
- frozen_builtins = frozendict(__builtins__)
+ if isinstance(__builtins__, types.ModuleType):
+ frozen_builtins = frozendict(__builtins__.__dict__)
+ else:
+ frozen_builtins = frozendict(__builtins__)
code = compile("__builtins__['superglobal']=2; print(superglobal)", "test", "exec")
self.assertRaises(frozendict_error,
exec, code, {'__builtins__': frozen_builtins})
@@ -839,8 +847,19 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(max(1, 2.0, 3), 3)
self.assertEqual(max(1.0, 2, 3), 3)
+ self.assertRaises(TypeError, max)
+ self.assertRaises(TypeError, max, 42)
+ self.assertRaises(ValueError, max, ())
+ class BadSeq:
+ def __getitem__(self, index):
+ raise ValueError
+ self.assertRaises(ValueError, max, BadSeq())
+
for stmt in (
"max(key=int)", # no args
+ "max(default=None)",
+ "max(1, 2, default=None)", # require container for default
+ "max(default=None, key=int)",
"max(1, key=int)", # single arg not iterable
"max(1, 2, keystone=int)", # wrong keyword
"max(1, 2, key=int, abc=int)", # two many keywords
@@ -857,6 +876,13 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(max((1,2), key=neg), 1) # two elem iterable
self.assertEqual(max(1, 2, key=neg), 1) # two elems
+ self.assertEqual(max((), default=None), None) # zero elem iterable
+ self.assertEqual(max((1,), default=None), 1) # one elem iterable
+ self.assertEqual(max((1,2), default=None), 2) # two elem iterable
+
+ self.assertEqual(max((), default=1, key=neg), 1)
+ self.assertEqual(max((1, 2), default=3, key=neg), 1)
+
data = [random.randrange(200) for i in range(100)]
keys = dict((elem, random.randrange(50)) for elem in data)
f = keys.__getitem__
@@ -883,6 +909,9 @@ class BuiltinTest(unittest.TestCase):
for stmt in (
"min(key=int)", # no args
+ "min(default=None)",
+ "min(1, 2, default=None)", # require container for default
+ "min(default=None, key=int)",
"min(1, key=int)", # single arg not iterable
"min(1, 2, keystone=int)", # wrong keyword
"min(1, 2, key=int, abc=int)", # two many keywords
@@ -899,6 +928,13 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(min((1,2), key=neg), 2) # two elem iterable
self.assertEqual(min(1, 2, key=neg), 2) # two elems
+ self.assertEqual(min((), default=None), None) # zero elem iterable
+ self.assertEqual(min((1,), default=None), 1) # one elem iterable
+ self.assertEqual(min((1,2), default=None), 1) # two elem iterable
+
+ self.assertEqual(min((), default=1, key=neg), 1)
+ self.assertEqual(min((1, 2), default=1, key=neg), 2)
+
data = [random.randrange(200) for i in range(100)]
keys = dict((elem, random.randrange(50)) for elem in data)
f = keys.__getitem__
@@ -940,29 +976,25 @@ class BuiltinTest(unittest.TestCase):
def write_testfile(self):
# NB the first 4 lines are also used to test input, below
fp = open(TESTFN, 'w')
- try:
+ self.addCleanup(unlink, TESTFN)
+ with fp:
fp.write('1+1\n')
fp.write('The quick brown fox jumps over the lazy dog')
fp.write('.\n')
fp.write('Dear John\n')
fp.write('XXX'*100)
fp.write('YYY'*100)
- finally:
- fp.close()
def test_open(self):
self.write_testfile()
fp = open(TESTFN, 'r')
- try:
+ with fp:
self.assertEqual(fp.readline(4), '1+1\n')
self.assertEqual(fp.readline(), 'The quick brown fox jumps over the lazy dog.\n')
self.assertEqual(fp.readline(4), 'Dear')
self.assertEqual(fp.readline(100), ' John\n')
self.assertEqual(fp.read(300), 'XXX'*100)
self.assertEqual(fp.read(1000), 'YYY'*100)
- finally:
- fp.close()
- unlink(TESTFN)
def test_open_default_encoding(self):
old_environ = dict(os.environ)
@@ -977,15 +1009,17 @@ class BuiltinTest(unittest.TestCase):
self.write_testfile()
current_locale_encoding = locale.getpreferredencoding(False)
fp = open(TESTFN, 'w')
- try:
+ with fp:
self.assertEqual(fp.encoding, current_locale_encoding)
- finally:
- fp.close()
- unlink(TESTFN)
finally:
os.environ.clear()
os.environ.update(old_environ)
+ def test_open_non_inheritable(self):
+ fileobj = open(__file__)
+ with fileobj:
+ self.assertFalse(os.get_inheritable(fileobj.fileno()))
+
def test_ord(self):
self.assertEqual(ord(' '), 32)
self.assertEqual(ord('A'), 65)
@@ -1096,7 +1130,6 @@ class BuiltinTest(unittest.TestCase):
sys.stdin = savestdin
sys.stdout = savestdout
fp.close()
- unlink(TESTFN)
@unittest.skipUnless(pty, "the pty and signal modules must be available")
def check_input_tty(self, prompt, terminal_input, stdio_encoding=None):
@@ -1476,17 +1509,11 @@ class BuiltinTest(unittest.TestCase):
# --------------------------------------------------------------------
# Issue #7994: object.__format__ with a non-empty format string is
# deprecated
- def test_deprecated_format_string(obj, fmt_str, should_raise_warning):
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always", DeprecationWarning)
- format(obj, fmt_str)
- if should_raise_warning:
- self.assertEqual(len(w), 1)
- self.assertIsInstance(w[0].message, DeprecationWarning)
- self.assertIn('object.__format__ with a non-empty format '
- 'string', str(w[0].message))
+ def test_deprecated_format_string(obj, fmt_str, should_raise):
+ if should_raise:
+ self.assertRaises(TypeError, format, obj, fmt_str)
else:
- self.assertEqual(len(w), 0)
+ format(obj, fmt_str)
fmt_strs = ['', 's']
@@ -1565,21 +1592,10 @@ class TestSorted(unittest.TestCase):
data = 'The quick Brown fox Jumped over The lazy Dog'.split()
self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0)
-def test_main(verbose=None):
- test_classes = (BuiltinTest, TestSorted)
-
- run_unittest(*test_classes)
-
- # verify reference counting
- if verbose and hasattr(sys, "gettotalrefcount"):
- import gc
- counts = [None] * 5
- for i in range(len(counts)):
- run_unittest(*test_classes)
- gc.collect()
- counts[i] = sys.gettotalrefcount()
- print(counts)
-
+def load_tests(loader, tests, pattern):
+ from doctest import DocTestSuite
+ tests.addTest(DocTestSuite(builtins))
+ return tests
if __name__ == "__main__":
- test_main(verbose=True)
+ unittest.main()
diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index 3520e837a1..847c7a613f 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -288,8 +288,22 @@ class BaseBytesTest:
self.assertEqual(self.type2test(b"").join(lst), b"abc")
self.assertEqual(self.type2test(b"").join(tuple(lst)), b"abc")
self.assertEqual(self.type2test(b"").join(iter(lst)), b"abc")
- self.assertEqual(self.type2test(b".").join([b"ab", b"cd"]), b"ab.cd")
- # XXX more...
+ dot_join = self.type2test(b".:").join
+ self.assertEqual(dot_join([b"ab", b"cd"]), b"ab.:cd")
+ self.assertEqual(dot_join([memoryview(b"ab"), b"cd"]), b"ab.:cd")
+ self.assertEqual(dot_join([b"ab", memoryview(b"cd")]), b"ab.:cd")
+ self.assertEqual(dot_join([bytearray(b"ab"), b"cd"]), b"ab.:cd")
+ self.assertEqual(dot_join([b"ab", bytearray(b"cd")]), b"ab.:cd")
+ # Stress it with many items
+ seq = [b"abc"] * 1000
+ expected = b"abc" + b".:abc" * 999
+ self.assertEqual(dot_join(seq), expected)
+ # Error handling and cleanup when some item in the middle of the
+ # sequence has the wrong type.
+ with self.assertRaises(TypeError):
+ dot_join([bytearray(b"ab"), "cd", b"ef"])
+ with self.assertRaises(TypeError):
+ dot_join([memoryview(b"ab"), "cd", b"ef"])
def test_count(self):
b = self.type2test(b'mississippi')
@@ -759,7 +773,7 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase):
finally:
try:
os.remove(tfn)
- except os.error:
+ except OSError:
pass
def test_reverse(self):
@@ -895,6 +909,15 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase):
with self.assertRaises(ValueError):
b[3:4] = elem
+ def test_setslice_extend(self):
+ # Exercise the resizing logic (see issue #19087)
+ b = bytearray(range(100))
+ self.assertEqual(list(b), list(range(100)))
+ del b[:10]
+ self.assertEqual(list(b), list(range(10, 100)))
+ b.extend(range(100, 110))
+ self.assertEqual(list(b), list(range(10, 110)))
+
def test_extended_set_del_slice(self):
indices = (0, None, 1, 3, 19, 300, 1<<333, -1, -2, -31, -300)
for start in indices:
@@ -1274,6 +1297,11 @@ class BytearrayPEP3137Test(unittest.TestCase,
self.assertEqual(val, newval)
self.assertTrue(val is not newval,
expr+' returned val on a mutable object')
+ sep = self.marshal(b'')
+ newval = sep.join([val])
+ self.assertEqual(val, newval)
+ self.assertIsNot(val, newval)
+
class FixedStringTest(test.string_tests.BaseTest):
diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py
index 6764e59553..8d93e2d88f 100644
--- a/Lib/test/test_bz2.py
+++ b/Lib/test/test_bz2.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
from test import support
-from test.support import TESTFN, bigmemtest, _4G
+from test.support import bigmemtest, _4G
import unittest
from io import BytesIO
@@ -9,6 +9,7 @@ import pickle
import random
import subprocess
import sys
+from test.support import unlink
try:
import threading
@@ -19,10 +20,10 @@ except ImportError:
bz2 = support.import_module('bz2')
from bz2 import BZ2File, BZ2Compressor, BZ2Decompressor
-has_cmdline_bunzip2 = sys.platform not in ("win32", "os2emx")
class BaseTest(unittest.TestCase):
"Base for other testcases."
+
TEXT_LINES = [
b'root:x:0:0:root:/root:/bin/bash\n',
b'bin:x:1:1:bin:/bin:\n',
@@ -51,13 +52,17 @@ class BaseTest(unittest.TestCase):
EMPTY_DATA = b'BZh9\x17rE8P\x90\x00\x00\x00\x00'
def setUp(self):
- self.filename = TESTFN
+ self.filename = support.TESTFN
def tearDown(self):
if os.path.isfile(self.filename):
os.unlink(self.filename)
- if has_cmdline_bunzip2:
+ if sys.platform == "win32":
+ # bunzip2 isn't available to run on Windows.
+ def decompress(self, data):
+ return bz2.decompress(data)
+ else:
def decompress(self, data):
pop = subprocess.Popen("bunzip2", shell=True,
stdin=subprocess.PIPE,
@@ -71,31 +76,21 @@ class BaseTest(unittest.TestCase):
ret = bz2.decompress(data)
return ret
- else:
- # bunzip2 isn't available to run on Windows.
- def decompress(self, data):
- return bz2.decompress(data)
class BZ2FileTest(BaseTest):
- "Test BZ2File type miscellaneous methods."
+ "Test the BZ2File class."
def createTempFile(self, streams=1):
with open(self.filename, "wb") as f:
f.write(self.DATA * streams)
def testBadArgs(self):
- with self.assertRaises(TypeError):
- BZ2File(123.456)
- with self.assertRaises(ValueError):
- BZ2File("/dev/null", "z")
- with self.assertRaises(ValueError):
- BZ2File("/dev/null", "rx")
- with self.assertRaises(ValueError):
- BZ2File("/dev/null", "rbt")
- with self.assertRaises(ValueError):
- BZ2File("/dev/null", compresslevel=0)
- with self.assertRaises(ValueError):
- BZ2File("/dev/null", compresslevel=10)
+ self.assertRaises(TypeError, BZ2File, 123.456)
+ self.assertRaises(ValueError, BZ2File, "/dev/null", "z")
+ self.assertRaises(ValueError, BZ2File, "/dev/null", "rx")
+ self.assertRaises(ValueError, BZ2File, "/dev/null", "rbt")
+ self.assertRaises(ValueError, BZ2File, "/dev/null", compresslevel=0)
+ self.assertRaises(ValueError, BZ2File, "/dev/null", compresslevel=10)
def testRead(self):
self.createTempFile()
@@ -216,9 +211,8 @@ class BZ2FileTest(BaseTest):
self.createTempFile()
bz2f = BZ2File(self.filename)
bz2f.close()
- self.assertRaises(ValueError, bz2f.__next__)
- # This call will deadlock if the above .__next__ call failed to
- # release the lock.
+ self.assertRaises(ValueError, next, bz2f)
+ # This call will deadlock if the above call failed to release the lock.
self.assertRaises(ValueError, bz2f.readlines)
def testWrite(self):
@@ -262,8 +256,8 @@ class BZ2FileTest(BaseTest):
bz2f.write(b"abc")
with BZ2File(self.filename, "r") as bz2f:
- self.assertRaises(IOError, bz2f.write, b"a")
- self.assertRaises(IOError, bz2f.writelines, [b"a"])
+ self.assertRaises(OSError, bz2f.write, b"a")
+ self.assertRaises(OSError, bz2f.writelines, [b"a"])
def testAppend(self):
with BZ2File(self.filename, "w") as bz2f:
@@ -381,7 +375,7 @@ class BZ2FileTest(BaseTest):
bz2f.close()
self.assertRaises(ValueError, bz2f.seekable)
- bz2f = BZ2File(BytesIO(), mode="w")
+ bz2f = BZ2File(BytesIO(), "w")
try:
self.assertFalse(bz2f.seekable())
finally:
@@ -407,7 +401,7 @@ class BZ2FileTest(BaseTest):
bz2f.close()
self.assertRaises(ValueError, bz2f.readable)
- bz2f = BZ2File(BytesIO(), mode="w")
+ bz2f = BZ2File(BytesIO(), "w")
try:
self.assertFalse(bz2f.readable())
finally:
@@ -424,7 +418,7 @@ class BZ2FileTest(BaseTest):
bz2f.close()
self.assertRaises(ValueError, bz2f.writable)
- bz2f = BZ2File(BytesIO(), mode="w")
+ bz2f = BZ2File(BytesIO(), "w")
try:
self.assertTrue(bz2f.writable())
finally:
@@ -438,7 +432,7 @@ class BZ2FileTest(BaseTest):
del o
def testOpenNonexistent(self):
- self.assertRaises(IOError, BZ2File, "/non/existent")
+ self.assertRaises(OSError, BZ2File, "/non/existent")
def testReadlinesNoNewline(self):
# Issue #1191043: readlines() fails on a file containing no newline.
@@ -478,7 +472,7 @@ class BZ2FileTest(BaseTest):
# Issue #7205: Using a BZ2File from several threads shouldn't deadlock.
data = b"1" * 2**20
nthreads = 10
- with bz2.BZ2File(self.filename, 'wb') as f:
+ with BZ2File(self.filename, 'wb') as f:
def comp():
for i in range(5):
f.write(data)
@@ -489,28 +483,27 @@ class BZ2FileTest(BaseTest):
t.join()
def testWithoutThreading(self):
- bz2 = support.import_fresh_module("bz2", blocked=("threading",))
- with bz2.BZ2File(self.filename, "wb") as f:
+ module = support.import_fresh_module("bz2", blocked=("threading",))
+ with module.BZ2File(self.filename, "wb") as f:
f.write(b"abc")
- with bz2.BZ2File(self.filename, "rb") as f:
+ with module.BZ2File(self.filename, "rb") as f:
self.assertEqual(f.read(), b"abc")
def testMixedIterationAndReads(self):
self.createTempFile()
linelen = len(self.TEXT_LINES[0])
halflen = linelen // 2
- with bz2.BZ2File(self.filename) as bz2f:
+ with BZ2File(self.filename) as bz2f:
bz2f.read(halflen)
self.assertEqual(next(bz2f), self.TEXT_LINES[0][halflen:])
self.assertEqual(bz2f.read(), self.TEXT[linelen:])
- with bz2.BZ2File(self.filename) as bz2f:
+ with BZ2File(self.filename) as bz2f:
bz2f.readline()
self.assertEqual(next(bz2f), self.TEXT_LINES[1])
self.assertEqual(bz2f.readline(), self.TEXT_LINES[2])
- with bz2.BZ2File(self.filename) as bz2f:
+ with BZ2File(self.filename) as bz2f:
bz2f.readlines()
- with self.assertRaises(StopIteration):
- next(bz2f)
+ self.assertRaises(StopIteration, next, bz2f)
self.assertEqual(bz2f.readlines(), [])
def testMultiStreamOrdering(self):
@@ -578,6 +571,20 @@ class BZ2FileTest(BaseTest):
bz2f.seek(-150, 1)
self.assertEqual(bz2f.read(), self.TEXT[500-150:])
+ def test_read_truncated(self):
+ # Drop the eos_magic field (6 bytes) and CRC (4 bytes).
+ truncated = self.DATA[:-10]
+ with BZ2File(BytesIO(truncated)) as f:
+ self.assertRaises(EOFError, f.read)
+ with BZ2File(BytesIO(truncated)) as f:
+ self.assertEqual(f.read(len(self.TEXT)), self.TEXT)
+ self.assertRaises(EOFError, f.read, 1)
+ # Incomplete 4-byte file header, and block header of at least 146 bits.
+ for i in range(22):
+ with BZ2File(BytesIO(truncated[:i])) as f:
+ self.assertRaises(EOFError, f.read, 1)
+
+
class BZ2CompressorTest(BaseTest):
def testCompress(self):
bz2c = BZ2Compressor()
@@ -713,97 +720,122 @@ class CompressDecompressTest(BaseTest):
class OpenTest(BaseTest):
+ "Test the open function."
+
+ def open(self, *args, **kwargs):
+ return bz2.open(*args, **kwargs)
+
def test_binary_modes(self):
- with bz2.open(self.filename, "wb") as f:
- f.write(self.TEXT)
- with open(self.filename, "rb") as f:
- file_data = bz2.decompress(f.read())
- self.assertEqual(file_data, self.TEXT)
- with bz2.open(self.filename, "rb") as f:
- self.assertEqual(f.read(), self.TEXT)
- with bz2.open(self.filename, "ab") as f:
- f.write(self.TEXT)
- with open(self.filename, "rb") as f:
- file_data = bz2.decompress(f.read())
- self.assertEqual(file_data, self.TEXT * 2)
+ for mode in ("wb", "xb"):
+ if mode == "xb":
+ unlink(self.filename)
+ with self.open(self.filename, mode) as f:
+ f.write(self.TEXT)
+ with open(self.filename, "rb") as f:
+ file_data = self.decompress(f.read())
+ self.assertEqual(file_data, self.TEXT)
+ with self.open(self.filename, "rb") as f:
+ self.assertEqual(f.read(), self.TEXT)
+ with self.open(self.filename, "ab") as f:
+ f.write(self.TEXT)
+ with open(self.filename, "rb") as f:
+ file_data = self.decompress(f.read())
+ self.assertEqual(file_data, self.TEXT * 2)
def test_implicit_binary_modes(self):
# Test implicit binary modes (no "b" or "t" in mode string).
- with bz2.open(self.filename, "w") as f:
- f.write(self.TEXT)
- with open(self.filename, "rb") as f:
- file_data = bz2.decompress(f.read())
- self.assertEqual(file_data, self.TEXT)
- with bz2.open(self.filename, "r") as f:
- self.assertEqual(f.read(), self.TEXT)
- with bz2.open(self.filename, "a") as f:
- f.write(self.TEXT)
- with open(self.filename, "rb") as f:
- file_data = bz2.decompress(f.read())
- self.assertEqual(file_data, self.TEXT * 2)
+ for mode in ("w", "x"):
+ if mode == "x":
+ unlink(self.filename)
+ with self.open(self.filename, mode) as f:
+ f.write(self.TEXT)
+ with open(self.filename, "rb") as f:
+ file_data = self.decompress(f.read())
+ self.assertEqual(file_data, self.TEXT)
+ with self.open(self.filename, "r") as f:
+ self.assertEqual(f.read(), self.TEXT)
+ with self.open(self.filename, "a") as f:
+ f.write(self.TEXT)
+ with open(self.filename, "rb") as f:
+ file_data = self.decompress(f.read())
+ self.assertEqual(file_data, self.TEXT * 2)
def test_text_modes(self):
text = self.TEXT.decode("ascii")
text_native_eol = text.replace("\n", os.linesep)
- with bz2.open(self.filename, "wt") as f:
- f.write(text)
- with open(self.filename, "rb") as f:
- file_data = bz2.decompress(f.read()).decode("ascii")
- self.assertEqual(file_data, text_native_eol)
- with bz2.open(self.filename, "rt") as f:
- self.assertEqual(f.read(), text)
- with bz2.open(self.filename, "at") as f:
- f.write(text)
- with open(self.filename, "rb") as f:
- file_data = bz2.decompress(f.read()).decode("ascii")
- self.assertEqual(file_data, text_native_eol * 2)
+ for mode in ("wt", "xt"):
+ if mode == "xt":
+ unlink(self.filename)
+ with self.open(self.filename, mode) as f:
+ f.write(text)
+ with open(self.filename, "rb") as f:
+ file_data = self.decompress(f.read()).decode("ascii")
+ self.assertEqual(file_data, text_native_eol)
+ with self.open(self.filename, "rt") as f:
+ self.assertEqual(f.read(), text)
+ with self.open(self.filename, "at") as f:
+ f.write(text)
+ with open(self.filename, "rb") as f:
+ file_data = self.decompress(f.read()).decode("ascii")
+ self.assertEqual(file_data, text_native_eol * 2)
+
+ def test_x_mode(self):
+ for mode in ("x", "xb", "xt"):
+ unlink(self.filename)
+ with self.open(self.filename, mode) as f:
+ pass
+ with self.assertRaises(FileExistsError):
+ with self.open(self.filename, mode) as f:
+ pass
def test_fileobj(self):
- with bz2.open(BytesIO(self.DATA), "r") as f:
+ with self.open(BytesIO(self.DATA), "r") as f:
self.assertEqual(f.read(), self.TEXT)
- with bz2.open(BytesIO(self.DATA), "rb") as f:
+ with self.open(BytesIO(self.DATA), "rb") as f:
self.assertEqual(f.read(), self.TEXT)
text = self.TEXT.decode("ascii")
- with bz2.open(BytesIO(self.DATA), "rt") as f:
+ with self.open(BytesIO(self.DATA), "rt") as f:
self.assertEqual(f.read(), text)
def test_bad_params(self):
# Test invalid parameter combinations.
- with self.assertRaises(ValueError):
- bz2.open(self.filename, "wbt")
- with self.assertRaises(ValueError):
- bz2.open(self.filename, "rb", encoding="utf-8")
- with self.assertRaises(ValueError):
- bz2.open(self.filename, "rb", errors="ignore")
- with self.assertRaises(ValueError):
- bz2.open(self.filename, "rb", newline="\n")
+ self.assertRaises(ValueError,
+ self.open, self.filename, "wbt")
+ self.assertRaises(ValueError,
+ self.open, self.filename, "xbt")
+ self.assertRaises(ValueError,
+ self.open, self.filename, "rb", encoding="utf-8")
+ self.assertRaises(ValueError,
+ self.open, self.filename, "rb", errors="ignore")
+ self.assertRaises(ValueError,
+ self.open, self.filename, "rb", newline="\n")
def test_encoding(self):
# Test non-default encoding.
text = self.TEXT.decode("ascii")
text_native_eol = text.replace("\n", os.linesep)
- with bz2.open(self.filename, "wt", encoding="utf-16-le") as f:
+ with self.open(self.filename, "wt", encoding="utf-16-le") as f:
f.write(text)
with open(self.filename, "rb") as f:
- file_data = bz2.decompress(f.read()).decode("utf-16-le")
+ file_data = self.decompress(f.read()).decode("utf-16-le")
self.assertEqual(file_data, text_native_eol)
- with bz2.open(self.filename, "rt", encoding="utf-16-le") as f:
+ with self.open(self.filename, "rt", encoding="utf-16-le") as f:
self.assertEqual(f.read(), text)
def test_encoding_error_handler(self):
# Test with non-default encoding error handler.
- with bz2.open(self.filename, "wb") as f:
+ with self.open(self.filename, "wb") as f:
f.write(b"foo\xffbar")
- with bz2.open(self.filename, "rt", encoding="ascii", errors="ignore") \
+ with self.open(self.filename, "rt", encoding="ascii", errors="ignore") \
as f:
self.assertEqual(f.read(), "foobar")
def test_newline(self):
# Test with explicit newline (universal newline mode disabled).
text = self.TEXT.decode("ascii")
- with bz2.open(self.filename, "wt", newline="\n") as f:
+ with self.open(self.filename, "wt", newline="\n") as f:
f.write(text)
- with bz2.open(self.filename, "rt", newline="\r") as f:
+ with self.open(self.filename, "rt", newline="\r") as f:
self.assertEqual(f.readlines(), [text])
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 9013a7b188..22c8eb0709 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -9,6 +9,7 @@ import sys
import time
import unittest
from test import support
+from test.support import MISSING_C_DOCSTRINGS
try:
import _posixsubprocess
except ImportError:
@@ -43,7 +44,7 @@ class CAPITest(unittest.TestCase):
@unittest.skipUnless(threading, 'Threading required for this test.')
def test_no_FatalError_infinite_loop(self):
- with support.suppress_crash_popup():
+ with support.SuppressCrashReport():
p = subprocess.Popen([sys.executable, "-c",
'import _testcapi;'
'_testcapi.crash_no_current_thread()'],
@@ -109,6 +110,37 @@ class CAPITest(unittest.TestCase):
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
Z(),[b'1'],3,[1, 2],5,6,7,8,9,10,11,12,13,14,15,16,17)
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_docstring_signature_parsing(self):
+
+ self.assertEqual(_testcapi.no_docstring.__doc__, None)
+ self.assertEqual(_testcapi.no_docstring.__text_signature__, None)
+
+ self.assertEqual(_testcapi.docstring_empty.__doc__, "")
+ self.assertEqual(_testcapi.docstring_empty.__text_signature__, None)
+
+ self.assertEqual(_testcapi.docstring_no_signature.__doc__,
+ "This docstring has no signature.")
+ self.assertEqual(_testcapi.docstring_no_signature.__text_signature__, None)
+
+ self.assertEqual(_testcapi.docstring_with_invalid_signature.__doc__,
+ "docstring_with_invalid_signature (boo)\n"
+ "\n"
+ "This docstring has an invalid signature."
+ )
+ self.assertEqual(_testcapi.docstring_with_invalid_signature.__text_signature__, None)
+
+ self.assertEqual(_testcapi.docstring_with_signature.__doc__,
+ "This docstring has a valid signature.")
+ self.assertEqual(_testcapi.docstring_with_signature.__text_signature__, "(sig)")
+
+ self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__doc__,
+ "This docstring has a valid signature and some extra newlines.")
+ self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__text_signature__,
+ "(parameter)")
+
+
@unittest.skipUnless(threading, 'Threading required for this test.')
class TestPendingCalls(unittest.TestCase):
@@ -192,6 +224,9 @@ class TestPendingCalls(unittest.TestCase):
self.pendingcalls_submit(l, n)
self.pendingcalls_wait(l, n)
+
+class SubinterpreterTest(unittest.TestCase):
+
def test_subinterps(self):
import builtins
r, w = os.pipe()
@@ -202,47 +237,109 @@ class TestPendingCalls(unittest.TestCase):
pickle.dump(id(builtins), f)
""".format(w)
with open(r, "rb") as f:
- ret = _testcapi.run_in_subinterp(code)
+ ret = support.run_in_subinterp(code)
self.assertEqual(ret, 0)
self.assertNotEqual(pickle.load(f), id(sys.modules))
self.assertNotEqual(pickle.load(f), id(builtins))
+
# Bug #6012
class Test6012(unittest.TestCase):
def test(self):
self.assertEqual(_testcapi.argparsing("Hello", "World"), 1)
-class EmbeddingTest(unittest.TestCase):
-
- @unittest.skipIf(
- sys.platform.startswith('win'),
- "test doesn't work under Windows")
- def test_subinterps(self):
- # XXX only tested under Unix checkouts
+class EmbeddingTests(unittest.TestCase):
+ def setUp(self):
basepath = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
- oldcwd = os.getcwd()
+ exename = "_testembed"
+ if sys.platform.startswith("win"):
+ ext = ("_d" if "_d" in sys.executable else "") + ".exe"
+ exename += ext
+ exepath = os.path.dirname(sys.executable)
+ else:
+ exepath = os.path.join(basepath, "Modules")
+ self.test_exe = exe = os.path.join(exepath, exename)
+ if not os.path.exists(exe):
+ self.skipTest("%r doesn't exist" % exe)
# This is needed otherwise we get a fatal error:
# "Py_Initialize: Unable to get the locale encoding
# LookupError: no codec search functions registered: can't find encoding"
+ self.oldcwd = os.getcwd()
os.chdir(basepath)
+
+ def tearDown(self):
+ os.chdir(self.oldcwd)
+
+ def run_embedded_interpreter(self, *args):
+ """Runs a test in the embedded interpreter"""
+ cmd = [self.test_exe]
+ cmd.extend(args)
+ p = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ (out, err) = p.communicate()
+ self.assertEqual(p.returncode, 0,
+ "bad returncode %d, stderr is %r" %
+ (p.returncode, err))
+ return out.decode("latin1"), err.decode("latin1")
+
+ def test_subinterps(self):
+ # This is just a "don't crash" test
+ out, err = self.run_embedded_interpreter()
+ if support.verbose:
+ print()
+ print(out)
+ print(err)
+
+ @staticmethod
+ def _get_default_pipe_encoding():
+ rp, wp = os.pipe()
try:
- exe = os.path.join(basepath, "Modules", "_testembed")
- if not os.path.exists(exe):
- self.skipTest("%r doesn't exist" % exe)
- p = subprocess.Popen([exe],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- (out, err) = p.communicate()
- self.assertEqual(p.returncode, 0,
- "bad returncode %d, stderr is %r" %
- (p.returncode, err))
- if support.verbose:
- print()
- print(out.decode('latin1'))
- print(err.decode('latin1'))
+ with os.fdopen(wp, 'w') as w:
+ default_pipe_encoding = w.encoding
finally:
- os.chdir(oldcwd)
+ os.close(rp)
+ return default_pipe_encoding
+
+ def test_forced_io_encoding(self):
+ # Checks forced configuration of embedded interpreter IO streams
+ out, err = self.run_embedded_interpreter("forced_io_encoding")
+ if support.verbose:
+ print()
+ print(out)
+ print(err)
+ expected_stdin_encoding = sys.__stdin__.encoding
+ expected_pipe_encoding = self._get_default_pipe_encoding()
+ expected_output = os.linesep.join([
+ "--- Use defaults ---",
+ "Expected encoding: default",
+ "Expected errors: default",
+ "stdin: {0}:strict",
+ "stdout: {1}:strict",
+ "stderr: {1}:backslashreplace",
+ "--- Set errors only ---",
+ "Expected encoding: default",
+ "Expected errors: surrogateescape",
+ "stdin: {0}:surrogateescape",
+ "stdout: {1}:surrogateescape",
+ "stderr: {1}:backslashreplace",
+ "--- Set encoding only ---",
+ "Expected encoding: latin-1",
+ "Expected errors: default",
+ "stdin: latin-1:strict",
+ "stdout: latin-1:strict",
+ "stderr: latin-1:backslashreplace",
+ "--- Set encoding and errors ---",
+ "Expected encoding: latin-1",
+ "Expected errors: surrogateescape",
+ "stdin: latin-1:surrogateescape",
+ "stdout: latin-1:surrogateescape",
+ "stderr: latin-1:backslashreplace"]).format(expected_stdin_encoding,
+ expected_pipe_encoding)
+ # This is useful if we ever trip over odd platform behaviour
+ self.maxDiff = None
+ self.assertEqual(out.strip(), expected_output)
class SkipitemTest(unittest.TestCase):
@@ -354,8 +451,9 @@ class Test_testcapi(unittest.TestCase):
def test__testcapi(self):
for name in dir(_testcapi):
if name.startswith('test_'):
- test = getattr(_testcapi, name)
- test()
+ with self.subTest("internal", name=name):
+ test = getattr(_testcapi, name)
+ test()
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index a89d7e4f10..327c1455fc 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -4,6 +4,7 @@
import test.support, unittest
import os
+import shutil
import sys
import subprocess
import tempfile
@@ -41,8 +42,10 @@ class CmdLineTest(unittest.TestCase):
def test_version(self):
version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii")
- rc, out, err = assert_python_ok('-V')
- self.assertTrue(err.startswith(version))
+ for switch in '-V', '--version':
+ 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
@@ -54,14 +57,49 @@ class CmdLineTest(unittest.TestCase):
self.assertNotIn(b'stack overflow', err)
def test_xoptions(self):
- rc, out, err = assert_python_ok('-c', 'import sys; print(sys._xoptions)')
- opts = eval(out.splitlines()[0])
+ def get_xoptions(*args):
+ # use subprocess module directly because test.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, {})
- rc, out, err = assert_python_ok(
- '-Xa', '-Xb=c,d=e', '-c', 'import sys; print(sys._xoptions)')
- opts = eval(out.splitlines()[0])
+
+ 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 hasattr(sys, 'gettotalrefcount'): # debug build
+ 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
@@ -70,9 +108,9 @@ class CmdLineTest(unittest.TestCase):
assert_python_failure('-m', 'fnord43520xyz')
# Check the runpy module also gives an error for
# a nonexistent module
- assert_python_failure('-m', 'runpy', 'fnord43520xyz'),
+ assert_python_failure('-m', 'runpy', 'fnord43520xyz')
# All good if module is located and run successfully
- assert_python_ok('-m', 'timeit', '-n', '1'),
+ assert_python_ok('-m', 'timeit', '-n', '1')
def test_run_module_bug1764407(self):
# -m and -i need to play well together
@@ -213,6 +251,23 @@ class CmdLineTest(unittest.TestCase):
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)
+
def test_displayhook_unencodable(self):
for encoding in ('ascii', 'latin-1', 'utf-8'):
env = os.environ.copy()
@@ -290,7 +345,7 @@ class CmdLineTest(unittest.TestCase):
rc, out, err = assert_python_ok('-c', code)
self.assertEqual(b'', out)
self.assertRegex(err.decode('ascii', 'ignore'),
- 'Exception OSError: .* ignored')
+ 'Exception ignored in.*\nOSError: .*')
def test_closed_stdout(self):
# Issue #13444: if stdout has been explicitly closed, we should
@@ -385,6 +440,31 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(b'', out)
+ 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 extranous -E
+ dummyvar="")
+ self.assertEqual(out.strip(), b'1 1 1')
+ with test.support.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")
+
def test_main():
test.support.run_unittest(CmdLineTest)
test.support.reap_children()
diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
index b4269b994e..f804d8645f 100644
--- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py
@@ -123,7 +123,7 @@ class CmdLineTest(unittest.TestCase):
if not __debug__:
cmd_line_switches += ('-' + 'O' * sys.flags.optimize,)
run_args = cmd_line_switches + (script_name,) + tuple(example_args)
- rc, out, err = assert_python_ok(*run_args)
+ rc, out, err = assert_python_ok(*run_args, __isolated=False)
self._check_output(script_name, rc, out + err, expected_file,
expected_argv0, expected_path0,
expected_package, expected_loader)
@@ -294,7 +294,7 @@ class CmdLineTest(unittest.TestCase):
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)
+ rc, out, err = assert_python_ok('-m', 'test_pkg.script', *example_args, __isolated=False)
if verbose > 1:
print(out)
expected = "init_argv0==%r" % '-m'
@@ -311,7 +311,8 @@ class CmdLineTest(unittest.TestCase):
with open("-c", "w") as f:
f.write("data")
rc, out, err = assert_python_ok('-c',
- 'import sys; print("sys.path[0]==%r" % sys.path[0])')
+ 'import sys; print("sys.path[0]==%r" % sys.path[0])',
+ __isolated=False)
if verbose > 1:
print(out)
expected = "sys.path[0]==%r" % ''
@@ -325,7 +326,8 @@ class CmdLineTest(unittest.TestCase):
with support.change_cwd(path=script_dir):
with open("-m", "w") as f:
f.write("data")
- rc, out, err = assert_python_ok('-m', 'other', *example_args)
+ rc, out, err = assert_python_ok('-m', 'other', *example_args,
+ __isolated=False)
self._check_output(script_name, rc, out,
script_name, script_name, '', '',
importlib.machinery.SourceFileLoader)
diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py
index adef1701d4..5fd21dc32c 100644
--- a/Lib/test/test_code_module.py
+++ b/Lib/test/test_code_module.py
@@ -64,6 +64,20 @@ class TestInteractiveConsole(unittest.TestCase):
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), 2)
+ 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), 1)
+
def test_main():
support.run_unittest(TestInteractiveConsole)
diff --git a/Lib/test/test_codeccallbacks.py b/Lib/test/test_codeccallbacks.py
index fd88505081..84804bb0da 100644
--- a/Lib/test/test_codeccallbacks.py
+++ b/Lib/test/test_codeccallbacks.py
@@ -875,8 +875,6 @@ class CodecCallbackTest(unittest.TestCase):
with self.assertRaises(TypeError):
data.decode(encoding, "test.replacing")
-def test_main():
- test.support.run_unittest(CodecCallbackTest)
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py
index 35170579a7..f6823805fe 100644
--- a/Lib/test/test_codecs.py
+++ b/Lib/test/test_codecs.py
@@ -1,10 +1,12 @@
import _testcapi
import codecs
+import contextlib
import io
import locale
import sys
import unittest
import warnings
+import encodings
from test import support
@@ -299,8 +301,46 @@ class ReadTest(MixInCheckStateHandling):
self.assertEqual(reader.readline(), s5)
self.assertEqual(reader.readline(), "")
+ ill_formed_sequence_replace = "\ufffd"
+
+ def test_lone_surrogates(self):
+ self.assertRaises(UnicodeEncodeError, "\ud800".encode, self.encoding)
+ self.assertEqual("[\uDC80]".encode(self.encoding, "backslashreplace"),
+ "[\\udc80]".encode(self.encoding))
+ self.assertEqual("[\uDC80]".encode(self.encoding, "xmlcharrefreplace"),
+ "[&#56448;]".encode(self.encoding))
+ self.assertEqual("[\uDC80]".encode(self.encoding, "ignore"),
+ "[]".encode(self.encoding))
+ self.assertEqual("[\uDC80]".encode(self.encoding, "replace"),
+ "[?]".encode(self.encoding))
+
+ bom = "".encode(self.encoding)
+ for before, after in [("\U00010fff", "A"), ("[", "]"),
+ ("A", "\U00010fff")]:
+ before_sequence = before.encode(self.encoding)[len(bom):]
+ after_sequence = after.encode(self.encoding)[len(bom):]
+ test_string = before + "\uDC80" + after
+ test_sequence = (bom + before_sequence +
+ self.ill_formed_sequence + after_sequence)
+ self.assertRaises(UnicodeDecodeError, test_sequence.decode,
+ self.encoding)
+ self.assertEqual(test_string.encode(self.encoding,
+ "surrogatepass"),
+ test_sequence)
+ self.assertEqual(test_sequence.decode(self.encoding,
+ "surrogatepass"),
+ test_string)
+ self.assertEqual(test_sequence.decode(self.encoding, "ignore"),
+ before + after)
+ self.assertEqual(test_sequence.decode(self.encoding, "replace"),
+ before + self.ill_formed_sequence_replace + after)
+
class UTF32Test(ReadTest, unittest.TestCase):
encoding = "utf-32"
+ if sys.byteorder == 'little':
+ ill_formed_sequence = b"\x80\xdc\x00\x00"
+ else:
+ ill_formed_sequence = b"\x00\x00\xdc\x80"
spamle = (b'\xff\xfe\x00\x00'
b's\x00\x00\x00p\x00\x00\x00a\x00\x00\x00m\x00\x00\x00'
@@ -392,6 +432,7 @@ class UTF32Test(ReadTest, unittest.TestCase):
class UTF32LETest(ReadTest, unittest.TestCase):
encoding = "utf-32-le"
+ ill_formed_sequence = b"\x80\xdc\x00\x00"
def test_partial(self):
self.check_partial(
@@ -436,6 +477,7 @@ class UTF32LETest(ReadTest, unittest.TestCase):
class UTF32BETest(ReadTest, unittest.TestCase):
encoding = "utf-32-be"
+ ill_formed_sequence = b"\x00\x00\xdc\x80"
def test_partial(self):
self.check_partial(
@@ -481,6 +523,10 @@ class UTF32BETest(ReadTest, unittest.TestCase):
class UTF16Test(ReadTest, unittest.TestCase):
encoding = "utf-16"
+ if sys.byteorder == 'little':
+ ill_formed_sequence = b"\x80\xdc"
+ else:
+ ill_formed_sequence = b"\xdc\x80"
spamle = b'\xff\xfes\x00p\x00a\x00m\x00s\x00p\x00a\x00m\x00'
spambe = b'\xfe\xff\x00s\x00p\x00a\x00m\x00s\x00p\x00a\x00m'
@@ -556,11 +602,14 @@ class UTF16Test(ReadTest, unittest.TestCase):
self.addCleanup(support.unlink, support.TESTFN)
with open(support.TESTFN, 'wb') as fp:
fp.write(s)
- with codecs.open(support.TESTFN, 'U', encoding=self.encoding) as reader:
+ with support.check_warnings(('', DeprecationWarning)):
+ reader = codecs.open(support.TESTFN, 'U', encoding=self.encoding)
+ with reader:
self.assertEqual(reader.read(), s1)
class UTF16LETest(ReadTest, unittest.TestCase):
encoding = "utf-16-le"
+ ill_formed_sequence = b"\x80\xdc"
def test_partial(self):
self.check_partial(
@@ -604,6 +653,7 @@ class UTF16LETest(ReadTest, unittest.TestCase):
class UTF16BETest(ReadTest, unittest.TestCase):
encoding = "utf-16-be"
+ ill_formed_sequence = b"\xdc\x80"
def test_partial(self):
self.check_partial(
@@ -647,6 +697,8 @@ class UTF16BETest(ReadTest, unittest.TestCase):
class UTF8Test(ReadTest, unittest.TestCase):
encoding = "utf-8"
+ ill_formed_sequence = b"\xed\xb2\x80"
+ ill_formed_sequence_replace = "\ufffd" * 3
def test_partial(self):
self.check_partial(
@@ -676,18 +728,11 @@ class UTF8Test(ReadTest, unittest.TestCase):
u, u.encode(self.encoding))
def test_lone_surrogates(self):
- self.assertRaises(UnicodeEncodeError, "\ud800".encode, "utf-8")
- self.assertRaises(UnicodeDecodeError, b"\xed\xa0\x80".decode, "utf-8")
- self.assertEqual("[\uDC80]".encode("utf-8", "backslashreplace"),
- b'[\\udc80]')
- self.assertEqual("[\uDC80]".encode("utf-8", "xmlcharrefreplace"),
- b'[&#56448;]')
- self.assertEqual("[\uDC80]".encode("utf-8", "surrogateescape"),
+ super().test_lone_surrogates()
+ # not sure if this is making sense for
+ # UTF-16 and UTF-32
+ self.assertEqual("[\uDC80]".encode('utf-8', "surrogateescape"),
b'[\x80]')
- self.assertEqual("[\uDC80]".encode("utf-8", "ignore"),
- b'[]')
- self.assertEqual("[\uDC80]".encode("utf-8", "replace"),
- b'[?]')
def test_surrogatepass_handler(self):
self.assertEqual("abc\ud800def".encode("utf-8", "surrogatepass"),
@@ -840,15 +885,19 @@ class UTF7Test(ReadTest, unittest.TestCase):
(b'a+////,+IKw-b', 'a\uffff\ufffd\u20acb'),
]
for raw, expected in tests:
- self.assertRaises(UnicodeDecodeError, codecs.utf_7_decode,
- raw, 'strict', True)
- self.assertEqual(raw.decode('utf-7', 'replace'), expected)
+ with self.subTest(raw=raw):
+ self.assertRaises(UnicodeDecodeError, codecs.utf_7_decode,
+ raw, 'strict', True)
+ self.assertEqual(raw.decode('utf-7', 'replace'), expected)
def test_nonbmp(self):
self.assertEqual('\U000104A0'.encode(self.encoding), b'+2AHcoA-')
self.assertEqual('\ud801\udca0'.encode(self.encoding), b'+2AHcoA-')
self.assertEqual(b'+2AHcoA-'.decode(self.encoding), '\U000104A0')
+ test_lone_surrogates = None
+
+
class UTF16ExTest(unittest.TestCase):
def test_errors(self):
@@ -873,7 +922,7 @@ class ReadBufferTest(unittest.TestCase):
self.assertRaises(TypeError, codecs.readbuffer_encode)
self.assertRaises(TypeError, codecs.readbuffer_encode, 42)
-class UTF8SigTest(ReadTest, unittest.TestCase):
+class UTF8SigTest(UTF8Test, unittest.TestCase):
encoding = "utf-8-sig"
def test_partial(self):
@@ -1555,6 +1604,7 @@ all_unicode_encodings = [
"cp037",
"cp1006",
"cp1026",
+ "cp1125",
"cp1140",
"cp1250",
"cp1251",
@@ -2273,46 +2323,319 @@ bytes_transform_encodings = [
"quopri_codec",
"hex_codec",
]
+
+transform_aliases = {
+ "base64_codec": ["base64", "base_64"],
+ "uu_codec": ["uu"],
+ "quopri_codec": ["quopri", "quoted_printable", "quotedprintable"],
+ "hex_codec": ["hex"],
+ "rot_13": ["rot13"],
+}
+
try:
import zlib
except ImportError:
pass
else:
bytes_transform_encodings.append("zlib_codec")
+ transform_aliases["zlib_codec"] = ["zip", "zlib"]
try:
import bz2
except ImportError:
pass
else:
bytes_transform_encodings.append("bz2_codec")
+ transform_aliases["bz2_codec"] = ["bz2"]
class TransformCodecTest(unittest.TestCase):
def test_basics(self):
binput = bytes(range(256))
for encoding in bytes_transform_encodings:
- # generic codecs interface
- (o, size) = codecs.getencoder(encoding)(binput)
- self.assertEqual(size, len(binput))
- (i, size) = codecs.getdecoder(encoding)(o)
- self.assertEqual(size, len(o))
- self.assertEqual(i, binput)
+ with self.subTest(encoding=encoding):
+ # generic codecs interface
+ (o, size) = codecs.getencoder(encoding)(binput)
+ self.assertEqual(size, len(binput))
+ (i, size) = codecs.getdecoder(encoding)(o)
+ self.assertEqual(size, len(o))
+ self.assertEqual(i, binput)
def test_read(self):
for encoding in bytes_transform_encodings:
- sin = codecs.encode(b"\x80", encoding)
- reader = codecs.getreader(encoding)(io.BytesIO(sin))
- sout = reader.read()
- self.assertEqual(sout, b"\x80")
+ with self.subTest(encoding=encoding):
+ sin = codecs.encode(b"\x80", encoding)
+ reader = codecs.getreader(encoding)(io.BytesIO(sin))
+ sout = reader.read()
+ self.assertEqual(sout, b"\x80")
def test_readline(self):
for encoding in bytes_transform_encodings:
if encoding in ['uu_codec', 'zlib_codec']:
continue
- sin = codecs.encode(b"\x80", encoding)
- reader = codecs.getreader(encoding)(io.BytesIO(sin))
- sout = reader.readline()
- self.assertEqual(sout, b"\x80")
+ with self.subTest(encoding=encoding):
+ sin = codecs.encode(b"\x80", encoding)
+ reader = codecs.getreader(encoding)(io.BytesIO(sin))
+ sout = reader.readline()
+ self.assertEqual(sout, b"\x80")
+
+ def test_buffer_api_usage(self):
+ # We check all the transform codecs accept memoryview input
+ # for encoding and decoding
+ # and also that they roundtrip correctly
+ original = b"12345\x80"
+ for encoding in bytes_transform_encodings:
+ with self.subTest(encoding=encoding):
+ data = original
+ view = memoryview(data)
+ data = codecs.encode(data, encoding)
+ view_encoded = codecs.encode(view, encoding)
+ self.assertEqual(view_encoded, data)
+ view = memoryview(data)
+ data = codecs.decode(data, encoding)
+ self.assertEqual(data, original)
+ view_decoded = codecs.decode(view, encoding)
+ self.assertEqual(view_decoded, data)
+
+ def test_text_to_binary_blacklists_binary_transforms(self):
+ # Check binary -> binary codecs give a good error for str input
+ bad_input = "bad input type"
+ for encoding in bytes_transform_encodings:
+ with self.subTest(encoding=encoding):
+ fmt = ( "{!r} is not a text encoding; "
+ "use codecs.encode\(\) to handle arbitrary codecs")
+ msg = fmt.format(encoding)
+ with self.assertRaisesRegex(LookupError, msg) as failure:
+ bad_input.encode(encoding)
+ self.assertIsNone(failure.exception.__cause__)
+
+ def test_text_to_binary_blacklists_text_transforms(self):
+ # Check str.encode gives a good error message for str -> str codecs
+ msg = (r"^'rot_13' is not a text encoding; "
+ "use codecs.encode\(\) to handle arbitrary codecs")
+ with self.assertRaisesRegex(LookupError, msg):
+ "just an example message".encode("rot_13")
+
+ def test_binary_to_text_blacklists_binary_transforms(self):
+ # Check bytes.decode and bytearray.decode give a good error
+ # message for binary -> binary codecs
+ data = b"encode first to ensure we meet any format restrictions"
+ for encoding in bytes_transform_encodings:
+ with self.subTest(encoding=encoding):
+ encoded_data = codecs.encode(data, encoding)
+ fmt = (r"{!r} is not a text encoding; "
+ "use codecs.decode\(\) to handle arbitrary codecs")
+ msg = fmt.format(encoding)
+ with self.assertRaisesRegex(LookupError, msg):
+ encoded_data.decode(encoding)
+ with self.assertRaisesRegex(LookupError, msg):
+ bytearray(encoded_data).decode(encoding)
+
+ def test_binary_to_text_blacklists_text_transforms(self):
+ # Check str -> str codec gives a good error for binary input
+ for bad_input in (b"immutable", bytearray(b"mutable")):
+ with self.subTest(bad_input=bad_input):
+ msg = (r"^'rot_13' is not a text encoding; "
+ "use codecs.decode\(\) to handle arbitrary codecs")
+ with self.assertRaisesRegex(LookupError, msg) as failure:
+ bad_input.decode("rot_13")
+ self.assertIsNone(failure.exception.__cause__)
+
+ def test_custom_zlib_error_is_wrapped(self):
+ # Check zlib codec gives a good error for malformed input
+ msg = "^decoding with 'zlib_codec' codec failed"
+ with self.assertRaisesRegex(Exception, msg) as failure:
+ codecs.decode(b"hello", "zlib_codec")
+ self.assertIsInstance(failure.exception.__cause__,
+ type(failure.exception))
+
+ def test_custom_hex_error_is_wrapped(self):
+ # Check hex codec gives a good error for malformed input
+ msg = "^decoding with 'hex_codec' codec failed"
+ with self.assertRaisesRegex(Exception, msg) as failure:
+ codecs.decode(b"hello", "hex_codec")
+ self.assertIsInstance(failure.exception.__cause__,
+ type(failure.exception))
+
+ # Unfortunately, the bz2 module throws OSError, which the codec
+ # machinery currently can't wrap :(
+
+ # Ensure codec aliases from http://bugs.python.org/issue7475 work
+ def test_aliases(self):
+ for codec_name, aliases in transform_aliases.items():
+ expected_name = codecs.lookup(codec_name).name
+ for alias in aliases:
+ with self.subTest(alias=alias):
+ info = codecs.lookup(alias)
+ self.assertEqual(info.name, expected_name)
+
+
+# The codec system tries to wrap exceptions in order to ensure the error
+# mentions the operation being performed and the codec involved. We
+# currently *only* want this to happen for relatively stateless
+# exceptions, where the only significant information they contain is their
+# type and a single str argument.
+
+# Use a local codec registry to avoid appearing to leak objects when
+# registering multiple seach functions
+_TEST_CODECS = {}
+
+def _get_test_codec(codec_name):
+ return _TEST_CODECS.get(codec_name)
+codecs.register(_get_test_codec) # Returns None, not usable as a decorator
+
+class ExceptionChainingTest(unittest.TestCase):
+
+ def setUp(self):
+ # There's no way to unregister a codec search function, so we just
+ # ensure we render this one fairly harmless after the test
+ # case finishes by using the test case repr as the codec name
+ # The codecs module normalizes codec names, although this doesn't
+ # appear to be formally documented...
+ # We also make sure we use a truly unique id for the custom codec
+ # to avoid issues with the codec cache when running these tests
+ # multiple times (e.g. when hunting for refleaks)
+ unique_id = repr(self) + str(id(self))
+ self.codec_name = encodings.normalize_encoding(unique_id).lower()
+
+ # We store the object to raise on the instance because of a bad
+ # interaction between the codec caching (which means we can't
+ # recreate the codec entry) and regrtest refleak hunting (which
+ # runs the same test instance multiple times). This means we
+ # need to ensure the codecs call back in to the instance to find
+ # out which exception to raise rather than binding them in a
+ # closure to an object that may change on the next run
+ self.obj_to_raise = RuntimeError
+
+ def tearDown(self):
+ _TEST_CODECS.pop(self.codec_name, None)
+
+ def set_codec(self, encode, decode):
+ codec_info = codecs.CodecInfo(encode, decode,
+ name=self.codec_name)
+ _TEST_CODECS[self.codec_name] = codec_info
+
+ @contextlib.contextmanager
+ def assertWrapped(self, operation, exc_type, msg):
+ full_msg = r"{} with {!r} codec failed \({}: {}\)".format(
+ operation, self.codec_name, exc_type.__name__, msg)
+ with self.assertRaisesRegex(exc_type, full_msg) as caught:
+ yield caught
+ self.assertIsInstance(caught.exception.__cause__, exc_type)
+
+ def raise_obj(self, *args, **kwds):
+ # Helper to dynamically change the object raised by a test codec
+ raise self.obj_to_raise
+
+ def check_wrapped(self, obj_to_raise, msg, exc_type=RuntimeError):
+ self.obj_to_raise = obj_to_raise
+ self.set_codec(self.raise_obj, self.raise_obj)
+ with self.assertWrapped("encoding", exc_type, msg):
+ "str_input".encode(self.codec_name)
+ with self.assertWrapped("encoding", exc_type, msg):
+ codecs.encode("str_input", self.codec_name)
+ with self.assertWrapped("decoding", exc_type, msg):
+ b"bytes input".decode(self.codec_name)
+ with self.assertWrapped("decoding", exc_type, msg):
+ codecs.decode(b"bytes input", self.codec_name)
+
+ def test_raise_by_type(self):
+ self.check_wrapped(RuntimeError, "")
+
+ def test_raise_by_value(self):
+ msg = "This should be wrapped"
+ self.check_wrapped(RuntimeError(msg), msg)
+
+ def test_raise_grandchild_subclass_exact_size(self):
+ msg = "This should be wrapped"
+ class MyRuntimeError(RuntimeError):
+ __slots__ = ()
+ self.check_wrapped(MyRuntimeError(msg), msg, MyRuntimeError)
+
+ def test_raise_subclass_with_weakref_support(self):
+ msg = "This should be wrapped"
+ class MyRuntimeError(RuntimeError):
+ pass
+ self.check_wrapped(MyRuntimeError(msg), msg, MyRuntimeError)
+
+ def check_not_wrapped(self, obj_to_raise, msg):
+ def raise_obj(*args, **kwds):
+ raise obj_to_raise
+ self.set_codec(raise_obj, raise_obj)
+ with self.assertRaisesRegex(RuntimeError, msg):
+ "str input".encode(self.codec_name)
+ with self.assertRaisesRegex(RuntimeError, msg):
+ codecs.encode("str input", self.codec_name)
+ with self.assertRaisesRegex(RuntimeError, msg):
+ b"bytes input".decode(self.codec_name)
+ with self.assertRaisesRegex(RuntimeError, msg):
+ codecs.decode(b"bytes input", self.codec_name)
+
+ def test_init_override_is_not_wrapped(self):
+ class CustomInit(RuntimeError):
+ def __init__(self):
+ pass
+ self.check_not_wrapped(CustomInit, "")
+
+ def test_new_override_is_not_wrapped(self):
+ class CustomNew(RuntimeError):
+ def __new__(cls):
+ return super().__new__(cls)
+ self.check_not_wrapped(CustomNew, "")
+
+ def test_instance_attribute_is_not_wrapped(self):
+ msg = "This should NOT be wrapped"
+ exc = RuntimeError(msg)
+ exc.attr = 1
+ self.check_not_wrapped(exc, "^{}$".format(msg))
+
+ def test_non_str_arg_is_not_wrapped(self):
+ self.check_not_wrapped(RuntimeError(1), "1")
+
+ def test_multiple_args_is_not_wrapped(self):
+ msg_re = r"^\('a', 'b', 'c'\)$"
+ self.check_not_wrapped(RuntimeError('a', 'b', 'c'), msg_re)
+
+ # http://bugs.python.org/issue19609
+ def test_codec_lookup_failure_not_wrapped(self):
+ msg = "^unknown encoding: {}$".format(self.codec_name)
+ # The initial codec lookup should not be wrapped
+ with self.assertRaisesRegex(LookupError, msg):
+ "str input".encode(self.codec_name)
+ with self.assertRaisesRegex(LookupError, msg):
+ codecs.encode("str input", self.codec_name)
+ with self.assertRaisesRegex(LookupError, msg):
+ b"bytes input".decode(self.codec_name)
+ with self.assertRaisesRegex(LookupError, msg):
+ codecs.decode(b"bytes input", self.codec_name)
+
+ def test_unflagged_non_text_codec_handling(self):
+ # The stdlib non-text codecs are now marked so they're
+ # pre-emptively skipped by the text model related methods
+ # However, third party codecs won't be flagged, so we still make
+ # sure the case where an inappropriate output type is produced is
+ # handled appropriately
+ def encode_to_str(*args, **kwds):
+ return "not bytes!", 0
+ def decode_to_bytes(*args, **kwds):
+ return b"not str!", 0
+ self.set_codec(encode_to_str, decode_to_bytes)
+ # No input or output type checks on the codecs module functions
+ encoded = codecs.encode(None, self.codec_name)
+ self.assertEqual(encoded, "not bytes!")
+ decoded = codecs.decode(None, self.codec_name)
+ self.assertEqual(decoded, b"not str!")
+ # Text model methods should complain
+ fmt = (r"^{!r} encoder returned 'str' instead of 'bytes'; "
+ "use codecs.encode\(\) to encode to arbitrary types$")
+ msg = fmt.format(self.codec_name)
+ with self.assertRaisesRegex(TypeError, msg):
+ "str_input".encode(self.codec_name)
+ fmt = (r"^{!r} decoder returned 'bytes' instead of 'str'; "
+ "use codecs.decode\(\) to decode to arbitrary types$")
+ msg = fmt.format(self.codec_name)
+ with self.assertRaisesRegex(TypeError, msg):
+ b"bytes input".decode(self.codec_name)
+
@unittest.skipUnless(sys.platform == 'win32',
@@ -2324,8 +2647,8 @@ class CodePageTest(unittest.TestCase):
def test_invalid_code_page(self):
self.assertRaises(ValueError, codecs.code_page_encode, -1, 'a')
self.assertRaises(ValueError, codecs.code_page_decode, -1, b'a')
- self.assertRaises(WindowsError, codecs.code_page_encode, 123, 'a')
- self.assertRaises(WindowsError, codecs.code_page_decode, 123, b'a')
+ self.assertRaises(OSError, codecs.code_page_encode, 123, 'a')
+ self.assertRaises(OSError, codecs.code_page_decode, 123, b'a')
def test_code_page_name(self):
self.assertRaisesRegex(UnicodeEncodeError, 'cp932',
diff --git a/Lib/test/test_coding.py b/Lib/test/test_coding.py
deleted file mode 100644
index 989c7a85d2..0000000000
--- a/Lib/test/test_coding.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import unittest
-from test.support import TESTFN, unlink, unload
-import importlib, os, sys
-
-class CodingTest(unittest.TestCase):
- def test_bad_coding(self):
- module_name = 'bad_coding'
- self.verify_bad_module(module_name)
-
- def test_bad_coding2(self):
- module_name = 'bad_coding2'
- self.verify_bad_module(module_name)
-
- def verify_bad_module(self, module_name):
- self.assertRaises(SyntaxError, __import__, 'test.' + module_name)
-
- path = os.path.dirname(__file__)
- filename = os.path.join(path, module_name + '.py')
- with open(filename, "rb") as fp:
- bytes = fp.read()
- self.assertRaises(SyntaxError, compile, bytes, filename, 'exec')
-
- def test_exec_valid_coding(self):
- d = {}
- exec(b'# coding: cp949\na = "\xaa\xa7"\n', d)
- self.assertEqual(d['a'], '\u3047')
-
- def test_file_parse(self):
- # issue1134: all encodings outside latin-1 and utf-8 fail on
- # multiline strings and long lines (>512 columns)
- unload(TESTFN)
- filename = TESTFN + ".py"
- f = open(filename, "w", encoding="cp1252")
- sys.path.insert(0, os.curdir)
- try:
- with f:
- f.write("# -*- coding: cp1252 -*-\n")
- f.write("'''A short string\n")
- f.write("'''\n")
- f.write("'A very long string %s'\n" % ("X" * 1000))
-
- importlib.invalidate_caches()
- __import__(TESTFN)
- finally:
- del sys.path[0]
- unlink(filename)
- unlink(filename + "c")
- unlink(filename + "o")
- unload(TESTFN)
-
- def test_error_from_string(self):
- # See http://bugs.python.org/issue6289
- input = "# coding: ascii\n\N{SNOWMAN}".encode('utf-8')
- with self.assertRaises(SyntaxError) as c:
- compile(input, "<string>", "exec")
- expected = "'ascii' codec can't decode byte 0xe2 in position 16: " \
- "ordinal not in range(128)"
- self.assertTrue(c.exception.args[0].startswith(expected),
- msg=c.exception.args[0])
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index ff52755354..56e8120705 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -112,6 +112,38 @@ class TestChainMap(unittest.TestCase):
self.assertEqual(dict(d), dict(a=1, b=2, c=30))
self.assertEqual(dict(d.items()), dict(a=1, b=2, c=30))
+ def test_new_child(self):
+ 'Tests for changes for issue #16613.'
+ c = ChainMap()
+ c['a'] = 1
+ c['b'] = 2
+ m = {'b':20, 'c': 30}
+ d = c.new_child(m)
+ self.assertEqual(d.maps, [{'b':20, 'c':30}, {'a':1, 'b':2}]) # check internal state
+ self.assertIs(m, d.maps[0])
+
+ # Use a different map than a dict
+ class lowerdict(dict):
+ def __getitem__(self, key):
+ if isinstance(key, str):
+ key = key.lower()
+ return dict.__getitem__(self, key)
+ def __contains__(self, key):
+ if isinstance(key, str):
+ key = key.lower()
+ return dict.__contains__(self, key)
+
+ c = ChainMap()
+ c['a'] = 1
+ c['b'] = 2
+ m = lowerdict(b=20, c=30)
+ d = c.new_child(m)
+ self.assertIs(m, d.maps[0])
+ for key in 'abc': # check contains
+ self.assertIn(key, d)
+ for k, v in dict(a=1, B=20, C=30, z=100).items(): # check get
+ self.assertEqual(d.get(k, 100), v)
+
################################################################################
### Named Tuples
@@ -750,6 +782,8 @@ class TestCollectionABCs(ABCTestCase):
self.assertTrue(issubclass(sample, Sequence))
self.assertIsInstance(range(10), Sequence)
self.assertTrue(issubclass(range, Sequence))
+ self.assertIsInstance(memoryview(b""), Sequence)
+ self.assertTrue(issubclass(memoryview, Sequence))
self.assertTrue(issubclass(str, Sequence))
self.validate_abstract_methods(Sequence, '__contains__', '__iter__', '__len__',
'__getitem__')
@@ -904,23 +938,26 @@ class TestCounter(unittest.TestCase):
words = Counter('which witch had which witches wrist watch'.split())
update_test = Counter()
update_test.update(words)
- for i, dup in enumerate([
- words.copy(),
- copy.copy(words),
- copy.deepcopy(words),
- pickle.loads(pickle.dumps(words, 0)),
- pickle.loads(pickle.dumps(words, 1)),
- pickle.loads(pickle.dumps(words, 2)),
- pickle.loads(pickle.dumps(words, -1)),
- eval(repr(words)),
- update_test,
- Counter(words),
- ]):
- msg = (i, dup, words)
- self.assertTrue(dup is not words)
- self.assertEqual(dup, words)
- self.assertEqual(len(dup), len(words))
- self.assertEqual(type(dup), type(words))
+ for label, dup in [
+ ('words.copy()', words.copy()),
+ ('copy.copy(words)', copy.copy(words)),
+ ('copy.deepcopy(words)', copy.deepcopy(words)),
+ ('pickle.loads(pickle.dumps(words, 0))',
+ pickle.loads(pickle.dumps(words, 0))),
+ ('pickle.loads(pickle.dumps(words, 1))',
+ pickle.loads(pickle.dumps(words, 1))),
+ ('pickle.loads(pickle.dumps(words, 2))',
+ pickle.loads(pickle.dumps(words, 2))),
+ ('pickle.loads(pickle.dumps(words, -1))',
+ pickle.loads(pickle.dumps(words, -1))),
+ ('eval(repr(words))', eval(repr(words))),
+ ('update_test', update_test),
+ ('Counter(words)', Counter(words)),
+ ]:
+ with self.subTest(label=label):
+ msg = "\ncopy: %s\nwords: %s" % (dup, words)
+ self.assertIsNot(dup, words, msg)
+ self.assertEqual(dup, words)
def test_copy_subclass(self):
class MyCounter(Counter):
@@ -1043,8 +1080,10 @@ class TestCounter(unittest.TestCase):
# test fidelity to the pure python version
c = CounterSubclassWithSetItem('abracadabra')
self.assertTrue(c.called)
+ self.assertEqual(dict(c), {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r':2 })
c = CounterSubclassWithGet('abracadabra')
self.assertTrue(c.called)
+ self.assertEqual(dict(c), {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r':2 })
################################################################################
@@ -1205,24 +1244,28 @@ class TestOrderedDict(unittest.TestCase):
od = OrderedDict(pairs)
update_test = OrderedDict()
update_test.update(od)
- for i, dup in enumerate([
- od.copy(),
- copy.copy(od),
- copy.deepcopy(od),
- pickle.loads(pickle.dumps(od, 0)),
- pickle.loads(pickle.dumps(od, 1)),
- pickle.loads(pickle.dumps(od, 2)),
- pickle.loads(pickle.dumps(od, 3)),
- pickle.loads(pickle.dumps(od, -1)),
- eval(repr(od)),
- update_test,
- OrderedDict(od),
- ]):
- self.assertTrue(dup is not od)
- self.assertEqual(dup, od)
- self.assertEqual(list(dup.items()), list(od.items()))
- self.assertEqual(len(dup), len(od))
- self.assertEqual(type(dup), type(od))
+ for label, dup in [
+ ('od.copy()', od.copy()),
+ ('copy.copy(od)', copy.copy(od)),
+ ('copy.deepcopy(od)', copy.deepcopy(od)),
+ ('pickle.loads(pickle.dumps(od, 0))',
+ pickle.loads(pickle.dumps(od, 0))),
+ ('pickle.loads(pickle.dumps(od, 1))',
+ pickle.loads(pickle.dumps(od, 1))),
+ ('pickle.loads(pickle.dumps(od, 2))',
+ pickle.loads(pickle.dumps(od, 2))),
+ ('pickle.loads(pickle.dumps(od, 3))',
+ pickle.loads(pickle.dumps(od, 3))),
+ ('pickle.loads(pickle.dumps(od, -1))',
+ pickle.loads(pickle.dumps(od, -1))),
+ ('eval(repr(od))', eval(repr(od))),
+ ('update_test', update_test),
+ ('OrderedDict(od)', OrderedDict(od)),
+ ]:
+ with self.subTest(label=label):
+ msg = "\ncopy: %s\nod: %s" % (dup, od)
+ self.assertIsNot(dup, od, msg)
+ self.assertEqual(dup, od)
def test_yaml_linkage(self):
# Verify that __reduce__ is setup in a way that supports PyYAML's dump() feature.
@@ -1237,9 +1280,18 @@ class TestOrderedDict(unittest.TestCase):
# do not save instance dictionary if not needed
pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
od = OrderedDict(pairs)
- self.assertEqual(len(od.__reduce__()), 2)
+ self.assertIsNone(od.__reduce__()[2])
od.x = 10
- self.assertEqual(len(od.__reduce__()), 3)
+ self.assertIsNotNone(od.__reduce__()[2])
+
+ def test_pickle_recursive(self):
+ od = OrderedDict()
+ od[1] = od
+ for proto in range(-1, pickle.HIGHEST_PROTOCOL + 1):
+ dup = pickle.loads(pickle.dumps(od, proto))
+ self.assertIsNot(dup, od)
+ self.assertEqual(list(dup.keys()), [1])
+ self.assertIs(dup[1], dup)
def test_repr(self):
od = OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)])
diff --git a/Lib/test/test_colorsys.py b/Lib/test/test_colorsys.py
index e405b8a4d0..a24e3adcb4 100644
--- a/Lib/test/test_colorsys.py
+++ b/Lib/test/test_colorsys.py
@@ -1,4 +1,4 @@
-import unittest, test.support
+import unittest
import colorsys
def frange(start, stop, step):
@@ -69,8 +69,32 @@ class ColorsysTest(unittest.TestCase):
self.assertTripleEqual(hls, colorsys.rgb_to_hls(*rgb))
self.assertTripleEqual(rgb, colorsys.hls_to_rgb(*hls))
-def test_main():
- test.support.run_unittest(ColorsysTest)
+ def test_yiq_roundtrip(self):
+ for r in frange(0.0, 1.0, 0.2):
+ for g in frange(0.0, 1.0, 0.2):
+ for b in frange(0.0, 1.0, 0.2):
+ rgb = (r, g, b)
+ self.assertTripleEqual(
+ rgb,
+ colorsys.yiq_to_rgb(*colorsys.rgb_to_yiq(*rgb))
+ )
+
+ def test_yiq_values(self):
+ values = [
+ # rgb, yiq
+ ((0.0, 0.0, 0.0), (0.0, 0.0, 0.0)), # black
+ ((0.0, 0.0, 1.0), (0.11, -0.3217, 0.3121)), # blue
+ ((0.0, 1.0, 0.0), (0.59, -0.2773, -0.5251)), # green
+ ((0.0, 1.0, 1.0), (0.7, -0.599, -0.213)), # cyan
+ ((1.0, 0.0, 0.0), (0.3, 0.599, 0.213)), # red
+ ((1.0, 0.0, 1.0), (0.41, 0.2773, 0.5251)), # purple
+ ((1.0, 1.0, 0.0), (0.89, 0.3217, -0.3121)), # yellow
+ ((1.0, 1.0, 1.0), (1.0, 0.0, 0.0)), # white
+ ((0.5, 0.5, 0.5), (0.5, 0.0, 0.0)), # grey
+ ]
+ for (rgb, yiq) in values:
+ self.assertTripleEqual(yiq, colorsys.rgb_to_yiq(*rgb))
+ self.assertTripleEqual(rgb, colorsys.yiq_to_rgb(*yiq))
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
index fddb538efb..ff2515df4c 100644
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -1,11 +1,12 @@
import sys
import compileall
-import imp
+import importlib.util
import os
import py_compile
import shutil
import struct
import subprocess
+import sys
import tempfile
import time
import unittest
@@ -18,11 +19,11 @@ class CompileallTests(unittest.TestCase):
def setUp(self):
self.directory = tempfile.mkdtemp()
self.source_path = os.path.join(self.directory, '_test.py')
- self.bc_path = imp.cache_from_source(self.source_path)
+ self.bc_path = importlib.util.cache_from_source(self.source_path)
with open(self.source_path, 'w') as file:
file.write('x = 123\n')
self.source_path2 = os.path.join(self.directory, '_test2.py')
- self.bc_path2 = imp.cache_from_source(self.source_path2)
+ self.bc_path2 = importlib.util.cache_from_source(self.source_path2)
shutil.copyfile(self.source_path, self.source_path2)
self.subdirectory = os.path.join(self.directory, '_subdir')
os.mkdir(self.subdirectory)
@@ -36,7 +37,7 @@ class CompileallTests(unittest.TestCase):
with open(self.bc_path, 'rb') as file:
data = file.read(8)
mtime = int(os.stat(self.source_path).st_mtime)
- compare = struct.pack('<4sl', imp.get_magic(), mtime)
+ compare = struct.pack('<4sl', importlib.util.MAGIC_NUMBER, mtime)
return data, compare
@unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()')
@@ -56,7 +57,8 @@ class CompileallTests(unittest.TestCase):
def test_mtime(self):
# Test a change in mtime leads to a new .pyc.
- self.recreation_check(struct.pack('<4sl', imp.get_magic(), 1))
+ self.recreation_check(struct.pack('<4sl', importlib.util.MAGIC_NUMBER,
+ 1))
def test_magic_number(self):
# Test a change in mtime leads to a new .pyc.
@@ -96,14 +98,14 @@ class CompileallTests(unittest.TestCase):
# interpreter's creates the correct file names
optimize = 1 if __debug__ else 0
compileall.compile_dir(self.directory, quiet=True, optimize=optimize)
- cached = imp.cache_from_source(self.source_path,
- debug_override=not optimize)
+ cached = importlib.util.cache_from_source(self.source_path,
+ debug_override=not optimize)
self.assertTrue(os.path.isfile(cached))
- cached2 = imp.cache_from_source(self.source_path2,
- debug_override=not optimize)
+ cached2 = importlib.util.cache_from_source(self.source_path2,
+ debug_override=not optimize)
self.assertTrue(os.path.isfile(cached2))
- cached3 = imp.cache_from_source(self.source_path3,
- debug_override=not optimize)
+ cached3 = importlib.util.cache_from_source(self.source_path3,
+ debug_override=not optimize)
self.assertTrue(os.path.isfile(cached3))
@@ -151,10 +153,12 @@ class CommandLineTests(unittest.TestCase):
return rc, out, err
def assertCompiled(self, fn):
- self.assertTrue(os.path.exists(imp.cache_from_source(fn)))
+ path = importlib.util.cache_from_source(fn)
+ self.assertTrue(os.path.exists(path))
def assertNotCompiled(self, fn):
- self.assertFalse(os.path.exists(imp.cache_from_source(fn)))
+ path = importlib.util.cache_from_source(fn)
+ self.assertFalse(os.path.exists(path))
def setUp(self):
self.addCleanup(self._cleanup)
@@ -189,8 +193,8 @@ class CommandLineTests(unittest.TestCase):
['-m', 'compileall', '-q', self.pkgdir]))
# Verify the __pycache__ directory contents.
self.assertTrue(os.path.exists(self.pkgdir_cachedir))
- expected = sorted(base.format(imp.get_tag(), ext) for base in
- ('__init__.{}.{}', 'bar.{}.{}'))
+ expected = sorted(base.format(sys.implementation.cache_tag, ext)
+ for base in ('__init__.{}.{}', 'bar.{}.{}'))
self.assertEqual(sorted(os.listdir(self.pkgdir_cachedir)), expected)
# Make sure there are no .pyc files in the source directory.
self.assertFalse([fn for fn in os.listdir(self.pkgdir)
@@ -223,7 +227,7 @@ class CommandLineTests(unittest.TestCase):
def test_force(self):
self.assertRunOK('-q', self.pkgdir)
- pycpath = imp.cache_from_source(self.barfn)
+ pycpath = importlib.util.cache_from_source(self.barfn)
# set atime/mtime backward to avoid file timestamp resolution issues
os.utime(pycpath, (time.time()-60,)*2)
mtime = os.stat(pycpath).st_mtime
@@ -287,10 +291,10 @@ class CommandLineTests(unittest.TestCase):
bazfn = script_helper.make_script(self.pkgdir, 'baz', 'raise Exception')
self.assertRunOK('-q', '-d', 'dinsdale', self.pkgdir)
fn = script_helper.make_script(self.pkgdir, 'bing', 'import baz')
- pyc = imp.cache_from_source(bazfn)
+ pyc = importlib.util.cache_from_source(bazfn)
os.rename(pyc, os.path.join(self.pkgdir, 'baz.pyc'))
os.remove(bazfn)
- rc, out, err = script_helper.assert_python_failure(fn)
+ rc, out, err = script_helper.assert_python_failure(fn, __isolated=False)
self.assertRegex(err, b'File "dinsdale')
def test_include_bad_file(self):
@@ -298,7 +302,7 @@ class CommandLineTests(unittest.TestCase):
'-i', os.path.join(self.directory, 'nosuchfile'), self.pkgdir)
self.assertRegex(out, b'rror.*nosuchfile')
self.assertNotRegex(err, b'Traceback')
- self.assertFalse(os.path.exists(imp.cache_from_source(
+ self.assertFalse(os.path.exists(importlib.util.cache_from_source(
self.pkgdir_cachedir)))
def test_include_file_with_arg(self):
@@ -355,13 +359,5 @@ class CommandLineTests(unittest.TestCase):
self.assertRegex(out, b"Can't list 'badfilename'")
-def test_main():
- support.run_unittest(
- CommandLineTests,
- CompileallTests,
- EncodingTest,
- )
-
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py
index 1bf409798b..cd55375bdb 100644
--- a/Lib/test/test_complex.py
+++ b/Lib/test/test_complex.py
@@ -220,6 +220,8 @@ class ComplexTest(unittest.TestCase):
self.assertRaises(TypeError, complex, OS(None))
self.assertRaises(TypeError, complex, NS(None))
self.assertRaises(TypeError, complex, {})
+ self.assertRaises(TypeError, complex, NS(1.5))
+ self.assertRaises(TypeError, complex, NS(1))
self.assertAlmostEqual(complex("1+10j"), 1+10j)
self.assertAlmostEqual(complex(10), 10+0j)
@@ -301,6 +303,7 @@ class ComplexTest(unittest.TestCase):
self.assertRaises(TypeError, float, 5+3j)
self.assertRaises(ValueError, complex, "")
self.assertRaises(TypeError, complex, None)
+ self.assertRaisesRegex(TypeError, "not 'NoneType'", complex, None)
self.assertRaises(ValueError, complex, "\0")
self.assertRaises(ValueError, complex, "3\09")
self.assertRaises(TypeError, complex, "1", "2")
diff --git a/Lib/test/test_concurrent_futures.py b/Lib/test/test_concurrent_futures.py
index 39230e1cf0..a7e945cb5e 100644
--- a/Lib/test/test_concurrent_futures.py
+++ b/Lib/test/test_concurrent_futures.py
@@ -15,6 +15,7 @@ import sys
import threading
import time
import unittest
+import weakref
from concurrent import futures
from concurrent.futures._base import (
@@ -52,6 +53,11 @@ def sleep_and_print(t, msg):
sys.stdout.flush()
+class MyObject(object):
+ def my_method(self):
+ pass
+
+
class ExecutorMixin:
worker_count = 5
@@ -396,6 +402,22 @@ class ExecutorTest:
self.executor.map(str, [2] * (self.worker_count + 1))
self.executor.shutdown()
+ @test.support.cpython_only
+ def test_no_stale_references(self):
+ # Issue #16284: check that the executors don't unnecessarily hang onto
+ # references.
+ my_object = MyObject()
+ my_object_collected = threading.Event()
+ my_object_callback = weakref.ref(
+ my_object, lambda obj: my_object_collected.set())
+ # Deliberately discarding the future.
+ self.executor.submit(my_object.my_method)
+ del my_object
+
+ collected = my_object_collected.wait(timeout=5.0)
+ self.assertTrue(collected,
+ "Stale reference not collected within timeout.")
+
class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, unittest.TestCase):
def test_map_submits_without_iteration(self):
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
index 9e45f70f85..b8770c828f 100644
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -1,5 +1,6 @@
"""Unit tests for contextlib.py, and other context managers."""
+import io
import sys
import tempfile
import unittest
@@ -100,15 +101,25 @@ class ContextManagerTestCase(unittest.TestCase):
self.assertEqual(baz.__name__,'baz')
self.assertEqual(baz.foo, 'bar')
- @unittest.skipIf(sys.flags.optimize >= 2,
- "Docstrings are omitted with -O2 and above")
+ @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!")
+
+
class ClosingTestCase(unittest.TestCase):
- # XXX This needs more work
+ @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 = []
@@ -204,6 +215,7 @@ class LockContextTestCase(unittest.TestCase):
class mycontext(ContextDecorator):
+ """Example decoration-compatible context manager for testing"""
started = False
exc = None
catch = False
@@ -219,6 +231,13 @@ class mycontext(ContextDecorator):
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:
@@ -372,6 +391,13 @@ class TestContextDecorator(unittest.TestCase):
class TestExitStack(unittest.TestCase):
+ @support.requires_docstrings
+ def test_instance_docs(self):
+ # Issue 19330: ensure context manager instances have good docstrings
+ cm_docstring = ExitStack.__doc__
+ obj = ExitStack()
+ self.assertEqual(obj.__doc__, cm_docstring)
+
def test_no_resources(self):
with ExitStack():
pass
@@ -631,10 +657,111 @@ class TestExitStack(unittest.TestCase):
stack.push(cm)
self.assertIs(stack._exit_callbacks[-1], cm)
+class TestRedirectStdout(unittest.TestCase):
+
+ @support.requires_docstrings
+ def test_instance_docs(self):
+ # Issue 19330: ensure context manager instances have good docstrings
+ cm_docstring = redirect_stdout.__doc__
+ obj = redirect_stdout(None)
+ self.assertEqual(obj.__doc__, cm_docstring)
+
+ def test_no_redirect_in_init(self):
+ orig_stdout = sys.stdout
+ redirect_stdout(None)
+ self.assertIs(sys.stdout, 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 = sys.stdout
+ with redirect_stdout(f):
+ print(msg)
+ self.assertIs(sys.stdout, orig_stdout)
+ s = f.getvalue().strip()
+ self.assertEqual(s, msg)
+
+ def test_enter_result_is_target(self):
+ f = io.StringIO()
+ with redirect_stdout(f) as enter_result:
+ self.assertIs(enter_result, f)
+
+ def test_cm_is_reusable(self):
+ f = io.StringIO()
+ write_to_f = redirect_stdout(f)
+ orig_stdout = sys.stdout
+ with write_to_f:
+ print("Hello", end=" ")
+ with write_to_f:
+ print("World!")
+ self.assertIs(sys.stdout, orig_stdout)
+ s = f.getvalue()
+ self.assertEqual(s, "Hello World!\n")
+
+ def test_cm_is_reentrant(self):
+ f = io.StringIO()
+ write_to_f = redirect_stdout(f)
+ orig_stdout = sys.stdout
+ with write_to_f:
+ print("Hello", end=" ")
+ with write_to_f:
+ print("World!")
+ self.assertIs(sys.stdout, orig_stdout)
+ s = f.getvalue()
+ self.assertEqual(s, "Hello World!\n")
+
+
+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
-# This is needed to make the test actually run under regrtest.py!
-def test_main():
- support.run_unittest(__name__)
+ 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:
+ 1/0
+ with ignore_exceptions: # Check nested usage
+ len(5)
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py
index 56766682b3..c3eb7faf8f 100644
--- a/Lib/test/test_cprofile.py
+++ b/Lib/test/test_cprofile.py
@@ -6,9 +6,11 @@ from test.support import run_unittest, TESTFN, unlink
# rip off all interesting stuff from test_profile
import cProfile
from test.test_profile import ProfileTest, regenerate_expected_output
+from test.profilee import testfunc
class CProfileTest(ProfileTest):
profilerclass = cProfile.Profile
+ profilermodule = cProfile
expected_max_output = "{built-in method max}"
def get_expected_output(self):
diff --git a/Lib/test/test_crypt.py b/Lib/test/test_crypt.py
index cfb7341e20..624d702f99 100644
--- a/Lib/test/test_crypt.py
+++ b/Lib/test/test_crypt.py
@@ -1,11 +1,7 @@
from test import support
import unittest
-def setUpModule():
- # this import will raise unittest.SkipTest if _crypt doesn't exist,
- # so it has to be done in setUpModule for test discovery to work
- global crypt
- crypt = support.import_module('crypt')
+crypt = support.import_module('crypt')
class CryptTestCase(unittest.TestCase):
diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index 479ebd9bf2..01fba21f0e 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -136,12 +136,12 @@ class Test_Csv(unittest.TestCase):
return 10;
def __getitem__(self, i):
if i > 2:
- raise IOError
- self.assertRaises(IOError, self._write_test, BadList(), '')
+ raise OSError
+ self.assertRaises(OSError, self._write_test, BadList(), '')
class BadItem:
def __str__(self):
- raise IOError
- self.assertRaises(IOError, self._write_test, [BadItem()], '')
+ raise OSError
+ self.assertRaises(OSError, self._write_test, [BadItem()], '')
def test_write_bigfield(self):
# This exercises the buffer realloc functionality
@@ -186,9 +186,9 @@ class Test_Csv(unittest.TestCase):
def test_writerows(self):
class BrokenFile:
def write(self, buf):
- raise IOError
+ raise OSError
writer = csv.writer(BrokenFile())
- self.assertRaises(IOError, writer.writerows, [['a']])
+ self.assertRaises(OSError, writer.writerows, [['a']])
with TemporaryFile("w+", newline='') as fileobj:
writer = csv.writer(fileobj)
@@ -308,6 +308,15 @@ class Test_Csv(unittest.TestCase):
for i, row in enumerate(csv.reader(fileobj)):
self.assertEqual(row, rows[i])
+ def test_roundtrip_escaped_unquoted_newlines(self):
+ with TemporaryFile("w+", newline='') as fileobj:
+ writer = csv.writer(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\")
+ rows = [['a\nb','b'],['c','x\r\nd']]
+ writer.writerows(rows)
+ fileobj.seek(0)
+ for i, row in enumerate(csv.reader(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\")):
+ self.assertEqual(row,rows[i])
+
class TestDialectRegistry(unittest.TestCase):
def test_registry_badargs(self):
self.assertRaises(TypeError, csv.list_dialects, None)
diff --git a/Lib/test/test_dbm.py b/Lib/test/test_dbm.py
index 243a0af103..1c57770242 100644
--- a/Lib/test/test_dbm.py
+++ b/Lib/test/test_dbm.py
@@ -62,7 +62,7 @@ class AnyDBMTestCase:
return keys
def test_error(self):
- self.assertTrue(issubclass(self.module.error, IOError))
+ self.assertTrue(issubclass(self.module.error, OSError))
def test_anydbm_not_existing(self):
self.assertRaises(dbm.error, dbm.open, _fname)
diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py
index 208bc4c382..9fd8cde59a 100644
--- a/Lib/test/test_dbm_dumb.py
+++ b/Lib/test/test_dbm_dumb.py
@@ -184,6 +184,19 @@ class DumbDBMTestCase(unittest.TestCase):
self.assertEqual(expected, got)
f.close()
+ def test_context_manager(self):
+ with dumbdbm.open(_fname, 'c') as db:
+ db["dumbdbm context manager"] = "context manager"
+
+ with dumbdbm.open(_fname, 'r') as db:
+ self.assertEqual(list(db.keys()), [b"dumbdbm context manager"])
+
+ # This currently just raises AttributeError rather than a specific
+ # exception like the GNU or NDBM based implementations. See
+ # http://bugs.python.org/issue19385 for details.
+ with self.assertRaises(Exception):
+ db.keys()
+
def tearDown(self):
_delete_files()
diff --git a/Lib/test/test_dbm_gnu.py b/Lib/test/test_dbm_gnu.py
index 4fb66c54b8..a7808f51c7 100644
--- a/Lib/test/test_dbm_gnu.py
+++ b/Lib/test/test_dbm_gnu.py
@@ -81,6 +81,17 @@ class TestGdbm(unittest.TestCase):
size2 = os.path.getsize(filename)
self.assertTrue(size1 > size2 >= size0)
+ def test_context_manager(self):
+ with gdbm.open(filename, 'c') as db:
+ db["gdbm context manager"] = "context manager"
+
+ with gdbm.open(filename, 'r') as db:
+ self.assertEqual(list(db.keys()), [b"gdbm context manager"])
+
+ with self.assertRaises(gdbm.error) as cm:
+ db.keys()
+ self.assertEqual(str(cm.exception),
+ "GDBM object has already been closed")
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_dbm_ndbm.py b/Lib/test/test_dbm_ndbm.py
index b57e1f0653..2291561def 100644
--- a/Lib/test/test_dbm_ndbm.py
+++ b/Lib/test/test_dbm_ndbm.py
@@ -37,5 +37,18 @@ class DbmTestCase(unittest.TestCase):
except error:
self.fail()
+ def test_context_manager(self):
+ with dbm.ndbm.open(self.filename, 'c') as db:
+ db["ndbm context manager"] = "context manager"
+
+ with dbm.ndbm.open(self.filename, 'r') as db:
+ self.assertEqual(list(db.keys()), [b"ndbm context manager"])
+
+ with self.assertRaises(dbm.ndbm.error) as cm:
+ db.keys()
+ self.assertEqual(str(cm.exception),
+ "DBM object has already been closed")
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 54a3615e9c..6378ef300e 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -290,7 +290,6 @@ class IBMTestCases(unittest.TestCase):
global skip_expected
if skip_expected:
raise unittest.SkipTest
- return
with open(file) as f:
for line in f:
line = line.replace('\r\n', '').replace('\n', '')
diff --git a/Lib/test/test_deque.py b/Lib/test/test_deque.py
index a8487d2d95..7bff1d2798 100644
--- a/Lib/test/test_deque.py
+++ b/Lib/test/test_deque.py
@@ -543,8 +543,8 @@ class TestBasic(unittest.TestCase):
check = self.check_sizeof
check(deque(), basesize + blocksize)
check(deque('a'), basesize + blocksize)
- check(deque('a' * (BLOCKLEN // 2)), basesize + blocksize)
- check(deque('a' * (BLOCKLEN // 2 + 1)), basesize + 2 * blocksize)
+ check(deque('a' * (BLOCKLEN - 1)), basesize + blocksize)
+ check(deque('a' * BLOCKLEN), basesize + 2 * blocksize)
check(deque('a' * (42 * BLOCKLEN)), basesize + 43 * blocksize)
class TestVariousIteratorArgs(unittest.TestCase):
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 8940ae9d54..f08a3d2bf6 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -1,8 +1,11 @@
import builtins
+import copyreg
import gc
+import itertools
+import math
+import pickle
import sys
import types
-import math
import unittest
import weakref
@@ -1797,6 +1800,7 @@ order (MRO) for bases """
("__trunc__", int, zero, set(), {}),
("__ceil__", math.ceil, zero, set(), {}),
("__dir__", dir, empty_seq, set(), {}),
+ ("__round__", round, zero, set(), {}),
]
class Checker(object):
@@ -2257,7 +2261,9 @@ order (MRO) for bases """
minstance = M("m")
minstance.b = 2
minstance.a = 1
- names = [x for x in dir(minstance) if x not in ["__name__", "__doc__"]]
+ default_attributes = ['__name__', '__doc__', '__package__',
+ '__loader__', '__spec__']
+ names = [x for x in dir(minstance) if x not in default_attributes]
self.assertEqual(names, ['a', 'b'])
class M2(M):
@@ -3150,176 +3156,6 @@ order (MRO) for bases """
self.assertEqual(e.a, 1)
self.assertEqual(can_delete_dict(e), can_delete_dict(ValueError()))
- def test_pickles(self):
- # Testing pickling and copying new-style classes and objects...
- import pickle
-
- def sorteditems(d):
- L = list(d.items())
- L.sort()
- return L
-
- global C
- class C(object):
- def __init__(self, a, b):
- super(C, self).__init__()
- self.a = a
- self.b = b
- def __repr__(self):
- return "C(%r, %r)" % (self.a, self.b)
-
- global C1
- class C1(list):
- def __new__(cls, a, b):
- return super(C1, cls).__new__(cls)
- def __getnewargs__(self):
- return (self.a, self.b)
- def __init__(self, a, b):
- self.a = a
- self.b = b
- def __repr__(self):
- return "C1(%r, %r)<%r>" % (self.a, self.b, list(self))
-
- global C2
- class C2(int):
- def __new__(cls, a, b, val=0):
- return super(C2, cls).__new__(cls, val)
- def __getnewargs__(self):
- return (self.a, self.b, int(self))
- def __init__(self, a, b, val=0):
- self.a = a
- self.b = b
- def __repr__(self):
- return "C2(%r, %r)<%r>" % (self.a, self.b, int(self))
-
- global C3
- class C3(object):
- def __init__(self, foo):
- self.foo = foo
- def __getstate__(self):
- return self.foo
- def __setstate__(self, foo):
- self.foo = foo
-
- global C4classic, C4
- class C4classic: # classic
- pass
- class C4(C4classic, object): # mixed inheritance
- pass
-
- for bin in 0, 1:
- for cls in C, C1, C2:
- s = pickle.dumps(cls, bin)
- cls2 = pickle.loads(s)
- self.assertIs(cls2, cls)
-
- a = C1(1, 2); a.append(42); a.append(24)
- b = C2("hello", "world", 42)
- s = pickle.dumps((a, b), bin)
- x, y = pickle.loads(s)
- self.assertEqual(x.__class__, a.__class__)
- self.assertEqual(sorteditems(x.__dict__), sorteditems(a.__dict__))
- self.assertEqual(y.__class__, b.__class__)
- self.assertEqual(sorteditems(y.__dict__), sorteditems(b.__dict__))
- self.assertEqual(repr(x), repr(a))
- self.assertEqual(repr(y), repr(b))
- # Test for __getstate__ and __setstate__ on new style class
- u = C3(42)
- s = pickle.dumps(u, bin)
- v = pickle.loads(s)
- self.assertEqual(u.__class__, v.__class__)
- self.assertEqual(u.foo, v.foo)
- # Test for picklability of hybrid class
- u = C4()
- u.foo = 42
- s = pickle.dumps(u, bin)
- v = pickle.loads(s)
- self.assertEqual(u.__class__, v.__class__)
- self.assertEqual(u.foo, v.foo)
-
- # Testing copy.deepcopy()
- import copy
- for cls in C, C1, C2:
- cls2 = copy.deepcopy(cls)
- self.assertIs(cls2, cls)
-
- a = C1(1, 2); a.append(42); a.append(24)
- b = C2("hello", "world", 42)
- x, y = copy.deepcopy((a, b))
- self.assertEqual(x.__class__, a.__class__)
- self.assertEqual(sorteditems(x.__dict__), sorteditems(a.__dict__))
- self.assertEqual(y.__class__, b.__class__)
- self.assertEqual(sorteditems(y.__dict__), sorteditems(b.__dict__))
- self.assertEqual(repr(x), repr(a))
- self.assertEqual(repr(y), repr(b))
-
- def test_pickle_slots(self):
- # Testing pickling of classes with __slots__ ...
- import pickle
- # Pickling of classes with __slots__ but without __getstate__ should fail
- # (if using protocol 0 or 1)
- global B, C, D, E
- class B(object):
- pass
- for base in [object, B]:
- class C(base):
- __slots__ = ['a']
- class D(C):
- pass
- try:
- pickle.dumps(C(), 0)
- except TypeError:
- pass
- else:
- self.fail("should fail: pickle C instance - %s" % base)
- try:
- pickle.dumps(C(), 0)
- except TypeError:
- pass
- else:
- self.fail("should fail: pickle D instance - %s" % base)
- # Give C a nice generic __getstate__ and __setstate__
- class C(base):
- __slots__ = ['a']
- def __getstate__(self):
- try:
- d = self.__dict__.copy()
- except AttributeError:
- d = {}
- for cls in self.__class__.__mro__:
- for sn in cls.__dict__.get('__slots__', ()):
- try:
- d[sn] = getattr(self, sn)
- except AttributeError:
- pass
- return d
- def __setstate__(self, d):
- for k, v in list(d.items()):
- setattr(self, k, v)
- class D(C):
- pass
- # Now it should work
- x = C()
- y = pickle.loads(pickle.dumps(x))
- self.assertNotHasAttr(y, 'a')
- x.a = 42
- y = pickle.loads(pickle.dumps(x))
- self.assertEqual(y.a, 42)
- x = D()
- x.a = 42
- x.b = 100
- y = pickle.loads(pickle.dumps(x))
- self.assertEqual(y.a + y.b, 142)
- # A subclass that adds a slot should also work
- class E(C):
- __slots__ = ['b']
- x = E()
- x.a = 42
- x.b = "foo"
- y = pickle.loads(pickle.dumps(x))
- self.assertEqual(y.a, x.a)
- self.assertEqual(y.b, x.b)
-
def test_binary_operator_override(self):
# Testing overrides of binary operations...
class I(int):
@@ -3744,18 +3580,8 @@ order (MRO) for bases """
# bug).
del c
- # If that didn't blow up, it's also interesting to see whether clearing
- # the last container slot works: that will attempt to delete c again,
- # which will cause c to get appended back to the container again
- # "during" the del. (On non-CPython implementations, however, __del__
- # is typically not called again.)
support.gc_collect()
self.assertEqual(len(C.container), 1)
- del C.container[-1]
- if support.check_impl_detail():
- support.gc_collect()
- self.assertEqual(len(C.container), 1)
- self.assertEqual(C.container[-1].attr, 42)
# Make c mortal again, so that the test framework with -l doesn't report
# it as a leak.
@@ -4535,6 +4361,13 @@ order (MRO) for bases """
self.assertRaises(TypeError, type.__dict__['__qualname__'].__set__,
str, 'Oink')
+ global Y
+ class Y:
+ class Inside:
+ pass
+ self.assertEqual(Y.__qualname__, 'Y')
+ self.assertEqual(Y.Inside.__qualname__, 'Y.Inside')
+
def test_qualname_dict(self):
ns = {'__qualname__': 'some.name'}
tp = type('Foo', (), ns)
@@ -4690,11 +4523,439 @@ class MiscTests(unittest.TestCase):
self.assertEqual(X.mykey2, 'from Base2')
+class PicklingTests(unittest.TestCase):
+
+ def _check_reduce(self, proto, obj, args=(), kwargs={}, state=None,
+ listitems=None, dictitems=None):
+ if proto >= 4:
+ reduce_value = obj.__reduce_ex__(proto)
+ self.assertEqual(reduce_value[:3],
+ (copyreg.__newobj_ex__,
+ (type(obj), args, kwargs),
+ state))
+ if listitems is not None:
+ self.assertListEqual(list(reduce_value[3]), listitems)
+ else:
+ self.assertIsNone(reduce_value[3])
+ if dictitems is not None:
+ self.assertDictEqual(dict(reduce_value[4]), dictitems)
+ else:
+ self.assertIsNone(reduce_value[4])
+ elif proto >= 2:
+ reduce_value = obj.__reduce_ex__(proto)
+ self.assertEqual(reduce_value[:3],
+ (copyreg.__newobj__,
+ (type(obj),) + args,
+ state))
+ if listitems is not None:
+ self.assertListEqual(list(reduce_value[3]), listitems)
+ else:
+ self.assertIsNone(reduce_value[3])
+ if dictitems is not None:
+ self.assertDictEqual(dict(reduce_value[4]), dictitems)
+ else:
+ self.assertIsNone(reduce_value[4])
+ else:
+ base_type = type(obj).__base__
+ reduce_value = (copyreg._reconstructor,
+ (type(obj),
+ base_type,
+ None if base_type is object else base_type(obj)))
+ if state is not None:
+ reduce_value += (state,)
+ self.assertEqual(obj.__reduce_ex__(proto), reduce_value)
+ self.assertEqual(obj.__reduce__(), reduce_value)
+
+ def test_reduce(self):
+ protocols = range(pickle.HIGHEST_PROTOCOL + 1)
+ args = (-101, "spam")
+ kwargs = {'bacon': -201, 'fish': -301}
+ state = {'cheese': -401}
+
+ class C1:
+ def __getnewargs__(self):
+ return args
+ obj = C1()
+ for proto in protocols:
+ self._check_reduce(proto, obj, args)
+
+ for name, value in state.items():
+ setattr(obj, name, value)
+ for proto in protocols:
+ self._check_reduce(proto, obj, args, state=state)
+
+ class C2:
+ def __getnewargs__(self):
+ return "bad args"
+ obj = C2()
+ for proto in protocols:
+ if proto >= 2:
+ with self.assertRaises(TypeError):
+ obj.__reduce_ex__(proto)
+
+ class C3:
+ def __getnewargs_ex__(self):
+ return (args, kwargs)
+ obj = C3()
+ for proto in protocols:
+ if proto >= 4:
+ self._check_reduce(proto, obj, args, kwargs)
+ elif proto >= 2:
+ with self.assertRaises(ValueError):
+ obj.__reduce_ex__(proto)
+
+ class C4:
+ def __getnewargs_ex__(self):
+ return (args, "bad dict")
+ class C5:
+ def __getnewargs_ex__(self):
+ return ("bad tuple", kwargs)
+ class C6:
+ def __getnewargs_ex__(self):
+ return ()
+ class C7:
+ def __getnewargs_ex__(self):
+ return "bad args"
+ for proto in protocols:
+ for cls in C4, C5, C6, C7:
+ obj = cls()
+ if proto >= 2:
+ with self.assertRaises((TypeError, ValueError)):
+ obj.__reduce_ex__(proto)
+
+ class C8:
+ def __getnewargs_ex__(self):
+ return (args, kwargs)
+ obj = C8()
+ for proto in protocols:
+ if 2 <= proto < 4:
+ with self.assertRaises(ValueError):
+ obj.__reduce_ex__(proto)
+ class C9:
+ def __getnewargs_ex__(self):
+ return (args, {})
+ obj = C9()
+ for proto in protocols:
+ self._check_reduce(proto, obj, args)
+
+ class C10:
+ def __getnewargs_ex__(self):
+ raise IndexError
+ obj = C10()
+ for proto in protocols:
+ if proto >= 2:
+ with self.assertRaises(IndexError):
+ obj.__reduce_ex__(proto)
+
+ class C11:
+ def __getstate__(self):
+ return state
+ obj = C11()
+ for proto in protocols:
+ self._check_reduce(proto, obj, state=state)
+
+ class C12:
+ def __getstate__(self):
+ return "not dict"
+ obj = C12()
+ for proto in protocols:
+ self._check_reduce(proto, obj, state="not dict")
+
+ class C13:
+ def __getstate__(self):
+ raise IndexError
+ obj = C13()
+ for proto in protocols:
+ with self.assertRaises(IndexError):
+ obj.__reduce_ex__(proto)
+ if proto < 2:
+ with self.assertRaises(IndexError):
+ obj.__reduce__()
+
+ class C14:
+ __slots__ = tuple(state)
+ def __init__(self):
+ for name, value in state.items():
+ setattr(self, name, value)
+
+ obj = C14()
+ for proto in protocols:
+ if proto >= 2:
+ self._check_reduce(proto, obj, state=(None, state))
+ else:
+ with self.assertRaises(TypeError):
+ obj.__reduce_ex__(proto)
+ with self.assertRaises(TypeError):
+ obj.__reduce__()
+
+ class C15(dict):
+ pass
+ obj = C15({"quebec": -601})
+ for proto in protocols:
+ self._check_reduce(proto, obj, dictitems=dict(obj))
+
+ class C16(list):
+ pass
+ obj = C16(["yukon"])
+ for proto in protocols:
+ self._check_reduce(proto, obj, listitems=list(obj))
+
+ def _assert_is_copy(self, obj, objcopy, msg=None):
+ """Utility method to verify if two objects are copies of each others.
+ """
+ if msg is None:
+ msg = "{!r} is not a copy of {!r}".format(obj, objcopy)
+ if type(obj).__repr__ is object.__repr__:
+ # We have this limitation for now because we use the object's repr
+ # to help us verify that the two objects are copies. This allows
+ # us to delegate the non-generic verification logic to the objects
+ # themselves.
+ raise ValueError("object passed to _assert_is_copy must " +
+ "override the __repr__ method.")
+ self.assertIsNot(obj, objcopy, msg=msg)
+ self.assertIs(type(obj), type(objcopy), msg=msg)
+ if hasattr(obj, '__dict__'):
+ self.assertDictEqual(obj.__dict__, objcopy.__dict__, msg=msg)
+ self.assertIsNot(obj.__dict__, objcopy.__dict__, msg=msg)
+ if hasattr(obj, '__slots__'):
+ self.assertListEqual(obj.__slots__, objcopy.__slots__, msg=msg)
+ for slot in obj.__slots__:
+ self.assertEqual(
+ hasattr(obj, slot), hasattr(objcopy, slot), msg=msg)
+ self.assertEqual(getattr(obj, slot, None),
+ getattr(objcopy, slot, None), msg=msg)
+ self.assertEqual(repr(obj), repr(objcopy), msg=msg)
+
+ @staticmethod
+ def _generate_pickle_copiers():
+ """Utility method to generate the many possible pickle configurations.
+ """
+ class PickleCopier:
+ "This class copies object using pickle."
+ def __init__(self, proto, dumps, loads):
+ self.proto = proto
+ self.dumps = dumps
+ self.loads = loads
+ def copy(self, obj):
+ return self.loads(self.dumps(obj, self.proto))
+ def __repr__(self):
+ # We try to be as descriptive as possible here since this is
+ # the string which we will allow us to tell the pickle
+ # configuration we are using during debugging.
+ return ("PickleCopier(proto={}, dumps={}.{}, loads={}.{})"
+ .format(self.proto,
+ self.dumps.__module__, self.dumps.__qualname__,
+ self.loads.__module__, self.loads.__qualname__))
+ return (PickleCopier(*args) for args in
+ itertools.product(range(pickle.HIGHEST_PROTOCOL + 1),
+ {pickle.dumps, pickle._dumps},
+ {pickle.loads, pickle._loads}))
+
+ def test_pickle_slots(self):
+ # Tests pickling of classes with __slots__.
+
+ # Pickling of classes with __slots__ but without __getstate__ should
+ # fail (if using protocol 0 or 1)
+ global C
+ class C:
+ __slots__ = ['a']
+ with self.assertRaises(TypeError):
+ pickle.dumps(C(), 0)
+
+ global D
+ class D(C):
+ pass
+ with self.assertRaises(TypeError):
+ pickle.dumps(D(), 0)
+
+ class C:
+ "A class with __getstate__ and __setstate__ implemented."
+ __slots__ = ['a']
+ def __getstate__(self):
+ state = getattr(self, '__dict__', {}).copy()
+ for cls in type(self).__mro__:
+ for slot in cls.__dict__.get('__slots__', ()):
+ try:
+ state[slot] = getattr(self, slot)
+ except AttributeError:
+ pass
+ return state
+ def __setstate__(self, state):
+ for k, v in state.items():
+ setattr(self, k, v)
+ def __repr__(self):
+ return "%s()<%r>" % (type(self).__name__, self.__getstate__())
+
+ class D(C):
+ "A subclass of a class with slots."
+ pass
+
+ global E
+ class E(C):
+ "A subclass with an extra slot."
+ __slots__ = ['b']
+
+ # Now it should work
+ for pickle_copier in self._generate_pickle_copiers():
+ with self.subTest(pickle_copier=pickle_copier):
+ x = C()
+ y = pickle_copier.copy(x)
+ self._assert_is_copy(x, y)
+
+ x.a = 42
+ y = pickle_copier.copy(x)
+ self._assert_is_copy(x, y)
+
+ x = D()
+ x.a = 42
+ x.b = 100
+ y = pickle_copier.copy(x)
+ self._assert_is_copy(x, y)
+
+ x = E()
+ x.a = 42
+ x.b = "foo"
+ y = pickle_copier.copy(x)
+ self._assert_is_copy(x, y)
+
+ def test_reduce_copying(self):
+ # Tests pickling and copying new-style classes and objects.
+ global C1
+ class C1:
+ "The state of this class is copyable via its instance dict."
+ ARGS = (1, 2)
+ NEED_DICT_COPYING = True
+ def __init__(self, a, b):
+ super().__init__()
+ self.a = a
+ self.b = b
+ def __repr__(self):
+ return "C1(%r, %r)" % (self.a, self.b)
+
+ global C2
+ class C2(list):
+ "A list subclass copyable via __getnewargs__."
+ ARGS = (1, 2)
+ NEED_DICT_COPYING = False
+ def __new__(cls, a, b):
+ self = super().__new__(cls)
+ self.a = a
+ self.b = b
+ return self
+ def __init__(self, *args):
+ super().__init__()
+ # This helps testing that __init__ is not called during the
+ # unpickling process, which would cause extra appends.
+ self.append("cheese")
+ @classmethod
+ def __getnewargs__(cls):
+ return cls.ARGS
+ def __repr__(self):
+ return "C2(%r, %r)<%r>" % (self.a, self.b, list(self))
+
+ global C3
+ class C3(list):
+ "A list subclass copyable via __getstate__."
+ ARGS = (1, 2)
+ NEED_DICT_COPYING = False
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+ # This helps testing that __init__ is not called during the
+ # unpickling process, which would cause extra appends.
+ self.append("cheese")
+ @classmethod
+ def __getstate__(cls):
+ return cls.ARGS
+ def __setstate__(self, state):
+ a, b = state
+ self.a = a
+ self.b = b
+ def __repr__(self):
+ return "C3(%r, %r)<%r>" % (self.a, self.b, list(self))
+
+ global C4
+ class C4(int):
+ "An int subclass copyable via __getnewargs__."
+ ARGS = ("hello", "world", 1)
+ NEED_DICT_COPYING = False
+ def __new__(cls, a, b, value):
+ self = super().__new__(cls, value)
+ self.a = a
+ self.b = b
+ return self
+ @classmethod
+ def __getnewargs__(cls):
+ return cls.ARGS
+ def __repr__(self):
+ return "C4(%r, %r)<%r>" % (self.a, self.b, int(self))
+
+ global C5
+ class C5(int):
+ "An int subclass copyable via __getnewargs_ex__."
+ ARGS = (1, 2)
+ KWARGS = {'value': 3}
+ NEED_DICT_COPYING = False
+ def __new__(cls, a, b, *, value=0):
+ self = super().__new__(cls, value)
+ self.a = a
+ self.b = b
+ return self
+ @classmethod
+ def __getnewargs_ex__(cls):
+ return (cls.ARGS, cls.KWARGS)
+ def __repr__(self):
+ return "C5(%r, %r)<%r>" % (self.a, self.b, int(self))
+
+ test_classes = (C1, C2, C3, C4, C5)
+ # Testing copying through pickle
+ pickle_copiers = self._generate_pickle_copiers()
+ for cls, pickle_copier in itertools.product(test_classes, pickle_copiers):
+ with self.subTest(cls=cls, pickle_copier=pickle_copier):
+ kwargs = getattr(cls, 'KWARGS', {})
+ obj = cls(*cls.ARGS, **kwargs)
+ proto = pickle_copier.proto
+ if 2 <= proto < 4 and hasattr(cls, '__getnewargs_ex__'):
+ with self.assertRaises(ValueError):
+ pickle_copier.dumps(obj, proto)
+ continue
+ objcopy = pickle_copier.copy(obj)
+ self._assert_is_copy(obj, objcopy)
+ # For test classes that supports this, make sure we didn't go
+ # around the reduce protocol by simply copying the attribute
+ # dictionary. We clear attributes using the previous copy to
+ # not mutate the original argument.
+ if proto >= 2 and not cls.NEED_DICT_COPYING:
+ objcopy.__dict__.clear()
+ objcopy2 = pickle_copier.copy(objcopy)
+ self._assert_is_copy(obj, objcopy2)
+
+ # Testing copying through copy.deepcopy()
+ for cls in test_classes:
+ with self.subTest(cls=cls):
+ kwargs = getattr(cls, 'KWARGS', {})
+ obj = cls(*cls.ARGS, **kwargs)
+ # XXX: We need to modify the copy module to support PEP 3154's
+ # reduce protocol 4.
+ if hasattr(cls, '__getnewargs_ex__'):
+ continue
+ objcopy = deepcopy(obj)
+ self._assert_is_copy(obj, objcopy)
+ # For test classes that supports this, make sure we didn't go
+ # around the reduce protocol by simply copying the attribute
+ # dictionary. We clear attributes using the previous copy to
+ # not mutate the original argument.
+ if not cls.NEED_DICT_COPYING:
+ objcopy.__dict__.clear()
+ objcopy2 = deepcopy(objcopy)
+ self._assert_is_copy(obj, objcopy2)
+
+
def test_main():
# Run all local test cases, with PTypesLongInitTest first.
support.run_unittest(PTypesLongInitTest, OperatorsTest,
ClassPropertiesAndMethods, DictProxyTests,
- MiscTests)
+ MiscTests, PicklingTests)
if __name__ == "__main__":
test_main()
diff --git a/Lib/test/test_devpoll.py b/Lib/test/test_devpoll.py
index bef4e1846b..40ebeee597 100644
--- a/Lib/test/test_devpoll.py
+++ b/Lib/test/test_devpoll.py
@@ -2,13 +2,17 @@
# Initial tests are copied as is from "test_poll.py"
-import os, select, random, unittest, sys
+import os
+import random
+import select
+import sys
+import unittest
from test.support import TESTFN, run_unittest
try:
select.devpoll
except AttributeError:
- raise unittest.SkipTest("select.devpoll not defined -- skipping test_devpoll")
+ raise unittest.SkipTest("select.devpoll not defined")
def find_ready_matching(ready, flag):
@@ -87,6 +91,36 @@ class DevPollTests(unittest.TestCase):
self.assertRaises(OverflowError, pollster.poll, 1 << 63)
self.assertRaises(OverflowError, pollster.poll, 1 << 64)
+ def test_close(self):
+ open_file = open(__file__, "rb")
+ self.addCleanup(open_file.close)
+ fd = open_file.fileno()
+ devpoll = select.devpoll()
+
+ # test fileno() method and closed attribute
+ self.assertIsInstance(devpoll.fileno(), int)
+ self.assertFalse(devpoll.closed)
+
+ # test close()
+ devpoll.close()
+ self.assertTrue(devpoll.closed)
+ self.assertRaises(ValueError, devpoll.fileno)
+
+ # close() can be called more than once
+ devpoll.close()
+
+ # operations must fail with ValueError("I/O operation on closed ...")
+ self.assertRaises(ValueError, devpoll.modify, fd, select.POLLIN)
+ self.assertRaises(ValueError, devpoll.poll)
+ self.assertRaises(ValueError, devpoll.register, fd, fd, select.POLLIN)
+ self.assertRaises(ValueError, devpoll.unregister, fd)
+
+ def test_fd_non_inheritable(self):
+ devpoll = select.devpoll()
+ self.addCleanup(devpoll.close)
+ self.assertEqual(os.get_inheritable(devpoll.fileno()), False)
+
+
def test_main():
run_unittest(DevPollTests)
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index d1355bbb96..94d6be735c 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -1,11 +1,29 @@
# Minimal tests for dis module
from test.support import run_unittest, captured_stdout
+from test.bytecode_helper import BytecodeTestCase
import difflib
import unittest
import sys
import dis
import io
+import types
+import contextlib
+
+def get_tb():
+ def _error():
+ try:
+ 1 / 0
+ except Exception as e:
+ tb = e.__traceback__
+ return tb
+
+ tb = _error()
+ while tb.tb_next:
+ tb = tb.tb_next
+ return tb
+
+TRACEBACK_CODE = get_tb().tb_frame.f_code
class _C:
def __init__(self, x):
@@ -22,12 +40,12 @@ dis_c_instance_method = """\
""" % (_C.__init__.__code__.co_firstlineno + 1,)
dis_c_instance_method_bytes = """\
- 0 LOAD_FAST 1 (1)
- 3 LOAD_CONST 1 (1)
- 6 COMPARE_OP 2 (==)
- 9 LOAD_FAST 0 (0)
- 12 STORE_ATTR 0 (0)
- 15 LOAD_CONST 0 (0)
+ 0 LOAD_FAST 1 (1)
+ 3 LOAD_CONST 1 (1)
+ 6 COMPARE_OP 2 (==)
+ 9 LOAD_FAST 0 (0)
+ 12 STORE_ATTR 0 (0)
+ 15 LOAD_CONST 0 (0)
18 RETURN_VALUE
"""
@@ -48,11 +66,11 @@ dis_f = """\
dis_f_co_code = """\
- 0 LOAD_GLOBAL 0 (0)
- 3 LOAD_FAST 0 (0)
- 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
+ 0 LOAD_GLOBAL 0 (0)
+ 3 LOAD_FAST 0 (0)
+ 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
- 10 LOAD_CONST 1 (1)
+ 10 LOAD_CONST 1 (1)
13 RETURN_VALUE
"""
@@ -171,33 +189,63 @@ dis_compound_stmt_str = """\
25 RETURN_VALUE
"""
+dis_traceback = """\
+ %-4d 0 SETUP_EXCEPT 12 (to 15)
+
+ %-4d 3 LOAD_CONST 1 (1)
+ 6 LOAD_CONST 2 (0)
+ --> 9 BINARY_TRUE_DIVIDE
+ 10 POP_TOP
+ 11 POP_BLOCK
+ 12 JUMP_FORWARD 46 (to 61)
+
+ %-4d >> 15 DUP_TOP
+ 16 LOAD_GLOBAL 0 (Exception)
+ 19 COMPARE_OP 10 (exception match)
+ 22 POP_JUMP_IF_FALSE 60
+ 25 POP_TOP
+ 26 STORE_FAST 0 (e)
+ 29 POP_TOP
+ 30 SETUP_FINALLY 14 (to 47)
+
+ %-4d 33 LOAD_FAST 0 (e)
+ 36 LOAD_ATTR 1 (__traceback__)
+ 39 STORE_FAST 1 (tb)
+ 42 POP_BLOCK
+ 43 POP_EXCEPT
+ 44 LOAD_CONST 0 (None)
+ >> 47 LOAD_CONST 0 (None)
+ 50 STORE_FAST 0 (e)
+ 53 DELETE_FAST 0 (e)
+ 56 END_FINALLY
+ 57 JUMP_FORWARD 1 (to 61)
+ >> 60 END_FINALLY
+
+ %-4d >> 61 LOAD_FAST 1 (tb)
+ 64 RETURN_VALUE
+""" % (TRACEBACK_CODE.co_firstlineno + 1,
+ TRACEBACK_CODE.co_firstlineno + 2,
+ TRACEBACK_CODE.co_firstlineno + 3,
+ TRACEBACK_CODE.co_firstlineno + 4,
+ TRACEBACK_CODE.co_firstlineno + 5)
+
class DisTests(unittest.TestCase):
def get_disassembly(self, func, lasti=-1, wrapper=True):
- s = io.StringIO()
- save_stdout = sys.stdout
- sys.stdout = s
- try:
+ # We want to test the default printing behaviour, not the file arg
+ output = io.StringIO()
+ with contextlib.redirect_stdout(output):
if wrapper:
dis.dis(func)
else:
dis.disassemble(func, lasti)
- finally:
- sys.stdout = save_stdout
- # Trim trailing blanks (if any).
- return [line.rstrip() for line in s.getvalue().splitlines()]
+ return output.getvalue()
def get_disassemble_as_string(self, func, lasti=-1):
- return '\n'.join(self.get_disassembly(func, lasti, False))
+ return self.get_disassembly(func, lasti, False)
def do_disassembly_test(self, func, expected):
- lines = self.get_disassembly(func)
- expected = expected.splitlines()
- if expected != lines:
- self.fail(
- "events did not match expectation:\n" +
- "\n".join(difflib.ndiff(expected,
- lines)))
+ self.assertEqual(self.get_disassembly(func), expected)
def test_opmap(self):
self.assertEqual(dis.opmap["NOP"], 9)
@@ -288,35 +336,35 @@ class DisTests(unittest.TestCase):
def test_dis_object(self):
self.assertRaises(TypeError, dis.dis, object())
+class DisWithFileTests(DisTests):
+
+ # Run the tests again, using the file arg instead of print
+ def get_disassembly(self, func, lasti=-1, wrapper=True):
+ output = io.StringIO()
+ if wrapper:
+ dis.dis(func, file=output)
+ else:
+ dis.disassemble(func, lasti, file=output)
+ return output.getvalue()
+
+
+
code_info_code_info = """\
Name: code_info
Filename: (.*)
Argument count: 1
Kw-only arguments: 0
Number of locals: 1
-Stack size: 4
+Stack size: 3
Flags: OPTIMIZED, NEWLOCALS, NOFREE
Constants:
0: %r
- 1: '__func__'
- 2: '__code__'
- 3: '<code_info>'
- 4: 'co_code'
- 5: "don't know how to disassemble %%s objects"
-%sNames:
- 0: hasattr
- 1: __func__
- 2: __code__
- 3: isinstance
- 4: str
- 5: _try_compile
- 6: _format_code_info
- 7: TypeError
- 8: type
- 9: __name__
+Names:
+ 0: _format_code_info
+ 1: _get_code_object
Variable names:
- 0: x""" % (('Formatted details of methods, functions, or code.', ' 6: None\n')
- if sys.flags.optimize < 2 else (None, ''))
+ 0: x""" % (('Formatted details of methods, functions, or code.',)
+ if sys.flags.optimize < 2 else (None,))
@staticmethod
def tricky(x, y, z=True, *args, c, d, e=[], **kwds):
@@ -380,7 +428,7 @@ Free variables:
code_info_expr_str = """\
Name: <module>
-Filename: <code_info>
+Filename: <disassembly>
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
@@ -393,7 +441,7 @@ Names:
code_info_simple_stmt_str = """\
Name: <module>
-Filename: <code_info>
+Filename: <disassembly>
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
@@ -407,7 +455,7 @@ Names:
code_info_compound_stmt_str = """\
Name: <module>
-Filename: <code_info>
+Filename: <disassembly>
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
@@ -441,6 +489,9 @@ class CodeInfoTests(unittest.TestCase):
with captured_stdout() as output:
dis.show_code(x)
self.assertRegex(output.getvalue(), expected+"\n")
+ output = io.StringIO()
+ dis.show_code(x, file=output)
+ self.assertRegex(output.getvalue(), expected)
def test_code_info_object(self):
self.assertRaises(TypeError, dis.code_info, object())
@@ -449,8 +500,334 @@ class CodeInfoTests(unittest.TestCase):
self.assertEqual(dis.pretty_flags(0), '0x0')
+# Fodder for instruction introspection tests
+# Editing any of these may require recalculating the expected output
+def outer(a=1, b=2):
+ def f(c=3, d=4):
+ def inner(e=5, f=6):
+ print(a, b, c, d, e, f)
+ print(a, b, c, d)
+ return inner
+ print(a, b, '', 1, [], {}, "Hello world!")
+ return f
+
+def jumpy():
+ # This won't actually run (but that's OK, we only disassemble it)
+ for i in range(10):
+ print(i)
+ if i < 4:
+ continue
+ if i > 6:
+ break
+ else:
+ print("I can haz else clause?")
+ while i:
+ print(i)
+ i -= 1
+ if i > 6:
+ continue
+ if i < 4:
+ break
+ else:
+ print("Who let lolcatz into this test suite?")
+ try:
+ 1 / 0
+ except ZeroDivisionError:
+ print("Here we go, here we go, here we go...")
+ else:
+ with i as dodgy:
+ print("Never reach this")
+ finally:
+ print("OK, now we're done")
+
+# End fodder for opinfo generation tests
+expected_outer_line = 1
+_line_offset = outer.__code__.co_firstlineno - 1
+code_object_f = outer.__code__.co_consts[3]
+expected_f_line = code_object_f.co_firstlineno - _line_offset
+code_object_inner = code_object_f.co_consts[3]
+expected_inner_line = code_object_inner.co_firstlineno - _line_offset
+expected_jumpy_line = 1
+
+# The following lines are useful to regenerate the expected results after
+# either the fodder is modified or the bytecode generation changes
+# After regeneration, update the references to code_object_f and
+# code_object_inner before rerunning the tests
+
+#_instructions = dis.get_instructions(outer, first_line=expected_outer_line)
+#print('expected_opinfo_outer = [\n ',
+ #',\n '.join(map(str, _instructions)), ',\n]', sep='')
+#_instructions = dis.get_instructions(outer(), first_line=expected_outer_line)
+#print('expected_opinfo_f = [\n ',
+ #',\n '.join(map(str, _instructions)), ',\n]', sep='')
+#_instructions = dis.get_instructions(outer()(), first_line=expected_outer_line)
+#print('expected_opinfo_inner = [\n ',
+ #',\n '.join(map(str, _instructions)), ',\n]', sep='')
+#_instructions = dis.get_instructions(jumpy, first_line=expected_jumpy_line)
+#print('expected_opinfo_jumpy = [\n ',
+ #',\n '.join(map(str, _instructions)), ',\n]', sep='')
+
+
+Instruction = dis.Instruction
+expected_opinfo_outer = [
+ Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=3, argrepr='3', offset=0, starts_line=2, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=3, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CLOSURE', opcode=135, arg=0, argval='a', argrepr='a', offset=6, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CLOSURE', opcode=135, arg=1, argval='b', argrepr='b', offset=9, starts_line=None, is_jump_target=False),
+ Instruction(opname='BUILD_TUPLE', opcode=102, arg=2, argval=2, argrepr='', offset=12, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_f, argrepr=repr(code_object_f), offset=15, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='outer.<locals>.f', argrepr="'outer.<locals>.f'", offset=18, starts_line=None, is_jump_target=False),
+ Instruction(opname='MAKE_CLOSURE', opcode=134, arg=2, argval=2, argrepr='', offset=21, starts_line=None, is_jump_target=False),
+ Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='f', argrepr='f', offset=24, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=27, starts_line=7, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='a', argrepr='a', offset=30, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='b', argrepr='b', offset=33, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval='', argrepr="''", offset=36, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval=1, argrepr='1', offset=39, starts_line=None, is_jump_target=False),
+ Instruction(opname='BUILD_LIST', opcode=103, arg=0, argval=0, argrepr='', offset=42, starts_line=None, is_jump_target=False),
+ Instruction(opname='BUILD_MAP', opcode=105, arg=0, argval=0, argrepr='', offset=45, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval='Hello world!', argrepr="'Hello world!'", offset=48, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=7, argval=7, argrepr='7 positional, 0 keyword pair', offset=51, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=54, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='f', argrepr='f', offset=55, starts_line=8, is_jump_target=False),
+ Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=58, starts_line=None, is_jump_target=False),
+]
+
+expected_opinfo_f = [
+ Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=5, argrepr='5', offset=0, starts_line=3, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=6, argrepr='6', offset=3, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CLOSURE', opcode=135, arg=2, argval='a', argrepr='a', offset=6, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CLOSURE', opcode=135, arg=3, argval='b', argrepr='b', offset=9, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CLOSURE', opcode=135, arg=0, argval='c', argrepr='c', offset=12, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CLOSURE', opcode=135, arg=1, argval='d', argrepr='d', offset=15, starts_line=None, is_jump_target=False),
+ Instruction(opname='BUILD_TUPLE', opcode=102, arg=4, argval=4, argrepr='', offset=18, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_inner, argrepr=repr(code_object_inner), offset=21, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='outer.<locals>.f.<locals>.inner', argrepr="'outer.<locals>.f.<locals>.inner'", offset=24, starts_line=None, is_jump_target=False),
+ Instruction(opname='MAKE_CLOSURE', opcode=134, arg=2, argval=2, argrepr='', offset=27, starts_line=None, is_jump_target=False),
+ Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='inner', argrepr='inner', offset=30, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=33, starts_line=5, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=2, argval='a', argrepr='a', offset=36, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=3, argval='b', argrepr='b', offset=39, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='c', argrepr='c', offset=42, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='d', argrepr='d', offset=45, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=4, argval=4, argrepr='4 positional, 0 keyword pair', offset=48, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=51, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='inner', argrepr='inner', offset=52, starts_line=6, is_jump_target=False),
+ Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=55, starts_line=None, is_jump_target=False),
+]
+
+expected_opinfo_inner = [
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=0, starts_line=4, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='a', argrepr='a', offset=3, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='b', argrepr='b', offset=6, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=2, argval='c', argrepr='c', offset=9, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_DEREF', opcode=136, arg=3, argval='d', argrepr='d', offset=12, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='e', argrepr='e', offset=15, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='f', argrepr='f', offset=18, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=6, argval=6, argrepr='6 positional, 0 keyword pair', offset=21, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=24, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=25, starts_line=None, is_jump_target=False),
+ Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=28, starts_line=None, is_jump_target=False),
+]
+
+expected_opinfo_jumpy = [
+ Instruction(opname='SETUP_LOOP', opcode=120, arg=74, argval=77, argrepr='to 77', offset=0, starts_line=3, is_jump_target=False),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='range', argrepr='range', offset=3, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=10, argrepr='10', offset=6, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=9, starts_line=None, is_jump_target=False),
+ Instruction(opname='GET_ITER', opcode=68, arg=None, argval=None, argrepr='', offset=12, starts_line=None, is_jump_target=False),
+ Instruction(opname='FOR_ITER', opcode=93, arg=50, argval=66, argrepr='to 66', offset=13, starts_line=None, is_jump_target=True),
+ Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=16, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=19, starts_line=4, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=22, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=25, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=28, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=29, starts_line=5, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=32, starts_line=None, is_jump_target=False),
+ Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=35, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=47, argval=47, argrepr='', offset=38, starts_line=None, is_jump_target=False),
+ Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=41, starts_line=6, is_jump_target=False),
+ Instruction(opname='JUMP_FORWARD', opcode=110, arg=0, argval=47, argrepr='to 47', offset=44, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=47, starts_line=7, is_jump_target=True),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=50, starts_line=None, is_jump_target=False),
+ Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=53, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=13, argval=13, argrepr='', offset=56, starts_line=None, is_jump_target=False),
+ Instruction(opname='BREAK_LOOP', opcode=80, arg=None, argval=None, argrepr='', offset=59, starts_line=8, is_jump_target=False),
+ Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=60, starts_line=None, is_jump_target=False),
+ Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=63, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=66, starts_line=None, is_jump_target=True),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=67, starts_line=10, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=70, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=73, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=76, starts_line=None, is_jump_target=False),
+ Instruction(opname='SETUP_LOOP', opcode=120, arg=74, argval=154, argrepr='to 154', offset=77, starts_line=11, is_jump_target=True),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=80, starts_line=None, is_jump_target=True),
+ Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=143, argval=143, argrepr='', offset=83, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=86, starts_line=12, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=89, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=92, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=95, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=96, starts_line=13, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=99, starts_line=None, is_jump_target=False),
+ Instruction(opname='INPLACE_SUBTRACT', opcode=56, arg=None, argval=None, argrepr='', offset=102, starts_line=None, is_jump_target=False),
+ Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=103, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=106, starts_line=14, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=109, starts_line=None, is_jump_target=False),
+ Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=112, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=124, argval=124, argrepr='', offset=115, starts_line=None, is_jump_target=False),
+ Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=118, starts_line=15, is_jump_target=False),
+ Instruction(opname='JUMP_FORWARD', opcode=110, arg=0, argval=124, argrepr='to 124', offset=121, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=124, starts_line=16, is_jump_target=True),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=127, starts_line=None, is_jump_target=False),
+ Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=130, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=80, argval=80, argrepr='', offset=133, starts_line=None, is_jump_target=False),
+ Instruction(opname='BREAK_LOOP', opcode=80, arg=None, argval=None, argrepr='', offset=136, starts_line=17, is_jump_target=False),
+ Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=137, starts_line=None, is_jump_target=False),
+ Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=140, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=143, starts_line=None, is_jump_target=True),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=144, starts_line=19, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=147, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=150, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=153, starts_line=None, is_jump_target=False),
+ Instruction(opname='SETUP_FINALLY', opcode=122, arg=72, argval=229, argrepr='to 229', offset=154, starts_line=20, is_jump_target=True),
+ Instruction(opname='SETUP_EXCEPT', opcode=121, arg=12, argval=172, argrepr='to 172', offset=157, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=160, starts_line=21, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=0, argrepr='0', offset=163, starts_line=None, is_jump_target=False),
+ Instruction(opname='BINARY_TRUE_DIVIDE', opcode=27, arg=None, argval=None, argrepr='', offset=166, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=167, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=168, starts_line=None, is_jump_target=False),
+ Instruction(opname='JUMP_FORWARD', opcode=110, arg=28, argval=200, argrepr='to 200', offset=169, starts_line=None, is_jump_target=False),
+ Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=172, starts_line=22, is_jump_target=True),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=2, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=173, starts_line=None, is_jump_target=False),
+ Instruction(opname='COMPARE_OP', opcode=107, arg=10, argval='exception match', argrepr='exception match', offset=176, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=199, argval=199, argrepr='', offset=179, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=182, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=183, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=184, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=185, starts_line=23, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=188, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=191, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=194, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=195, starts_line=None, is_jump_target=False),
+ Instruction(opname='JUMP_FORWARD', opcode=110, arg=26, argval=225, argrepr='to 225', offset=196, starts_line=None, is_jump_target=False),
+ Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=199, starts_line=None, is_jump_target=True),
+ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=200, starts_line=25, is_jump_target=True),
+ Instruction(opname='SETUP_WITH', opcode=143, arg=17, argval=223, argrepr='to 223', offset=203, starts_line=None, is_jump_target=False),
+ Instruction(opname='STORE_FAST', opcode=125, arg=1, argval='dodgy', argrepr='dodgy', offset=206, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=209, starts_line=26, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=9, argval='Never reach this', argrepr="'Never reach this'", offset=212, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=215, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=218, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=219, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=220, starts_line=None, is_jump_target=False),
+ Instruction(opname='WITH_CLEANUP', opcode=81, arg=None, argval=None, argrepr='', offset=223, starts_line=None, is_jump_target=True),
+ Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=224, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=225, starts_line=None, is_jump_target=True),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=226, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=229, starts_line=28, is_jump_target=True),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=232, starts_line=None, is_jump_target=False),
+ Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=235, starts_line=None, is_jump_target=False),
+ Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=238, starts_line=None, is_jump_target=False),
+ Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=239, starts_line=None, is_jump_target=False),
+ Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=240, starts_line=None, is_jump_target=False),
+ Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=243, starts_line=None, is_jump_target=False),
+]
+
+# One last piece of inspect fodder to check the default line number handling
+def simple(): pass
+expected_opinfo_simple = [
+ Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=0, starts_line=simple.__code__.co_firstlineno, is_jump_target=False),
+ Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=3, starts_line=None, is_jump_target=False)
+]
+
+
+class InstructionTests(BytecodeTestCase):
+
+ def test_default_first_line(self):
+ actual = dis.get_instructions(simple)
+ self.assertEqual(list(actual), expected_opinfo_simple)
+
+ def test_first_line_set_to_None(self):
+ actual = dis.get_instructions(simple, first_line=None)
+ self.assertEqual(list(actual), expected_opinfo_simple)
+
+ def test_outer(self):
+ actual = dis.get_instructions(outer, first_line=expected_outer_line)
+ self.assertEqual(list(actual), expected_opinfo_outer)
+
+ def test_nested(self):
+ with captured_stdout():
+ f = outer()
+ actual = dis.get_instructions(f, first_line=expected_f_line)
+ self.assertEqual(list(actual), expected_opinfo_f)
+
+ def test_doubly_nested(self):
+ with captured_stdout():
+ inner = outer()()
+ actual = dis.get_instructions(inner, first_line=expected_inner_line)
+ self.assertEqual(list(actual), expected_opinfo_inner)
+
+ def test_jumpy(self):
+ actual = dis.get_instructions(jumpy, first_line=expected_jumpy_line)
+ self.assertEqual(list(actual), expected_opinfo_jumpy)
+
+# get_instructions has its own tests above, so can rely on it to validate
+# the object oriented API
+class BytecodeTests(unittest.TestCase):
+ def test_instantiation(self):
+ # Test with function, method, code string and code object
+ for obj in [_f, _C(1).__init__, "a=1", _f.__code__]:
+ with self.subTest(obj=obj):
+ b = dis.Bytecode(obj)
+ self.assertIsInstance(b.codeobj, types.CodeType)
+
+ self.assertRaises(TypeError, dis.Bytecode, object())
+
+ def test_iteration(self):
+ for obj in [_f, _C(1).__init__, "a=1", _f.__code__]:
+ with self.subTest(obj=obj):
+ via_object = list(dis.Bytecode(obj))
+ via_generator = list(dis.get_instructions(obj))
+ self.assertEqual(via_object, via_generator)
+
+ def test_explicit_first_line(self):
+ actual = dis.Bytecode(outer, first_line=expected_outer_line)
+ self.assertEqual(list(actual), expected_opinfo_outer)
+
+ def test_source_line_in_disassembly(self):
+ # Use the line in the source code
+ actual = dis.Bytecode(simple).dis()[:3]
+ expected = "{:>3}".format(simple.__code__.co_firstlineno)
+ self.assertEqual(actual, expected)
+ # Use an explicit first line number
+ actual = dis.Bytecode(simple, first_line=350).dis()[:3]
+ self.assertEqual(actual, "350")
+
+ def test_info(self):
+ self.maxDiff = 1000
+ for x, expected in CodeInfoTests.test_pairs:
+ b = dis.Bytecode(x)
+ self.assertRegex(b.info(), expected)
+
+ def test_disassembled(self):
+ actual = dis.Bytecode(_f).dis()
+ self.assertEqual(actual, dis_f)
+
+ def test_from_traceback(self):
+ tb = get_tb()
+ b = dis.Bytecode.from_traceback(tb)
+ while tb.tb_next: tb = tb.tb_next
+
+ self.assertEqual(b.current_offset, tb.tb_lasti)
+
+ def test_from_traceback_dis(self):
+ tb = get_tb()
+ b = dis.Bytecode.from_traceback(tb)
+ self.assertEqual(b.dis(), dis_traceback)
+
def test_main():
- run_unittest(DisTests, CodeInfoTests)
+ run_unittest(DisTests, DisWithFileTests, CodeInfoTests,
+ InstructionTests, BytecodeTests)
if __name__ == "__main__":
test_main()
diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py
index 8f8c7c7e82..5b53ca8e02 100644
--- a/Lib/test/test_doctest.py
+++ b/Lib/test/test_doctest.py
@@ -409,7 +409,8 @@ Compare `DocTestCase`:
"""
-def test_DocTestFinder(): r"""
+class test_DocTestFinder:
+ def basics(): r"""
Unit tests for the `DocTestFinder` class.
DocTestFinder is used to extract DocTests from an object's docstring
@@ -646,6 +647,39 @@ DocTestFinder finds the line number of each example:
[1, 9, 12]
"""
+ if int.__doc__: # simple check for --without-doc-strings, skip if lacking
+ def non_Python_modules(): r"""
+
+Finding Doctests in Modules Not Written in Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+DocTestFinder can also find doctests in most modules not written in Python.
+We'll use builtins as an example, since it almost certainly isn't written in
+plain ol' Python and is guaranteed to be available.
+
+ >>> import builtins
+ >>> tests = doctest.DocTestFinder().find(builtins)
+ >>> 790 < len(tests) < 800 # approximate number of objects with docstrings
+ True
+ >>> real_tests = [t for t in tests if len(t.examples) > 0]
+ >>> len(real_tests) # objects that actually have doctests
+ 8
+ >>> for t in real_tests:
+ ... print('{} {}'.format(len(t.examples), t.name))
+ ...
+ 1 builtins.bin
+ 3 builtins.float.as_integer_ratio
+ 2 builtins.float.fromhex
+ 2 builtins.float.hex
+ 1 builtins.hex
+ 1 builtins.int
+ 2 builtins.int.bit_length
+ 1 builtins.oct
+
+Note here that 'bin', 'oct', and 'hex' are functions; 'float.as_integer_ratio',
+'float.hex', and 'int.bit_length' are methods; 'float.fromhex' is a classmethod,
+and 'int' is a type.
+"""
+
def test_DocTestParser(): r"""
Unit tests for the `DocTestParser` class.
@@ -1409,8 +1443,40 @@ However, output from `report_start` is not suppressed:
2
TestResults(failed=3, attempted=5)
-For the purposes of REPORT_ONLY_FIRST_FAILURE, unexpected exceptions
-count as failures:
+The FAIL_FAST flag causes the runner to exit after the first failing example,
+so subsequent examples are not even attempted:
+
+ >>> flags = doctest.FAIL_FAST
+ >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+ ... # doctest: +ELLIPSIS
+ **********************************************************************
+ File ..., line 5, in f
+ Failed example:
+ print(2) # first failure
+ Expected:
+ 200
+ Got:
+ 2
+ TestResults(failed=1, attempted=2)
+
+Specifying both FAIL_FAST and REPORT_ONLY_FIRST_FAILURE is equivalent to
+FAIL_FAST only:
+
+ >>> flags = doctest.FAIL_FAST | doctest.REPORT_ONLY_FIRST_FAILURE
+ >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+ ... # doctest: +ELLIPSIS
+ **********************************************************************
+ File ..., line 5, in f
+ Failed example:
+ print(2) # first failure
+ Expected:
+ 200
+ Got:
+ 2
+ TestResults(failed=1, attempted=2)
+
+For the purposes of both REPORT_ONLY_FIRST_FAILURE and FAIL_FAST, unexpected
+exceptions count as failures:
>>> def f(x):
... r'''
@@ -1437,6 +1503,17 @@ count as failures:
...
ValueError: 2
TestResults(failed=3, attempted=5)
+ >>> flags = doctest.FAIL_FAST
+ >>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
+ ... # doctest: +ELLIPSIS
+ **********************************************************************
+ File ..., line 5, in f
+ Failed example:
+ raise ValueError(2) # first failure
+ Exception raised:
+ ...
+ ValueError: 2
+ TestResults(failed=1, attempted=2)
New option flags can also be registered, via register_optionflag(). Here
we reach into doctest's internals a bit.
@@ -2553,6 +2630,240 @@ Check doctest with a non-ascii filename:
TestResults(failed=1, attempted=1)
"""
+def test_CLI(): r"""
+The doctest module can be used to run doctests against an arbitrary file.
+These tests test this CLI functionality.
+
+We'll use the support module's script_helpers for this, and write a test files
+to a temp dir to run the command against. Due to a current limitation in
+script_helpers, though, we need a little utility function to turn the returned
+output into something we can doctest against:
+
+ >>> def normalize(s):
+ ... return '\n'.join(s.decode().splitlines())
+
+Note: we also pass TERM='' to all the assert_python calls to avoid a bug
+in the readline library that is triggered in these tests because we are
+running them in a new python process. See:
+
+ http://lists.gnu.org/archive/html/bug-readline/2013-06/msg00000.html
+
+With those preliminaries out of the way, we'll start with a file with two
+simple tests and no errors. We'll run both the unadorned doctest command, and
+the verbose version, and then check the output:
+
+ >>> from test import script_helper
+ >>> with script_helper.temp_dir() as tmpdir:
+ ... fn = os.path.join(tmpdir, 'myfile.doc')
+ ... with open(fn, 'w') as f:
+ ... _ = f.write('This is a very simple test file.\n')
+ ... _ = f.write(' >>> 1 + 1\n')
+ ... _ = f.write(' 2\n')
+ ... _ = f.write(' >>> "a"\n')
+ ... _ = f.write(" 'a'\n")
+ ... _ = f.write('\n')
+ ... _ = f.write('And that is it.\n')
+ ... rc1, out1, err1 = script_helper.assert_python_ok(
+ ... '-m', 'doctest', fn, TERM='')
+ ... rc2, out2, err2 = script_helper.assert_python_ok(
+ ... '-m', 'doctest', '-v', fn, TERM='')
+
+With no arguments and passing tests, we should get no output:
+
+ >>> rc1, out1, err1
+ (0, b'', b'')
+
+With the verbose flag, we should see the test output, but no error output:
+
+ >>> rc2, err2
+ (0, b'')
+ >>> print(normalize(out2))
+ Trying:
+ 1 + 1
+ Expecting:
+ 2
+ ok
+ Trying:
+ "a"
+ Expecting:
+ 'a'
+ ok
+ 1 items passed all tests:
+ 2 tests in myfile.doc
+ 2 tests in 1 items.
+ 2 passed and 0 failed.
+ Test passed.
+
+Now we'll write a couple files, one with three tests, the other a python module
+with two tests, both of the files having "errors" in the tests that can be made
+non-errors by applying the appropriate doctest options to the run (ELLIPSIS in
+the first file, NORMALIZE_WHITESPACE in the second). This combination will
+allow to thoroughly test the -f and -o flags, as well as the doctest command's
+ability to process more than one file on the command line and, since the second
+file ends in '.py', its handling of python module files (as opposed to straight
+text files).
+
+ >>> from test import script_helper
+ >>> with script_helper.temp_dir() as tmpdir:
+ ... fn = os.path.join(tmpdir, 'myfile.doc')
+ ... with open(fn, 'w') as f:
+ ... _ = f.write('This is another simple test file.\n')
+ ... _ = f.write(' >>> 1 + 1\n')
+ ... _ = f.write(' 2\n')
+ ... _ = f.write(' >>> "abcdef"\n')
+ ... _ = f.write(" 'a...f'\n")
+ ... _ = f.write(' >>> "ajkml"\n')
+ ... _ = f.write(" 'a...l'\n")
+ ... _ = f.write('\n')
+ ... _ = f.write('And that is it.\n')
+ ... fn2 = os.path.join(tmpdir, 'myfile2.py')
+ ... with open(fn2, 'w') as f:
+ ... _ = f.write('def test_func():\n')
+ ... _ = f.write(' \"\"\"\n')
+ ... _ = f.write(' This is simple python test function.\n')
+ ... _ = f.write(' >>> 1 + 1\n')
+ ... _ = f.write(' 2\n')
+ ... _ = f.write(' >>> "abc def"\n')
+ ... _ = f.write(" 'abc def'\n")
+ ... _ = f.write("\n")
+ ... _ = f.write(' \"\"\"\n')
+ ... import shutil
+ ... rc1, out1, err1 = script_helper.assert_python_failure(
+ ... '-m', 'doctest', fn, fn2, TERM='')
+ ... rc2, out2, err2 = script_helper.assert_python_ok(
+ ... '-m', 'doctest', '-o', 'ELLIPSIS', fn, TERM='')
+ ... rc3, out3, err3 = script_helper.assert_python_ok(
+ ... '-m', 'doctest', '-o', 'ELLIPSIS',
+ ... '-o', 'NORMALIZE_WHITESPACE', fn, fn2, TERM='')
+ ... rc4, out4, err4 = script_helper.assert_python_failure(
+ ... '-m', 'doctest', '-f', fn, fn2, TERM='')
+ ... rc5, out5, err5 = script_helper.assert_python_ok(
+ ... '-m', 'doctest', '-v', '-o', 'ELLIPSIS',
+ ... '-o', 'NORMALIZE_WHITESPACE', fn, fn2, TERM='')
+
+Our first test run will show the errors from the first file (doctest stops if a
+file has errors). Note that doctest test-run error output appears on stdout,
+not stderr:
+
+ >>> rc1, err1
+ (1, b'')
+ >>> print(normalize(out1)) # doctest: +ELLIPSIS
+ **********************************************************************
+ File "...myfile.doc", line 4, in myfile.doc
+ Failed example:
+ "abcdef"
+ Expected:
+ 'a...f'
+ Got:
+ 'abcdef'
+ **********************************************************************
+ File "...myfile.doc", line 6, in myfile.doc
+ Failed example:
+ "ajkml"
+ Expected:
+ 'a...l'
+ Got:
+ 'ajkml'
+ **********************************************************************
+ 1 items had failures:
+ 2 of 3 in myfile.doc
+ ***Test Failed*** 2 failures.
+
+With -o ELLIPSIS specified, the second run, against just the first file, should
+produce no errors, and with -o NORMALIZE_WHITESPACE also specified, neither
+should the third, which ran against both files:
+
+ >>> rc2, out2, err2
+ (0, b'', b'')
+ >>> rc3, out3, err3
+ (0, b'', b'')
+
+The fourth run uses FAIL_FAST, so we should see only one error:
+
+ >>> rc4, err4
+ (1, b'')
+ >>> print(normalize(out4)) # doctest: +ELLIPSIS
+ **********************************************************************
+ File "...myfile.doc", line 4, in myfile.doc
+ Failed example:
+ "abcdef"
+ Expected:
+ 'a...f'
+ Got:
+ 'abcdef'
+ **********************************************************************
+ 1 items had failures:
+ 1 of 2 in myfile.doc
+ ***Test Failed*** 1 failures.
+
+The fifth test uses verbose with the two options, so we should get verbose
+success output for the tests in both files:
+
+ >>> rc5, err5
+ (0, b'')
+ >>> print(normalize(out5))
+ Trying:
+ 1 + 1
+ Expecting:
+ 2
+ ok
+ Trying:
+ "abcdef"
+ Expecting:
+ 'a...f'
+ ok
+ Trying:
+ "ajkml"
+ Expecting:
+ 'a...l'
+ ok
+ 1 items passed all tests:
+ 3 tests in myfile.doc
+ 3 tests in 1 items.
+ 3 passed and 0 failed.
+ Test passed.
+ Trying:
+ 1 + 1
+ Expecting:
+ 2
+ ok
+ Trying:
+ "abc def"
+ Expecting:
+ 'abc def'
+ ok
+ 1 items had no tests:
+ myfile2
+ 1 items passed all tests:
+ 2 tests in myfile2.test_func
+ 2 tests in 2 items.
+ 2 passed and 0 failed.
+ Test passed.
+
+We should also check some typical error cases.
+
+Invalid file name:
+
+ >>> rc, out, err = script_helper.assert_python_failure(
+ ... '-m', 'doctest', 'nosuchfile', TERM='')
+ >>> rc, out
+ (1, b'')
+ >>> print(normalize(err)) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ FileNotFoundError: [Errno ...] No such file or directory: 'nosuchfile'
+
+Invalid doctest option:
+
+ >>> rc, out, err = script_helper.assert_python_failure(
+ ... '-m', 'doctest', '-o', 'nosuchoption', TERM='')
+ >>> rc, out
+ (2, b'')
+ >>> print(normalize(err)) # doctest: +ELLIPSIS
+ usage...invalid...nosuchoption...
+
+"""
+
######################################################################
## Main
######################################################################
diff --git a/Lib/test/test_dynamicclassattribute.py b/Lib/test/test_dynamicclassattribute.py
new file mode 100644
index 0000000000..079d6c3224
--- /dev/null
+++ b/Lib/test/test_dynamicclassattribute.py
@@ -0,0 +1,304 @@
+# Test case for DynamicClassAttribute
+# more tests are in test_descr
+
+import abc
+import sys
+import unittest
+from test.support import run_unittest
+from types import DynamicClassAttribute
+
+class PropertyBase(Exception):
+ pass
+
+class PropertyGet(PropertyBase):
+ pass
+
+class PropertySet(PropertyBase):
+ pass
+
+class PropertyDel(PropertyBase):
+ pass
+
+class BaseClass(object):
+ def __init__(self):
+ self._spam = 5
+
+ @DynamicClassAttribute
+ def spam(self):
+ """BaseClass.getter"""
+ return self._spam
+
+ @spam.setter
+ def spam(self, value):
+ self._spam = value
+
+ @spam.deleter
+ def spam(self):
+ del self._spam
+
+class SubClass(BaseClass):
+
+ spam = BaseClass.__dict__['spam']
+
+ @spam.getter
+ def spam(self):
+ """SubClass.getter"""
+ raise PropertyGet(self._spam)
+
+ @spam.setter
+ def spam(self, value):
+ raise PropertySet(self._spam)
+
+ @spam.deleter
+ def spam(self):
+ raise PropertyDel(self._spam)
+
+class PropertyDocBase(object):
+ _spam = 1
+ def _get_spam(self):
+ return self._spam
+ spam = DynamicClassAttribute(_get_spam, doc="spam spam spam")
+
+class PropertyDocSub(PropertyDocBase):
+ spam = PropertyDocBase.__dict__['spam']
+ @spam.getter
+ def spam(self):
+ """The decorator does not use this doc string"""
+ return self._spam
+
+class PropertySubNewGetter(BaseClass):
+ spam = BaseClass.__dict__['spam']
+ @spam.getter
+ def spam(self):
+ """new docstring"""
+ return 5
+
+class PropertyNewGetter(object):
+ @DynamicClassAttribute
+ def spam(self):
+ """original docstring"""
+ return 1
+ @spam.getter
+ def spam(self):
+ """new docstring"""
+ return 8
+
+class ClassWithAbstractVirtualProperty(metaclass=abc.ABCMeta):
+ @DynamicClassAttribute
+ @abc.abstractmethod
+ def color():
+ pass
+
+class ClassWithPropertyAbstractVirtual(metaclass=abc.ABCMeta):
+ @abc.abstractmethod
+ @DynamicClassAttribute
+ def color():
+ pass
+
+class PropertyTests(unittest.TestCase):
+ def test_property_decorator_baseclass(self):
+ # see #1620
+ base = BaseClass()
+ self.assertEqual(base.spam, 5)
+ self.assertEqual(base._spam, 5)
+ base.spam = 10
+ self.assertEqual(base.spam, 10)
+ self.assertEqual(base._spam, 10)
+ delattr(base, "spam")
+ self.assertTrue(not hasattr(base, "spam"))
+ self.assertTrue(not hasattr(base, "_spam"))
+ base.spam = 20
+ self.assertEqual(base.spam, 20)
+ self.assertEqual(base._spam, 20)
+
+ def test_property_decorator_subclass(self):
+ # see #1620
+ sub = SubClass()
+ self.assertRaises(PropertyGet, getattr, sub, "spam")
+ self.assertRaises(PropertySet, setattr, sub, "spam", None)
+ self.assertRaises(PropertyDel, delattr, sub, "spam")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_decorator_subclass_doc(self):
+ sub = SubClass()
+ self.assertEqual(sub.__class__.__dict__['spam'].__doc__, "SubClass.getter")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_decorator_baseclass_doc(self):
+ base = BaseClass()
+ self.assertEqual(base.__class__.__dict__['spam'].__doc__, "BaseClass.getter")
+
+ def test_property_decorator_doc(self):
+ base = PropertyDocBase()
+ sub = PropertyDocSub()
+ self.assertEqual(base.__class__.__dict__['spam'].__doc__, "spam spam spam")
+ self.assertEqual(sub.__class__.__dict__['spam'].__doc__, "spam spam spam")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_getter_doc_override(self):
+ newgettersub = PropertySubNewGetter()
+ self.assertEqual(newgettersub.spam, 5)
+ self.assertEqual(newgettersub.__class__.__dict__['spam'].__doc__, "new docstring")
+ newgetter = PropertyNewGetter()
+ self.assertEqual(newgetter.spam, 8)
+ self.assertEqual(newgetter.__class__.__dict__['spam'].__doc__, "new docstring")
+
+ def test_property___isabstractmethod__descriptor(self):
+ for val in (True, False, [], [1], '', '1'):
+ class C(object):
+ def foo(self):
+ pass
+ foo.__isabstractmethod__ = val
+ foo = DynamicClassAttribute(foo)
+ self.assertIs(C.__dict__['foo'].__isabstractmethod__, bool(val))
+
+ # check that the DynamicClassAttribute's __isabstractmethod__ descriptor does the
+ # right thing when presented with a value that fails truth testing:
+ class NotBool(object):
+ def __nonzero__(self):
+ raise ValueError()
+ __len__ = __nonzero__
+ with self.assertRaises(ValueError):
+ class C(object):
+ def foo(self):
+ pass
+ foo.__isabstractmethod__ = NotBool()
+ foo = DynamicClassAttribute(foo)
+
+ def test_abstract_virtual(self):
+ self.assertRaises(TypeError, ClassWithAbstractVirtualProperty)
+ self.assertRaises(TypeError, ClassWithPropertyAbstractVirtual)
+ class APV(ClassWithPropertyAbstractVirtual):
+ pass
+ self.assertRaises(TypeError, APV)
+ class AVP(ClassWithAbstractVirtualProperty):
+ pass
+ self.assertRaises(TypeError, AVP)
+ class Okay1(ClassWithAbstractVirtualProperty):
+ @DynamicClassAttribute
+ def color(self):
+ return self._color
+ def __init__(self):
+ self._color = 'cyan'
+ with self.assertRaises(AttributeError):
+ Okay1.color
+ self.assertEqual(Okay1().color, 'cyan')
+ class Okay2(ClassWithAbstractVirtualProperty):
+ @DynamicClassAttribute
+ def color(self):
+ return self._color
+ def __init__(self):
+ self._color = 'magenta'
+ with self.assertRaises(AttributeError):
+ Okay2.color
+ self.assertEqual(Okay2().color, 'magenta')
+
+
+# Issue 5890: subclasses of DynamicClassAttribute do not preserve method __doc__ strings
+class PropertySub(DynamicClassAttribute):
+ """This is a subclass of DynamicClassAttribute"""
+
+class PropertySubSlots(DynamicClassAttribute):
+ """This is a subclass of DynamicClassAttribute that defines __slots__"""
+ __slots__ = ()
+
+class PropertySubclassTests(unittest.TestCase):
+
+ @unittest.skipIf(hasattr(PropertySubSlots, '__doc__'),
+ "__doc__ is already present, __slots__ will have no effect")
+ def test_slots_docstring_copy_exception(self):
+ try:
+ class Foo(object):
+ @PropertySubSlots
+ def spam(self):
+ """Trying to copy this docstring will raise an exception"""
+ return 1
+ print('\n',spam.__doc__)
+ except AttributeError:
+ pass
+ else:
+ raise Exception("AttributeError not raised")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_docstring_copy(self):
+ class Foo(object):
+ @PropertySub
+ def spam(self):
+ """spam wrapped in DynamicClassAttribute subclass"""
+ return 1
+ self.assertEqual(
+ Foo.__dict__['spam'].__doc__,
+ "spam wrapped in DynamicClassAttribute subclass")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_setter_copies_getter_docstring(self):
+ class Foo(object):
+ def __init__(self): self._spam = 1
+ @PropertySub
+ def spam(self):
+ """spam wrapped in DynamicClassAttribute subclass"""
+ return self._spam
+ @spam.setter
+ def spam(self, value):
+ """this docstring is ignored"""
+ self._spam = value
+ foo = Foo()
+ self.assertEqual(foo.spam, 1)
+ foo.spam = 2
+ self.assertEqual(foo.spam, 2)
+ self.assertEqual(
+ Foo.__dict__['spam'].__doc__,
+ "spam wrapped in DynamicClassAttribute subclass")
+ class FooSub(Foo):
+ spam = Foo.__dict__['spam']
+ @spam.setter
+ def spam(self, value):
+ """another ignored docstring"""
+ self._spam = 'eggs'
+ foosub = FooSub()
+ self.assertEqual(foosub.spam, 1)
+ foosub.spam = 7
+ self.assertEqual(foosub.spam, 'eggs')
+ self.assertEqual(
+ FooSub.__dict__['spam'].__doc__,
+ "spam wrapped in DynamicClassAttribute subclass")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_new_getter_new_docstring(self):
+
+ class Foo(object):
+ @PropertySub
+ def spam(self):
+ """a docstring"""
+ return 1
+ @spam.getter
+ def spam(self):
+ """a new docstring"""
+ return 2
+ self.assertEqual(Foo.__dict__['spam'].__doc__, "a new docstring")
+ class FooBase(object):
+ @PropertySub
+ def spam(self):
+ """a docstring"""
+ return 1
+ class Foo2(FooBase):
+ spam = FooBase.__dict__['spam']
+ @spam.getter
+ def spam(self):
+ """a new docstring"""
+ return 2
+ self.assertEqual(Foo.__dict__['spam'].__doc__, "a new docstring")
+
+
+
+def test_main():
+ run_unittest(PropertyTests, PropertySubclassTests)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/test/test_email/__init__.py b/Lib/test/test_email/__init__.py
index f206ace10c..d8896eed46 100644
--- a/Lib/test/test_email/__init__.py
+++ b/Lib/test/test_email/__init__.py
@@ -2,6 +2,7 @@ import os
import sys
import unittest
import test.support
+import collections
import email
from email.message import Message
from email._policybase import compat32
@@ -42,6 +43,8 @@ class TestEmailBase(unittest.TestCase):
# here we make minimal changes in the test_email tests compared to their
# pre-3.3 state.
policy = compat32
+ # Likewise, the default message object is Message.
+ message = Message
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
@@ -54,11 +57,23 @@ class TestEmailBase(unittest.TestCase):
with openfile(filename) as fp:
return email.message_from_file(fp, policy=self.policy)
- def _str_msg(self, string, message=Message, policy=None):
+ def _str_msg(self, string, message=None, policy=None):
if policy is None:
policy = self.policy
+ if message is None:
+ message = self.message
return email.message_from_string(string, message, policy=policy)
+ def _bytes_msg(self, bytestring, message=None, policy=None):
+ if policy is None:
+ policy = self.policy
+ if message is None:
+ message = self.message
+ return email.message_from_bytes(bytestring, message, policy=policy)
+
+ def _make_message(self):
+ return self.message(policy=self.policy)
+
def _bytes_repr(self, b):
return [repr(x) for x in b.splitlines(keepends=True)]
@@ -123,6 +138,7 @@ def parameterize(cls):
"""
paramdicts = {}
+ testers = collections.defaultdict(list)
for name, attr in cls.__dict__.items():
if name.endswith('_params'):
if not hasattr(attr, 'keys'):
@@ -134,7 +150,15 @@ def parameterize(cls):
d[n] = x
attr = d
paramdicts[name[:-7] + '_as_'] = attr
+ if '_as_' in name:
+ testers[name.split('_as_')[0] + '_as_'].append(name)
testfuncs = {}
+ for name in paramdicts:
+ if name not in testers:
+ raise ValueError("No tester found for {}".format(name))
+ for name in testers:
+ if name not in paramdicts:
+ raise ValueError("No params found for {}".format(name))
for name, attr in cls.__dict__.items():
for paramsname, paramsdict in paramdicts.items():
if name.startswith(paramsname):
diff --git a/Lib/test/test_email/test_contentmanager.py b/Lib/test/test_email/test_contentmanager.py
new file mode 100644
index 0000000000..1629e2d8fd
--- /dev/null
+++ b/Lib/test/test_email/test_contentmanager.py
@@ -0,0 +1,796 @@
+import unittest
+from test.test_email import TestEmailBase, parameterize
+import textwrap
+from email import policy
+from email.message import EmailMessage
+from email.contentmanager import ContentManager, raw_data_manager
+
+
+@parameterize
+class TestContentManager(TestEmailBase):
+
+ policy = policy.default
+ message = EmailMessage
+
+ get_key_params = {
+ 'full_type': (1, 'text/plain',),
+ 'maintype_only': (2, 'text',),
+ 'null_key': (3, '',),
+ }
+
+ def get_key_as_get_content_key(self, order, key):
+ def foo_getter(msg, foo=None):
+ bar = msg['X-Bar-Header']
+ return foo, bar
+ cm = ContentManager()
+ cm.add_get_handler(key, foo_getter)
+ m = self._make_message()
+ m['Content-Type'] = 'text/plain'
+ m['X-Bar-Header'] = 'foo'
+ self.assertEqual(cm.get_content(m, foo='bar'), ('bar', 'foo'))
+
+ def get_key_as_get_content_key_order(self, order, key):
+ def bar_getter(msg):
+ return msg['X-Bar-Header']
+ def foo_getter(msg):
+ return msg['X-Foo-Header']
+ cm = ContentManager()
+ cm.add_get_handler(key, foo_getter)
+ for precedence, key in self.get_key_params.values():
+ if precedence > order:
+ cm.add_get_handler(key, bar_getter)
+ m = self._make_message()
+ m['Content-Type'] = 'text/plain'
+ m['X-Bar-Header'] = 'bar'
+ m['X-Foo-Header'] = 'foo'
+ self.assertEqual(cm.get_content(m), ('foo'))
+
+ def test_get_content_raises_if_unknown_mimetype_and_no_default(self):
+ cm = ContentManager()
+ m = self._make_message()
+ m['Content-Type'] = 'text/plain'
+ with self.assertRaisesRegex(KeyError, 'text/plain'):
+ cm.get_content(m)
+
+ class BaseThing(str):
+ pass
+ baseobject_full_path = __name__ + '.' + 'TestContentManager.BaseThing'
+ class Thing(BaseThing):
+ pass
+ testobject_full_path = __name__ + '.' + 'TestContentManager.Thing'
+
+ set_key_params = {
+ 'type': (0, Thing,),
+ 'full_path': (1, testobject_full_path,),
+ 'qualname': (2, 'TestContentManager.Thing',),
+ 'name': (3, 'Thing',),
+ 'base_type': (4, BaseThing,),
+ 'base_full_path': (5, baseobject_full_path,),
+ 'base_qualname': (6, 'TestContentManager.BaseThing',),
+ 'base_name': (7, 'BaseThing',),
+ 'str_type': (8, str,),
+ 'str_full_path': (9, 'builtins.str',),
+ 'str_name': (10, 'str',), # str name and qualname are the same
+ 'null_key': (11, None,),
+ }
+
+ def set_key_as_set_content_key(self, order, key):
+ def foo_setter(msg, obj, foo=None):
+ msg['X-Foo-Header'] = foo
+ msg.set_payload(obj)
+ cm = ContentManager()
+ cm.add_set_handler(key, foo_setter)
+ m = self._make_message()
+ msg_obj = self.Thing()
+ cm.set_content(m, msg_obj, foo='bar')
+ self.assertEqual(m['X-Foo-Header'], 'bar')
+ self.assertEqual(m.get_payload(), msg_obj)
+
+ def set_key_as_set_content_key_order(self, order, key):
+ def foo_setter(msg, obj):
+ msg['X-FooBar-Header'] = 'foo'
+ msg.set_payload(obj)
+ def bar_setter(msg, obj):
+ msg['X-FooBar-Header'] = 'bar'
+ cm = ContentManager()
+ cm.add_set_handler(key, foo_setter)
+ for precedence, key in self.get_key_params.values():
+ if precedence > order:
+ cm.add_set_handler(key, bar_setter)
+ m = self._make_message()
+ msg_obj = self.Thing()
+ cm.set_content(m, msg_obj)
+ self.assertEqual(m['X-FooBar-Header'], 'foo')
+ self.assertEqual(m.get_payload(), msg_obj)
+
+ def test_set_content_raises_if_unknown_type_and_no_default(self):
+ cm = ContentManager()
+ m = self._make_message()
+ msg_obj = self.Thing()
+ with self.assertRaisesRegex(KeyError, self.testobject_full_path):
+ cm.set_content(m, msg_obj)
+
+ def test_set_content_raises_if_called_on_multipart(self):
+ cm = ContentManager()
+ m = self._make_message()
+ m['Content-Type'] = 'multipart/foo'
+ with self.assertRaises(TypeError):
+ cm.set_content(m, 'test')
+
+ def test_set_content_calls_clear_content(self):
+ m = self._make_message()
+ m['Content-Foo'] = 'bar'
+ m['Content-Type'] = 'text/html'
+ m['To'] = 'test'
+ m.set_payload('abc')
+ cm = ContentManager()
+ cm.add_set_handler(str, lambda *args, **kw: None)
+ m.set_content('xyz', content_manager=cm)
+ self.assertIsNone(m['Content-Foo'])
+ self.assertIsNone(m['Content-Type'])
+ self.assertEqual(m['To'], 'test')
+ self.assertIsNone(m.get_payload())
+
+
+@parameterize
+class TestRawDataManager(TestEmailBase):
+ # Note: these tests are dependent on the order in which headers are added
+ # to the message objects by the code. There's no defined ordering in
+ # RFC5322/MIME, so this makes the tests more fragile than the standards
+ # require. However, if the header order changes it is best to understand
+ # *why*, and make sure it isn't a subtle bug in whatever change was
+ # applied.
+
+ policy = policy.default.clone(max_line_length=60,
+ content_manager=raw_data_manager)
+ message = EmailMessage
+
+ def test_get_text_plain(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: text/plain
+
+ Basic text.
+ """))
+ self.assertEqual(raw_data_manager.get_content(m), "Basic text.\n")
+
+ def test_get_text_html(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: text/html
+
+ <p>Basic text.</p>
+ """))
+ self.assertEqual(raw_data_manager.get_content(m),
+ "<p>Basic text.</p>\n")
+
+ def test_get_text_plain_latin1(self):
+ m = self._bytes_msg(textwrap.dedent("""\
+ Content-Type: text/plain; charset=latin1
+
+ Basìc tëxt.
+ """).encode('latin1'))
+ self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")
+
+ def test_get_text_plain_latin1_quoted_printable(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: text/plain; charset="latin-1"
+ Content-Transfer-Encoding: quoted-printable
+
+ Bas=ECc t=EBxt.
+ """))
+ self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")
+
+ def test_get_text_plain_utf8_base64(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf8"
+ Content-Transfer-Encoding: base64
+
+ QmFzw6xjIHTDq3h0Lgo=
+ """))
+ self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")
+
+ def test_get_text_plain_bad_utf8_quoted_printable(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf8"
+ Content-Transfer-Encoding: quoted-printable
+
+ Bas=c3=acc t=c3=abxt=fd.
+ """))
+ self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt�.\n")
+
+ def test_get_text_plain_bad_utf8_quoted_printable_ignore_errors(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf8"
+ Content-Transfer-Encoding: quoted-printable
+
+ Bas=c3=acc t=c3=abxt=fd.
+ """))
+ self.assertEqual(raw_data_manager.get_content(m, errors='ignore'),
+ "Basìc tëxt.\n")
+
+ def test_get_text_plain_utf8_base64_recoverable_bad_CTE_data(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf8"
+ Content-Transfer-Encoding: base64
+
+ QmFzw6xjIHTDq3h0Lgo\xFF=
+ """))
+ self.assertEqual(raw_data_manager.get_content(m, errors='ignore'),
+ "Basìc tëxt.\n")
+
+ def test_get_text_invalid_keyword(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: text/plain
+
+ Basic text.
+ """))
+ with self.assertRaises(TypeError):
+ raw_data_manager.get_content(m, foo='ignore')
+
+ def test_get_non_text(self):
+ template = textwrap.dedent("""\
+ Content-Type: {}
+ Content-Transfer-Encoding: base64
+
+ Ym9ndXMgZGF0YQ==
+ """)
+ for maintype in 'audio image video application'.split():
+ with self.subTest(maintype=maintype):
+ m = self._str_msg(template.format(maintype+'/foo'))
+ self.assertEqual(raw_data_manager.get_content(m), b"bogus data")
+
+ def test_get_non_text_invalid_keyword(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: image/jpg
+ Content-Transfer-Encoding: base64
+
+ Ym9ndXMgZGF0YQ==
+ """))
+ with self.assertRaises(TypeError):
+ raw_data_manager.get_content(m, errors='ignore')
+
+ def test_get_raises_on_multipart(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: multipart/mixed; boundary="==="
+
+ --===
+ --===--
+ """))
+ with self.assertRaises(KeyError):
+ raw_data_manager.get_content(m)
+
+ def test_get_message_rfc822_and_external_body(self):
+ template = textwrap.dedent("""\
+ Content-Type: message/{}
+
+ To: foo@example.com
+ From: bar@example.com
+ Subject: example
+
+ an example message
+ """)
+ for subtype in 'rfc822 external-body'.split():
+ with self.subTest(subtype=subtype):
+ m = self._str_msg(template.format(subtype))
+ sub_msg = raw_data_manager.get_content(m)
+ self.assertIsInstance(sub_msg, self.message)
+ self.assertEqual(raw_data_manager.get_content(sub_msg),
+ "an example message\n")
+ self.assertEqual(sub_msg['to'], 'foo@example.com')
+ self.assertEqual(sub_msg['from'].addresses[0].username, 'bar')
+
+ def test_get_message_non_rfc822_or_external_body_yields_bytes(self):
+ m = self._str_msg(textwrap.dedent("""\
+ Content-Type: message/partial
+
+ To: foo@example.com
+ From: bar@example.com
+ Subject: example
+
+ The real body is in another message.
+ """))
+ self.assertEqual(raw_data_manager.get_content(m)[:10], b'To: foo@ex')
+
+ def test_set_text_plain(self):
+ m = self._make_message()
+ content = "Simple message.\n"
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(str(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 7bit
+
+ Simple message.
+ """))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_html(self):
+ m = self._make_message()
+ content = "<p>Simple message.</p>\n"
+ raw_data_manager.set_content(m, content, subtype='html')
+ self.assertEqual(str(m), textwrap.dedent("""\
+ Content-Type: text/html; charset="utf-8"
+ Content-Transfer-Encoding: 7bit
+
+ <p>Simple message.</p>
+ """))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_charset_latin_1(self):
+ m = self._make_message()
+ content = "Simple message.\n"
+ raw_data_manager.set_content(m, content, charset='latin-1')
+ self.assertEqual(str(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="iso-8859-1"
+ Content-Transfer-Encoding: 7bit
+
+ Simple message.
+ """))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_short_line_minimal_non_ascii_heuristics(self):
+ m = self._make_message()
+ content = "et là il est monté sur moi et il commence à m'éto.\n"
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 8bit
+
+ et là il est monté sur moi et il commence à m'éto.
+ """).encode('utf-8'))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_long_line_minimal_non_ascii_heuristics(self):
+ m = self._make_message()
+ content = ("j'ai un problème de python. il est sorti de son"
+ " vivarium. et là il est monté sur moi et il commence"
+ " à m'éto.\n")
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: quoted-printable
+
+ j'ai un probl=C3=A8me de python. il est sorti de son vivari=
+ um. et l=C3=A0 il est mont=C3=A9 sur moi et il commence =
+ =C3=A0 m'=C3=A9to.
+ """).encode('utf-8'))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_11_lines_long_line_minimal_non_ascii_heuristics(self):
+ m = self._make_message()
+ content = '\n'*10 + (
+ "j'ai un problème de python. il est sorti de son"
+ " vivarium. et là il est monté sur moi et il commence"
+ " à m'éto.\n")
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: quoted-printable
+ """ + '\n'*10 + """
+ j'ai un probl=C3=A8me de python. il est sorti de son vivari=
+ um. et l=C3=A0 il est mont=C3=A9 sur moi et il commence =
+ =C3=A0 m'=C3=A9to.
+ """).encode('utf-8'))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_maximal_non_ascii_heuristics(self):
+ m = self._make_message()
+ content = "áàäéèęöő.\n"
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 8bit
+
+ áàäéèęöő.
+ """).encode('utf-8'))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_11_lines_maximal_non_ascii_heuristics(self):
+ m = self._make_message()
+ content = '\n'*10 + "áàäéèęöő.\n"
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 8bit
+ """ + '\n'*10 + """
+ áàäéèęöő.
+ """).encode('utf-8'))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_long_line_maximal_non_ascii_heuristics(self):
+ m = self._make_message()
+ content = ("áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
+ "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
+ "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő.\n")
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: base64
+
+ w6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TDqcOoxJnD
+ tsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TDqcOo
+ xJnDtsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TD
+ qcOoxJnDtsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOg
+ w6TDqcOoxJnDtsWRLgo=
+ """).encode('utf-8'))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_11_lines_long_line_maximal_non_ascii_heuristics(self):
+ # Yes, it chooses "wrong" here. It's a heuristic. So this result
+ # could change if we come up with a better heuristic.
+ m = self._make_message()
+ content = ('\n'*10 +
+ "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
+ "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
+ "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő.\n")
+ raw_data_manager.set_content(m, "\n"*10 +
+ "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
+ "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő"
+ "áàäéèęöőáàäéèęöőáàäéèęöőáàäéèęöő.\n")
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: quoted-printable
+ """ + '\n'*10 + """
+ =C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=
+ =A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=
+ =C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=
+ =A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=
+ =C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=
+ =91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=
+ =C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=
+ =A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=
+ =C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=
+ =99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=
+ =C5=91.
+ """).encode('utf-8'))
+ self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_text_non_ascii_with_cte_7bit_raises(self):
+ m = self._make_message()
+ with self.assertRaises(UnicodeError):
+ raw_data_manager.set_content(m,"áàäéèęöő.\n", cte='7bit')
+
+ def test_set_text_non_ascii_with_charset_ascii_raises(self):
+ m = self._make_message()
+ with self.assertRaises(UnicodeError):
+ raw_data_manager.set_content(m,"áàäéèęöő.\n", charset='ascii')
+
+ def test_set_text_non_ascii_with_cte_7bit_and_charset_ascii_raises(self):
+ m = self._make_message()
+ with self.assertRaises(UnicodeError):
+ raw_data_manager.set_content(m,"áàäéèęöő.\n", cte='7bit', charset='ascii')
+
+ def test_set_message(self):
+ m = self._make_message()
+ m['Subject'] = "Forwarded message"
+ content = self._make_message()
+ content['To'] = 'python@vivarium.org'
+ content['From'] = 'police@monty.org'
+ content['Subject'] = "get back in your box"
+ content.set_content("Or face the comfy chair.")
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(str(m), textwrap.dedent("""\
+ Subject: Forwarded message
+ Content-Type: message/rfc822
+ Content-Transfer-Encoding: 8bit
+
+ To: python@vivarium.org
+ From: police@monty.org
+ Subject: get back in your box
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+
+ Or face the comfy chair.
+ """))
+ payload = m.get_payload(0)
+ self.assertIsInstance(payload, self.message)
+ self.assertEqual(str(payload), str(content))
+ self.assertIsInstance(m.get_content(), self.message)
+ self.assertEqual(str(m.get_content()), str(content))
+
+ def test_set_message_with_non_ascii_and_coercion_to_7bit(self):
+ m = self._make_message()
+ m['Subject'] = "Escape report"
+ content = self._make_message()
+ content['To'] = 'police@monty.org'
+ content['From'] = 'victim@monty.org'
+ content['Subject'] = "Help"
+ content.set_content("j'ai un problème de python. il est sorti de son"
+ " vivarium.")
+ raw_data_manager.set_content(m, content)
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Subject: Escape report
+ Content-Type: message/rfc822
+ Content-Transfer-Encoding: 8bit
+
+ To: police@monty.org
+ From: victim@monty.org
+ Subject: Help
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 8bit
+ MIME-Version: 1.0
+
+ j'ai un problème de python. il est sorti de son vivarium.
+ """).encode('utf-8'))
+ # The choice of base64 for the body encoding is because generator
+ # doesn't bother with heuristics and uses it unconditionally for utf-8
+ # text.
+ # XXX: the first cte should be 7bit, too...that's a generator bug.
+ # XXX: the line length in the body also looks like a generator bug.
+ self.assertEqual(m.as_string(maxheaderlen=self.policy.max_line_length),
+ textwrap.dedent("""\
+ Subject: Escape report
+ Content-Type: message/rfc822
+ Content-Transfer-Encoding: 8bit
+
+ To: police@monty.org
+ From: victim@monty.org
+ Subject: Help
+ Content-Type: text/plain; charset="utf-8"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: base64
+
+ aidhaSB1biBwcm9ibMOobWUgZGUgcHl0aG9uLiBpbCBlc3Qgc29ydGkgZGUgc29uIHZpdmFyaXVt
+ Lgo=
+ """))
+ self.assertIsInstance(m.get_content(), self.message)
+ self.assertEqual(str(m.get_content()), str(content))
+
+ def test_set_message_invalid_cte_raises(self):
+ m = self._make_message()
+ content = self._make_message()
+ for cte in 'quoted-printable base64'.split():
+ for subtype in 'rfc822 external-body'.split():
+ with self.subTest(cte=cte, subtype=subtype):
+ with self.assertRaises(ValueError) as ar:
+ m.set_content(content, subtype, cte=cte)
+ exc = str(ar.exception)
+ self.assertIn(cte, exc)
+ self.assertIn(subtype, exc)
+ subtype = 'external-body'
+ for cte in '8bit binary'.split():
+ with self.subTest(cte=cte, subtype=subtype):
+ with self.assertRaises(ValueError) as ar:
+ m.set_content(content, subtype, cte=cte)
+ exc = str(ar.exception)
+ self.assertIn(cte, exc)
+ self.assertIn(subtype, exc)
+
+ def test_set_image_jpg(self):
+ for content in (b"bogus content",
+ bytearray(b"bogus content"),
+ memoryview(b"bogus content")):
+ with self.subTest(content=content):
+ m = self._make_message()
+ raw_data_manager.set_content(m, content, 'image', 'jpeg')
+ self.assertEqual(str(m), textwrap.dedent("""\
+ Content-Type: image/jpeg
+ Content-Transfer-Encoding: base64
+
+ Ym9ndXMgY29udGVudA==
+ """))
+ self.assertEqual(m.get_payload(decode=True), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_audio_aif_with_quoted_printable_cte(self):
+ # Why you would use qp, I don't know, but it is technically supported.
+ # XXX: the incorrect line length is because binascii.b2a_qp doesn't
+ # support a line length parameter, but we must use it to get newline
+ # encoding.
+ # XXX: what about that lack of tailing newline? Do we actually handle
+ # that correctly in all cases? That is, if the *source* has an
+ # unencoded newline, do we add an extra newline to the returned payload
+ # or not? And can that actually be disambiguated based on the RFC?
+ m = self._make_message()
+ content = b'b\xFFgus\tcon\nt\rent ' + b'z'*100
+ m.set_content(content, 'audio', 'aif', cte='quoted-printable')
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: audio/aif
+ Content-Transfer-Encoding: quoted-printable
+ MIME-Version: 1.0
+
+ b=FFgus=09con=0At=0Dent=20zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz=
+ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz""").encode('latin-1'))
+ self.assertEqual(m.get_payload(decode=True), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_video_mpeg_with_binary_cte(self):
+ m = self._make_message()
+ content = b'b\xFFgus\tcon\nt\rent ' + b'z'*100
+ m.set_content(content, 'video', 'mpeg', cte='binary')
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: video/mpeg
+ Content-Transfer-Encoding: binary
+ MIME-Version: 1.0
+
+ """).encode('ascii') +
+ # XXX: the second \n ought to be a \r, but generator gets it wrong.
+ # THIS MEANS WE DON'T ACTUALLY SUPPORT THE 'binary' CTE.
+ b'b\xFFgus\tcon\nt\nent zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' +
+ b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz')
+ self.assertEqual(m.get_payload(decode=True), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_application_octet_stream_with_8bit_cte(self):
+ # In 8bit mode, univeral line end logic applies. It is up to the
+ # application to make sure the lines are short enough; we don't check.
+ m = self._make_message()
+ content = b'b\xFFgus\tcon\nt\rent\n' + b'z'*60 + b'\n'
+ m.set_content(content, 'application', 'octet-stream', cte='8bit')
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: application/octet-stream
+ Content-Transfer-Encoding: 8bit
+ MIME-Version: 1.0
+
+ """).encode('ascii') +
+ b'b\xFFgus\tcon\nt\nent\n' +
+ b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\n')
+ self.assertEqual(m.get_payload(decode=True), content)
+ self.assertEqual(m.get_content(), content)
+
+ def test_set_headers_from_header_objects(self):
+ m = self._make_message()
+ content = "Simple message.\n"
+ header_factory = self.policy.header_factory
+ raw_data_manager.set_content(m, content, headers=(
+ header_factory("To", "foo@example.com"),
+ header_factory("From", "foo@example.com"),
+ header_factory("Subject", "I'm talking to myself.")))
+ self.assertEqual(str(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ To: foo@example.com
+ From: foo@example.com
+ Subject: I'm talking to myself.
+ Content-Transfer-Encoding: 7bit
+
+ Simple message.
+ """))
+
+ def test_set_headers_from_strings(self):
+ m = self._make_message()
+ content = "Simple message.\n"
+ raw_data_manager.set_content(m, content, headers=(
+ "X-Foo-Header: foo",
+ "X-Bar-Header: bar",))
+ self.assertEqual(str(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ X-Foo-Header: foo
+ X-Bar-Header: bar
+ Content-Transfer-Encoding: 7bit
+
+ Simple message.
+ """))
+
+ def test_set_headers_with_invalid_duplicate_string_header_raises(self):
+ m = self._make_message()
+ content = "Simple message.\n"
+ with self.assertRaisesRegex(ValueError, 'Content-Type'):
+ raw_data_manager.set_content(m, content, headers=(
+ "Content-Type: foo/bar",)
+ )
+
+ def test_set_headers_with_invalid_duplicate_header_header_raises(self):
+ m = self._make_message()
+ content = "Simple message.\n"
+ header_factory = self.policy.header_factory
+ with self.assertRaisesRegex(ValueError, 'Content-Type'):
+ raw_data_manager.set_content(m, content, headers=(
+ header_factory("Content-Type", " foo/bar"),)
+ )
+
+ def test_set_headers_with_defective_string_header_raises(self):
+ m = self._make_message()
+ content = "Simple message.\n"
+ with self.assertRaisesRegex(ValueError, 'a@fairly@@invalid@address'):
+ raw_data_manager.set_content(m, content, headers=(
+ 'To: a@fairly@@invalid@address',)
+ )
+ print(m['To'].defects)
+
+ def test_set_headers_with_defective_header_header_raises(self):
+ m = self._make_message()
+ content = "Simple message.\n"
+ header_factory = self.policy.header_factory
+ with self.assertRaisesRegex(ValueError, 'a@fairly@@invalid@address'):
+ raw_data_manager.set_content(m, content, headers=(
+ header_factory('To', 'a@fairly@@invalid@address'),)
+ )
+ print(m['To'].defects)
+
+ def test_set_disposition_inline(self):
+ m = self._make_message()
+ m.set_content('foo', disposition='inline')
+ self.assertEqual(m['Content-Disposition'], 'inline')
+
+ def test_set_disposition_attachment(self):
+ m = self._make_message()
+ m.set_content('foo', disposition='attachment')
+ self.assertEqual(m['Content-Disposition'], 'attachment')
+
+ def test_set_disposition_foo(self):
+ m = self._make_message()
+ m.set_content('foo', disposition='foo')
+ self.assertEqual(m['Content-Disposition'], 'foo')
+
+ # XXX: we should have a 'strict' policy mode (beyond raise_on_defect) that
+ # would cause 'foo' above to raise.
+
+ def test_set_filename(self):
+ m = self._make_message()
+ m.set_content('foo', filename='bar.txt')
+ self.assertEqual(m['Content-Disposition'],
+ 'attachment; filename="bar.txt"')
+
+ def test_set_filename_and_disposition_inline(self):
+ m = self._make_message()
+ m.set_content('foo', disposition='inline', filename='bar.txt')
+ self.assertEqual(m['Content-Disposition'], 'inline; filename="bar.txt"')
+
+ def test_set_non_ascii_filename(self):
+ m = self._make_message()
+ m.set_content('foo', filename='ábárî.txt')
+ self.assertEqual(bytes(m), textwrap.dedent("""\
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 7bit
+ Content-Disposition: attachment;
+ filename*=utf-8''%C3%A1b%C3%A1r%C3%AE.txt
+ MIME-Version: 1.0
+
+ foo
+ """).encode('ascii'))
+
+ content_object_params = {
+ 'text_plain': ('content', ()),
+ 'text_html': ('content', ('html',)),
+ 'application_octet_stream': (b'content',
+ ('application', 'octet_stream')),
+ 'image_jpeg': (b'content', ('image', 'jpeg')),
+ 'message_rfc822': (message(), ()),
+ 'message_external_body': (message(), ('external-body',)),
+ }
+
+ def content_object_as_header_receiver(self, obj, mimetype):
+ m = self._make_message()
+ m.set_content(obj, *mimetype, headers=(
+ 'To: foo@example.com',
+ 'From: bar@simple.net'))
+ self.assertEqual(m['to'], 'foo@example.com')
+ self.assertEqual(m['from'], 'bar@simple.net')
+
+ def content_object_as_disposition_inline_receiver(self, obj, mimetype):
+ m = self._make_message()
+ m.set_content(obj, *mimetype, disposition='inline')
+ self.assertEqual(m['Content-Disposition'], 'inline')
+
+ def content_object_as_non_ascii_filename_receiver(self, obj, mimetype):
+ m = self._make_message()
+ m.set_content(obj, *mimetype, disposition='inline', filename='bár.txt')
+ self.assertEqual(m['Content-Disposition'], 'inline; filename="bár.txt"')
+ self.assertEqual(m.get_filename(), "bár.txt")
+ self.assertEqual(m['Content-Disposition'].params['filename'], "bár.txt")
+
+ def content_object_as_cid_receiver(self, obj, mimetype):
+ m = self._make_message()
+ m.set_content(obj, *mimetype, cid='some_random_stuff')
+ self.assertEqual(m['Content-ID'], 'some_random_stuff')
+
+ def content_object_as_params_receiver(self, obj, mimetype):
+ m = self._make_message()
+ params = {'foo': 'bár', 'abc': 'xyz'}
+ m.set_content(obj, *mimetype, params=params)
+ if isinstance(obj, str):
+ params['charset'] = 'utf-8'
+ self.assertEqual(m['Content-Type'].params, params)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
index cd4f757c1f..57f12ae466 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -249,15 +249,42 @@ class TestMessageAPI(TestEmailBase):
self.assertIn('TO', msg)
def test_as_string(self):
- eq = self.ndiffAssertEqual
msg = self._msgobj('msg_01.txt')
with openfile('msg_01.txt') as fp:
text = fp.read()
- eq(text, str(msg))
+ self.assertEqual(text, str(msg))
fullrepr = msg.as_string(unixfrom=True)
lines = fullrepr.split('\n')
self.assertTrue(lines[0].startswith('From '))
- eq(text, NL.join(lines[1:]))
+ self.assertEqual(text, NL.join(lines[1:]))
+
+ def test_as_string_policy(self):
+ msg = self._msgobj('msg_01.txt')
+ newpolicy = msg.policy.clone(linesep='\r\n')
+ fullrepr = msg.as_string(policy=newpolicy)
+ s = StringIO()
+ g = Generator(s, policy=newpolicy)
+ g.flatten(msg)
+ self.assertEqual(fullrepr, s.getvalue())
+
+ def test_as_bytes(self):
+ msg = self._msgobj('msg_01.txt')
+ with openfile('msg_01.txt') as fp:
+ data = fp.read().encode('ascii')
+ self.assertEqual(data, bytes(msg))
+ fullrepr = msg.as_bytes(unixfrom=True)
+ lines = fullrepr.split(b'\n')
+ self.assertTrue(lines[0].startswith(b'From '))
+ self.assertEqual(data, b'\n'.join(lines[1:]))
+
+ def test_as_bytes_policy(self):
+ msg = self._msgobj('msg_01.txt')
+ newpolicy = msg.policy.clone(linesep='\r\n')
+ fullrepr = msg.as_bytes(policy=newpolicy)
+ s = BytesIO()
+ g = BytesGenerator(s,policy=newpolicy)
+ g.flatten(msg)
+ self.assertEqual(fullrepr, s.getvalue())
# test_headerregistry.TestContentTypeHeader.bad_params
def test_bad_param(self):
diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py
index f829f83e32..f2df3720eb 100644
--- a/Lib/test/test_email/test_headerregistry.py
+++ b/Lib/test/test_email/test_headerregistry.py
@@ -661,7 +661,7 @@ class TestContentTypeHeader(TestHeaderBase):
'text/plain; name="ascii_is_the_default"'),
'rfc2231_bad_character_in_charset_parameter_value': (
- "text/plain; charset*=ascii''utf-8%E2%80%9D",
+ "text/plain; charset*=ascii''utf-8%F1%F2%F3",
'text/plain',
'text',
'plain',
@@ -669,6 +669,18 @@ class TestContentTypeHeader(TestHeaderBase):
[errors.UndecodableBytesDefect],
'text/plain; charset="utf-8\uFFFD\uFFFD\uFFFD"'),
+ 'rfc2231_utf_8_in_supposedly_ascii_charset_parameter_value': (
+ "text/plain; charset*=ascii''utf-8%E2%80%9D",
+ 'text/plain',
+ 'text',
+ 'plain',
+ {'charset': 'utf-8”'},
+ [errors.UndecodableBytesDefect],
+ 'text/plain; charset="utf-8”"',
+ ),
+ # XXX: if the above were *re*folded, it would get tagged as utf-8
+ # instead of ascii in the param, since it now contains non-ASCII.
+
'rfc2231_encoded_then_unencoded_segments': (
('application/x-foo;'
'\tname*0*="us-ascii\'en-us\'My";'
diff --git a/Lib/test/test_email/test_message.py b/Lib/test/test_email/test_message.py
index 8cc3f80e46..e0ebb8ad6c 100644
--- a/Lib/test/test_email/test_message.py
+++ b/Lib/test/test_email/test_message.py
@@ -1,6 +1,13 @@
import unittest
+import textwrap
from email import policy
-from test.test_email import TestEmailBase
+from email.message import EmailMessage, MIMEPart
+from test.test_email import TestEmailBase, parameterize
+
+
+# Helper.
+def first(iterable):
+ return next(filter(lambda x: x is not None, iterable), None)
class Test(TestEmailBase):
@@ -14,5 +21,738 @@ class Test(TestEmailBase):
m['To'] = 'xyz@abc'
+@parameterize
+class TestEmailMessageBase:
+
+ policy = policy.default
+
+ # The first argument is a triple (related, html, plain) of indices into the
+ # list returned by 'walk' called on a Message constructed from the third.
+ # The indices indicate which part should match the corresponding part-type
+ # when passed to get_body (ie: the "first" part of that type in the
+ # message). The second argument is a list of indices into the 'walk' list
+ # of the attachments that should be returned by a call to
+ # 'iter_attachments'. The third argument is a list of indices into 'walk'
+ # that should be returned by a call to 'iter_parts'. Note that the first
+ # item returned by 'walk' is the Message itself.
+
+ message_params = {
+
+ 'empty_message': (
+ (None, None, 0),
+ (),
+ (),
+ ""),
+
+ 'non_mime_plain': (
+ (None, None, 0),
+ (),
+ (),
+ textwrap.dedent("""\
+ To: foo@example.com
+
+ simple text body
+ """)),
+
+ 'mime_non_text': (
+ (None, None, None),
+ (),
+ (),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: image/jpg
+
+ bogus body.
+ """)),
+
+ 'plain_html_alternative': (
+ (None, 2, 1),
+ (),
+ (1, 2),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/alternative; boundary="==="
+
+ preamble
+
+ --===
+ Content-Type: text/plain
+
+ simple body
+
+ --===
+ Content-Type: text/html
+
+ <p>simple body</p>
+ --===--
+ """)),
+
+ 'plain_html_mixed': (
+ (None, 2, 1),
+ (),
+ (1, 2),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="==="
+
+ preamble
+
+ --===
+ Content-Type: text/plain
+
+ simple body
+
+ --===
+ Content-Type: text/html
+
+ <p>simple body</p>
+
+ --===--
+ """)),
+
+ 'plain_html_attachment_mixed': (
+ (None, None, 1),
+ (2,),
+ (1, 2),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="==="
+
+ --===
+ Content-Type: text/plain
+
+ simple body
+
+ --===
+ Content-Type: text/html
+ Content-Disposition: attachment
+
+ <p>simple body</p>
+
+ --===--
+ """)),
+
+ 'html_text_attachment_mixed': (
+ (None, 2, None),
+ (1,),
+ (1, 2),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="==="
+
+ --===
+ Content-Type: text/plain
+ Content-Disposition: AtTaChment
+
+ simple body
+
+ --===
+ Content-Type: text/html
+
+ <p>simple body</p>
+
+ --===--
+ """)),
+
+ 'html_text_attachment_inline_mixed': (
+ (None, 2, 1),
+ (),
+ (1, 2),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="==="
+
+ --===
+ Content-Type: text/plain
+ Content-Disposition: InLine
+
+ simple body
+
+ --===
+ Content-Type: text/html
+ Content-Disposition: inline
+
+ <p>simple body</p>
+
+ --===--
+ """)),
+
+ # RFC 2387
+ 'related': (
+ (0, 1, None),
+ (2,),
+ (1, 2),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/related; boundary="==="; type=text/html
+
+ --===
+ Content-Type: text/html
+
+ <p>simple body</p>
+
+ --===
+ Content-Type: image/jpg
+ Content-ID: <image1>
+
+ bogus data
+
+ --===--
+ """)),
+
+ # This message structure will probably never be seen in the wild, but
+ # it proves we distinguish between text parts based on 'start'. The
+ # content would not, of course, actually work :)
+ 'related_with_start': (
+ (0, 2, None),
+ (1,),
+ (1, 2),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/related; boundary="==="; type=text/html;
+ start="<body>"
+
+ --===
+ Content-Type: text/html
+ Content-ID: <include>
+
+ useless text
+
+ --===
+ Content-Type: text/html
+ Content-ID: <body>
+
+ <p>simple body</p>
+ <!--#include file="<include>"-->
+
+ --===--
+ """)),
+
+
+ 'mixed_alternative_plain_related': (
+ (3, 4, 2),
+ (6, 7),
+ (1, 6, 7),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="==="
+
+ --===
+ Content-Type: multipart/alternative; boundary="+++"
+
+ --+++
+ Content-Type: text/plain
+
+ simple body
+
+ --+++
+ Content-Type: multipart/related; boundary="___"
+
+ --___
+ Content-Type: text/html
+
+ <p>simple body</p>
+
+ --___
+ Content-Type: image/jpg
+ Content-ID: <image1@cid>
+
+ bogus jpg body
+
+ --___--
+
+ --+++--
+
+ --===
+ Content-Type: image/jpg
+ Content-Disposition: attachment
+
+ bogus jpg body
+
+ --===
+ Content-Type: image/jpg
+ Content-Disposition: AttacHmenT
+
+ another bogus jpg body
+
+ --===--
+ """)),
+
+ # This structure suggested by Stephen J. Turnbull...may not exist/be
+ # supported in the wild, but we want to support it.
+ 'mixed_related_alternative_plain_html': (
+ (1, 4, 3),
+ (6, 7),
+ (1, 6, 7),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="==="
+
+ --===
+ Content-Type: multipart/related; boundary="+++"
+
+ --+++
+ Content-Type: multipart/alternative; boundary="___"
+
+ --___
+ Content-Type: text/plain
+
+ simple body
+
+ --___
+ Content-Type: text/html
+
+ <p>simple body</p>
+
+ --___--
+
+ --+++
+ Content-Type: image/jpg
+ Content-ID: <image1@cid>
+
+ bogus jpg body
+
+ --+++--
+
+ --===
+ Content-Type: image/jpg
+ Content-Disposition: attachment
+
+ bogus jpg body
+
+ --===
+ Content-Type: image/jpg
+ Content-Disposition: attachment
+
+ another bogus jpg body
+
+ --===--
+ """)),
+
+ # Same thing, but proving we only look at the root part, which is the
+ # first one if there isn't any start parameter. That is, this is a
+ # broken related.
+ 'mixed_related_alternative_plain_html_wrong_order': (
+ (1, None, None),
+ (6, 7),
+ (1, 6, 7),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="==="
+
+ --===
+ Content-Type: multipart/related; boundary="+++"
+
+ --+++
+ Content-Type: image/jpg
+ Content-ID: <image1@cid>
+
+ bogus jpg body
+
+ --+++
+ Content-Type: multipart/alternative; boundary="___"
+
+ --___
+ Content-Type: text/plain
+
+ simple body
+
+ --___
+ Content-Type: text/html
+
+ <p>simple body</p>
+
+ --___--
+
+ --+++--
+
+ --===
+ Content-Type: image/jpg
+ Content-Disposition: attachment
+
+ bogus jpg body
+
+ --===
+ Content-Type: image/jpg
+ Content-Disposition: attachment
+
+ another bogus jpg body
+
+ --===--
+ """)),
+
+ 'message_rfc822': (
+ (None, None, None),
+ (),
+ (),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: message/rfc822
+
+ To: bar@example.com
+ From: robot@examp.com
+
+ this is a message body.
+ """)),
+
+ 'mixed_text_message_rfc822': (
+ (None, None, 1),
+ (2,),
+ (1, 2),
+ textwrap.dedent("""\
+ To: foo@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="==="
+
+ --===
+ Content-Type: text/plain
+
+ Your message has bounced, ser.
+
+ --===
+ Content-Type: message/rfc822
+
+ To: bar@example.com
+ From: robot@examp.com
+
+ this is a message body.
+
+ --===--
+ """)),
+
+ }
+
+ def message_as_get_body(self, body_parts, attachments, parts, msg):
+ m = self._str_msg(msg)
+ allparts = list(m.walk())
+ expected = [None if n is None else allparts[n] for n in body_parts]
+ related = 0; html = 1; plain = 2
+ self.assertEqual(m.get_body(), first(expected))
+ self.assertEqual(m.get_body(preferencelist=(
+ 'related', 'html', 'plain')),
+ first(expected))
+ self.assertEqual(m.get_body(preferencelist=('related', 'html')),
+ first(expected[related:html+1]))
+ self.assertEqual(m.get_body(preferencelist=('related', 'plain')),
+ first([expected[related], expected[plain]]))
+ self.assertEqual(m.get_body(preferencelist=('html', 'plain')),
+ first(expected[html:plain+1]))
+ self.assertEqual(m.get_body(preferencelist=['related']),
+ expected[related])
+ self.assertEqual(m.get_body(preferencelist=['html']), expected[html])
+ self.assertEqual(m.get_body(preferencelist=['plain']), expected[plain])
+ self.assertEqual(m.get_body(preferencelist=('plain', 'html')),
+ first(expected[plain:html-1:-1]))
+ self.assertEqual(m.get_body(preferencelist=('plain', 'related')),
+ first([expected[plain], expected[related]]))
+ self.assertEqual(m.get_body(preferencelist=('html', 'related')),
+ first(expected[html::-1]))
+ self.assertEqual(m.get_body(preferencelist=('plain', 'html', 'related')),
+ first(expected[::-1]))
+ self.assertEqual(m.get_body(preferencelist=('html', 'plain', 'related')),
+ first([expected[html],
+ expected[plain],
+ expected[related]]))
+
+ def message_as_iter_attachment(self, body_parts, attachments, parts, msg):
+ m = self._str_msg(msg)
+ allparts = list(m.walk())
+ attachments = [allparts[n] for n in attachments]
+ self.assertEqual(list(m.iter_attachments()), attachments)
+
+ def message_as_iter_parts(self, body_parts, attachments, parts, msg):
+ m = self._str_msg(msg)
+ allparts = list(m.walk())
+ parts = [allparts[n] for n in parts]
+ self.assertEqual(list(m.iter_parts()), parts)
+
+ class _TestContentManager:
+ def get_content(self, msg, *args, **kw):
+ return msg, args, kw
+ def set_content(self, msg, *args, **kw):
+ self.msg = msg
+ self.args = args
+ self.kw = kw
+
+ def test_get_content_with_cm(self):
+ m = self._str_msg('')
+ cm = self._TestContentManager()
+ self.assertEqual(m.get_content(content_manager=cm), (m, (), {}))
+ msg, args, kw = m.get_content('foo', content_manager=cm, bar=1, k=2)
+ self.assertEqual(msg, m)
+ self.assertEqual(args, ('foo',))
+ self.assertEqual(kw, dict(bar=1, k=2))
+
+ def test_get_content_default_cm_comes_from_policy(self):
+ p = policy.default.clone(content_manager=self._TestContentManager())
+ m = self._str_msg('', policy=p)
+ self.assertEqual(m.get_content(), (m, (), {}))
+ msg, args, kw = m.get_content('foo', bar=1, k=2)
+ self.assertEqual(msg, m)
+ self.assertEqual(args, ('foo',))
+ self.assertEqual(kw, dict(bar=1, k=2))
+
+ def test_set_content_with_cm(self):
+ m = self._str_msg('')
+ cm = self._TestContentManager()
+ m.set_content(content_manager=cm)
+ self.assertEqual(cm.msg, m)
+ self.assertEqual(cm.args, ())
+ self.assertEqual(cm.kw, {})
+ m.set_content('foo', content_manager=cm, bar=1, k=2)
+ self.assertEqual(cm.msg, m)
+ self.assertEqual(cm.args, ('foo',))
+ self.assertEqual(cm.kw, dict(bar=1, k=2))
+
+ def test_set_content_default_cm_comes_from_policy(self):
+ cm = self._TestContentManager()
+ p = policy.default.clone(content_manager=cm)
+ m = self._str_msg('', policy=p)
+ m.set_content()
+ self.assertEqual(cm.msg, m)
+ self.assertEqual(cm.args, ())
+ self.assertEqual(cm.kw, {})
+ m.set_content('foo', bar=1, k=2)
+ self.assertEqual(cm.msg, m)
+ self.assertEqual(cm.args, ('foo',))
+ self.assertEqual(cm.kw, dict(bar=1, k=2))
+
+ # outcome is whether xxx_method should raise ValueError error when called
+ # on multipart/subtype. Blank outcome means it depends on xxx (add
+ # succeeds, make raises). Note: 'none' means there are content-type
+ # headers but payload is None...this happening in practice would be very
+ # unusual, so treating it as if there were content seems reasonable.
+ # method subtype outcome
+ subtype_params = (
+ ('related', 'no_content', 'succeeds'),
+ ('related', 'none', 'succeeds'),
+ ('related', 'plain', 'succeeds'),
+ ('related', 'related', ''),
+ ('related', 'alternative', 'raises'),
+ ('related', 'mixed', 'raises'),
+ ('alternative', 'no_content', 'succeeds'),
+ ('alternative', 'none', 'succeeds'),
+ ('alternative', 'plain', 'succeeds'),
+ ('alternative', 'related', 'succeeds'),
+ ('alternative', 'alternative', ''),
+ ('alternative', 'mixed', 'raises'),
+ ('mixed', 'no_content', 'succeeds'),
+ ('mixed', 'none', 'succeeds'),
+ ('mixed', 'plain', 'succeeds'),
+ ('mixed', 'related', 'succeeds'),
+ ('mixed', 'alternative', 'succeeds'),
+ ('mixed', 'mixed', ''),
+ )
+
+ def _make_subtype_test_message(self, subtype):
+ m = self.message()
+ payload = None
+ msg_headers = [
+ ('To', 'foo@bar.com'),
+ ('From', 'bar@foo.com'),
+ ]
+ if subtype != 'no_content':
+ ('content-shadow', 'Logrus'),
+ msg_headers.append(('X-Random-Header', 'Corwin'))
+ if subtype == 'text':
+ payload = ''
+ msg_headers.append(('Content-Type', 'text/plain'))
+ m.set_payload('')
+ elif subtype != 'no_content':
+ payload = []
+ msg_headers.append(('Content-Type', 'multipart/' + subtype))
+ msg_headers.append(('X-Trump', 'Random'))
+ m.set_payload(payload)
+ for name, value in msg_headers:
+ m[name] = value
+ return m, msg_headers, payload
+
+ def _check_disallowed_subtype_raises(self, m, method_name, subtype, method):
+ with self.assertRaises(ValueError) as ar:
+ getattr(m, method)()
+ exc_text = str(ar.exception)
+ self.assertIn(subtype, exc_text)
+ self.assertIn(method_name, exc_text)
+
+ def _check_make_multipart(self, m, msg_headers, payload):
+ count = 0
+ for name, value in msg_headers:
+ if not name.lower().startswith('content-'):
+ self.assertEqual(m[name], value)
+ count += 1
+ self.assertEqual(len(m), count+1) # +1 for new Content-Type
+ part = next(m.iter_parts())
+ count = 0
+ for name, value in msg_headers:
+ if name.lower().startswith('content-'):
+ self.assertEqual(part[name], value)
+ count += 1
+ self.assertEqual(len(part), count)
+ self.assertEqual(part.get_payload(), payload)
+
+ def subtype_as_make(self, method, subtype, outcome):
+ m, msg_headers, payload = self._make_subtype_test_message(subtype)
+ make_method = 'make_' + method
+ if outcome in ('', 'raises'):
+ self._check_disallowed_subtype_raises(m, method, subtype, make_method)
+ return
+ getattr(m, make_method)()
+ self.assertEqual(m.get_content_maintype(), 'multipart')
+ self.assertEqual(m.get_content_subtype(), method)
+ if subtype == 'no_content':
+ self.assertEqual(len(m.get_payload()), 0)
+ self.assertEqual(m.items(),
+ msg_headers + [('Content-Type',
+ 'multipart/'+method)])
+ else:
+ self.assertEqual(len(m.get_payload()), 1)
+ self._check_make_multipart(m, msg_headers, payload)
+
+ def subtype_as_make_with_boundary(self, method, subtype, outcome):
+ # Doing all variation is a bit of overkill...
+ m = self.message()
+ if outcome in ('', 'raises'):
+ m['Content-Type'] = 'multipart/' + subtype
+ with self.assertRaises(ValueError) as cm:
+ getattr(m, 'make_' + method)()
+ return
+ if subtype == 'plain':
+ m['Content-Type'] = 'text/plain'
+ elif subtype != 'no_content':
+ m['Content-Type'] = 'multipart/' + subtype
+ getattr(m, 'make_' + method)(boundary="abc")
+ self.assertTrue(m.is_multipart())
+ self.assertEqual(m.get_boundary(), 'abc')
+
+ def test_policy_on_part_made_by_make_comes_from_message(self):
+ for method in ('make_related', 'make_alternative', 'make_mixed'):
+ m = self.message(policy=self.policy.clone(content_manager='foo'))
+ m['Content-Type'] = 'text/plain'
+ getattr(m, method)()
+ self.assertEqual(m.get_payload(0).policy.content_manager, 'foo')
+
+ class _TestSetContentManager:
+ def set_content(self, msg, content, *args, **kw):
+ msg['Content-Type'] = 'text/plain'
+ msg.set_payload(content)
+
+ def subtype_as_add(self, method, subtype, outcome):
+ m, msg_headers, payload = self._make_subtype_test_message(subtype)
+ cm = self._TestSetContentManager()
+ add_method = 'add_attachment' if method=='mixed' else 'add_' + method
+ if outcome == 'raises':
+ self._check_disallowed_subtype_raises(m, method, subtype, add_method)
+ return
+ getattr(m, add_method)('test', content_manager=cm)
+ self.assertEqual(m.get_content_maintype(), 'multipart')
+ self.assertEqual(m.get_content_subtype(), method)
+ if method == subtype or subtype == 'no_content':
+ self.assertEqual(len(m.get_payload()), 1)
+ for name, value in msg_headers:
+ self.assertEqual(m[name], value)
+ part = m.get_payload()[0]
+ else:
+ self.assertEqual(len(m.get_payload()), 2)
+ self._check_make_multipart(m, msg_headers, payload)
+ part = m.get_payload()[1]
+ self.assertEqual(part.get_content_type(), 'text/plain')
+ self.assertEqual(part.get_payload(), 'test')
+ if method=='mixed':
+ self.assertEqual(part['Content-Disposition'], 'attachment')
+ elif method=='related':
+ self.assertEqual(part['Content-Disposition'], 'inline')
+ else:
+ # Otherwise we don't guess.
+ self.assertIsNone(part['Content-Disposition'])
+
+ class _TestSetRaisingContentManager:
+ def set_content(self, msg, content, *args, **kw):
+ raise Exception('test')
+
+ def test_default_content_manager_for_add_comes_from_policy(self):
+ cm = self._TestSetRaisingContentManager()
+ m = self.message(policy=self.policy.clone(content_manager=cm))
+ for method in ('add_related', 'add_alternative', 'add_attachment'):
+ with self.assertRaises(Exception) as ar:
+ getattr(m, method)('')
+ self.assertEqual(str(ar.exception), 'test')
+
+ def message_as_clear(self, body_parts, attachments, parts, msg):
+ m = self._str_msg(msg)
+ m.clear()
+ self.assertEqual(len(m), 0)
+ self.assertEqual(list(m.items()), [])
+ self.assertIsNone(m.get_payload())
+ self.assertEqual(list(m.iter_parts()), [])
+
+ def message_as_clear_content(self, body_parts, attachments, parts, msg):
+ m = self._str_msg(msg)
+ expected_headers = [h for h in m.keys()
+ if not h.lower().startswith('content-')]
+ m.clear_content()
+ self.assertEqual(list(m.keys()), expected_headers)
+ self.assertIsNone(m.get_payload())
+ self.assertEqual(list(m.iter_parts()), [])
+
+ def test_is_attachment(self):
+ m = self._make_message()
+ self.assertFalse(m.is_attachment)
+ m['Content-Disposition'] = 'inline'
+ self.assertFalse(m.is_attachment)
+ m.replace_header('Content-Disposition', 'attachment')
+ self.assertTrue(m.is_attachment)
+ m.replace_header('Content-Disposition', 'AtTachMent')
+ self.assertTrue(m.is_attachment)
+
+
+
+class TestEmailMessage(TestEmailMessageBase, TestEmailBase):
+ message = EmailMessage
+
+ def test_set_content_adds_MIME_Version(self):
+ m = self._str_msg('')
+ cm = self._TestContentManager()
+ self.assertNotIn('MIME-Version', m)
+ m.set_content(content_manager=cm)
+ self.assertEqual(m['MIME-Version'], '1.0')
+
+ class _MIME_Version_adding_CM:
+ def set_content(self, msg, *args, **kw):
+ msg['MIME-Version'] = '1.0'
+
+ def test_set_content_does_not_duplicate_MIME_Version(self):
+ m = self._str_msg('')
+ cm = self._MIME_Version_adding_CM()
+ self.assertNotIn('MIME-Version', m)
+ m.set_content(content_manager=cm)
+ self.assertEqual(m['MIME-Version'], '1.0')
+
+
+class TestMIMEPart(TestEmailMessageBase, TestEmailBase):
+ # Doing the full test run here may seem a bit redundant, since the two
+ # classes are almost identical. But what if they drift apart? So we do
+ # the full tests so that any future drift doesn't introduce bugs.
+ message = MIMEPart
+
+ def test_set_content_does_not_add_MIME_Version(self):
+ m = self._str_msg('')
+ cm = self._TestContentManager()
+ self.assertNotIn('MIME-Version', m)
+ m.set_content(content_manager=cm)
+ self.assertNotIn('MIME-Version', m)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py
index 983bd49a11..06ad5f24b8 100644
--- a/Lib/test/test_email/test_policy.py
+++ b/Lib/test/test_email/test_policy.py
@@ -30,6 +30,7 @@ class PolicyAPITests(unittest.TestCase):
'raise_on_defect': False,
'header_factory': email.policy.EmailPolicy.header_factory,
'refold_source': 'long',
+ 'content_manager': email.policy.EmailPolicy.content_manager,
})
# For each policy under test, we give here what we expect the defaults to
diff --git a/Lib/test/test_email/torture_test.py b/Lib/test/test_email/torture_test.py
index 544b1bbb39..19cf64f0c7 100644
--- a/Lib/test/test_email/torture_test.py
+++ b/Lib/test/test_email/torture_test.py
@@ -27,7 +27,7 @@ def openfile(filename):
# Prevent this test from running in the Python distro
try:
openfile('crispin-torture.txt')
-except IOError:
+except OSError:
raise TestSkipped
diff --git a/Lib/test/test_ensurepip.py b/Lib/test/test_ensurepip.py
new file mode 100644
index 0000000000..c119327d42
--- /dev/null
+++ b/Lib/test/test_ensurepip.py
@@ -0,0 +1,203 @@
+import unittest
+import unittest.mock
+import ensurepip
+import test.support
+import os
+import os.path
+import contextlib
+import sys
+
+
+class TestEnsurePipVersion(unittest.TestCase):
+
+ def test_returns_version(self):
+ self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version())
+
+
+class TestBootstrap(unittest.TestCase):
+
+ def setUp(self):
+ run_pip_patch = unittest.mock.patch("ensurepip._run_pip")
+ self.run_pip = run_pip_patch.start()
+ self.addCleanup(run_pip_patch.stop)
+
+ # Avoid side effects on the actual os module
+ os_patch = unittest.mock.patch("ensurepip.os")
+ patched_os = os_patch.start()
+ self.addCleanup(os_patch.stop)
+ patched_os.path = os.path
+ self.os_environ = patched_os.environ = os.environ.copy()
+
+ def test_basic_bootstrapping(self):
+ ensurepip.bootstrap()
+
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-index", "--find-links",
+ unittest.mock.ANY, "--pre", "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ additional_paths = self.run_pip.call_args[0][1]
+ self.assertEqual(len(additional_paths), 2)
+
+ def test_bootstrapping_with_root(self):
+ ensurepip.bootstrap(root="/foo/bar/")
+
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-index", "--find-links",
+ unittest.mock.ANY, "--pre", "--root", "/foo/bar/",
+ "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_bootstrapping_with_user(self):
+ ensurepip.bootstrap(user=True)
+
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-index", "--find-links",
+ unittest.mock.ANY, "--pre", "--user", "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_bootstrapping_with_upgrade(self):
+ ensurepip.bootstrap(upgrade=True)
+
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-index", "--find-links",
+ unittest.mock.ANY, "--pre", "--upgrade", "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_bootstrapping_with_verbosity_1(self):
+ ensurepip.bootstrap(verbosity=1)
+
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-index", "--find-links",
+ unittest.mock.ANY, "--pre", "-v", "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_bootstrapping_with_verbosity_2(self):
+ ensurepip.bootstrap(verbosity=2)
+
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-index", "--find-links",
+ unittest.mock.ANY, "--pre", "-vv", "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_bootstrapping_with_verbosity_3(self):
+ ensurepip.bootstrap(verbosity=3)
+
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-index", "--find-links",
+ unittest.mock.ANY, "--pre", "-vvv", "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
+ def test_bootstrapping_with_regular_install(self):
+ ensurepip.bootstrap()
+ self.assertEqual(self.os_environ["ENSUREPIP_OPTIONS"], "install")
+
+ def test_bootstrapping_with_alt_install(self):
+ ensurepip.bootstrap(altinstall=True)
+ self.assertEqual(self.os_environ["ENSUREPIP_OPTIONS"], "altinstall")
+
+ def test_bootstrapping_with_default_pip(self):
+ ensurepip.bootstrap(default_pip=True)
+ self.assertNotIn("ENSUREPIP_OPTIONS", self.os_environ)
+
+ def test_altinstall_default_pip_conflict(self):
+ with self.assertRaises(ValueError):
+ ensurepip.bootstrap(altinstall=True, default_pip=True)
+ self.run_pip.assert_not_called()
+
+@contextlib.contextmanager
+def fake_pip(version=ensurepip._PIP_VERSION):
+ if version is None:
+ pip = None
+ else:
+ class FakePip():
+ __version__ = version
+ pip = FakePip()
+ sentinel = object()
+ orig_pip = sys.modules.get("pip", sentinel)
+ sys.modules["pip"] = pip
+ try:
+ yield pip
+ finally:
+ if orig_pip is sentinel:
+ del sys.modules["pip"]
+ else:
+ sys.modules["pip"] = orig_pip
+
+class TestUninstall(unittest.TestCase):
+
+ def setUp(self):
+ run_pip_patch = unittest.mock.patch("ensurepip._run_pip")
+ self.run_pip = run_pip_patch.start()
+ self.addCleanup(run_pip_patch.stop)
+
+ def test_uninstall_skipped_when_not_installed(self):
+ with fake_pip(None):
+ ensurepip._uninstall()
+ self.run_pip.assert_not_called()
+
+ def test_uninstall_fails_with_wrong_version(self):
+ with fake_pip("not a valid version"):
+ with self.assertRaises(RuntimeError):
+ ensurepip._uninstall()
+ self.run_pip.assert_not_called()
+
+
+ def test_uninstall(self):
+ with fake_pip():
+ ensurepip._uninstall()
+
+ self.run_pip.assert_called_once_with(
+ ["uninstall", "-y", "pip", "setuptools"]
+ )
+
+ def test_uninstall_with_verbosity_1(self):
+ with fake_pip():
+ ensurepip._uninstall(verbosity=1)
+
+ self.run_pip.assert_called_once_with(
+ ["uninstall", "-y", "-v", "pip", "setuptools"]
+ )
+
+ def test_uninstall_with_verbosity_2(self):
+ with fake_pip():
+ ensurepip._uninstall(verbosity=2)
+
+ self.run_pip.assert_called_once_with(
+ ["uninstall", "-y", "-vv", "pip", "setuptools"]
+ )
+
+ def test_uninstall_with_verbosity_3(self):
+ with fake_pip():
+ ensurepip._uninstall(verbosity=3)
+
+ self.run_pip.assert_called_once_with(
+ ["uninstall", "-y", "-vvv", "pip", "setuptools"]
+ )
+
+
+
+
+if __name__ == "__main__":
+ test.support.run_unittest(__name__)
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
new file mode 100644
index 0000000000..03f0e5d5cd
--- /dev/null
+++ b/Lib/test/test_enum.py
@@ -0,0 +1,1325 @@
+import enum
+import inspect
+import pydoc
+import unittest
+from collections import OrderedDict
+from enum import Enum, IntEnum, EnumMeta, unique
+from io import StringIO
+from pickle import dumps, loads, PicklingError
+
+# for pickle tests
+try:
+ class Stooges(Enum):
+ LARRY = 1
+ CURLY = 2
+ MOE = 3
+except Exception as exc:
+ Stooges = exc
+
+try:
+ class IntStooges(int, Enum):
+ LARRY = 1
+ CURLY = 2
+ MOE = 3
+except Exception as exc:
+ IntStooges = exc
+
+try:
+ class FloatStooges(float, Enum):
+ LARRY = 1.39
+ CURLY = 2.72
+ MOE = 3.142596
+except Exception as exc:
+ FloatStooges = exc
+
+# for pickle test and subclass tests
+try:
+ class StrEnum(str, Enum):
+ 'accepts only string values'
+ class Name(StrEnum):
+ BDFL = 'Guido van Rossum'
+ FLUFL = 'Barry Warsaw'
+except Exception as exc:
+ Name = exc
+
+try:
+ Question = Enum('Question', 'who what when where why', module=__name__)
+except Exception as exc:
+ Question = exc
+
+try:
+ Answer = Enum('Answer', 'him this then there because')
+except Exception as exc:
+ Answer = exc
+
+# for doctests
+try:
+ class Fruit(Enum):
+ tomato = 1
+ banana = 2
+ cherry = 3
+except Exception:
+ pass
+
+
+class TestHelpers(unittest.TestCase):
+ # _is_descriptor, _is_sunder, _is_dunder
+
+ def test_is_descriptor(self):
+ class foo:
+ pass
+ for attr in ('__get__','__set__','__delete__'):
+ obj = foo()
+ self.assertFalse(enum._is_descriptor(obj))
+ setattr(obj, attr, 1)
+ self.assertTrue(enum._is_descriptor(obj))
+
+ def test_is_sunder(self):
+ for s in ('_a_', '_aa_'):
+ self.assertTrue(enum._is_sunder(s))
+
+ for s in ('a', 'a_', '_a', '__a', 'a__', '__a__', '_a__', '__a_', '_',
+ '__', '___', '____', '_____',):
+ self.assertFalse(enum._is_sunder(s))
+
+ def test_is_dunder(self):
+ for s in ('__a__', '__aa__'):
+ self.assertTrue(enum._is_dunder(s))
+ for s in ('a', 'a_', '_a', '__a', 'a__', '_a_', '_a__', '__a_', '_',
+ '__', '___', '____', '_____',):
+ self.assertFalse(enum._is_dunder(s))
+
+
+class TestEnum(unittest.TestCase):
+ def setUp(self):
+ class Season(Enum):
+ SPRING = 1
+ SUMMER = 2
+ AUTUMN = 3
+ WINTER = 4
+ self.Season = Season
+
+ class Konstants(float, Enum):
+ E = 2.7182818
+ PI = 3.1415926
+ TAU = 2 * PI
+ self.Konstants = Konstants
+
+ class Grades(IntEnum):
+ A = 5
+ B = 4
+ C = 3
+ D = 2
+ F = 0
+ self.Grades = Grades
+
+ class Directional(str, Enum):
+ EAST = 'east'
+ WEST = 'west'
+ NORTH = 'north'
+ SOUTH = 'south'
+ self.Directional = Directional
+
+ from datetime import date
+ class Holiday(date, Enum):
+ NEW_YEAR = 2013, 1, 1
+ IDES_OF_MARCH = 2013, 3, 15
+ self.Holiday = Holiday
+
+ def test_dir_on_class(self):
+ Season = self.Season
+ self.assertEqual(
+ set(dir(Season)),
+ set(['__class__', '__doc__', '__members__', '__module__',
+ 'SPRING', 'SUMMER', 'AUTUMN', 'WINTER']),
+ )
+
+ def test_dir_on_item(self):
+ Season = self.Season
+ self.assertEqual(
+ set(dir(Season.WINTER)),
+ set(['__class__', '__doc__', '__module__', 'name', 'value']),
+ )
+
+ def test_dir_with_added_behavior(self):
+ class Test(Enum):
+ this = 'that'
+ these = 'those'
+ def wowser(self):
+ return ("Wowser! I'm %s!" % self.name)
+ self.assertEqual(
+ set(dir(Test)),
+ set(['__class__', '__doc__', '__members__', '__module__', 'this', 'these']),
+ )
+ self.assertEqual(
+ set(dir(Test.this)),
+ set(['__class__', '__doc__', '__module__', 'name', 'value', 'wowser']),
+ )
+
+ def test_enum_in_enum_out(self):
+ Season = self.Season
+ self.assertIs(Season(Season.WINTER), Season.WINTER)
+
+ def test_enum_value(self):
+ Season = self.Season
+ self.assertEqual(Season.SPRING.value, 1)
+
+ def test_intenum_value(self):
+ self.assertEqual(IntStooges.CURLY.value, 2)
+
+ def test_enum(self):
+ Season = self.Season
+ lst = list(Season)
+ self.assertEqual(len(lst), len(Season))
+ self.assertEqual(len(Season), 4, Season)
+ self.assertEqual(
+ [Season.SPRING, Season.SUMMER, Season.AUTUMN, Season.WINTER], lst)
+
+ for i, season in enumerate('SPRING SUMMER AUTUMN WINTER'.split(), 1):
+ e = Season(i)
+ self.assertEqual(e, getattr(Season, season))
+ self.assertEqual(e.value, i)
+ self.assertNotEqual(e, i)
+ self.assertEqual(e.name, season)
+ self.assertIn(e, Season)
+ self.assertIs(type(e), Season)
+ self.assertIsInstance(e, Season)
+ self.assertEqual(str(e), 'Season.' + season)
+ self.assertEqual(
+ repr(e),
+ '<Season.{0}: {1}>'.format(season, i),
+ )
+
+ def test_value_name(self):
+ Season = self.Season
+ self.assertEqual(Season.SPRING.name, 'SPRING')
+ self.assertEqual(Season.SPRING.value, 1)
+ with self.assertRaises(AttributeError):
+ Season.SPRING.name = 'invierno'
+ with self.assertRaises(AttributeError):
+ Season.SPRING.value = 2
+
+ def test_changing_member(self):
+ Season = self.Season
+ with self.assertRaises(AttributeError):
+ Season.WINTER = 'really cold'
+
+ def test_attribute_deletion(self):
+ class Season(Enum):
+ SPRING = 1
+ SUMMER = 2
+ AUTUMN = 3
+ WINTER = 4
+
+ def spam(cls):
+ pass
+
+ self.assertTrue(hasattr(Season, 'spam'))
+ del Season.spam
+ self.assertFalse(hasattr(Season, 'spam'))
+
+ with self.assertRaises(AttributeError):
+ del Season.SPRING
+ with self.assertRaises(AttributeError):
+ del Season.DRY
+ with self.assertRaises(AttributeError):
+ del Season.SPRING.name
+
+ def test_invalid_names(self):
+ with self.assertRaises(ValueError):
+ class Wrong(Enum):
+ mro = 9
+ with self.assertRaises(ValueError):
+ class Wrong(Enum):
+ _create_= 11
+ with self.assertRaises(ValueError):
+ class Wrong(Enum):
+ _get_mixins_ = 9
+ with self.assertRaises(ValueError):
+ class Wrong(Enum):
+ _find_new_ = 1
+ with self.assertRaises(ValueError):
+ class Wrong(Enum):
+ _any_name_ = 9
+
+ def test_contains(self):
+ Season = self.Season
+ self.assertIn(Season.AUTUMN, Season)
+ self.assertNotIn(3, Season)
+
+ val = Season(3)
+ self.assertIn(val, Season)
+
+ class OtherEnum(Enum):
+ one = 1; two = 2
+ self.assertNotIn(OtherEnum.two, Season)
+
+ def test_comparisons(self):
+ Season = self.Season
+ with self.assertRaises(TypeError):
+ Season.SPRING < Season.WINTER
+ with self.assertRaises(TypeError):
+ Season.SPRING > 4
+
+ self.assertNotEqual(Season.SPRING, 1)
+
+ class Part(Enum):
+ SPRING = 1
+ CLIP = 2
+ BARREL = 3
+
+ self.assertNotEqual(Season.SPRING, Part.SPRING)
+ with self.assertRaises(TypeError):
+ Season.SPRING < Part.CLIP
+
+ def test_enum_duplicates(self):
+ class Season(Enum):
+ SPRING = 1
+ SUMMER = 2
+ AUTUMN = FALL = 3
+ WINTER = 4
+ ANOTHER_SPRING = 1
+ lst = list(Season)
+ self.assertEqual(
+ lst,
+ [Season.SPRING, Season.SUMMER,
+ Season.AUTUMN, Season.WINTER,
+ ])
+ self.assertIs(Season.FALL, Season.AUTUMN)
+ self.assertEqual(Season.FALL.value, 3)
+ self.assertEqual(Season.AUTUMN.value, 3)
+ self.assertIs(Season(3), Season.AUTUMN)
+ self.assertIs(Season(1), Season.SPRING)
+ self.assertEqual(Season.FALL.name, 'AUTUMN')
+ self.assertEqual(
+ [k for k,v in Season.__members__.items() if v.name != k],
+ ['FALL', 'ANOTHER_SPRING'],
+ )
+
+ def test_duplicate_name(self):
+ with self.assertRaises(TypeError):
+ class Color(Enum):
+ red = 1
+ green = 2
+ blue = 3
+ red = 4
+
+ with self.assertRaises(TypeError):
+ class Color(Enum):
+ red = 1
+ green = 2
+ blue = 3
+ def red(self):
+ return 'red'
+
+ with self.assertRaises(TypeError):
+ class Color(Enum):
+ @property
+ def red(self):
+ return 'redder'
+ red = 1
+ green = 2
+ blue = 3
+
+
+ def test_enum_with_value_name(self):
+ class Huh(Enum):
+ name = 1
+ value = 2
+ self.assertEqual(
+ list(Huh),
+ [Huh.name, Huh.value],
+ )
+ self.assertIs(type(Huh.name), Huh)
+ self.assertEqual(Huh.name.name, 'name')
+ self.assertEqual(Huh.name.value, 1)
+
+ def test_format_enum(self):
+ Season = self.Season
+ self.assertEqual('{}'.format(Season.SPRING),
+ '{}'.format(str(Season.SPRING)))
+ self.assertEqual( '{:}'.format(Season.SPRING),
+ '{:}'.format(str(Season.SPRING)))
+ self.assertEqual('{:20}'.format(Season.SPRING),
+ '{:20}'.format(str(Season.SPRING)))
+ self.assertEqual('{:^20}'.format(Season.SPRING),
+ '{:^20}'.format(str(Season.SPRING)))
+ self.assertEqual('{:>20}'.format(Season.SPRING),
+ '{:>20}'.format(str(Season.SPRING)))
+ self.assertEqual('{:<20}'.format(Season.SPRING),
+ '{:<20}'.format(str(Season.SPRING)))
+
+ def test_format_enum_custom(self):
+ class TestFloat(float, Enum):
+ one = 1.0
+ two = 2.0
+ def __format__(self, spec):
+ return 'TestFloat success!'
+ self.assertEqual('{}'.format(TestFloat.one), 'TestFloat success!')
+
+ def assertFormatIsValue(self, spec, member):
+ self.assertEqual(spec.format(member), spec.format(member.value))
+
+ def test_format_enum_date(self):
+ Holiday = self.Holiday
+ self.assertFormatIsValue('{}', Holiday.IDES_OF_MARCH)
+ self.assertFormatIsValue('{:}', Holiday.IDES_OF_MARCH)
+ self.assertFormatIsValue('{:20}', Holiday.IDES_OF_MARCH)
+ self.assertFormatIsValue('{:^20}', Holiday.IDES_OF_MARCH)
+ self.assertFormatIsValue('{:>20}', Holiday.IDES_OF_MARCH)
+ self.assertFormatIsValue('{:<20}', Holiday.IDES_OF_MARCH)
+ self.assertFormatIsValue('{:%Y %m}', Holiday.IDES_OF_MARCH)
+ self.assertFormatIsValue('{:%Y %m %M:00}', Holiday.IDES_OF_MARCH)
+
+ def test_format_enum_float(self):
+ Konstants = self.Konstants
+ self.assertFormatIsValue('{}', Konstants.TAU)
+ self.assertFormatIsValue('{:}', Konstants.TAU)
+ self.assertFormatIsValue('{:20}', Konstants.TAU)
+ self.assertFormatIsValue('{:^20}', Konstants.TAU)
+ self.assertFormatIsValue('{:>20}', Konstants.TAU)
+ self.assertFormatIsValue('{:<20}', Konstants.TAU)
+ self.assertFormatIsValue('{:n}', Konstants.TAU)
+ self.assertFormatIsValue('{:5.2}', Konstants.TAU)
+ self.assertFormatIsValue('{:f}', Konstants.TAU)
+
+ def test_format_enum_int(self):
+ Grades = self.Grades
+ self.assertFormatIsValue('{}', Grades.C)
+ self.assertFormatIsValue('{:}', Grades.C)
+ self.assertFormatIsValue('{:20}', Grades.C)
+ self.assertFormatIsValue('{:^20}', Grades.C)
+ self.assertFormatIsValue('{:>20}', Grades.C)
+ self.assertFormatIsValue('{:<20}', Grades.C)
+ self.assertFormatIsValue('{:+}', Grades.C)
+ self.assertFormatIsValue('{:08X}', Grades.C)
+ self.assertFormatIsValue('{:b}', Grades.C)
+
+ def test_format_enum_str(self):
+ Directional = self.Directional
+ self.assertFormatIsValue('{}', Directional.WEST)
+ self.assertFormatIsValue('{:}', Directional.WEST)
+ self.assertFormatIsValue('{:20}', Directional.WEST)
+ self.assertFormatIsValue('{:^20}', Directional.WEST)
+ self.assertFormatIsValue('{:>20}', Directional.WEST)
+ self.assertFormatIsValue('{:<20}', Directional.WEST)
+
+ def test_hash(self):
+ Season = self.Season
+ dates = {}
+ dates[Season.WINTER] = '1225'
+ dates[Season.SPRING] = '0315'
+ dates[Season.SUMMER] = '0704'
+ dates[Season.AUTUMN] = '1031'
+ self.assertEqual(dates[Season.AUTUMN], '1031')
+
+ def test_intenum_from_scratch(self):
+ class phy(int, Enum):
+ pi = 3
+ tau = 2 * pi
+ self.assertTrue(phy.pi < phy.tau)
+
+ def test_intenum_inherited(self):
+ class IntEnum(int, Enum):
+ pass
+ class phy(IntEnum):
+ pi = 3
+ tau = 2 * pi
+ self.assertTrue(phy.pi < phy.tau)
+
+ def test_floatenum_from_scratch(self):
+ class phy(float, Enum):
+ pi = 3.1415926
+ tau = 2 * pi
+ self.assertTrue(phy.pi < phy.tau)
+
+ def test_floatenum_inherited(self):
+ class FloatEnum(float, Enum):
+ pass
+ class phy(FloatEnum):
+ pi = 3.1415926
+ tau = 2 * pi
+ self.assertTrue(phy.pi < phy.tau)
+
+ def test_strenum_from_scratch(self):
+ class phy(str, Enum):
+ pi = 'Pi'
+ tau = 'Tau'
+ self.assertTrue(phy.pi < phy.tau)
+
+ def test_strenum_inherited(self):
+ class StrEnum(str, Enum):
+ pass
+ class phy(StrEnum):
+ pi = 'Pi'
+ tau = 'Tau'
+ self.assertTrue(phy.pi < phy.tau)
+
+
+ def test_intenum(self):
+ class WeekDay(IntEnum):
+ SUNDAY = 1
+ MONDAY = 2
+ TUESDAY = 3
+ WEDNESDAY = 4
+ THURSDAY = 5
+ FRIDAY = 6
+ SATURDAY = 7
+
+ self.assertEqual(['a', 'b', 'c'][WeekDay.MONDAY], 'c')
+ self.assertEqual([i for i in range(WeekDay.TUESDAY)], [0, 1, 2])
+
+ lst = list(WeekDay)
+ self.assertEqual(len(lst), len(WeekDay))
+ self.assertEqual(len(WeekDay), 7)
+ target = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY'
+ target = target.split()
+ for i, weekday in enumerate(target, 1):
+ e = WeekDay(i)
+ self.assertEqual(e, i)
+ self.assertEqual(int(e), i)
+ self.assertEqual(e.name, weekday)
+ self.assertIn(e, WeekDay)
+ self.assertEqual(lst.index(e)+1, i)
+ self.assertTrue(0 < e < 8)
+ self.assertIs(type(e), WeekDay)
+ self.assertIsInstance(e, int)
+ self.assertIsInstance(e, Enum)
+
+ def test_intenum_duplicates(self):
+ class WeekDay(IntEnum):
+ SUNDAY = 1
+ MONDAY = 2
+ TUESDAY = TEUSDAY = 3
+ WEDNESDAY = 4
+ THURSDAY = 5
+ FRIDAY = 6
+ SATURDAY = 7
+ self.assertIs(WeekDay.TEUSDAY, WeekDay.TUESDAY)
+ self.assertEqual(WeekDay(3).name, 'TUESDAY')
+ self.assertEqual([k for k,v in WeekDay.__members__.items()
+ if v.name != k], ['TEUSDAY', ])
+
+ def test_pickle_enum(self):
+ if isinstance(Stooges, Exception):
+ raise Stooges
+ self.assertIs(Stooges.CURLY, loads(dumps(Stooges.CURLY)))
+ self.assertIs(Stooges, loads(dumps(Stooges)))
+
+ def test_pickle_int(self):
+ if isinstance(IntStooges, Exception):
+ raise IntStooges
+ self.assertIs(IntStooges.CURLY, loads(dumps(IntStooges.CURLY)))
+ self.assertIs(IntStooges, loads(dumps(IntStooges)))
+
+ def test_pickle_float(self):
+ if isinstance(FloatStooges, Exception):
+ raise FloatStooges
+ self.assertIs(FloatStooges.CURLY, loads(dumps(FloatStooges.CURLY)))
+ self.assertIs(FloatStooges, loads(dumps(FloatStooges)))
+
+ def test_pickle_enum_function(self):
+ if isinstance(Answer, Exception):
+ raise Answer
+ self.assertIs(Answer.him, loads(dumps(Answer.him)))
+ self.assertIs(Answer, loads(dumps(Answer)))
+
+ def test_pickle_enum_function_with_module(self):
+ if isinstance(Question, Exception):
+ raise Question
+ self.assertIs(Question.who, loads(dumps(Question.who)))
+ self.assertIs(Question, loads(dumps(Question)))
+
+ def test_exploding_pickle(self):
+ BadPickle = Enum('BadPickle', 'dill sweet bread-n-butter')
+ enum._make_class_unpicklable(BadPickle)
+ globals()['BadPickle'] = BadPickle
+ with self.assertRaises(TypeError):
+ dumps(BadPickle.dill)
+ with self.assertRaises(PicklingError):
+ dumps(BadPickle)
+
+ def test_string_enum(self):
+ class SkillLevel(str, Enum):
+ master = 'what is the sound of one hand clapping?'
+ journeyman = 'why did the chicken cross the road?'
+ apprentice = 'knock, knock!'
+ self.assertEqual(SkillLevel.apprentice, 'knock, knock!')
+
+ def test_getattr_getitem(self):
+ class Period(Enum):
+ morning = 1
+ noon = 2
+ evening = 3
+ night = 4
+ self.assertIs(Period(2), Period.noon)
+ self.assertIs(getattr(Period, 'night'), Period.night)
+ self.assertIs(Period['morning'], Period.morning)
+
+ def test_getattr_dunder(self):
+ Season = self.Season
+ self.assertTrue(getattr(Season, '__eq__'))
+
+ def test_iteration_order(self):
+ class Season(Enum):
+ SUMMER = 2
+ WINTER = 4
+ AUTUMN = 3
+ SPRING = 1
+ self.assertEqual(
+ list(Season),
+ [Season.SUMMER, Season.WINTER, Season.AUTUMN, Season.SPRING],
+ )
+
+ def test_reversed_iteration_order(self):
+ self.assertEqual(
+ list(reversed(self.Season)),
+ [self.Season.WINTER, self.Season.AUTUMN, self.Season.SUMMER,
+ self.Season.SPRING]
+ )
+
+ def test_programatic_function_string(self):
+ SummerMonth = Enum('SummerMonth', 'june july august')
+ lst = list(SummerMonth)
+ self.assertEqual(len(lst), len(SummerMonth))
+ self.assertEqual(len(SummerMonth), 3, SummerMonth)
+ self.assertEqual(
+ [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+ lst,
+ )
+ for i, month in enumerate('june july august'.split(), 1):
+ e = SummerMonth(i)
+ self.assertEqual(int(e.value), i)
+ self.assertNotEqual(e, i)
+ self.assertEqual(e.name, month)
+ self.assertIn(e, SummerMonth)
+ self.assertIs(type(e), SummerMonth)
+
+ def test_programatic_function_string_list(self):
+ SummerMonth = Enum('SummerMonth', ['june', 'july', 'august'])
+ lst = list(SummerMonth)
+ self.assertEqual(len(lst), len(SummerMonth))
+ self.assertEqual(len(SummerMonth), 3, SummerMonth)
+ self.assertEqual(
+ [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+ lst,
+ )
+ for i, month in enumerate('june july august'.split(), 1):
+ e = SummerMonth(i)
+ self.assertEqual(int(e.value), i)
+ self.assertNotEqual(e, i)
+ self.assertEqual(e.name, month)
+ self.assertIn(e, SummerMonth)
+ self.assertIs(type(e), SummerMonth)
+
+ def test_programatic_function_iterable(self):
+ SummerMonth = Enum(
+ 'SummerMonth',
+ (('june', 1), ('july', 2), ('august', 3))
+ )
+ lst = list(SummerMonth)
+ self.assertEqual(len(lst), len(SummerMonth))
+ self.assertEqual(len(SummerMonth), 3, SummerMonth)
+ self.assertEqual(
+ [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+ lst,
+ )
+ for i, month in enumerate('june july august'.split(), 1):
+ e = SummerMonth(i)
+ self.assertEqual(int(e.value), i)
+ self.assertNotEqual(e, i)
+ self.assertEqual(e.name, month)
+ self.assertIn(e, SummerMonth)
+ self.assertIs(type(e), SummerMonth)
+
+ def test_programatic_function_from_dict(self):
+ SummerMonth = Enum(
+ 'SummerMonth',
+ OrderedDict((('june', 1), ('july', 2), ('august', 3)))
+ )
+ lst = list(SummerMonth)
+ self.assertEqual(len(lst), len(SummerMonth))
+ self.assertEqual(len(SummerMonth), 3, SummerMonth)
+ self.assertEqual(
+ [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+ lst,
+ )
+ for i, month in enumerate('june july august'.split(), 1):
+ e = SummerMonth(i)
+ self.assertEqual(int(e.value), i)
+ self.assertNotEqual(e, i)
+ self.assertEqual(e.name, month)
+ self.assertIn(e, SummerMonth)
+ self.assertIs(type(e), SummerMonth)
+
+ def test_programatic_function_type(self):
+ SummerMonth = Enum('SummerMonth', 'june july august', type=int)
+ lst = list(SummerMonth)
+ self.assertEqual(len(lst), len(SummerMonth))
+ self.assertEqual(len(SummerMonth), 3, SummerMonth)
+ self.assertEqual(
+ [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+ lst,
+ )
+ for i, month in enumerate('june july august'.split(), 1):
+ e = SummerMonth(i)
+ self.assertEqual(e, i)
+ self.assertEqual(e.name, month)
+ self.assertIn(e, SummerMonth)
+ self.assertIs(type(e), SummerMonth)
+
+ def test_programatic_function_type_from_subclass(self):
+ SummerMonth = IntEnum('SummerMonth', 'june july august')
+ lst = list(SummerMonth)
+ self.assertEqual(len(lst), len(SummerMonth))
+ self.assertEqual(len(SummerMonth), 3, SummerMonth)
+ self.assertEqual(
+ [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+ lst,
+ )
+ for i, month in enumerate('june july august'.split(), 1):
+ e = SummerMonth(i)
+ self.assertEqual(e, i)
+ self.assertEqual(e.name, month)
+ self.assertIn(e, SummerMonth)
+ self.assertIs(type(e), SummerMonth)
+
+ def test_subclassing(self):
+ if isinstance(Name, Exception):
+ raise Name
+ self.assertEqual(Name.BDFL, 'Guido van Rossum')
+ self.assertTrue(Name.BDFL, Name('Guido van Rossum'))
+ self.assertIs(Name.BDFL, getattr(Name, 'BDFL'))
+ self.assertIs(Name.BDFL, loads(dumps(Name.BDFL)))
+
+ def test_extending(self):
+ class Color(Enum):
+ red = 1
+ green = 2
+ blue = 3
+ with self.assertRaises(TypeError):
+ class MoreColor(Color):
+ cyan = 4
+ magenta = 5
+ yellow = 6
+
+ def test_exclude_methods(self):
+ class whatever(Enum):
+ this = 'that'
+ these = 'those'
+ def really(self):
+ return 'no, not %s' % self.value
+ self.assertIsNot(type(whatever.really), whatever)
+ self.assertEqual(whatever.this.really(), 'no, not that')
+
+ def test_wrong_inheritance_order(self):
+ with self.assertRaises(TypeError):
+ class Wrong(Enum, str):
+ NotHere = 'error before this point'
+
+ def test_intenum_transitivity(self):
+ class number(IntEnum):
+ one = 1
+ two = 2
+ three = 3
+ class numero(IntEnum):
+ uno = 1
+ dos = 2
+ tres = 3
+ self.assertEqual(number.one, numero.uno)
+ self.assertEqual(number.two, numero.dos)
+ self.assertEqual(number.three, numero.tres)
+
+ def test_wrong_enum_in_call(self):
+ class Monochrome(Enum):
+ black = 0
+ white = 1
+ class Gender(Enum):
+ male = 0
+ female = 1
+ self.assertRaises(ValueError, Monochrome, Gender.male)
+
+ def test_wrong_enum_in_mixed_call(self):
+ class Monochrome(IntEnum):
+ black = 0
+ white = 1
+ class Gender(Enum):
+ male = 0
+ female = 1
+ self.assertRaises(ValueError, Monochrome, Gender.male)
+
+ def test_mixed_enum_in_call_1(self):
+ class Monochrome(IntEnum):
+ black = 0
+ white = 1
+ class Gender(IntEnum):
+ male = 0
+ female = 1
+ self.assertIs(Monochrome(Gender.female), Monochrome.white)
+
+ def test_mixed_enum_in_call_2(self):
+ class Monochrome(Enum):
+ black = 0
+ white = 1
+ class Gender(IntEnum):
+ male = 0
+ female = 1
+ self.assertIs(Monochrome(Gender.male), Monochrome.black)
+
+ def test_flufl_enum(self):
+ class Fluflnum(Enum):
+ def __int__(self):
+ return int(self.value)
+ class MailManOptions(Fluflnum):
+ option1 = 1
+ option2 = 2
+ option3 = 3
+ self.assertEqual(int(MailManOptions.option1), 1)
+
+ def test_introspection(self):
+ class Number(IntEnum):
+ one = 100
+ two = 200
+ self.assertIs(Number.one._member_type_, int)
+ self.assertIs(Number._member_type_, int)
+ class String(str, Enum):
+ yarn = 'soft'
+ rope = 'rough'
+ wire = 'hard'
+ self.assertIs(String.yarn._member_type_, str)
+ self.assertIs(String._member_type_, str)
+ class Plain(Enum):
+ vanilla = 'white'
+ one = 1
+ self.assertIs(Plain.vanilla._member_type_, object)
+ self.assertIs(Plain._member_type_, object)
+
+ def test_no_such_enum_member(self):
+ class Color(Enum):
+ red = 1
+ green = 2
+ blue = 3
+ with self.assertRaises(ValueError):
+ Color(4)
+ with self.assertRaises(KeyError):
+ Color['chartreuse']
+
+ def test_new_repr(self):
+ class Color(Enum):
+ red = 1
+ green = 2
+ blue = 3
+ def __repr__(self):
+ return "don't you just love shades of %s?" % self.name
+ self.assertEqual(
+ repr(Color.blue),
+ "don't you just love shades of blue?",
+ )
+
+ def test_inherited_repr(self):
+ class MyEnum(Enum):
+ def __repr__(self):
+ return "My name is %s." % self.name
+ class MyIntEnum(int, MyEnum):
+ this = 1
+ that = 2
+ theother = 3
+ self.assertEqual(repr(MyIntEnum.that), "My name is that.")
+
+ def test_multiple_mixin_mro(self):
+ class auto_enum(type(Enum)):
+ def __new__(metacls, cls, bases, classdict):
+ temp = type(classdict)()
+ names = set(classdict._member_names)
+ i = 0
+ for k in classdict._member_names:
+ v = classdict[k]
+ if v is Ellipsis:
+ v = i
+ else:
+ i = v
+ i += 1
+ temp[k] = v
+ for k, v in classdict.items():
+ if k not in names:
+ temp[k] = v
+ return super(auto_enum, metacls).__new__(
+ metacls, cls, bases, temp)
+
+ class AutoNumberedEnum(Enum, metaclass=auto_enum):
+ pass
+
+ class AutoIntEnum(IntEnum, metaclass=auto_enum):
+ pass
+
+ class TestAutoNumber(AutoNumberedEnum):
+ a = ...
+ b = 3
+ c = ...
+
+ class TestAutoInt(AutoIntEnum):
+ a = ...
+ b = 3
+ c = ...
+
+ def test_subclasses_with_getnewargs(self):
+ class NamedInt(int):
+ def __new__(cls, *args):
+ _args = args
+ name, *args = args
+ if len(args) == 0:
+ raise TypeError("name and value must be specified")
+ self = int.__new__(cls, *args)
+ self._intname = name
+ self._args = _args
+ return self
+ def __getnewargs__(self):
+ return self._args
+ @property
+ def __name__(self):
+ return self._intname
+ def __repr__(self):
+ # repr() is updated to include the name and type info
+ return "{}({!r}, {})".format(type(self).__name__,
+ self.__name__,
+ int.__repr__(self))
+ def __str__(self):
+ # str() is unchanged, even if it relies on the repr() fallback
+ base = int
+ base_str = base.__str__
+ if base_str.__objclass__ is object:
+ return base.__repr__(self)
+ return base_str(self)
+ # for simplicity, we only define one operator that
+ # propagates expressions
+ def __add__(self, other):
+ temp = int(self) + int( other)
+ if isinstance(self, NamedInt) and isinstance(other, NamedInt):
+ return NamedInt(
+ '({0} + {1})'.format(self.__name__, other.__name__),
+ temp )
+ else:
+ return temp
+
+ class NEI(NamedInt, Enum):
+ x = ('the-x', 1)
+ y = ('the-y', 2)
+
+
+ self.assertIs(NEI.__new__, Enum.__new__)
+ self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
+ globals()['NamedInt'] = NamedInt
+ globals()['NEI'] = NEI
+ NI5 = NamedInt('test', 5)
+ self.assertEqual(NI5, 5)
+ self.assertEqual(loads(dumps(NI5)), 5)
+ self.assertEqual(NEI.y.value, 2)
+ self.assertIs(loads(dumps(NEI.y)), NEI.y)
+
+ def test_subclasses_without_getnewargs(self):
+ class NamedInt(int):
+ def __new__(cls, *args):
+ _args = args
+ name, *args = args
+ if len(args) == 0:
+ raise TypeError("name and value must be specified")
+ self = int.__new__(cls, *args)
+ self._intname = name
+ self._args = _args
+ return self
+ @property
+ def __name__(self):
+ return self._intname
+ def __repr__(self):
+ # repr() is updated to include the name and type info
+ return "{}({!r}, {})".format(type(self).__name__,
+ self.__name__,
+ int.__repr__(self))
+ def __str__(self):
+ # str() is unchanged, even if it relies on the repr() fallback
+ base = int
+ base_str = base.__str__
+ if base_str.__objclass__ is object:
+ return base.__repr__(self)
+ return base_str(self)
+ # for simplicity, we only define one operator that
+ # propagates expressions
+ def __add__(self, other):
+ temp = int(self) + int( other)
+ if isinstance(self, NamedInt) and isinstance(other, NamedInt):
+ return NamedInt(
+ '({0} + {1})'.format(self.__name__, other.__name__),
+ temp )
+ else:
+ return temp
+
+ class NEI(NamedInt, Enum):
+ x = ('the-x', 1)
+ y = ('the-y', 2)
+
+ self.assertIs(NEI.__new__, Enum.__new__)
+ self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
+ globals()['NamedInt'] = NamedInt
+ globals()['NEI'] = NEI
+ NI5 = NamedInt('test', 5)
+ self.assertEqual(NI5, 5)
+ self.assertEqual(NEI.y.value, 2)
+ with self.assertRaises(TypeError):
+ dumps(NEI.x)
+ with self.assertRaises(PicklingError):
+ dumps(NEI)
+
+ def test_tuple_subclass(self):
+ class SomeTuple(tuple, Enum):
+ first = (1, 'for the money')
+ second = (2, 'for the show')
+ third = (3, 'for the music')
+ self.assertIs(type(SomeTuple.first), SomeTuple)
+ self.assertIsInstance(SomeTuple.second, tuple)
+ self.assertEqual(SomeTuple.third, (3, 'for the music'))
+ globals()['SomeTuple'] = SomeTuple
+ self.assertIs(loads(dumps(SomeTuple.first)), SomeTuple.first)
+
+ def test_duplicate_values_give_unique_enum_items(self):
+ class AutoNumber(Enum):
+ first = ()
+ second = ()
+ third = ()
+ def __new__(cls):
+ value = len(cls.__members__) + 1
+ obj = object.__new__(cls)
+ obj._value_ = value
+ return obj
+ def __int__(self):
+ return int(self._value_)
+ self.assertEqual(
+ list(AutoNumber),
+ [AutoNumber.first, AutoNumber.second, AutoNumber.third],
+ )
+ self.assertEqual(int(AutoNumber.second), 2)
+ self.assertEqual(AutoNumber.third.value, 3)
+ self.assertIs(AutoNumber(1), AutoNumber.first)
+
+ def test_inherited_new_from_enhanced_enum(self):
+ class AutoNumber(Enum):
+ def __new__(cls):
+ value = len(cls.__members__) + 1
+ obj = object.__new__(cls)
+ obj._value_ = value
+ return obj
+ def __int__(self):
+ return int(self._value_)
+ class Color(AutoNumber):
+ red = ()
+ green = ()
+ blue = ()
+ self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
+ self.assertEqual(list(map(int, Color)), [1, 2, 3])
+
+ def test_inherited_new_from_mixed_enum(self):
+ class AutoNumber(IntEnum):
+ def __new__(cls):
+ value = len(cls.__members__) + 1
+ obj = int.__new__(cls, value)
+ obj._value_ = value
+ return obj
+ class Color(AutoNumber):
+ red = ()
+ green = ()
+ blue = ()
+ self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
+ self.assertEqual(list(map(int, Color)), [1, 2, 3])
+
+ def test_equality(self):
+ class AlwaysEqual:
+ def __eq__(self, other):
+ return True
+ class OrdinaryEnum(Enum):
+ a = 1
+ self.assertEqual(AlwaysEqual(), OrdinaryEnum.a)
+ self.assertEqual(OrdinaryEnum.a, AlwaysEqual())
+
+ def test_ordered_mixin(self):
+ class OrderedEnum(Enum):
+ def __ge__(self, other):
+ if self.__class__ is other.__class__:
+ return self._value_ >= other._value_
+ return NotImplemented
+ def __gt__(self, other):
+ if self.__class__ is other.__class__:
+ return self._value_ > other._value_
+ return NotImplemented
+ def __le__(self, other):
+ if self.__class__ is other.__class__:
+ return self._value_ <= other._value_
+ return NotImplemented
+ def __lt__(self, other):
+ if self.__class__ is other.__class__:
+ return self._value_ < other._value_
+ return NotImplemented
+ class Grade(OrderedEnum):
+ A = 5
+ B = 4
+ C = 3
+ D = 2
+ F = 1
+ self.assertGreater(Grade.A, Grade.B)
+ self.assertLessEqual(Grade.F, Grade.C)
+ self.assertLess(Grade.D, Grade.A)
+ self.assertGreaterEqual(Grade.B, Grade.B)
+ self.assertEqual(Grade.B, Grade.B)
+ self.assertNotEqual(Grade.C, Grade.D)
+
+ def test_extending2(self):
+ class Shade(Enum):
+ def shade(self):
+ print(self.name)
+ class Color(Shade):
+ red = 1
+ green = 2
+ blue = 3
+ with self.assertRaises(TypeError):
+ class MoreColor(Color):
+ cyan = 4
+ magenta = 5
+ yellow = 6
+
+ def test_extending3(self):
+ class Shade(Enum):
+ def shade(self):
+ return self.name
+ class Color(Shade):
+ def hex(self):
+ return '%s hexlified!' % self.value
+ class MoreColor(Color):
+ cyan = 4
+ magenta = 5
+ yellow = 6
+ self.assertEqual(MoreColor.magenta.hex(), '5 hexlified!')
+
+
+ def test_no_duplicates(self):
+ class UniqueEnum(Enum):
+ def __init__(self, *args):
+ cls = self.__class__
+ if any(self.value == e.value for e in cls):
+ a = self.name
+ e = cls(self.value).name
+ raise ValueError(
+ "aliases not allowed in UniqueEnum: %r --> %r"
+ % (a, e)
+ )
+ class Color(UniqueEnum):
+ red = 1
+ green = 2
+ blue = 3
+ with self.assertRaises(ValueError):
+ class Color(UniqueEnum):
+ red = 1
+ green = 2
+ blue = 3
+ grene = 2
+
+ def test_init(self):
+ class Planet(Enum):
+ MERCURY = (3.303e+23, 2.4397e6)
+ VENUS = (4.869e+24, 6.0518e6)
+ EARTH = (5.976e+24, 6.37814e6)
+ MARS = (6.421e+23, 3.3972e6)
+ JUPITER = (1.9e+27, 7.1492e7)
+ SATURN = (5.688e+26, 6.0268e7)
+ URANUS = (8.686e+25, 2.5559e7)
+ NEPTUNE = (1.024e+26, 2.4746e7)
+ def __init__(self, mass, radius):
+ self.mass = mass # in kilograms
+ self.radius = radius # in meters
+ @property
+ def surface_gravity(self):
+ # universal gravitational constant (m3 kg-1 s-2)
+ G = 6.67300E-11
+ return G * self.mass / (self.radius * self.radius)
+ self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80)
+ self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6))
+
+ def test_nonhash_value(self):
+ class AutoNumberInAList(Enum):
+ def __new__(cls):
+ value = [len(cls.__members__) + 1]
+ obj = object.__new__(cls)
+ obj._value_ = value
+ return obj
+ class ColorInAList(AutoNumberInAList):
+ red = ()
+ green = ()
+ blue = ()
+ self.assertEqual(list(ColorInAList), [ColorInAList.red, ColorInAList.green, ColorInAList.blue])
+ for enum, value in zip(ColorInAList, range(3)):
+ value += 1
+ self.assertEqual(enum.value, [value])
+ self.assertIs(ColorInAList([value]), enum)
+
+ def test_conflicting_types_resolved_in_new(self):
+ class LabelledIntEnum(int, Enum):
+ def __new__(cls, *args):
+ value, label = args
+ obj = int.__new__(cls, value)
+ obj.label = label
+ obj._value_ = value
+ return obj
+
+ class LabelledList(LabelledIntEnum):
+ unprocessed = (1, "Unprocessed")
+ payment_complete = (2, "Payment Complete")
+
+ self.assertEqual(list(LabelledList), [LabelledList.unprocessed, LabelledList.payment_complete])
+ self.assertEqual(LabelledList.unprocessed, 1)
+ self.assertEqual(LabelledList(1), LabelledList.unprocessed)
+
+
+class TestUnique(unittest.TestCase):
+
+ def test_unique_clean(self):
+ @unique
+ class Clean(Enum):
+ one = 1
+ two = 'dos'
+ tres = 4.0
+ @unique
+ class Cleaner(IntEnum):
+ single = 1
+ double = 2
+ triple = 3
+
+ def test_unique_dirty(self):
+ with self.assertRaisesRegex(ValueError, 'tres.*one'):
+ @unique
+ class Dirty(Enum):
+ one = 1
+ two = 'dos'
+ tres = 1
+ with self.assertRaisesRegex(
+ ValueError,
+ 'double.*single.*turkey.*triple',
+ ):
+ @unique
+ class Dirtier(IntEnum):
+ single = 1
+ double = 1
+ triple = 3
+ turkey = 3
+
+
+expected_help_output = """
+Help on class Color in module %s:
+
+class Color(enum.Enum)
+ | Method resolution order:
+ | Color
+ | enum.Enum
+ | builtins.object
+ |\x20\x20
+ | Data and other attributes defined here:
+ |\x20\x20
+ | blue = <Color.blue: 3>
+ |\x20\x20
+ | green = <Color.green: 2>
+ |\x20\x20
+ | red = <Color.red: 1>
+ |\x20\x20
+ | ----------------------------------------------------------------------
+ | Data descriptors inherited from enum.Enum:
+ |\x20\x20
+ | name
+ | The name of the Enum member.
+ |\x20\x20
+ | value
+ | The value of the Enum member.
+ |\x20\x20
+ | ----------------------------------------------------------------------
+ | Data descriptors inherited from enum.EnumMeta:
+ |\x20\x20
+ | __members__
+ | Returns a mapping of member name->value.
+ |\x20\x20\x20\x20\x20\x20
+ | This mapping lists all enum members, including aliases. Note that this
+ | is a read-only view of the internal mapping.
+""".strip()
+
+class TestStdLib(unittest.TestCase):
+
+ class Color(Enum):
+ red = 1
+ green = 2
+ blue = 3
+
+ def test_pydoc(self):
+ # indirectly test __objclass__
+ expected_text = expected_help_output % __name__
+ output = StringIO()
+ helper = pydoc.Helper(output=output)
+ helper(self.Color)
+ result = output.getvalue().strip()
+ if result != expected_text:
+ print_diffs(expected_text, result)
+ self.fail("outputs are not equal, see diff above")
+
+ def test_inspect_getmembers(self):
+ values = dict((
+ ('__class__', EnumMeta),
+ ('__doc__', None),
+ ('__members__', self.Color.__members__),
+ ('__module__', __name__),
+ ('blue', self.Color.blue),
+ ('green', self.Color.green),
+ ('name', Enum.__dict__['name']),
+ ('red', self.Color.red),
+ ('value', Enum.__dict__['value']),
+ ))
+ result = dict(inspect.getmembers(self.Color))
+ self.assertEqual(values.keys(), result.keys())
+ failed = False
+ for k in values.keys():
+ if result[k] != values[k]:
+ print()
+ print('\n%s\n key: %s\n result: %s\nexpected: %s\n%s\n' %
+ ('=' * 75, k, result[k], values[k], '=' * 75), sep='')
+ failed = True
+ if failed:
+ self.fail("result does not equal expected, see print above")
+
+ def test_inspect_classify_class_attrs(self):
+ # indirectly test __objclass__
+ from inspect import Attribute
+ values = [
+ Attribute(name='__class__', kind='data',
+ defining_class=object, object=EnumMeta),
+ Attribute(name='__doc__', kind='data',
+ defining_class=self.Color, object=None),
+ Attribute(name='__members__', kind='property',
+ defining_class=EnumMeta, object=EnumMeta.__members__),
+ Attribute(name='__module__', kind='data',
+ defining_class=self.Color, object=__name__),
+ Attribute(name='blue', kind='data',
+ defining_class=self.Color, object=self.Color.blue),
+ Attribute(name='green', kind='data',
+ defining_class=self.Color, object=self.Color.green),
+ Attribute(name='red', kind='data',
+ defining_class=self.Color, object=self.Color.red),
+ Attribute(name='name', kind='data',
+ defining_class=Enum, object=Enum.__dict__['name']),
+ Attribute(name='value', kind='data',
+ defining_class=Enum, object=Enum.__dict__['value']),
+ ]
+ values.sort(key=lambda item: item.name)
+ result = list(inspect.classify_class_attrs(self.Color))
+ result.sort(key=lambda item: item.name)
+ failed = False
+ for v, r in zip(values, result):
+ if r != v:
+ print('\n%s\n%s\n%s\n%s\n' % ('=' * 75, r, v, '=' * 75), sep='')
+ failed = True
+ if failed:
+ self.fail("result does not equal expected, see print above")
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py
index 4af217b733..8742afcea8 100644
--- a/Lib/test/test_enumerate.py
+++ b/Lib/test/test_enumerate.py
@@ -1,4 +1,5 @@
import unittest
+import operator
import sys
import pickle
@@ -168,15 +169,12 @@ class TestReversed(unittest.TestCase, PickleTest):
x = range(1)
self.assertEqual(type(reversed(x)), type(iter(x)))
- @support.cpython_only
def test_len(self):
- # This is an implementation detail, not an interface requirement
- from test.test_iterlen import len
for s in ('hello', tuple('hello'), list('hello'), range(5)):
- self.assertEqual(len(reversed(s)), len(s))
+ self.assertEqual(operator.length_hint(reversed(s)), len(s))
r = reversed(s)
list(r)
- self.assertEqual(len(r), 0)
+ self.assertEqual(operator.length_hint(r), 0)
class SeqWithWeirdLen:
called = False
def __len__(self):
@@ -187,7 +185,7 @@ class TestReversed(unittest.TestCase, PickleTest):
def __getitem__(self, index):
return index
r = reversed(SeqWithWeirdLen())
- self.assertRaises(ZeroDivisionError, len, r)
+ self.assertRaises(ZeroDivisionError, operator.length_hint, r)
def test_gc(self):
diff --git a/Lib/test/test_epoll.py b/Lib/test/test_epoll.py
index 7f9547ff95..6459fbafc8 100644
--- a/Lib/test/test_epoll.py
+++ b/Lib/test/test_epoll.py
@@ -21,10 +21,11 @@
"""
Tests for epoll wrapper.
"""
-import socket
import errno
-import time
+import os
import select
+import socket
+import time
import unittest
from test import support
@@ -33,7 +34,7 @@ if not hasattr(select, "epoll"):
try:
select.epoll()
-except IOError as e:
+except OSError as e:
if e.errno == errno.ENOSYS:
raise unittest.SkipTest("kernel doesn't support epoll()")
raise
@@ -56,7 +57,7 @@ class TestEPoll(unittest.TestCase):
client.setblocking(False)
try:
client.connect(('127.0.0.1', self.serverSocket.getsockname()[1]))
- except socket.error as e:
+ except OSError as e:
self.assertEqual(e.args[0], errno.EINPROGRESS)
else:
raise AssertionError("Connect should have raised EINPROGRESS")
@@ -87,6 +88,13 @@ class TestEPoll(unittest.TestCase):
self.assertRaises(TypeError, select.epoll, ['foo'])
self.assertRaises(TypeError, select.epoll, {})
+ def test_context_manager(self):
+ with select.epoll(16) as ep:
+ self.assertGreater(ep.fileno(), 0)
+ self.assertFalse(ep.closed)
+ self.assertTrue(ep.closed)
+ self.assertRaises(ValueError, ep.fileno)
+
def test_add(self):
server, client = self._connected_pair()
@@ -115,12 +123,12 @@ class TestEPoll(unittest.TestCase):
# ValueError: file descriptor cannot be a negative integer (-1)
self.assertRaises(ValueError, ep.register, -1,
select.EPOLLIN | select.EPOLLOUT)
- # IOError: [Errno 9] Bad file descriptor
- self.assertRaises(IOError, ep.register, 10000,
+ # OSError: [Errno 9] Bad file descriptor
+ self.assertRaises(OSError, ep.register, 10000,
select.EPOLLIN | select.EPOLLOUT)
# registering twice also raises an exception
ep.register(server, select.EPOLLIN | select.EPOLLOUT)
- self.assertRaises(IOError, ep.register, server,
+ self.assertRaises(OSError, ep.register, server,
select.EPOLLIN | select.EPOLLOUT)
finally:
ep.close()
@@ -142,7 +150,7 @@ class TestEPoll(unittest.TestCase):
ep.close()
try:
ep2.poll(1, 4)
- except IOError as e:
+ except OSError as e:
self.assertEqual(e.args[0], errno.EBADF, e)
else:
self.fail("epoll on closed fd didn't raise EBADF")
@@ -218,6 +226,36 @@ class TestEPoll(unittest.TestCase):
server.close()
ep.unregister(fd)
+ def test_close(self):
+ open_file = open(__file__, "rb")
+ self.addCleanup(open_file.close)
+ fd = open_file.fileno()
+ epoll = select.epoll()
+
+ # test fileno() method and closed attribute
+ self.assertIsInstance(epoll.fileno(), int)
+ self.assertFalse(epoll.closed)
+
+ # test close()
+ epoll.close()
+ self.assertTrue(epoll.closed)
+ self.assertRaises(ValueError, epoll.fileno)
+
+ # close() can be called more than once
+ epoll.close()
+
+ # operations must fail with ValueError("I/O operation on closed ...")
+ self.assertRaises(ValueError, epoll.modify, fd, select.EPOLLIN)
+ self.assertRaises(ValueError, epoll.poll, 1.0)
+ self.assertRaises(ValueError, epoll.register, fd, select.EPOLLIN)
+ self.assertRaises(ValueError, epoll.unregister, fd)
+
+ def test_fd_non_inheritable(self):
+ epoll = select.epoll()
+ self.addCleanup(epoll.close)
+ self.assertEqual(os.get_inheritable(epoll.fileno()), False)
+
+
def test_main():
support.run_unittest(TestEPoll)
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 1ad7f97b74..f0851bdbe2 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -8,8 +8,8 @@ import weakref
import errno
from test.support import (TESTFN, captured_output, check_impl_detail,
- cpython_only, gc_collect, run_unittest, no_tracing,
- unlink)
+ check_warnings, cpython_only, gc_collect, run_unittest,
+ no_tracing, unlink)
class NaiveException(Exception):
def __init__(self, x):
@@ -244,22 +244,22 @@ class ExceptionTests(unittest.TestCase):
{'args' : ('foo', 1)}),
(SystemExit, ('foo',),
{'args' : ('foo',), 'code' : 'foo'}),
- (IOError, ('foo',),
+ (OSError, ('foo',),
{'args' : ('foo',), 'filename' : None,
'errno' : None, 'strerror' : None}),
- (IOError, ('foo', 'bar'),
+ (OSError, ('foo', 'bar'),
{'args' : ('foo', 'bar'), 'filename' : None,
'errno' : 'foo', 'strerror' : 'bar'}),
- (IOError, ('foo', 'bar', 'baz'),
+ (OSError, ('foo', 'bar', 'baz'),
{'args' : ('foo', 'bar'), 'filename' : 'baz',
'errno' : 'foo', 'strerror' : 'bar'}),
- (IOError, ('foo', 'bar', 'baz', 'quux'),
+ (OSError, ('foo', 'bar', 'baz', 'quux'),
{'args' : ('foo', 'bar', 'baz', 'quux')}),
- (EnvironmentError, ('errnoStr', 'strErrorStr', 'filenameStr'),
+ (OSError, ('errnoStr', 'strErrorStr', 'filenameStr'),
{'args' : ('errnoStr', 'strErrorStr'),
'strerror' : 'strErrorStr', 'errno' : 'errnoStr',
'filename' : 'filenameStr'}),
- (EnvironmentError, (1, 'strErrorStr', 'filenameStr'),
+ (OSError, (1, 'strErrorStr', 'filenameStr'),
{'args' : (1, 'strErrorStr'), 'errno' : 1,
'strerror' : 'strErrorStr', 'filename' : 'filenameStr'}),
(SyntaxError, (), {'msg' : None, 'text' : None,
@@ -409,7 +409,7 @@ class ExceptionTests(unittest.TestCase):
self.assertIsNone(e.__context__)
self.assertIsNone(e.__cause__)
- class MyException(EnvironmentError):
+ class MyException(OSError):
pass
e = MyException()
@@ -947,13 +947,11 @@ class ImportErrorTests(unittest.TestCase):
def test_non_str_argument(self):
# Issue #15778
- arg = b'abc'
- exc = ImportError(arg)
- self.assertEqual(str(arg), str(exc))
+ with check_warnings(('', BytesWarning), quiet=True):
+ arg = b'abc'
+ exc = ImportError(arg)
+ self.assertEqual(str(arg), str(exc))
-def test_main():
- run_unittest(ExceptionTests, ImportErrorTests)
-
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index 770e70c77d..ebd99edc8f 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -19,18 +19,6 @@ except ImportError:
TIMEOUT = 0.5
-try:
- from resource import setrlimit, RLIMIT_CORE, error as resource_error
-except ImportError:
- prepare_subprocess = None
-else:
- def prepare_subprocess():
- # don't create core file
- try:
- setrlimit(RLIMIT_CORE, (0, 0))
- except (ValueError, resource_error):
- pass
-
def expected_traceback(lineno1, lineno2, header, min_count=1):
regex = header
regex += ' File "<string>", line %s in func\n' % lineno1
@@ -59,10 +47,8 @@ class FaultHandlerTests(unittest.TestCase):
build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
thread XXX".
"""
- options = {}
- if prepare_subprocess:
- options['preexec_fn'] = prepare_subprocess
- process = script_helper.spawn_python('-c', code, **options)
+ with support.SuppressCrashReport():
+ process = script_helper.spawn_python('-c', code)
stdout, stderr = process.communicate()
exitcode = process.wait()
output = support.strip_python_stderr(stdout)
@@ -86,9 +72,9 @@ class FaultHandlerTests(unittest.TestCase):
Raise an error if the output doesn't match the expected format.
"""
if all_threads:
- header = 'Current thread XXX'
+ header = 'Current thread XXX (most recent call first)'
else:
- header = 'Traceback (most recent call first)'
+ header = 'Stack (most recent call first)'
regex = """
^Fatal Python error: {name}
@@ -101,8 +87,7 @@ class FaultHandlerTests(unittest.TestCase):
header=re.escape(header))
if other_regex:
regex += '|' + other_regex
- with support.suppress_crash_popup():
- output, exitcode = self.get_output(code, filename)
+ output, exitcode = self.get_output(code, filename)
output = '\n'.join(output)
self.assertRegex(output, regex)
self.assertNotEqual(exitcode, 0)
@@ -232,8 +217,7 @@ faulthandler.disable()
faulthandler._sigsegv()
""".strip()
not_expected = 'Fatal Python error'
- with support.suppress_crash_popup():
- stderr, exitcode = self.get_output(code)
+ stderr, exitcode = self.get_output(code)
stder = '\n'.join(stderr)
self.assertTrue(not_expected not in stderr,
"%r is present in %r" % (not_expected, stderr))
@@ -264,16 +248,34 @@ faulthandler._sigsegv()
def test_disabled_by_default(self):
# By default, the module should be disabled
code = "import faulthandler; print(faulthandler.is_enabled())"
- rc, stdout, stderr = assert_python_ok("-c", code)
- stdout = (stdout + stderr).strip()
- self.assertEqual(stdout, b"False")
+ args = (sys.executable, '-E', '-c', code)
+ # don't use assert_python_ok() because it always enable faulthandler
+ output = subprocess.check_output(args)
+ self.assertEqual(output.rstrip(), b"False")
def test_sys_xoptions(self):
# Test python -X faulthandler
code = "import faulthandler; print(faulthandler.is_enabled())"
- rc, stdout, stderr = assert_python_ok("-X", "faulthandler", "-c", code)
- stdout = (stdout + stderr).strip()
- self.assertEqual(stdout, b"True")
+ args = (sys.executable, "-E", "-X", "faulthandler", "-c", code)
+ # don't use assert_python_ok() because it always enable faulthandler
+ output = subprocess.check_output(args)
+ self.assertEqual(output.rstrip(), b"True")
+
+ def test_env_var(self):
+ # empty env var
+ code = "import faulthandler; print(faulthandler.is_enabled())"
+ args = (sys.executable, "-c", code)
+ env = os.environ.copy()
+ env['PYTHONFAULTHANDLER'] = ''
+ # don't use assert_python_ok() because it always enable faulthandler
+ output = subprocess.check_output(args, env=env)
+ self.assertEqual(output.rstrip(), b"False")
+
+ # non-empty env var
+ env = os.environ.copy()
+ env['PYTHONFAULTHANDLER'] = '1'
+ output = subprocess.check_output(args, env=env)
+ self.assertEqual(output.rstrip(), b"True")
def check_dump_traceback(self, filename):
"""
@@ -304,7 +306,7 @@ funcA()
else:
lineno = 8
expected = [
- 'Traceback (most recent call first):',
+ 'Stack (most recent call first):',
' File "<string>", line %s in funcB' % lineno,
' File "<string>", line 11 in funcA',
' File "<string>", line 13 in <module>'
@@ -336,7 +338,7 @@ def {func_name}():
func_name=func_name,
)
expected = [
- 'Traceback (most recent call first):',
+ 'Stack (most recent call first):',
' File "<string>", line 4 in %s' % truncated,
' File "<string>", line 6 in <module>'
]
@@ -390,13 +392,13 @@ waiter.join()
else:
lineno = 10
regex = """
-^Thread 0x[0-9a-f]+:
+^Thread 0x[0-9a-f]+ \(most recent call first\):
(?: File ".*threading.py", line [0-9]+ in [_a-z]+
){{1,3}} File "<string>", line 23 in run
File ".*threading.py", line [0-9]+ in _bootstrap_inner
File ".*threading.py", line [0-9]+ in _bootstrap
-Current thread XXX:
+Current thread XXX \(most recent call first\):
File "<string>", line {lineno} in dump
File "<string>", line 28 in <module>$
""".strip()
@@ -459,7 +461,7 @@ if file is not None:
count = loops
if repeat:
count *= 2
- header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+:\n' % timeout_str
+ header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+ \(most recent call first\):\n' % timeout_str
regex = expected_traceback(9, 20, header, min_count=count)
self.assertRegex(trace, regex)
else:
@@ -561,9 +563,9 @@ sys.exit(exitcode)
trace = '\n'.join(trace)
if not unregister:
if all_threads:
- regex = 'Current thread XXX:\n'
+ regex = 'Current thread XXX \(most recent call first\):\n'
else:
- regex = 'Traceback \(most recent call first\):\n'
+ regex = 'Stack \(most recent call first\):\n'
regex = expected_traceback(7, 28, regex)
self.assertRegex(trace, regex)
else:
@@ -590,8 +592,5 @@ sys.exit(exitcode)
self.check_register(chain=True)
-def test_main():
- support.run_unittest(FaultHandlerTests)
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py
index b8cda2f108..c816d970d7 100644
--- a/Lib/test/test_fcntl.py
+++ b/Lib/test/test_fcntl.py
@@ -1,7 +1,4 @@
"""Test program for the fcntl C module.
-
-OS/2+EMX doesn't support the file locking operations.
-
"""
import platform
import os
@@ -39,8 +36,6 @@ def get_lockdata():
lockdata = struct.pack('qqihhi', 0, 0, 0, fcntl.F_WRLCK, 0, 0)
elif sys.platform in ['aix3', 'aix4', 'hp-uxB', 'unixware7']:
lockdata = struct.pack('hhlllii', fcntl.F_WRLCK, 0, 0, 0, 0, 0, 0)
- elif sys.platform in ['os2emx']:
- lockdata = None
else:
lockdata = struct.pack('hh'+start_len+'hh', fcntl.F_WRLCK, 0, 0, 0, 0, 0)
if lockdata:
@@ -66,18 +61,20 @@ class TestFcntl(unittest.TestCase):
rv = fcntl.fcntl(self.f.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
if verbose:
print('Status from fcntl with O_NONBLOCK: ', rv)
- if sys.platform not in ['os2emx']:
- rv = fcntl.fcntl(self.f.fileno(), fcntl.F_SETLKW, lockdata)
- if verbose:
- print('String from fcntl with F_SETLKW: ', repr(rv))
+ rv = fcntl.fcntl(self.f.fileno(), fcntl.F_SETLKW, lockdata)
+ if verbose:
+ print('String from fcntl with F_SETLKW: ', repr(rv))
self.f.close()
def test_fcntl_file_descriptor(self):
# again, but pass the file rather than numeric descriptor
self.f = open(TESTFN, 'wb')
rv = fcntl.fcntl(self.f, fcntl.F_SETFL, os.O_NONBLOCK)
- if sys.platform not in ['os2emx']:
- rv = fcntl.fcntl(self.f, fcntl.F_SETLKW, lockdata)
+ if verbose:
+ print('Status from fcntl with O_NONBLOCK: ', rv)
+ rv = fcntl.fcntl(self.f, fcntl.F_SETLKW, lockdata)
+ if verbose:
+ print('String from fcntl with F_SETLKW: ', repr(rv))
self.f.close()
def test_fcntl_bad_file(self):
diff --git a/Lib/test/test_file.py b/Lib/test/test_file.py
index 76b1694e98..d54e976143 100644
--- a/Lib/test/test_file.py
+++ b/Lib/test/test_file.py
@@ -87,7 +87,7 @@ class AutoFileTests:
self.assertTrue(not f.closed)
if hasattr(f, "readinto"):
- self.assertRaises((IOError, TypeError), f.readinto, "")
+ self.assertRaises((OSError, TypeError), f.readinto, "")
f.close()
self.assertTrue(f.closed)
@@ -126,7 +126,7 @@ class AutoFileTests:
self.assertEqual(self.f.__exit__(*sys.exc_info()), None)
def testReadWhenWriting(self):
- self.assertRaises(IOError, self.f.read)
+ self.assertRaises(OSError, self.f.read)
class CAutoFileTests(AutoFileTests, unittest.TestCase):
open = io.open
@@ -177,7 +177,7 @@ class OtherFileTests:
d = int(f.read().decode("ascii"))
f.close()
f.close()
- except IOError as msg:
+ except OSError as msg:
self.fail('error setting buffer size %d: %s' % (s, str(msg)))
self.assertEqual(d, s)
diff --git a/Lib/test/test_filecmp.py b/Lib/test/test_filecmp.py
index 09599794ad..c7cb3fcf36 100644
--- a/Lib/test/test_filecmp.py
+++ b/Lib/test/test_filecmp.py
@@ -1,4 +1,3 @@
-
import os, filecmp, shutil, tempfile
import unittest
from test import support
@@ -40,15 +39,27 @@ class FileCompareTestCase(unittest.TestCase):
self.assertFalse(filecmp.cmp(self.name, self.dir),
"File and directory compare as equal")
+ def test_cache_clear(self):
+ first_compare = filecmp.cmp(self.name, self.name_same, shallow=False)
+ second_compare = filecmp.cmp(self.name, self.name_diff, shallow=False)
+ filecmp.clear_cache()
+ self.assertTrue(len(filecmp._cache) == 0,
+ "Cache not cleared after calling clear_cache")
+
class DirCompareTestCase(unittest.TestCase):
def setUp(self):
tmpdir = tempfile.gettempdir()
self.dir = os.path.join(tmpdir, 'dir')
self.dir_same = os.path.join(tmpdir, 'dir-same')
self.dir_diff = os.path.join(tmpdir, 'dir-diff')
+
+ # Another dir is created under dir_same, but it has a name from the
+ # ignored list so it should not affect testing results.
+ self.dir_ignored = os.path.join(self.dir_same, '.hg')
+
self.caseinsensitive = os.path.normcase('A') == os.path.normcase('a')
data = 'Contents of file go here.\n'
- for dir in [self.dir, self.dir_same, self.dir_diff]:
+ for dir in (self.dir, self.dir_same, self.dir_diff, self.dir_ignored):
shutil.rmtree(dir, True)
os.mkdir(dir)
if self.caseinsensitive and dir is self.dir_same:
@@ -64,9 +75,11 @@ class DirCompareTestCase(unittest.TestCase):
output.close()
def tearDown(self):
- shutil.rmtree(self.dir)
- shutil.rmtree(self.dir_same)
- shutil.rmtree(self.dir_diff)
+ for dir in (self.dir, self.dir_same, self.dir_diff):
+ shutil.rmtree(dir)
+
+ def test_default_ignores(self):
+ self.assertIn('.hg', filecmp.DEFAULT_IGNORES)
def test_cmpfiles(self):
self.assertTrue(filecmp.cmpfiles(self.dir, self.dir, ['file']) ==
diff --git a/Lib/test/test_fileinput.py b/Lib/test/test_fileinput.py
index 1e70641150..db6082cb12 100644
--- a/Lib/test/test_fileinput.py
+++ b/Lib/test/test_fileinput.py
@@ -22,7 +22,7 @@ except ImportError:
from io import StringIO
from fileinput import FileInput, hook_encoded
-from test.support import verbose, TESTFN, run_unittest
+from test.support import verbose, TESTFN, run_unittest, check_warnings
from test.support import unlink as safe_unlink
@@ -224,8 +224,10 @@ class FileInputTests(unittest.TestCase):
try:
# try opening in universal newline mode
t1 = writeTmp(1, [b"A\nB\r\nC\rD"], mode="wb")
- fi = FileInput(files=t1, mode="U")
- lines = list(fi)
+ with check_warnings(('', DeprecationWarning)):
+ fi = FileInput(files=t1, mode="U")
+ with check_warnings(('', DeprecationWarning)):
+ lines = list(fi)
self.assertEqual(lines, ["A\n", "B\n", "C\n", "D"])
finally:
remove_tempfiles(t1)
@@ -275,8 +277,8 @@ class FileInputTests(unittest.TestCase):
try:
t1 = writeTmp(1, [""])
with FileInput(files=t1) as fi:
- raise IOError
- except IOError:
+ raise OSError
+ except OSError:
self.assertEqual(fi._files, ())
finally:
remove_tempfiles(t1)
@@ -835,22 +837,6 @@ class Test_hook_encoded(unittest.TestCase):
self.assertIs(kwargs.pop('encoding'), encoding)
self.assertFalse(kwargs)
-def test_main():
- run_unittest(
- BufferSizesTests,
- FileInputTests,
- Test_fileinput_input,
- Test_fileinput_close,
- Test_fileinput_nextfile,
- Test_fileinput_filename,
- Test_fileinput_lineno,
- Test_fileinput_filelineno,
- Test_fileinput_fileno,
- Test_fileinput_isfirstline,
- Test_fileinput_isstdin,
- Test_hook_compressed,
- Test_hook_encoded,
- )
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py
index f91ccaa427..e67876985e 100644
--- a/Lib/test/test_fileio.py
+++ b/Lib/test/test_fileio.py
@@ -145,16 +145,16 @@ class AutoFileTests(unittest.TestCase):
# Unix calls dircheck() and returns "[Errno 21]: Is a directory"
try:
_FileIO('.', 'r')
- except IOError as e:
+ except OSError as e:
self.assertNotEqual(e.errno, 0)
self.assertEqual(e.filename, ".")
else:
- self.fail("Should have raised IOError")
+ self.fail("Should have raised OSError")
@unittest.skipIf(os.name == 'nt', "test only works on a POSIX-like system")
def testOpenDirFD(self):
fd = os.open('.', os.O_RDONLY)
- with self.assertRaises(IOError) as cm:
+ with self.assertRaises(OSError) as cm:
_FileIO(fd, 'r')
os.close(fd)
self.assertEqual(cm.exception.errno, errno.EISDIR)
@@ -172,7 +172,7 @@ class AutoFileTests(unittest.TestCase):
finally:
try:
self.f.close()
- except IOError:
+ except OSError:
pass
return wrapper
@@ -184,14 +184,14 @@ class AutoFileTests(unittest.TestCase):
os.close(f.fileno())
try:
func(self, f)
- except IOError as e:
+ except OSError as e:
self.assertEqual(e.errno, errno.EBADF)
else:
- self.fail("Should have raised IOError")
+ self.fail("Should have raised OSError")
finally:
try:
self.f.close()
- except IOError:
+ except OSError:
pass
return wrapper
@@ -238,7 +238,7 @@ class AutoFileTests(unittest.TestCase):
def ReopenForRead(self):
try:
self.f.close()
- except IOError:
+ except OSError:
pass
self.f = _FileIO(TESTFN, 'r')
os.close(self.f.fileno())
@@ -286,7 +286,7 @@ class OtherFileTests(unittest.TestCase):
if sys.platform != "win32":
try:
f = _FileIO("/dev/tty", "a")
- except EnvironmentError:
+ except OSError:
# When run in a cron job there just aren't any
# ttys, so skip the test. This also handles other
# OS'es that don't support /dev/tty.
@@ -362,7 +362,7 @@ class OtherFileTests(unittest.TestCase):
self.assertRaises(OSError, _FileIO, make_bad_fd())
if sys.platform == 'win32':
import msvcrt
- self.assertRaises(IOError, msvcrt.get_osfhandle, make_bad_fd())
+ self.assertRaises(OSError, msvcrt.get_osfhandle, make_bad_fd())
# Issue 15989
self.assertRaises(TypeError, _FileIO, _testcapi.INT_MAX + 1)
self.assertRaises(TypeError, _FileIO, _testcapi.INT_MIN - 1)
diff --git a/Lib/test/test_finalization.py b/Lib/test/test_finalization.py
new file mode 100644
index 0000000000..80c9b87988
--- /dev/null
+++ b/Lib/test/test_finalization.py
@@ -0,0 +1,513 @@
+"""
+Tests for object finalization semantics, as outlined in PEP 442.
+"""
+
+import contextlib
+import gc
+import unittest
+import weakref
+
+import _testcapi
+from test import support
+
+
+class NonGCSimpleBase:
+ """
+ The base class for all the objects under test, equipped with various
+ testing features.
+ """
+
+ survivors = []
+ del_calls = []
+ tp_del_calls = []
+ errors = []
+
+ _cleaning = False
+
+ __slots__ = ()
+
+ @classmethod
+ def _cleanup(cls):
+ cls.survivors.clear()
+ cls.errors.clear()
+ gc.garbage.clear()
+ gc.collect()
+ cls.del_calls.clear()
+ cls.tp_del_calls.clear()
+
+ @classmethod
+ @contextlib.contextmanager
+ def test(cls):
+ """
+ A context manager to use around all finalization tests.
+ """
+ with support.disable_gc():
+ cls.del_calls.clear()
+ cls.tp_del_calls.clear()
+ NonGCSimpleBase._cleaning = False
+ try:
+ yield
+ if cls.errors:
+ raise cls.errors[0]
+ finally:
+ NonGCSimpleBase._cleaning = True
+ cls._cleanup()
+
+ def check_sanity(self):
+ """
+ Check the object is sane (non-broken).
+ """
+
+ def __del__(self):
+ """
+ PEP 442 finalizer. Record that this was called, check the
+ object is in a sane state, and invoke a side effect.
+ """
+ try:
+ if not self._cleaning:
+ self.del_calls.append(id(self))
+ self.check_sanity()
+ self.side_effect()
+ except Exception as e:
+ self.errors.append(e)
+
+ def side_effect(self):
+ """
+ A side effect called on destruction.
+ """
+
+
+class SimpleBase(NonGCSimpleBase):
+
+ def __init__(self):
+ self.id_ = id(self)
+
+ def check_sanity(self):
+ assert self.id_ == id(self)
+
+
+class NonGC(NonGCSimpleBase):
+ __slots__ = ()
+
+class NonGCResurrector(NonGCSimpleBase):
+ __slots__ = ()
+
+ def side_effect(self):
+ """
+ Resurrect self by storing self in a class-wide list.
+ """
+ self.survivors.append(self)
+
+class Simple(SimpleBase):
+ pass
+
+class SimpleResurrector(NonGCResurrector, SimpleBase):
+ pass
+
+
+class TestBase:
+
+ def setUp(self):
+ self.old_garbage = gc.garbage[:]
+ gc.garbage[:] = []
+
+ def tearDown(self):
+ # None of the tests here should put anything in gc.garbage
+ try:
+ self.assertEqual(gc.garbage, [])
+ finally:
+ del self.old_garbage
+ gc.collect()
+
+ def assert_del_calls(self, ids):
+ self.assertEqual(sorted(SimpleBase.del_calls), sorted(ids))
+
+ def assert_tp_del_calls(self, ids):
+ self.assertEqual(sorted(SimpleBase.tp_del_calls), sorted(ids))
+
+ def assert_survivors(self, ids):
+ self.assertEqual(sorted(id(x) for x in SimpleBase.survivors), sorted(ids))
+
+ def assert_garbage(self, ids):
+ self.assertEqual(sorted(id(x) for x in gc.garbage), sorted(ids))
+
+ def clear_survivors(self):
+ SimpleBase.survivors.clear()
+
+
+class SimpleFinalizationTest(TestBase, unittest.TestCase):
+ """
+ Test finalization without refcycles.
+ """
+
+ def test_simple(self):
+ with SimpleBase.test():
+ s = Simple()
+ ids = [id(s)]
+ wr = weakref.ref(s)
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+ self.assertIs(wr(), None)
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+
+ def test_simple_resurrect(self):
+ with SimpleBase.test():
+ s = SimpleResurrector()
+ ids = [id(s)]
+ wr = weakref.ref(s)
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors(ids)
+ self.assertIsNot(wr(), None)
+ self.clear_survivors()
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+ self.assertIs(wr(), None)
+
+ def test_non_gc(self):
+ with SimpleBase.test():
+ s = NonGC()
+ self.assertFalse(gc.is_tracked(s))
+ ids = [id(s)]
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+
+ def test_non_gc_resurrect(self):
+ with SimpleBase.test():
+ s = NonGCResurrector()
+ self.assertFalse(gc.is_tracked(s))
+ ids = [id(s)]
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors(ids)
+ self.clear_survivors()
+ gc.collect()
+ self.assert_del_calls(ids * 2)
+ self.assert_survivors(ids)
+
+
+class SelfCycleBase:
+
+ def __init__(self):
+ super().__init__()
+ self.ref = self
+
+ def check_sanity(self):
+ super().check_sanity()
+ assert self.ref is self
+
+class SimpleSelfCycle(SelfCycleBase, Simple):
+ pass
+
+class SelfCycleResurrector(SelfCycleBase, SimpleResurrector):
+ pass
+
+class SuicidalSelfCycle(SelfCycleBase, Simple):
+
+ def side_effect(self):
+ """
+ Explicitly break the reference cycle.
+ """
+ self.ref = None
+
+
+class SelfCycleFinalizationTest(TestBase, unittest.TestCase):
+ """
+ Test finalization of an object having a single cyclic reference to
+ itself.
+ """
+
+ def test_simple(self):
+ with SimpleBase.test():
+ s = SimpleSelfCycle()
+ ids = [id(s)]
+ wr = weakref.ref(s)
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+ self.assertIs(wr(), None)
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+
+ def test_simple_resurrect(self):
+ # Test that __del__ can resurrect the object being finalized.
+ with SimpleBase.test():
+ s = SelfCycleResurrector()
+ ids = [id(s)]
+ wr = weakref.ref(s)
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors(ids)
+ # XXX is this desirable?
+ self.assertIs(wr(), None)
+ # When trying to destroy the object a second time, __del__
+ # isn't called anymore (and the object isn't resurrected).
+ self.clear_survivors()
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+ self.assertIs(wr(), None)
+
+ def test_simple_suicide(self):
+ # Test the GC is able to deal with an object that kills its last
+ # reference during __del__.
+ with SimpleBase.test():
+ s = SuicidalSelfCycle()
+ ids = [id(s)]
+ wr = weakref.ref(s)
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+ self.assertIs(wr(), None)
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+ self.assertIs(wr(), None)
+
+
+class ChainedBase:
+
+ def chain(self, left):
+ self.suicided = False
+ self.left = left
+ left.right = self
+
+ def check_sanity(self):
+ super().check_sanity()
+ if self.suicided:
+ assert self.left is None
+ assert self.right is None
+ else:
+ left = self.left
+ if left.suicided:
+ assert left.right is None
+ else:
+ assert left.right is self
+ right = self.right
+ if right.suicided:
+ assert right.left is None
+ else:
+ assert right.left is self
+
+class SimpleChained(ChainedBase, Simple):
+ pass
+
+class ChainedResurrector(ChainedBase, SimpleResurrector):
+ pass
+
+class SuicidalChained(ChainedBase, Simple):
+
+ def side_effect(self):
+ """
+ Explicitly break the reference cycle.
+ """
+ self.suicided = True
+ self.left = None
+ self.right = None
+
+
+class CycleChainFinalizationTest(TestBase, unittest.TestCase):
+ """
+ Test finalization of a cyclic chain. These tests are similar in
+ spirit to the self-cycle tests above, but the collectable object
+ graph isn't trivial anymore.
+ """
+
+ def build_chain(self, classes):
+ nodes = [cls() for cls in classes]
+ for i in range(len(nodes)):
+ nodes[i].chain(nodes[i-1])
+ return nodes
+
+ def check_non_resurrecting_chain(self, classes):
+ N = len(classes)
+ with SimpleBase.test():
+ nodes = self.build_chain(classes)
+ ids = [id(s) for s in nodes]
+ wrs = [weakref.ref(s) for s in nodes]
+ del nodes
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+ self.assertEqual([wr() for wr in wrs], [None] * N)
+ gc.collect()
+ self.assert_del_calls(ids)
+
+ def check_resurrecting_chain(self, classes):
+ N = len(classes)
+ with SimpleBase.test():
+ nodes = self.build_chain(classes)
+ N = len(nodes)
+ ids = [id(s) for s in nodes]
+ survivor_ids = [id(s) for s in nodes if isinstance(s, SimpleResurrector)]
+ wrs = [weakref.ref(s) for s in nodes]
+ del nodes
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors(survivor_ids)
+ # XXX desirable?
+ self.assertEqual([wr() for wr in wrs], [None] * N)
+ self.clear_survivors()
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_survivors([])
+
+ def test_homogenous(self):
+ self.check_non_resurrecting_chain([SimpleChained] * 3)
+
+ def test_homogenous_resurrect(self):
+ self.check_resurrecting_chain([ChainedResurrector] * 3)
+
+ def test_homogenous_suicidal(self):
+ self.check_non_resurrecting_chain([SuicidalChained] * 3)
+
+ def test_heterogenous_suicidal_one(self):
+ self.check_non_resurrecting_chain([SuicidalChained, SimpleChained] * 2)
+
+ def test_heterogenous_suicidal_two(self):
+ self.check_non_resurrecting_chain(
+ [SuicidalChained] * 2 + [SimpleChained] * 2)
+
+ def test_heterogenous_resurrect_one(self):
+ self.check_resurrecting_chain([ChainedResurrector, SimpleChained] * 2)
+
+ def test_heterogenous_resurrect_two(self):
+ self.check_resurrecting_chain(
+ [ChainedResurrector, SimpleChained, SuicidalChained] * 2)
+
+ def test_heterogenous_resurrect_three(self):
+ self.check_resurrecting_chain(
+ [ChainedResurrector] * 2 + [SimpleChained] * 2 + [SuicidalChained] * 2)
+
+
+# NOTE: the tp_del slot isn't automatically inherited, so we have to call
+# with_tp_del() for each instantiated class.
+
+class LegacyBase(SimpleBase):
+
+ def __del__(self):
+ try:
+ # Do not invoke side_effect here, since we are now exercising
+ # the tp_del slot.
+ if not self._cleaning:
+ self.del_calls.append(id(self))
+ self.check_sanity()
+ except Exception as e:
+ self.errors.append(e)
+
+ def __tp_del__(self):
+ """
+ Legacy (pre-PEP 442) finalizer, mapped to a tp_del slot.
+ """
+ try:
+ if not self._cleaning:
+ self.tp_del_calls.append(id(self))
+ self.check_sanity()
+ self.side_effect()
+ except Exception as e:
+ self.errors.append(e)
+
+@_testcapi.with_tp_del
+class Legacy(LegacyBase):
+ pass
+
+@_testcapi.with_tp_del
+class LegacyResurrector(LegacyBase):
+
+ def side_effect(self):
+ """
+ Resurrect self by storing self in a class-wide list.
+ """
+ self.survivors.append(self)
+
+@_testcapi.with_tp_del
+class LegacySelfCycle(SelfCycleBase, LegacyBase):
+ pass
+
+
+class LegacyFinalizationTest(TestBase, unittest.TestCase):
+ """
+ Test finalization of objects with a tp_del.
+ """
+
+ def tearDown(self):
+ # These tests need to clean up a bit more, since they create
+ # uncollectable objects.
+ gc.garbage.clear()
+ gc.collect()
+ super().tearDown()
+
+ def test_legacy(self):
+ with SimpleBase.test():
+ s = Legacy()
+ ids = [id(s)]
+ wr = weakref.ref(s)
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_tp_del_calls(ids)
+ self.assert_survivors([])
+ self.assertIs(wr(), None)
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_tp_del_calls(ids)
+
+ def test_legacy_resurrect(self):
+ with SimpleBase.test():
+ s = LegacyResurrector()
+ ids = [id(s)]
+ wr = weakref.ref(s)
+ del s
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_tp_del_calls(ids)
+ self.assert_survivors(ids)
+ # weakrefs are cleared before tp_del is called.
+ self.assertIs(wr(), None)
+ self.clear_survivors()
+ gc.collect()
+ self.assert_del_calls(ids)
+ self.assert_tp_del_calls(ids * 2)
+ self.assert_survivors(ids)
+ self.assertIs(wr(), None)
+
+ def test_legacy_self_cycle(self):
+ # Self-cycles with legacy finalizers end up in gc.garbage.
+ with SimpleBase.test():
+ s = LegacySelfCycle()
+ ids = [id(s)]
+ wr = weakref.ref(s)
+ del s
+ gc.collect()
+ self.assert_del_calls([])
+ self.assert_tp_del_calls([])
+ self.assert_survivors([])
+ self.assert_garbage(ids)
+ self.assertIsNot(wr(), None)
+ # Break the cycle to allow collection
+ gc.garbage[0].ref = None
+ self.assert_garbage([])
+ self.assertIs(wr(), None)
+
+
+def test_main():
+ support.run_unittest(__name__)
+
+if __name__ == "__main__":
+ test_main()
diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
index 502292f615..5c2dc3ff3b 100644
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -41,6 +41,7 @@ class GeneralFloatCases(unittest.TestCase):
self.assertRaises(ValueError, float, "-.")
self.assertRaises(ValueError, float, b"-")
self.assertRaises(TypeError, float, {})
+ self.assertRaisesRegex(TypeError, "not 'dict'", float, {})
# Lone surrogate
self.assertRaises(UnicodeEncodeError, float, '\uD8F0')
# check that we don't accept alternate exponent markers
diff --git a/Lib/test/test_fork1.py b/Lib/test/test_fork1.py
index 8192c38a44..e0626dffdc 100644
--- a/Lib/test/test_fork1.py
+++ b/Lib/test/test_fork1.py
@@ -1,7 +1,7 @@
"""This test checks for correct fork() behavior.
"""
-import imp
+import _imp as imp
import os
import signal
import sys
diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py
index bd159f5689..29330f9171 100644
--- a/Lib/test/test_format.py
+++ b/Lib/test/test_format.py
@@ -307,27 +307,41 @@ class FormatTest(unittest.TestCase):
finally:
locale.setlocale(locale.LC_ALL, oldloc)
+ @support.cpython_only
+ def test_optimisations(self):
+ text = "abcde" # 5 characters
+ self.assertIs("%s" % text, text)
+ self.assertIs("%.5s" % text, text)
+ self.assertIs("%.10s" % text, text)
+ self.assertIs("%1s" % text, text)
+ self.assertIs("%5s" % text, text)
-def test_main():
- support.run_unittest(FormatTest)
+ self.assertIs("{0}".format(text), text)
+ self.assertIs("{0:s}".format(text), text)
+ self.assertIs("{0:.5s}".format(text), text)
+ self.assertIs("{0:.10s}".format(text), text)
+ self.assertIs("{0:1s}".format(text), text)
+ self.assertIs("{0:5s}".format(text), text)
+ self.assertIs(text % (), text)
+ self.assertIs(text.format(), text)
+
+ @support.cpython_only
def test_precision(self):
- INT_MAX = 2147483647
+ from _testcapi import INT_MAX
f = 1.2
self.assertEqual(format(f, ".0f"), "1")
self.assertEqual(format(f, ".3f"), "1.200")
with self.assertRaises(ValueError) as cm:
format(f, ".%sf" % (INT_MAX + 1))
- self.assertEqual(str(cm.exception), "precision too big")
c = complex(f)
- self.assertEqual(format(f, ".0f"), "1")
- self.assertEqual(format(f, ".3f"), "1.200")
+ self.assertEqual(format(c, ".0f"), "1+0j")
+ self.assertEqual(format(c, ".3f"), "1.200+0.000j")
with self.assertRaises(ValueError) as cm:
- format(f, ".%sf" % (INT_MAX + 1))
- self.assertEqual(str(cm.exception), "precision too big")
+ format(c, ".%sf" % (INT_MAX + 1))
if __name__ == "__main__":
diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py
index 1fad9215c0..33365322b9 100644
--- a/Lib/test/test_fractions.py
+++ b/Lib/test/test_fractions.py
@@ -146,9 +146,10 @@ class FractionTest(unittest.TestCase):
self.assertEqual((0, 1), _components(F(-0.0)))
self.assertEqual((3602879701896397, 36028797018963968),
_components(F(0.1)))
- self.assertRaises(TypeError, F, float('nan'))
- self.assertRaises(TypeError, F, float('inf'))
- self.assertRaises(TypeError, F, float('-inf'))
+ # bug 16469: error types should be consistent with float -> int
+ self.assertRaises(ValueError, F, float('nan'))
+ self.assertRaises(OverflowError, F, float('inf'))
+ self.assertRaises(OverflowError, F, float('-inf'))
def testInitFromDecimal(self):
self.assertEqual((11, 10),
@@ -157,10 +158,11 @@ class FractionTest(unittest.TestCase):
_components(F(Decimal('3.5e-2'))))
self.assertEqual((0, 1),
_components(F(Decimal('.000e20'))))
- self.assertRaises(TypeError, F, Decimal('nan'))
- self.assertRaises(TypeError, F, Decimal('snan'))
- self.assertRaises(TypeError, F, Decimal('inf'))
- self.assertRaises(TypeError, F, Decimal('-inf'))
+ # bug 16469: error types should be consistent with decimal -> int
+ self.assertRaises(ValueError, F, Decimal('nan'))
+ self.assertRaises(ValueError, F, Decimal('snan'))
+ self.assertRaises(OverflowError, F, Decimal('inf'))
+ self.assertRaises(OverflowError, F, Decimal('-inf'))
def testFromString(self):
self.assertEqual((5, 1), _components(F("5")))
@@ -248,14 +250,15 @@ class FractionTest(unittest.TestCase):
inf = 1e1000
nan = inf - inf
+ # bug 16469: error types should be consistent with float -> int
self.assertRaisesMessage(
- TypeError, "Cannot convert inf to Fraction.",
+ OverflowError, "Cannot convert inf to Fraction.",
F.from_float, inf)
self.assertRaisesMessage(
- TypeError, "Cannot convert -inf to Fraction.",
+ OverflowError, "Cannot convert -inf to Fraction.",
F.from_float, -inf)
self.assertRaisesMessage(
- TypeError, "Cannot convert nan to Fraction.",
+ ValueError, "Cannot convert nan to Fraction.",
F.from_float, nan)
def testFromDecimal(self):
@@ -268,17 +271,18 @@ class FractionTest(unittest.TestCase):
self.assertEqual(1 - F(1, 10**30),
F.from_decimal(Decimal("0." + "9" * 30)))
+ # bug 16469: error types should be consistent with decimal -> int
self.assertRaisesMessage(
- TypeError, "Cannot convert Infinity to Fraction.",
+ OverflowError, "Cannot convert Infinity to Fraction.",
F.from_decimal, Decimal("inf"))
self.assertRaisesMessage(
- TypeError, "Cannot convert -Infinity to Fraction.",
+ OverflowError, "Cannot convert -Infinity to Fraction.",
F.from_decimal, Decimal("-inf"))
self.assertRaisesMessage(
- TypeError, "Cannot convert NaN to Fraction.",
+ ValueError, "Cannot convert NaN to Fraction.",
F.from_decimal, Decimal("nan"))
self.assertRaisesMessage(
- TypeError, "Cannot convert sNaN to Fraction.",
+ ValueError, "Cannot convert sNaN to Fraction.",
F.from_decimal, Decimal("snan"))
def testLimitDenominator(self):
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
new file mode 100644
index 0000000000..2dd5780c88
--- /dev/null
+++ b/Lib/test/test_frame.py
@@ -0,0 +1,116 @@
+import gc
+import sys
+import unittest
+import weakref
+
+from test import support
+
+
+class ClearTest(unittest.TestCase):
+ """
+ Tests for frame.clear().
+ """
+
+ def inner(self, x=5, **kwargs):
+ 1/0
+
+ def outer(self, **kwargs):
+ try:
+ self.inner(**kwargs)
+ except ZeroDivisionError as e:
+ exc = e
+ return exc
+
+ def clear_traceback_frames(self, tb):
+ """
+ Clear all frames in a traceback.
+ """
+ while tb is not None:
+ tb.tb_frame.clear()
+ tb = tb.tb_next
+
+ def test_clear_locals(self):
+ class C:
+ pass
+ c = C()
+ wr = weakref.ref(c)
+ exc = self.outer(c=c)
+ del c
+ support.gc_collect()
+ # A reference to c is held through the frames
+ self.assertIsNot(None, wr())
+ self.clear_traceback_frames(exc.__traceback__)
+ support.gc_collect()
+ # The reference was released by .clear()
+ self.assertIs(None, wr())
+
+ def test_clear_generator(self):
+ endly = False
+ def g():
+ nonlocal endly
+ try:
+ yield
+ inner()
+ finally:
+ endly = True
+ gen = g()
+ next(gen)
+ self.assertFalse(endly)
+ # Clearing the frame closes the generator
+ gen.gi_frame.clear()
+ self.assertTrue(endly)
+
+ def test_clear_executing(self):
+ # Attempting to clear an executing frame is forbidden.
+ try:
+ 1/0
+ except ZeroDivisionError as e:
+ f = e.__traceback__.tb_frame
+ with self.assertRaises(RuntimeError):
+ f.clear()
+ with self.assertRaises(RuntimeError):
+ f.f_back.clear()
+
+ def test_clear_executing_generator(self):
+ # Attempting to clear an executing generator frame is forbidden.
+ endly = False
+ def g():
+ nonlocal endly
+ try:
+ 1/0
+ except ZeroDivisionError as e:
+ f = e.__traceback__.tb_frame
+ with self.assertRaises(RuntimeError):
+ f.clear()
+ with self.assertRaises(RuntimeError):
+ f.f_back.clear()
+ yield f
+ finally:
+ endly = True
+ gen = g()
+ f = next(gen)
+ self.assertFalse(endly)
+ # Clearing the frame closes the generator
+ f.clear()
+ self.assertTrue(endly)
+
+ @support.cpython_only
+ def test_clear_refcycles(self):
+ # .clear() doesn't leave any refcycle behind
+ with support.disable_gc():
+ class C:
+ pass
+ c = C()
+ wr = weakref.ref(c)
+ exc = self.outer(c=c)
+ del c
+ self.assertIsNot(None, wr())
+ self.clear_traceback_frames(exc.__traceback__)
+ self.assertIs(None, wr())
+
+
+def test_main():
+ support.run_unittest(__name__)
+
+if __name__ == "__main__":
+ test_main()
diff --git a/Lib/test/test_frozen.py b/Lib/test/test_frozen.py
deleted file mode 100644
index fd6761c651..0000000000
--- a/Lib/test/test_frozen.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Test the frozen module defined in frozen.c.
-
-from test.support import captured_stdout, run_unittest
-import unittest
-import sys
-
-class FrozenTests(unittest.TestCase):
-
- module_attrs = frozenset(['__builtins__', '__cached__', '__doc__',
- '__loader__', '__name__',
- '__package__'])
- package_attrs = frozenset(list(module_attrs) + ['__path__'])
-
- def test_frozen(self):
- with captured_stdout() as stdout:
- try:
- import __hello__
- except ImportError as x:
- self.fail("import __hello__ failed:" + str(x))
- self.assertEqual(__hello__.initialized, True)
- expect = set(self.module_attrs)
- expect.add('initialized')
- self.assertEqual(set(dir(__hello__)), expect)
- self.assertEqual(stdout.getvalue(), 'Hello world!\n')
-
- with captured_stdout() as stdout:
- try:
- import __phello__
- except ImportError as x:
- self.fail("import __phello__ failed:" + str(x))
- self.assertEqual(__phello__.initialized, True)
- expect = set(self.package_attrs)
- expect.add('initialized')
- if not "__phello__.spam" in sys.modules:
- self.assertEqual(set(dir(__phello__)), expect)
- else:
- expect.add('spam')
- self.assertEqual(set(dir(__phello__)), expect)
- self.assertEqual(__phello__.__path__, [__phello__.__name__])
- self.assertEqual(stdout.getvalue(), 'Hello world!\n')
-
- with captured_stdout() as stdout:
- try:
- import __phello__.spam
- except ImportError as x:
- self.fail("import __phello__.spam failed:" + str(x))
- self.assertEqual(__phello__.spam.initialized, True)
- spam_expect = set(self.module_attrs)
- spam_expect.add('initialized')
- self.assertEqual(set(dir(__phello__.spam)), spam_expect)
- phello_expect = set(self.package_attrs)
- phello_expect.add('initialized')
- phello_expect.add('spam')
- self.assertEqual(set(dir(__phello__)), phello_expect)
- self.assertEqual(stdout.getvalue(), 'Hello world!\n')
-
- try:
- import __phello__.foo
- except ImportError:
- pass
- else:
- self.fail("import __phello__.foo should have failed")
-
- try:
- import __phello__.foo
- except ImportError:
- pass
- else:
- self.fail("import __phello__.foo should have failed")
-
- del sys.modules['__hello__']
- del sys.modules['__phello__']
- del sys.modules['__phello__.spam']
-
-def test_main():
- run_unittest(FrozenTests)
-
-if __name__ == "__main__":
- test_main()
diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
index 64cd80bc14..15458a8ebf 100644
--- a/Lib/test/test_ftplib.py
+++ b/Lib/test/test_ftplib.py
@@ -21,6 +21,7 @@ from test import support
from test.support import HOST, HOSTv6
threading = support.import_module('threading')
+TIMEOUT = 3
# the dummy data returned by server over the data channel when
# RETR, LIST, NLST, MLSD commands are issued
RETR_DATA = 'abcde12345\r\n' * 1000
@@ -126,7 +127,7 @@ class DummyFTPHandler(asynchat.async_chat):
addr = list(map(int, arg.split(',')))
ip = '%d.%d.%d.%d' %tuple(addr[:4])
port = (addr[4] * 256) + addr[5]
- s = socket.create_connection((ip, port), timeout=2)
+ s = socket.create_connection((ip, port), timeout=TIMEOUT)
self.dtp = self.dtp_handler(s, baseclass=self)
self.push('200 active data connection established')
@@ -134,7 +135,7 @@ class DummyFTPHandler(asynchat.async_chat):
with socket.socket() as sock:
sock.bind((self.socket.getsockname()[0], 0))
sock.listen(5)
- sock.settimeout(10)
+ sock.settimeout(TIMEOUT)
ip, port = sock.getsockname()[:2]
ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256
self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2))
@@ -144,7 +145,7 @@ class DummyFTPHandler(asynchat.async_chat):
def cmd_eprt(self, arg):
af, ip, port = arg.split(arg[0])[1:-1]
port = int(port)
- s = socket.create_connection((ip, port), timeout=2)
+ s = socket.create_connection((ip, port), timeout=TIMEOUT)
self.dtp = self.dtp_handler(s, baseclass=self)
self.push('200 active data connection established')
@@ -152,7 +153,7 @@ class DummyFTPHandler(asynchat.async_chat):
with socket.socket(socket.AF_INET6) as sock:
sock.bind((self.socket.getsockname()[0], 0))
sock.listen(5)
- sock.settimeout(10)
+ sock.settimeout(TIMEOUT)
port = sock.getsockname()[1]
self.push('229 entering extended passive mode (|||%d|)' %port)
conn, addr = sock.accept()
@@ -300,7 +301,8 @@ class DummyFTPServer(asyncore.dispatcher, threading.Thread):
if ssl is not None:
- CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem")
+ CERTFILE = os.path.join(os.path.dirname(__file__), "keycert3.pem")
+ CAFILE = os.path.join(os.path.dirname(__file__), "pycacert.pem")
class SSLConnection(asyncore.dispatcher):
"""An asyncore.dispatcher subclass supporting TLS/SSL."""
@@ -327,7 +329,7 @@ if ssl is not None:
elif err.args[0] == ssl.SSL_ERROR_EOF:
return self.handle_close()
raise
- except socket.error as err:
+ except OSError as err:
if err.args[0] == errno.ECONNABORTED:
return self.handle_close()
else:
@@ -341,7 +343,7 @@ if ssl is not None:
if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
ssl.SSL_ERROR_WANT_WRITE):
return
- except socket.error as err:
+ except OSError as err:
# Any "socket error" corresponds to a SSL_ERROR_SYSCALL return
# from OpenSSL's SSL_shutdown(), corresponding to a
# closed socket condition. See also:
@@ -460,7 +462,7 @@ class TestFTPClass(TestCase):
def setUp(self):
self.server = DummyFTPServer((HOST, 0))
self.server.start()
- self.client = ftplib.FTP(timeout=2)
+ self.client = ftplib.FTP(timeout=TIMEOUT)
self.client.connect(self.server.host, self.server.port)
def tearDown(self):
@@ -488,7 +490,7 @@ class TestFTPClass(TestCase):
def test_all_errors(self):
exceptions = (ftplib.error_reply, ftplib.error_temp, ftplib.error_perm,
- ftplib.error_proto, ftplib.Error, IOError, EOFError)
+ ftplib.error_proto, ftplib.Error, OSError, EOFError)
for x in exceptions:
try:
raise x('exception not included in all_errors set')
@@ -678,7 +680,7 @@ class TestFTPClass(TestCase):
def test_makepasv(self):
host, port = self.client.makepasv()
- conn = socket.create_connection((host, port), 10)
+ conn = socket.create_connection((host, port), timeout=TIMEOUT)
conn.close()
# IPv4 is in use, just make sure send_epsv has not been used
self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv')
@@ -691,12 +693,12 @@ class TestFTPClass(TestCase):
return False
try:
self.client.sendcmd('noop')
- except (socket.error, EOFError):
+ except (OSError, EOFError):
return False
return True
# base test
- with ftplib.FTP(timeout=2) as self.client:
+ with ftplib.FTP(timeout=TIMEOUT) as self.client:
self.client.connect(self.server.host, self.server.port)
self.client.sendcmd('noop')
self.assertTrue(is_client_connected())
@@ -704,7 +706,7 @@ class TestFTPClass(TestCase):
self.assertFalse(is_client_connected())
# QUIT sent inside the with block
- with ftplib.FTP(timeout=2) as self.client:
+ with ftplib.FTP(timeout=TIMEOUT) as self.client:
self.client.connect(self.server.host, self.server.port)
self.client.sendcmd('noop')
self.client.quit()
@@ -714,7 +716,7 @@ class TestFTPClass(TestCase):
# force a wrong response code to be sent on QUIT: error_perm
# is expected and the connection is supposed to be closed
try:
- with ftplib.FTP(timeout=2) as self.client:
+ with ftplib.FTP(timeout=TIMEOUT) as self.client:
self.client.connect(self.server.host, self.server.port)
self.client.sendcmd('noop')
self.server.handler_instance.next_response = '550 error on quit'
@@ -736,7 +738,7 @@ class TestFTPClass(TestCase):
source_address=(HOST, port))
self.assertEqual(self.client.sock.getsockname()[1], port)
self.client.quit()
- except IOError as e:
+ except OSError as e:
if e.errno == errno.EADDRINUSE:
self.skipTest("couldn't bind to port %d" % port)
raise
@@ -747,7 +749,7 @@ class TestFTPClass(TestCase):
try:
with self.client.transfercmd('list') as sock:
self.assertEqual(sock.getsockname()[1], port)
- except IOError as e:
+ except OSError as e:
if e.errno == errno.EADDRINUSE:
self.skipTest("couldn't bind to port %d" % port)
raise
@@ -785,7 +787,7 @@ class TestIPv6Environment(TestCase):
def setUp(self):
self.server = DummyFTPServer((HOSTv6, 0), af=socket.AF_INET6)
self.server.start()
- self.client = ftplib.FTP()
+ self.client = ftplib.FTP(timeout=TIMEOUT)
self.client.connect(self.server.host, self.server.port)
def tearDown(self):
@@ -802,7 +804,7 @@ class TestIPv6Environment(TestCase):
def test_makepasv(self):
host, port = self.client.makepasv()
- conn = socket.create_connection((host, port), 10)
+ conn = socket.create_connection((host, port), timeout=TIMEOUT)
conn.close()
self.assertEqual(self.server.handler_instance.last_received_cmd, 'epsv')
@@ -829,7 +831,7 @@ class TestTLS_FTPClassMixin(TestFTPClass):
def setUp(self):
self.server = DummyTLS_FTPServer((HOST, 0))
self.server.start()
- self.client = ftplib.FTP_TLS(timeout=2)
+ self.client = ftplib.FTP_TLS(timeout=TIMEOUT)
self.client.connect(self.server.host, self.server.port)
# enable TLS
self.client.auth()
@@ -843,7 +845,7 @@ class TestTLS_FTPClass(TestCase):
def setUp(self):
self.server = DummyTLS_FTPServer((HOST, 0))
self.server.start()
- self.client = ftplib.FTP_TLS(timeout=2)
+ self.client = ftplib.FTP_TLS(timeout=TIMEOUT)
self.client.connect(self.server.host, self.server.port)
def tearDown(self):
@@ -903,7 +905,7 @@ class TestTLS_FTPClass(TestCase):
self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE,
keyfile=CERTFILE, context=ctx)
- self.client = ftplib.FTP_TLS(context=ctx, timeout=2)
+ self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT)
self.client.connect(self.server.host, self.server.port)
self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
self.client.auth()
@@ -922,6 +924,36 @@ class TestTLS_FTPClass(TestCase):
self.client.ccc()
self.assertRaises(ValueError, self.client.sock.unwrap)
+ def test_check_hostname(self):
+ self.client.quit()
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ ctx.verify_mode = ssl.CERT_REQUIRED
+ ctx.check_hostname = True
+ ctx.load_verify_locations(CAFILE)
+ self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT)
+
+ # 127.0.0.1 doesn't match SAN
+ self.client.connect(self.server.host, self.server.port)
+ with self.assertRaises(ssl.CertificateError):
+ self.client.auth()
+ # exception quits connection
+
+ self.client.connect(self.server.host, self.server.port)
+ self.client.prot_p()
+ with self.assertRaises(ssl.CertificateError):
+ with self.client.transfercmd("list") as sock:
+ pass
+ self.client.quit()
+
+ self.client.connect("localhost", self.server.port)
+ self.client.auth()
+ self.client.quit()
+
+ self.client.connect("localhost", self.server.port)
+ self.client.prot_p()
+ with self.client.transfercmd("list") as sock:
+ pass
+
class TestTimeouts(TestCase):
@@ -1017,8 +1049,19 @@ class TestTimeouts(TestCase):
ftp.close()
+class TestNetrcDeprecation(TestCase):
+
+ def test_deprecation(self):
+ with support.temp_cwd(), support.EnvironmentVarGuard() as env:
+ env['HOME'] = os.getcwd()
+ open('.netrc', 'w').close()
+ with self.assertWarns(DeprecationWarning):
+ ftplib.Netrc()
+
+
+
def test_main():
- tests = [TestFTPClass, TestTimeouts,
+ tests = [TestFTPClass, TestTimeouts, TestNetrcDeprecation,
TestIPv6Environment,
TestTLS_FTPClassMixin, TestTLS_FTPClass]
diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py
index c8ed83020e..5094f7ba1f 100644
--- a/Lib/test/test_funcattrs.py
+++ b/Lib/test/test_funcattrs.py
@@ -7,6 +7,11 @@ def global_function():
def inner_function():
class LocalClass:
pass
+ global inner_global_function
+ def inner_global_function():
+ def inner_function2():
+ pass
+ return inner_function2
return LocalClass
return lambda: inner_function
@@ -116,6 +121,8 @@ class FunctionPropertiesTest(FuncAttrsTest):
'global_function.<locals>.inner_function')
self.assertEqual(global_function()()().__qualname__,
'global_function.<locals>.inner_function.<locals>.LocalClass')
+ self.assertEqual(inner_global_function.__qualname__, 'inner_global_function')
+ self.assertEqual(inner_global_function().__qualname__, 'inner_global_function.<locals>.inner_function2')
self.b.__qualname__ = 'c'
self.assertEqual(self.b.__qualname__, 'c')
self.b.__qualname__ = 'd'
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index db1e9348dd..38c9713bf3 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -1,57 +1,55 @@
-import functools
+import abc
import collections
+from itertools import permutations
+import pickle
+from random import choice
import sys
-import unittest
from test import support
+import unittest
from weakref import proxy
-import pickle
-from random import choice
-@staticmethod
-def PythonPartial(func, *args, **keywords):
- 'Pure Python approximation of partial()'
- def newfunc(*fargs, **fkeywords):
- newkeywords = keywords.copy()
- newkeywords.update(fkeywords)
- return func(*(args + fargs), **newkeywords)
- newfunc.func = func
- newfunc.args = args
- newfunc.keywords = keywords
- return newfunc
+import functools
+
+py_functools = support.import_fresh_module('functools', blocked=['_functools'])
+c_functools = support.import_fresh_module('functools', fresh=['_functools'])
+
+decimal = support.import_fresh_module('decimal', fresh=['_decimal'])
+
def capture(*args, **kw):
"""capture all positional and keyword arguments"""
return args, kw
+
def signature(part):
""" return the signature of a partial object """
return (part.func, part.args, part.keywords, part.__dict__)
-class TestPartial(unittest.TestCase):
- thetype = functools.partial
+class TestPartial:
def test_basic_examples(self):
- p = self.thetype(capture, 1, 2, a=10, b=20)
+ p = self.partial(capture, 1, 2, a=10, b=20)
+ self.assertTrue(callable(p))
self.assertEqual(p(3, 4, b=30, c=40),
((1, 2, 3, 4), dict(a=10, b=30, c=40)))
- p = self.thetype(map, lambda x: x*10)
+ p = self.partial(map, lambda x: x*10)
self.assertEqual(list(p([1,2,3,4])), [10, 20, 30, 40])
def test_attributes(self):
- p = self.thetype(capture, 1, 2, a=10, b=20)
+ p = self.partial(capture, 1, 2, a=10, b=20)
# attributes should be readable
self.assertEqual(p.func, capture)
self.assertEqual(p.args, (1, 2))
self.assertEqual(p.keywords, dict(a=10, b=20))
# attributes should not be writable
- if not isinstance(self.thetype, type):
+ if not isinstance(self.partial, type):
return
self.assertRaises(AttributeError, setattr, p, 'func', map)
self.assertRaises(AttributeError, setattr, p, 'args', (1, 2))
self.assertRaises(AttributeError, setattr, p, 'keywords', dict(a=1, b=2))
- p = self.thetype(hex)
+ p = self.partial(hex)
try:
del p.__dict__
except TypeError:
@@ -60,9 +58,9 @@ class TestPartial(unittest.TestCase):
self.fail('partial object allowed __dict__ to be deleted')
def test_argument_checking(self):
- self.assertRaises(TypeError, self.thetype) # need at least a func arg
+ self.assertRaises(TypeError, self.partial) # need at least a func arg
try:
- self.thetype(2)()
+ self.partial(2)()
except TypeError:
pass
else:
@@ -73,7 +71,7 @@ class TestPartial(unittest.TestCase):
def func(a=10, b=20):
return a
d = {'a':3}
- p = self.thetype(func, a=5)
+ p = self.partial(func, a=5)
self.assertEqual(p(**d), 3)
self.assertEqual(d, {'a':3})
p(b=7)
@@ -82,20 +80,20 @@ class TestPartial(unittest.TestCase):
def test_arg_combinations(self):
# exercise special code paths for zero args in either partial
# object or the caller
- p = self.thetype(capture)
+ p = self.partial(capture)
self.assertEqual(p(), ((), {}))
self.assertEqual(p(1,2), ((1,2), {}))
- p = self.thetype(capture, 1, 2)
+ p = self.partial(capture, 1, 2)
self.assertEqual(p(), ((1,2), {}))
self.assertEqual(p(3,4), ((1,2,3,4), {}))
def test_kw_combinations(self):
# exercise special code paths for no keyword args in
# either the partial object or the caller
- p = self.thetype(capture)
+ p = self.partial(capture)
self.assertEqual(p(), ((), {}))
self.assertEqual(p(a=1), ((), {'a':1}))
- p = self.thetype(capture, a=1)
+ p = self.partial(capture, a=1)
self.assertEqual(p(), ((), {'a':1}))
self.assertEqual(p(b=2), ((), {'a':1, 'b':2}))
# keyword args in the call override those in the partial object
@@ -104,7 +102,7 @@ class TestPartial(unittest.TestCase):
def test_positional(self):
# make sure positional arguments are captured correctly
for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]:
- p = self.thetype(capture, *args)
+ p = self.partial(capture, *args)
expected = args + ('x',)
got, empty = p('x')
self.assertTrue(expected == got and empty == {})
@@ -112,14 +110,14 @@ class TestPartial(unittest.TestCase):
def test_keyword(self):
# make sure keyword arguments are captured correctly
for a in ['a', 0, None, 3.5]:
- p = self.thetype(capture, a=a)
+ p = self.partial(capture, a=a)
expected = {'a':a,'x':None}
empty, got = p(x=None)
self.assertTrue(expected == got and empty == ())
def test_no_side_effects(self):
# make sure there are no side effects that affect subsequent calls
- p = self.thetype(capture, 0, a=1)
+ p = self.partial(capture, 0, a=1)
args1, kw1 = p(1, b=2)
self.assertTrue(args1 == (0,1) and kw1 == {'a':1,'b':2})
args2, kw2 = p()
@@ -128,13 +126,13 @@ class TestPartial(unittest.TestCase):
def test_error_propagation(self):
def f(x, y):
x / y
- self.assertRaises(ZeroDivisionError, self.thetype(f, 1, 0))
- self.assertRaises(ZeroDivisionError, self.thetype(f, 1), 0)
- self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0)
- self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1)
+ self.assertRaises(ZeroDivisionError, self.partial(f, 1, 0))
+ self.assertRaises(ZeroDivisionError, self.partial(f, 1), 0)
+ self.assertRaises(ZeroDivisionError, self.partial(f), 1, 0)
+ self.assertRaises(ZeroDivisionError, self.partial(f, y=0), 1)
def test_weakref(self):
- f = self.thetype(int, base=16)
+ f = self.partial(int, base=16)
p = proxy(f)
self.assertEqual(f.func, p.func)
f = None
@@ -142,39 +140,46 @@ class TestPartial(unittest.TestCase):
def test_with_bound_and_unbound_methods(self):
data = list(map(str, range(10)))
- join = self.thetype(str.join, '')
+ join = self.partial(str.join, '')
self.assertEqual(join(data), '0123456789')
- join = self.thetype(''.join)
+ join = self.partial(''.join)
self.assertEqual(join(data), '0123456789')
+
+@unittest.skipUnless(c_functools, 'requires the C _functools module')
+class TestPartialC(TestPartial, unittest.TestCase):
+ if c_functools:
+ partial = c_functools.partial
+
def test_repr(self):
args = (object(), object())
args_repr = ', '.join(repr(a) for a in args)
- kwargs = {'a': object(), 'b': object()}
+ #kwargs = {'a': object(), 'b': object()}
+ kwargs = {'a': object()}
kwargs_repr = ', '.join("%s=%r" % (k, v) for k, v in kwargs.items())
- if self.thetype is functools.partial:
+ if self.partial is c_functools.partial:
name = 'functools.partial'
else:
- name = self.thetype.__name__
+ name = self.partial.__name__
- f = self.thetype(capture)
+ f = self.partial(capture)
self.assertEqual('{}({!r})'.format(name, capture),
repr(f))
- f = self.thetype(capture, *args)
+ f = self.partial(capture, *args)
self.assertEqual('{}({!r}, {})'.format(name, capture, args_repr),
repr(f))
- f = self.thetype(capture, **kwargs)
+ f = self.partial(capture, **kwargs)
self.assertEqual('{}({!r}, {})'.format(name, capture, kwargs_repr),
repr(f))
- f = self.thetype(capture, *args, **kwargs)
+ f = self.partial(capture, *args, **kwargs)
self.assertEqual('{}({!r}, {}, {})'.format(name, capture, args_repr, kwargs_repr),
repr(f))
def test_pickle(self):
- f = self.thetype(signature, 'asdf', bar=True)
+ f = self.partial(signature, 'asdf', bar=True)
f.add_something_to__dict__ = True
f_copy = pickle.loads(pickle.dumps(f))
self.assertEqual(signature(f), signature(f_copy))
@@ -193,28 +198,140 @@ class TestPartial(unittest.TestCase):
return {}
raise IndexError
- f = self.thetype(object)
+ f = self.partial(object)
self.assertRaisesRegex(SystemError,
"new style getargs format but argument is not a tuple",
f.__setstate__, BadSequence())
-class PartialSubclass(functools.partial):
- pass
-class TestPartialSubclass(TestPartial):
+class TestPartialPy(TestPartial, unittest.TestCase):
+ partial = staticmethod(py_functools.partial)
+
+
+if c_functools:
+ class PartialSubclass(c_functools.partial):
+ pass
+
- thetype = PartialSubclass
+@unittest.skipUnless(c_functools, 'requires the C _functools module')
+class TestPartialCSubclass(TestPartialC):
+ if c_functools:
+ partial = PartialSubclass
-class TestPythonPartial(TestPartial):
- thetype = PythonPartial
+class TestPartialMethod(unittest.TestCase):
- # the python version hasn't a nice repr
- def test_repr(self): pass
+ class A(object):
+ nothing = functools.partialmethod(capture)
+ positional = functools.partialmethod(capture, 1)
+ keywords = functools.partialmethod(capture, a=2)
+ both = functools.partialmethod(capture, 3, b=4)
+
+ nested = functools.partialmethod(positional, 5)
+
+ over_partial = functools.partialmethod(functools.partial(capture, c=6), 7)
+
+ static = functools.partialmethod(staticmethod(capture), 8)
+ cls = functools.partialmethod(classmethod(capture), d=9)
+
+ a = A()
+
+ def test_arg_combinations(self):
+ self.assertEqual(self.a.nothing(), ((self.a,), {}))
+ self.assertEqual(self.a.nothing(5), ((self.a, 5), {}))
+ self.assertEqual(self.a.nothing(c=6), ((self.a,), {'c': 6}))
+ self.assertEqual(self.a.nothing(5, c=6), ((self.a, 5), {'c': 6}))
+
+ self.assertEqual(self.a.positional(), ((self.a, 1), {}))
+ self.assertEqual(self.a.positional(5), ((self.a, 1, 5), {}))
+ self.assertEqual(self.a.positional(c=6), ((self.a, 1), {'c': 6}))
+ self.assertEqual(self.a.positional(5, c=6), ((self.a, 1, 5), {'c': 6}))
+
+ self.assertEqual(self.a.keywords(), ((self.a,), {'a': 2}))
+ self.assertEqual(self.a.keywords(5), ((self.a, 5), {'a': 2}))
+ self.assertEqual(self.a.keywords(c=6), ((self.a,), {'a': 2, 'c': 6}))
+ self.assertEqual(self.a.keywords(5, c=6), ((self.a, 5), {'a': 2, 'c': 6}))
+
+ self.assertEqual(self.a.both(), ((self.a, 3), {'b': 4}))
+ self.assertEqual(self.a.both(5), ((self.a, 3, 5), {'b': 4}))
+ self.assertEqual(self.a.both(c=6), ((self.a, 3), {'b': 4, 'c': 6}))
+ self.assertEqual(self.a.both(5, c=6), ((self.a, 3, 5), {'b': 4, 'c': 6}))
+
+ self.assertEqual(self.A.both(self.a, 5, c=6), ((self.a, 3, 5), {'b': 4, 'c': 6}))
+
+ def test_nested(self):
+ self.assertEqual(self.a.nested(), ((self.a, 1, 5), {}))
+ self.assertEqual(self.a.nested(6), ((self.a, 1, 5, 6), {}))
+ self.assertEqual(self.a.nested(d=7), ((self.a, 1, 5), {'d': 7}))
+ self.assertEqual(self.a.nested(6, d=7), ((self.a, 1, 5, 6), {'d': 7}))
+
+ self.assertEqual(self.A.nested(self.a, 6, d=7), ((self.a, 1, 5, 6), {'d': 7}))
+
+ def test_over_partial(self):
+ self.assertEqual(self.a.over_partial(), ((self.a, 7), {'c': 6}))
+ self.assertEqual(self.a.over_partial(5), ((self.a, 7, 5), {'c': 6}))
+ self.assertEqual(self.a.over_partial(d=8), ((self.a, 7), {'c': 6, 'd': 8}))
+ self.assertEqual(self.a.over_partial(5, d=8), ((self.a, 7, 5), {'c': 6, 'd': 8}))
+
+ self.assertEqual(self.A.over_partial(self.a, 5, d=8), ((self.a, 7, 5), {'c': 6, 'd': 8}))
+
+ def test_bound_method_introspection(self):
+ obj = self.a
+ self.assertIs(obj.both.__self__, obj)
+ self.assertIs(obj.nested.__self__, obj)
+ self.assertIs(obj.over_partial.__self__, obj)
+ self.assertIs(obj.cls.__self__, self.A)
+ self.assertIs(self.A.cls.__self__, self.A)
+
+ def test_unbound_method_retrieval(self):
+ obj = self.A
+ self.assertFalse(hasattr(obj.both, "__self__"))
+ self.assertFalse(hasattr(obj.nested, "__self__"))
+ self.assertFalse(hasattr(obj.over_partial, "__self__"))
+ self.assertFalse(hasattr(obj.static, "__self__"))
+ self.assertFalse(hasattr(self.a.static, "__self__"))
+
+ def test_descriptors(self):
+ for obj in [self.A, self.a]:
+ with self.subTest(obj=obj):
+ self.assertEqual(obj.static(), ((8,), {}))
+ self.assertEqual(obj.static(5), ((8, 5), {}))
+ self.assertEqual(obj.static(d=8), ((8,), {'d': 8}))
+ self.assertEqual(obj.static(5, d=8), ((8, 5), {'d': 8}))
+
+ self.assertEqual(obj.cls(), ((self.A,), {'d': 9}))
+ self.assertEqual(obj.cls(5), ((self.A, 5), {'d': 9}))
+ self.assertEqual(obj.cls(c=8), ((self.A,), {'c': 8, 'd': 9}))
+ self.assertEqual(obj.cls(5, c=8), ((self.A, 5), {'c': 8, 'd': 9}))
+
+ def test_overriding_keywords(self):
+ self.assertEqual(self.a.keywords(a=3), ((self.a,), {'a': 3}))
+ self.assertEqual(self.A.keywords(self.a, a=3), ((self.a,), {'a': 3}))
+
+ def test_invalid_args(self):
+ with self.assertRaises(TypeError):
+ class B(object):
+ method = functools.partialmethod(None, 1)
+
+ def test_repr(self):
+ self.assertEqual(repr(vars(self.A)['both']),
+ 'functools.partialmethod({}, 3, b=4)'.format(capture))
+
+ def test_abstract(self):
+ class Abstract(abc.ABCMeta):
+
+ @abc.abstractmethod
+ def add(self, x, y):
+ pass
+
+ add5 = functools.partialmethod(add, 5)
+
+ self.assertTrue(Abstract.add.__isabstractmethod__)
+ self.assertTrue(Abstract.add5.__isabstractmethod__)
+
+ for func in [self.A.static, self.A.cls, self.A.over_partial, self.A.nested, self.A.both]:
+ self.assertFalse(getattr(func, '__isabstractmethod__', False))
- # the python version isn't picklable
- def test_pickle(self): pass
- def test_setstate_refcount(self): pass
class TestUpdateWrapper(unittest.TestCase):
@@ -223,19 +340,26 @@ class TestUpdateWrapper(unittest.TestCase):
updated=functools.WRAPPER_UPDATES):
# Check attributes were assigned
for name in assigned:
- self.assertTrue(getattr(wrapper, name) is getattr(wrapped, name))
+ self.assertIs(getattr(wrapper, name), getattr(wrapped, name))
# Check attributes were updated
for name in updated:
wrapper_attr = getattr(wrapper, name)
wrapped_attr = getattr(wrapped, name)
for key in wrapped_attr:
- self.assertTrue(wrapped_attr[key] is wrapper_attr[key])
+ if name == "__dict__" and key == "__wrapped__":
+ # __wrapped__ is overwritten by the update code
+ continue
+ self.assertIs(wrapped_attr[key], wrapper_attr[key])
+ # Check __wrapped__
+ self.assertIs(wrapper.__wrapped__, wrapped)
+
def _default_update(self):
def f(a:'This is a new annotation'):
"""This is a test"""
pass
f.attr = 'This is also a test'
+ f.__wrapped__ = "This is a bald faced lie"
def wrapper(b:'This is the prior annotation'):
pass
functools.update_wrapper(wrapper, f)
@@ -322,6 +446,7 @@ class TestUpdateWrapper(unittest.TestCase):
self.assertTrue(wrapper.__doc__.startswith('max('))
self.assertEqual(wrapper.__annotations__, {})
+
class TestWraps(TestUpdateWrapper):
def _default_update(self):
@@ -329,14 +454,15 @@ class TestWraps(TestUpdateWrapper):
"""This is a test"""
pass
f.attr = 'This is also a test'
+ f.__wrapped__ = "This is still a bald faced lie"
@functools.wraps(f)
def wrapper():
pass
- self.check_wrapper(wrapper, f)
return wrapper, f
def test_default_update(self):
wrapper, f = self._default_update()
+ self.check_wrapper(wrapper, f)
self.assertEqual(wrapper.__name__, 'f')
self.assertEqual(wrapper.__qualname__, f.__qualname__)
self.assertEqual(wrapper.attr, 'This is also a test')
@@ -382,6 +508,7 @@ class TestWraps(TestUpdateWrapper):
self.assertEqual(wrapper.attr, 'This is a different test')
self.assertEqual(wrapper.dict_attr, f.dict_attr)
+
class TestReduce(unittest.TestCase):
func = functools.reduce
@@ -462,24 +589,29 @@ class TestReduce(unittest.TestCase):
d = {"one": 1, "two": 2, "three": 3}
self.assertEqual(self.func(add, d), "".join(d.keys()))
-class TestCmpToKey(unittest.TestCase):
+
+class TestCmpToKey:
def test_cmp_to_key(self):
def cmp1(x, y):
return (x > y) - (x < y)
- key = functools.cmp_to_key(cmp1)
+ key = self.cmp_to_key(cmp1)
self.assertEqual(key(3), key(3))
self.assertGreater(key(3), key(1))
+ self.assertGreaterEqual(key(3), key(3))
+
def cmp2(x, y):
return int(x) - int(y)
- key = functools.cmp_to_key(cmp2)
+ key = self.cmp_to_key(cmp2)
self.assertEqual(key(4.0), key('4'))
self.assertLess(key(2), key('35'))
+ self.assertLessEqual(key(2), key('35'))
+ self.assertNotEqual(key(2), key('35'))
def test_cmp_to_key_arguments(self):
def cmp1(x, y):
return (x > y) - (x < y)
- key = functools.cmp_to_key(mycmp=cmp1)
+ key = self.cmp_to_key(mycmp=cmp1)
self.assertEqual(key(obj=3), key(obj=3))
self.assertGreater(key(obj=3), key(obj=1))
with self.assertRaises((TypeError, AttributeError)):
@@ -487,10 +619,10 @@ class TestCmpToKey(unittest.TestCase):
with self.assertRaises((TypeError, AttributeError)):
1 < key(3) # lhs is not a K object
with self.assertRaises(TypeError):
- key = functools.cmp_to_key() # too few args
+ key = self.cmp_to_key() # too few args
with self.assertRaises(TypeError):
- key = functools.cmp_to_key(cmp1, None) # too many args
- key = functools.cmp_to_key(cmp1)
+ key = self.cmp_to_key(cmp1, None) # too many args
+ key = self.cmp_to_key(cmp1)
with self.assertRaises(TypeError):
key() # too few args
with self.assertRaises(TypeError):
@@ -499,7 +631,7 @@ class TestCmpToKey(unittest.TestCase):
def test_bad_cmp(self):
def cmp1(x, y):
raise ZeroDivisionError
- key = functools.cmp_to_key(cmp1)
+ key = self.cmp_to_key(cmp1)
with self.assertRaises(ZeroDivisionError):
key(3) > key(1)
@@ -514,13 +646,13 @@ class TestCmpToKey(unittest.TestCase):
def test_obj_field(self):
def cmp1(x, y):
return (x > y) - (x < y)
- key = functools.cmp_to_key(mycmp=cmp1)
+ key = self.cmp_to_key(mycmp=cmp1)
self.assertEqual(key(50).obj, 50)
def test_sort_int(self):
def mycmp(x, y):
return y - x
- self.assertEqual(sorted(range(5), key=functools.cmp_to_key(mycmp)),
+ self.assertEqual(sorted(range(5), key=self.cmp_to_key(mycmp)),
[4, 3, 2, 1, 0])
def test_sort_int_str(self):
@@ -528,18 +660,29 @@ class TestCmpToKey(unittest.TestCase):
x, y = int(x), int(y)
return (x > y) - (x < y)
values = [5, '3', 7, 2, '0', '1', 4, '10', 1]
- values = sorted(values, key=functools.cmp_to_key(mycmp))
+ values = sorted(values, key=self.cmp_to_key(mycmp))
self.assertEqual([int(value) for value in values],
[0, 1, 1, 2, 3, 4, 5, 7, 10])
def test_hash(self):
def mycmp(x, y):
return y - x
- key = functools.cmp_to_key(mycmp)
+ key = self.cmp_to_key(mycmp)
k = key(10)
self.assertRaises(TypeError, hash, k)
self.assertNotIsInstance(k, collections.Hashable)
+
+@unittest.skipUnless(c_functools, 'requires the C _functools module')
+class TestCmpToKeyC(TestCmpToKey, unittest.TestCase):
+ if c_functools:
+ cmp_to_key = c_functools.cmp_to_key
+
+
+class TestCmpToKeyPy(TestCmpToKey, unittest.TestCase):
+ cmp_to_key = staticmethod(py_functools.cmp_to_key)
+
+
class TestTotalOrdering(unittest.TestCase):
def test_total_ordering_lt(self):
@@ -557,6 +700,7 @@ class TestTotalOrdering(unittest.TestCase):
self.assertTrue(A(2) >= A(1))
self.assertTrue(A(2) <= A(2))
self.assertTrue(A(2) >= A(2))
+ self.assertFalse(A(1) > A(2))
def test_total_ordering_le(self):
@functools.total_ordering
@@ -573,6 +717,7 @@ class TestTotalOrdering(unittest.TestCase):
self.assertTrue(A(2) >= A(1))
self.assertTrue(A(2) <= A(2))
self.assertTrue(A(2) >= A(2))
+ self.assertFalse(A(1) >= A(2))
def test_total_ordering_gt(self):
@functools.total_ordering
@@ -589,6 +734,7 @@ class TestTotalOrdering(unittest.TestCase):
self.assertTrue(A(2) >= A(1))
self.assertTrue(A(2) <= A(2))
self.assertTrue(A(2) >= A(2))
+ self.assertFalse(A(2) < A(1))
def test_total_ordering_ge(self):
@functools.total_ordering
@@ -605,6 +751,7 @@ class TestTotalOrdering(unittest.TestCase):
self.assertTrue(A(2) >= A(1))
self.assertTrue(A(2) <= A(2))
self.assertTrue(A(2) >= A(2))
+ self.assertFalse(A(2) <= A(1))
def test_total_ordering_no_overwrite(self):
# new methods should not overwrite existing
@@ -624,27 +771,118 @@ class TestTotalOrdering(unittest.TestCase):
class A:
pass
- def test_bug_10042(self):
+ def test_type_error_when_not_implemented(self):
+ # bug 10042; ensure stack overflow does not occur
+ # when decorated types return NotImplemented
@functools.total_ordering
- class TestTO:
+ class ImplementsLessThan:
def __init__(self, value):
self.value = value
def __eq__(self, other):
- if isinstance(other, TestTO):
+ if isinstance(other, ImplementsLessThan):
return self.value == other.value
return False
def __lt__(self, other):
- if isinstance(other, TestTO):
+ if isinstance(other, ImplementsLessThan):
return self.value < other.value
- raise TypeError
- with self.assertRaises(TypeError):
- TestTO(8) <= ()
+ return NotImplemented
+
+ @functools.total_ordering
+ class ImplementsGreaterThan:
+ def __init__(self, value):
+ self.value = value
+ def __eq__(self, other):
+ if isinstance(other, ImplementsGreaterThan):
+ return self.value == other.value
+ return False
+ def __gt__(self, other):
+ if isinstance(other, ImplementsGreaterThan):
+ return self.value > other.value
+ return NotImplemented
+
+ @functools.total_ordering
+ class ImplementsLessThanEqualTo:
+ def __init__(self, value):
+ self.value = value
+ def __eq__(self, other):
+ if isinstance(other, ImplementsLessThanEqualTo):
+ return self.value == other.value
+ return False
+ def __le__(self, other):
+ if isinstance(other, ImplementsLessThanEqualTo):
+ return self.value <= other.value
+ return NotImplemented
+
+ @functools.total_ordering
+ class ImplementsGreaterThanEqualTo:
+ def __init__(self, value):
+ self.value = value
+ def __eq__(self, other):
+ if isinstance(other, ImplementsGreaterThanEqualTo):
+ return self.value == other.value
+ return False
+ def __ge__(self, other):
+ if isinstance(other, ImplementsGreaterThanEqualTo):
+ return self.value >= other.value
+ return NotImplemented
+
+ @functools.total_ordering
+ class ComparatorNotImplemented:
+ def __init__(self, value):
+ self.value = value
+ def __eq__(self, other):
+ if isinstance(other, ComparatorNotImplemented):
+ return self.value == other.value
+ return False
+ def __lt__(self, other):
+ return NotImplemented
+
+ with self.subTest("LT < 1"), self.assertRaises(TypeError):
+ ImplementsLessThan(-1) < 1
+
+ with self.subTest("LT < LE"), self.assertRaises(TypeError):
+ ImplementsLessThan(0) < ImplementsLessThanEqualTo(0)
+
+ with self.subTest("LT < GT"), self.assertRaises(TypeError):
+ ImplementsLessThan(1) < ImplementsGreaterThan(1)
+
+ with self.subTest("LE <= LT"), self.assertRaises(TypeError):
+ ImplementsLessThanEqualTo(2) <= ImplementsLessThan(2)
+
+ with self.subTest("LE <= GE"), self.assertRaises(TypeError):
+ ImplementsLessThanEqualTo(3) <= ImplementsGreaterThanEqualTo(3)
+
+ with self.subTest("GT > GE"), self.assertRaises(TypeError):
+ ImplementsGreaterThan(4) > ImplementsGreaterThanEqualTo(4)
+
+ with self.subTest("GT > LT"), self.assertRaises(TypeError):
+ ImplementsGreaterThan(5) > ImplementsLessThan(5)
+
+ with self.subTest("GE >= GT"), self.assertRaises(TypeError):
+ ImplementsGreaterThanEqualTo(6) >= ImplementsGreaterThan(6)
+
+ with self.subTest("GE >= LE"), self.assertRaises(TypeError):
+ ImplementsGreaterThanEqualTo(7) >= ImplementsLessThanEqualTo(7)
+
+ with self.subTest("GE when equal"):
+ a = ComparatorNotImplemented(8)
+ b = ComparatorNotImplemented(8)
+ self.assertEqual(a, b)
+ with self.assertRaises(TypeError):
+ a >= b
+
+ with self.subTest("LE when equal"):
+ a = ComparatorNotImplemented(9)
+ b = ComparatorNotImplemented(9)
+ self.assertEqual(a, b)
+ with self.assertRaises(TypeError):
+ a <= b
class TestLRU(unittest.TestCase):
def test_lru(self):
def orig(x, y):
- return 3*x+y
+ return 3 * x + y
f = functools.lru_cache(maxsize=20)(orig)
hits, misses, maxsize, currsize = f.cache_info()
self.assertEqual(maxsize, 20)
@@ -749,7 +987,7 @@ class TestLRU(unittest.TestCase):
# Verify that user_function exceptions get passed through without
# creating a hard-to-read chained exception.
# http://bugs.python.org/issue13177
- for maxsize in (None, 100):
+ for maxsize in (None, 128):
@functools.lru_cache(maxsize)
def func(i):
return 'abc'[i]
@@ -762,7 +1000,7 @@ class TestLRU(unittest.TestCase):
func(15)
def test_lru_with_types(self):
- for maxsize in (None, 100):
+ for maxsize in (None, 128):
@functools.lru_cache(maxsize=maxsize, typed=True)
def square(x):
return x * x
@@ -777,6 +1015,36 @@ class TestLRU(unittest.TestCase):
self.assertEqual(square.cache_info().hits, 4)
self.assertEqual(square.cache_info().misses, 4)
+ def test_lru_with_keyword_args(self):
+ @functools.lru_cache()
+ def fib(n):
+ if n < 2:
+ return n
+ return fib(n=n-1) + fib(n=n-2)
+ self.assertEqual(
+ [fib(n=number) for number in range(16)],
+ [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
+ )
+ self.assertEqual(fib.cache_info(),
+ functools._CacheInfo(hits=28, misses=16, maxsize=128, currsize=16))
+ fib.cache_clear()
+ self.assertEqual(fib.cache_info(),
+ functools._CacheInfo(hits=0, misses=0, maxsize=128, currsize=0))
+
+ def test_lru_with_keyword_args_maxsize_none(self):
+ @functools.lru_cache(maxsize=None)
+ def fib(n):
+ if n < 2:
+ return n
+ return fib(n=n-1) + fib(n=n-2)
+ self.assertEqual([fib(n=number) for number in range(16)],
+ [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])
+ self.assertEqual(fib.cache_info(),
+ functools._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16))
+ fib.cache_clear()
+ self.assertEqual(fib.cache_info(),
+ functools._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
+
def test_need_for_rlock(self):
# This will deadlock on an LRU cache that uses a regular lock
@@ -802,17 +1070,494 @@ class TestLRU(unittest.TestCase):
DoubleEq(2)) # Verify the correct return value
+class TestSingleDispatch(unittest.TestCase):
+ def test_simple_overloads(self):
+ @functools.singledispatch
+ def g(obj):
+ return "base"
+ def g_int(i):
+ return "integer"
+ g.register(int, g_int)
+ self.assertEqual(g("str"), "base")
+ self.assertEqual(g(1), "integer")
+ self.assertEqual(g([1,2,3]), "base")
+
+ def test_mro(self):
+ @functools.singledispatch
+ def g(obj):
+ return "base"
+ class A:
+ pass
+ class C(A):
+ pass
+ class B(A):
+ pass
+ class D(C, B):
+ pass
+ def g_A(a):
+ return "A"
+ def g_B(b):
+ return "B"
+ g.register(A, g_A)
+ g.register(B, g_B)
+ self.assertEqual(g(A()), "A")
+ self.assertEqual(g(B()), "B")
+ self.assertEqual(g(C()), "A")
+ self.assertEqual(g(D()), "B")
+
+ def test_register_decorator(self):
+ @functools.singledispatch
+ def g(obj):
+ return "base"
+ @g.register(int)
+ def g_int(i):
+ return "int %s" % (i,)
+ self.assertEqual(g(""), "base")
+ self.assertEqual(g(12), "int 12")
+ self.assertIs(g.dispatch(int), g_int)
+ self.assertIs(g.dispatch(object), g.dispatch(str))
+ # Note: in the assert above this is not g.
+ # @singledispatch returns the wrapper.
+
+ def test_wrapping_attributes(self):
+ @functools.singledispatch
+ def g(obj):
+ "Simple test"
+ return "Test"
+ self.assertEqual(g.__name__, "g")
+ self.assertEqual(g.__doc__, "Simple test")
+
+ @unittest.skipUnless(decimal, 'requires _decimal')
+ @support.cpython_only
+ def test_c_classes(self):
+ @functools.singledispatch
+ def g(obj):
+ return "base"
+ @g.register(decimal.DecimalException)
+ def _(obj):
+ return obj.args
+ subn = decimal.Subnormal("Exponent < Emin")
+ rnd = decimal.Rounded("Number got rounded")
+ self.assertEqual(g(subn), ("Exponent < Emin",))
+ self.assertEqual(g(rnd), ("Number got rounded",))
+ @g.register(decimal.Subnormal)
+ def _(obj):
+ return "Too small to care."
+ self.assertEqual(g(subn), "Too small to care.")
+ self.assertEqual(g(rnd), ("Number got rounded",))
+
+ def test_compose_mro(self):
+ # None of the examples in this test depend on haystack ordering.
+ c = collections
+ mro = functools._compose_mro
+ bases = [c.Sequence, c.MutableMapping, c.Mapping, c.Set]
+ for haystack in permutations(bases):
+ m = mro(dict, haystack)
+ self.assertEqual(m, [dict, c.MutableMapping, c.Mapping, c.Sized,
+ c.Iterable, c.Container, object])
+ bases = [c.Container, c.Mapping, c.MutableMapping, c.OrderedDict]
+ for haystack in permutations(bases):
+ m = mro(c.ChainMap, haystack)
+ self.assertEqual(m, [c.ChainMap, c.MutableMapping, c.Mapping,
+ c.Sized, c.Iterable, c.Container, object])
+
+ # If there's a generic function with implementations registered for
+ # both Sized and Container, passing a defaultdict to it results in an
+ # ambiguous dispatch which will cause a RuntimeError (see
+ # test_mro_conflicts).
+ bases = [c.Container, c.Sized, str]
+ for haystack in permutations(bases):
+ m = mro(c.defaultdict, [c.Sized, c.Container, str])
+ self.assertEqual(m, [c.defaultdict, dict, c.Sized, c.Container,
+ object])
+
+ # MutableSequence below is registered directly on D. In other words, it
+ # preceeds MutableMapping which means single dispatch will always
+ # choose MutableSequence here.
+ class D(c.defaultdict):
+ pass
+ c.MutableSequence.register(D)
+ bases = [c.MutableSequence, c.MutableMapping]
+ for haystack in permutations(bases):
+ m = mro(D, bases)
+ self.assertEqual(m, [D, c.MutableSequence, c.Sequence,
+ c.defaultdict, dict, c.MutableMapping,
+ c.Mapping, c.Sized, c.Iterable, c.Container,
+ object])
+
+ # Container and Callable are registered on different base classes and
+ # a generic function supporting both should always pick the Callable
+ # implementation if a C instance is passed.
+ class C(c.defaultdict):
+ def __call__(self):
+ pass
+ bases = [c.Sized, c.Callable, c.Container, c.Mapping]
+ for haystack in permutations(bases):
+ m = mro(C, haystack)
+ self.assertEqual(m, [C, c.Callable, c.defaultdict, dict, c.Mapping,
+ c.Sized, c.Iterable, c.Container, object])
+
+ def test_register_abc(self):
+ c = collections
+ d = {"a": "b"}
+ l = [1, 2, 3]
+ s = {object(), None}
+ f = frozenset(s)
+ t = (1, 2, 3)
+ @functools.singledispatch
+ def g(obj):
+ return "base"
+ self.assertEqual(g(d), "base")
+ self.assertEqual(g(l), "base")
+ self.assertEqual(g(s), "base")
+ self.assertEqual(g(f), "base")
+ self.assertEqual(g(t), "base")
+ g.register(c.Sized, lambda obj: "sized")
+ self.assertEqual(g(d), "sized")
+ self.assertEqual(g(l), "sized")
+ self.assertEqual(g(s), "sized")
+ self.assertEqual(g(f), "sized")
+ self.assertEqual(g(t), "sized")
+ g.register(c.MutableMapping, lambda obj: "mutablemapping")
+ self.assertEqual(g(d), "mutablemapping")
+ self.assertEqual(g(l), "sized")
+ self.assertEqual(g(s), "sized")
+ self.assertEqual(g(f), "sized")
+ self.assertEqual(g(t), "sized")
+ g.register(c.ChainMap, lambda obj: "chainmap")
+ self.assertEqual(g(d), "mutablemapping") # irrelevant ABCs registered
+ self.assertEqual(g(l), "sized")
+ self.assertEqual(g(s), "sized")
+ self.assertEqual(g(f), "sized")
+ self.assertEqual(g(t), "sized")
+ g.register(c.MutableSequence, lambda obj: "mutablesequence")
+ self.assertEqual(g(d), "mutablemapping")
+ self.assertEqual(g(l), "mutablesequence")
+ self.assertEqual(g(s), "sized")
+ self.assertEqual(g(f), "sized")
+ self.assertEqual(g(t), "sized")
+ g.register(c.MutableSet, lambda obj: "mutableset")
+ self.assertEqual(g(d), "mutablemapping")
+ self.assertEqual(g(l), "mutablesequence")
+ self.assertEqual(g(s), "mutableset")
+ self.assertEqual(g(f), "sized")
+ self.assertEqual(g(t), "sized")
+ g.register(c.Mapping, lambda obj: "mapping")
+ self.assertEqual(g(d), "mutablemapping") # not specific enough
+ self.assertEqual(g(l), "mutablesequence")
+ self.assertEqual(g(s), "mutableset")
+ self.assertEqual(g(f), "sized")
+ self.assertEqual(g(t), "sized")
+ g.register(c.Sequence, lambda obj: "sequence")
+ self.assertEqual(g(d), "mutablemapping")
+ self.assertEqual(g(l), "mutablesequence")
+ self.assertEqual(g(s), "mutableset")
+ self.assertEqual(g(f), "sized")
+ self.assertEqual(g(t), "sequence")
+ g.register(c.Set, lambda obj: "set")
+ self.assertEqual(g(d), "mutablemapping")
+ self.assertEqual(g(l), "mutablesequence")
+ self.assertEqual(g(s), "mutableset")
+ self.assertEqual(g(f), "set")
+ self.assertEqual(g(t), "sequence")
+ g.register(dict, lambda obj: "dict")
+ self.assertEqual(g(d), "dict")
+ self.assertEqual(g(l), "mutablesequence")
+ self.assertEqual(g(s), "mutableset")
+ self.assertEqual(g(f), "set")
+ self.assertEqual(g(t), "sequence")
+ g.register(list, lambda obj: "list")
+ self.assertEqual(g(d), "dict")
+ self.assertEqual(g(l), "list")
+ self.assertEqual(g(s), "mutableset")
+ self.assertEqual(g(f), "set")
+ self.assertEqual(g(t), "sequence")
+ g.register(set, lambda obj: "concrete-set")
+ self.assertEqual(g(d), "dict")
+ self.assertEqual(g(l), "list")
+ self.assertEqual(g(s), "concrete-set")
+ self.assertEqual(g(f), "set")
+ self.assertEqual(g(t), "sequence")
+ g.register(frozenset, lambda obj: "frozen-set")
+ self.assertEqual(g(d), "dict")
+ self.assertEqual(g(l), "list")
+ self.assertEqual(g(s), "concrete-set")
+ self.assertEqual(g(f), "frozen-set")
+ self.assertEqual(g(t), "sequence")
+ g.register(tuple, lambda obj: "tuple")
+ self.assertEqual(g(d), "dict")
+ self.assertEqual(g(l), "list")
+ self.assertEqual(g(s), "concrete-set")
+ self.assertEqual(g(f), "frozen-set")
+ self.assertEqual(g(t), "tuple")
+
+ def test_c3_abc(self):
+ c = collections
+ mro = functools._c3_mro
+ class A(object):
+ pass
+ class B(A):
+ def __len__(self):
+ return 0 # implies Sized
+ @c.Container.register
+ class C(object):
+ pass
+ class D(object):
+ pass # unrelated
+ class X(D, C, B):
+ def __call__(self):
+ pass # implies Callable
+ expected = [X, c.Callable, D, C, c.Container, B, c.Sized, A, object]
+ for abcs in permutations([c.Sized, c.Callable, c.Container]):
+ self.assertEqual(mro(X, abcs=abcs), expected)
+ # unrelated ABCs don't appear in the resulting MRO
+ many_abcs = [c.Mapping, c.Sized, c.Callable, c.Container, c.Iterable]
+ self.assertEqual(mro(X, abcs=many_abcs), expected)
+
+ def test_mro_conflicts(self):
+ c = collections
+ @functools.singledispatch
+ def g(arg):
+ return "base"
+ class O(c.Sized):
+ def __len__(self):
+ return 0
+ o = O()
+ self.assertEqual(g(o), "base")
+ g.register(c.Iterable, lambda arg: "iterable")
+ g.register(c.Container, lambda arg: "container")
+ g.register(c.Sized, lambda arg: "sized")
+ g.register(c.Set, lambda arg: "set")
+ self.assertEqual(g(o), "sized")
+ c.Iterable.register(O)
+ self.assertEqual(g(o), "sized") # because it's explicitly in __mro__
+ c.Container.register(O)
+ self.assertEqual(g(o), "sized") # see above: Sized is in __mro__
+ c.Set.register(O)
+ self.assertEqual(g(o), "set") # because c.Set is a subclass of
+ # c.Sized and c.Container
+ class P:
+ pass
+ p = P()
+ self.assertEqual(g(p), "base")
+ c.Iterable.register(P)
+ self.assertEqual(g(p), "iterable")
+ c.Container.register(P)
+ with self.assertRaises(RuntimeError) as re_one:
+ g(p)
+ self.assertIn(
+ str(re_one.exception),
+ (("Ambiguous dispatch: <class 'collections.abc.Container'> "
+ "or <class 'collections.abc.Iterable'>"),
+ ("Ambiguous dispatch: <class 'collections.abc.Iterable'> "
+ "or <class 'collections.abc.Container'>")),
+ )
+ class Q(c.Sized):
+ def __len__(self):
+ return 0
+ q = Q()
+ self.assertEqual(g(q), "sized")
+ c.Iterable.register(Q)
+ self.assertEqual(g(q), "sized") # because it's explicitly in __mro__
+ c.Set.register(Q)
+ self.assertEqual(g(q), "set") # because c.Set is a subclass of
+ # c.Sized and c.Iterable
+ @functools.singledispatch
+ def h(arg):
+ return "base"
+ @h.register(c.Sized)
+ def _(arg):
+ return "sized"
+ @h.register(c.Container)
+ def _(arg):
+ return "container"
+ # Even though Sized and Container are explicit bases of MutableMapping,
+ # this ABC is implicitly registered on defaultdict which makes all of
+ # MutableMapping's bases implicit as well from defaultdict's
+ # perspective.
+ with self.assertRaises(RuntimeError) as re_two:
+ h(c.defaultdict(lambda: 0))
+ self.assertIn(
+ str(re_two.exception),
+ (("Ambiguous dispatch: <class 'collections.abc.Container'> "
+ "or <class 'collections.abc.Sized'>"),
+ ("Ambiguous dispatch: <class 'collections.abc.Sized'> "
+ "or <class 'collections.abc.Container'>")),
+ )
+ class R(c.defaultdict):
+ pass
+ c.MutableSequence.register(R)
+ @functools.singledispatch
+ def i(arg):
+ return "base"
+ @i.register(c.MutableMapping)
+ def _(arg):
+ return "mapping"
+ @i.register(c.MutableSequence)
+ def _(arg):
+ return "sequence"
+ r = R()
+ self.assertEqual(i(r), "sequence")
+ class S:
+ pass
+ class T(S, c.Sized):
+ def __len__(self):
+ return 0
+ t = T()
+ self.assertEqual(h(t), "sized")
+ c.Container.register(T)
+ self.assertEqual(h(t), "sized") # because it's explicitly in the MRO
+ class U:
+ def __len__(self):
+ return 0
+ u = U()
+ self.assertEqual(h(u), "sized") # implicit Sized subclass inferred
+ # from the existence of __len__()
+ c.Container.register(U)
+ # There is no preference for registered versus inferred ABCs.
+ with self.assertRaises(RuntimeError) as re_three:
+ h(u)
+ self.assertIn(
+ str(re_three.exception),
+ (("Ambiguous dispatch: <class 'collections.abc.Container'> "
+ "or <class 'collections.abc.Sized'>"),
+ ("Ambiguous dispatch: <class 'collections.abc.Sized'> "
+ "or <class 'collections.abc.Container'>")),
+ )
+ class V(c.Sized, S):
+ def __len__(self):
+ return 0
+ @functools.singledispatch
+ def j(arg):
+ return "base"
+ @j.register(S)
+ def _(arg):
+ return "s"
+ @j.register(c.Container)
+ def _(arg):
+ return "container"
+ v = V()
+ self.assertEqual(j(v), "s")
+ c.Container.register(V)
+ self.assertEqual(j(v), "container") # because it ends up right after
+ # Sized in the MRO
+
+ def test_cache_invalidation(self):
+ from collections import UserDict
+ class TracingDict(UserDict):
+ def __init__(self, *args, **kwargs):
+ super(TracingDict, self).__init__(*args, **kwargs)
+ self.set_ops = []
+ self.get_ops = []
+ def __getitem__(self, key):
+ result = self.data[key]
+ self.get_ops.append(key)
+ return result
+ def __setitem__(self, key, value):
+ self.set_ops.append(key)
+ self.data[key] = value
+ def clear(self):
+ self.data.clear()
+ _orig_wkd = functools.WeakKeyDictionary
+ td = TracingDict()
+ functools.WeakKeyDictionary = lambda: td
+ c = collections
+ @functools.singledispatch
+ def g(arg):
+ return "base"
+ d = {}
+ l = []
+ self.assertEqual(len(td), 0)
+ self.assertEqual(g(d), "base")
+ self.assertEqual(len(td), 1)
+ self.assertEqual(td.get_ops, [])
+ self.assertEqual(td.set_ops, [dict])
+ self.assertEqual(td.data[dict], g.registry[object])
+ self.assertEqual(g(l), "base")
+ self.assertEqual(len(td), 2)
+ self.assertEqual(td.get_ops, [])
+ self.assertEqual(td.set_ops, [dict, list])
+ self.assertEqual(td.data[dict], g.registry[object])
+ self.assertEqual(td.data[list], g.registry[object])
+ self.assertEqual(td.data[dict], td.data[list])
+ self.assertEqual(g(l), "base")
+ self.assertEqual(g(d), "base")
+ self.assertEqual(td.get_ops, [list, dict])
+ self.assertEqual(td.set_ops, [dict, list])
+ g.register(list, lambda arg: "list")
+ self.assertEqual(td.get_ops, [list, dict])
+ self.assertEqual(len(td), 0)
+ self.assertEqual(g(d), "base")
+ self.assertEqual(len(td), 1)
+ self.assertEqual(td.get_ops, [list, dict])
+ self.assertEqual(td.set_ops, [dict, list, dict])
+ self.assertEqual(td.data[dict],
+ functools._find_impl(dict, g.registry))
+ self.assertEqual(g(l), "list")
+ self.assertEqual(len(td), 2)
+ self.assertEqual(td.get_ops, [list, dict])
+ self.assertEqual(td.set_ops, [dict, list, dict, list])
+ self.assertEqual(td.data[list],
+ functools._find_impl(list, g.registry))
+ class X:
+ pass
+ c.MutableMapping.register(X) # Will not invalidate the cache,
+ # not using ABCs yet.
+ self.assertEqual(g(d), "base")
+ self.assertEqual(g(l), "list")
+ self.assertEqual(td.get_ops, [list, dict, dict, list])
+ self.assertEqual(td.set_ops, [dict, list, dict, list])
+ g.register(c.Sized, lambda arg: "sized")
+ self.assertEqual(len(td), 0)
+ self.assertEqual(g(d), "sized")
+ self.assertEqual(len(td), 1)
+ self.assertEqual(td.get_ops, [list, dict, dict, list])
+ self.assertEqual(td.set_ops, [dict, list, dict, list, dict])
+ self.assertEqual(g(l), "list")
+ self.assertEqual(len(td), 2)
+ self.assertEqual(td.get_ops, [list, dict, dict, list])
+ self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])
+ self.assertEqual(g(l), "list")
+ self.assertEqual(g(d), "sized")
+ self.assertEqual(td.get_ops, [list, dict, dict, list, list, dict])
+ self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])
+ g.dispatch(list)
+ g.dispatch(dict)
+ self.assertEqual(td.get_ops, [list, dict, dict, list, list, dict,
+ list, dict])
+ self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])
+ c.MutableSet.register(X) # Will invalidate the cache.
+ self.assertEqual(len(td), 2) # Stale cache.
+ self.assertEqual(g(l), "list")
+ self.assertEqual(len(td), 1)
+ g.register(c.MutableMapping, lambda arg: "mutablemapping")
+ self.assertEqual(len(td), 0)
+ self.assertEqual(g(d), "mutablemapping")
+ self.assertEqual(len(td), 1)
+ self.assertEqual(g(l), "list")
+ self.assertEqual(len(td), 2)
+ g.register(dict, lambda arg: "dict")
+ self.assertEqual(g(d), "dict")
+ self.assertEqual(g(l), "list")
+ g._clear_cache()
+ self.assertEqual(len(td), 0)
+ functools.WeakKeyDictionary = _orig_wkd
+
+
def test_main(verbose=None):
test_classes = (
- TestPartial,
- TestPartialSubclass,
- TestPythonPartial,
+ TestPartialC,
+ TestPartialPy,
+ TestPartialCSubclass,
+ TestPartialMethod,
TestUpdateWrapper,
TestTotalOrdering,
- TestCmpToKey,
+ TestCmpToKeyC,
+ TestCmpToKeyPy,
TestWraps,
TestReduce,
TestLRU,
+ TestSingleDispatch,
)
support.run_unittest(*test_classes)
diff --git a/Lib/test/test_future.py b/Lib/test/test_future.py
index a0c156f5f7..beac993e4d 100644
--- a/Lib/test/test_future.py
+++ b/Lib/test/test_future.py
@@ -82,6 +82,14 @@ class FutureTest(unittest.TestCase):
else:
self.fail("expected exception didn't occur")
+ def test_badfuture10(self):
+ try:
+ from test import badsyntax_future10
+ except SyntaxError as msg:
+ self.assertEqual(get_error_location(msg), ("badsyntax_future10", '3'))
+ else:
+ self.fail("expected exception didn't occur")
+
def test_parserhack(self):
# test that the parser.c::future_hack function works as expected
# Note: although this test must pass, it's not testing the original
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index c59b72eacf..e8f52a508f 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -1,6 +1,9 @@
+import _testcapi
import unittest
from test.support import (verbose, refcount_test, run_unittest,
strip_python_stderr)
+from test.script_helper import assert_python_ok, make_script, temp_dir
+
import sys
import time
import gc
@@ -38,6 +41,7 @@ class GC_Detector(object):
# gc collects it.
self.wr = weakref.ref(C1055820(666), it_happened)
+@_testcapi.with_tp_del
class Uncollectable(object):
"""Create a reference cycle with multiple __del__ methods.
@@ -50,7 +54,7 @@ class Uncollectable(object):
self.partner = Uncollectable(partner=self)
else:
self.partner = partner
- def __del__(self):
+ def __tp_del__(self):
pass
### Tests
@@ -139,11 +143,12 @@ class GCTests(unittest.TestCase):
del a
self.assertNotEqual(gc.collect(), 0)
- def test_finalizer(self):
+ def test_legacy_finalizer(self):
# A() is uncollectable if it is part of a cycle, make sure it shows up
# in gc.garbage.
+ @_testcapi.with_tp_del
class A:
- def __del__(self): pass
+ def __tp_del__(self): pass
class B:
pass
a = A()
@@ -163,11 +168,12 @@ class GCTests(unittest.TestCase):
self.fail("didn't find obj in garbage (finalizer)")
gc.garbage.remove(obj)
- def test_finalizer_newclass(self):
+ def test_legacy_finalizer_newclass(self):
# A() is uncollectable if it is part of a cycle, make sure it shows up
# in gc.garbage.
+ @_testcapi.with_tp_del
class A(object):
- def __del__(self): pass
+ def __tp_del__(self): pass
class B(object):
pass
a = A()
@@ -568,12 +574,14 @@ class GCTests(unittest.TestCase):
import subprocess
code = """if 1:
import gc
+ import _testcapi
+ @_testcapi.with_tp_del
class X:
def __init__(self, name):
self.name = name
def __repr__(self):
return "<X %%r>" %% self.name
- def __del__(self):
+ def __tp_del__(self):
pass
x = X('first')
@@ -610,6 +618,66 @@ class GCTests(unittest.TestCase):
stderr = run_command(code % "gc.DEBUG_SAVEALL")
self.assertNotIn(b"uncollectable objects at shutdown", stderr)
+ def test_gc_main_module_at_shutdown(self):
+ # Create a reference cycle through the __main__ module and check
+ # it gets collected at interpreter shutdown.
+ code = """if 1:
+ import weakref
+ class C:
+ def __del__(self):
+ print('__del__ called')
+ l = [C()]
+ l.append(l)
+ """
+ rc, out, err = assert_python_ok('-c', code)
+ self.assertEqual(out.strip(), b'__del__ called')
+
+ def test_gc_ordinary_module_at_shutdown(self):
+ # Same as above, but with a non-__main__ module.
+ with temp_dir() as script_dir:
+ module = """if 1:
+ import weakref
+ class C:
+ def __del__(self):
+ print('__del__ called')
+ l = [C()]
+ l.append(l)
+ """
+ code = """if 1:
+ import sys
+ sys.path.insert(0, %r)
+ import gctest
+ """ % (script_dir,)
+ make_script(script_dir, 'gctest', module)
+ rc, out, err = assert_python_ok('-c', code)
+ self.assertEqual(out.strip(), b'__del__ called')
+
+ def test_get_stats(self):
+ stats = gc.get_stats()
+ self.assertEqual(len(stats), 3)
+ for st in stats:
+ self.assertIsInstance(st, dict)
+ self.assertEqual(set(st),
+ {"collected", "collections", "uncollectable"})
+ self.assertGreaterEqual(st["collected"], 0)
+ self.assertGreaterEqual(st["collections"], 0)
+ self.assertGreaterEqual(st["uncollectable"], 0)
+ # Check that collection counts are incremented correctly
+ if gc.isenabled():
+ self.addCleanup(gc.enable)
+ gc.disable()
+ old = gc.get_stats()
+ gc.collect(0)
+ new = gc.get_stats()
+ self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
+ self.assertEqual(new[1]["collections"], old[1]["collections"])
+ self.assertEqual(new[2]["collections"], old[2]["collections"])
+ gc.collect(2)
+ new = gc.get_stats()
+ self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
+ self.assertEqual(new[1]["collections"], old[1]["collections"])
+ self.assertEqual(new[2]["collections"], old[2]["collections"] + 1)
+
class GCCallbackTests(unittest.TestCase):
def setUp(self):
diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py
index 846422b837..0e254a2487 100644
--- a/Lib/test/test_gdb.py
+++ b/Lib/test/test_gdb.py
@@ -5,6 +5,7 @@
import os
import re
+import pprint
import subprocess
import sys
import sysconfig
@@ -17,6 +18,7 @@ try:
except ImportError:
_thread = None
+from test import support
from test.support import run_unittest, findfile, python_is_optimized
try:
@@ -40,6 +42,8 @@ if not sysconfig.is_python_build():
checkout_hook_path = os.path.join(os.path.dirname(sys.executable),
'python-gdb.py')
+PYTHONHASHSEED = '123'
+
def run_gdb(*args, **env_vars):
"""Runs gdb in --batch mode with the additional arguments given by *args.
@@ -144,7 +148,7 @@ class DebuggerTests(unittest.TestCase):
# print (' '.join(args))
# Use "args" to invoke gdb, capturing stdout, stderr:
- out, err = run_gdb(*args, PYTHONHASHSEED='0')
+ out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
errlines = err.splitlines()
unexpected_errlines = []
@@ -165,6 +169,10 @@ class DebuggerTests(unittest.TestCase):
'linux-gate.so',
'Do you need "set solib-search-path" or '
'"set sysroot"?',
+ 'warning: Source file is more recent than executable.',
+ # Issue #19753: missing symbols on System Z
+ 'Missing separate debuginfo for ',
+ 'Try: zypper install -C ',
)
for line in errlines:
if not line.startswith(ignore_patterns):
@@ -248,9 +256,8 @@ class PrettyPrintTests(DebuggerTests):
def test_dicts(self):
'Verify the pretty-printing of dictionaries'
self.assertGdbRepr({})
- self.assertGdbRepr({'foo': 'bar'})
- self.assertGdbRepr({'foo': 'bar', 'douglas': 42},
- "{'foo': 'bar', 'douglas': 42}")
+ self.assertGdbRepr({'foo': 'bar'}, "{'foo': 'bar'}")
+ self.assertGdbRepr({'foo': 'bar', 'douglas': 42}, "{'douglas': 42, 'foo': 'bar'}")
def test_lists(self):
'Verify the pretty-printing of lists'
@@ -305,26 +312,30 @@ class PrettyPrintTests(DebuggerTests):
def test_tuples(self):
'Verify the pretty-printing of tuples'
- self.assertGdbRepr(tuple())
+ self.assertGdbRepr(tuple(), '()')
self.assertGdbRepr((1,), '(1,)')
self.assertGdbRepr(('foo', 'bar', 'baz'))
def test_sets(self):
'Verify the pretty-printing of sets'
- self.assertGdbRepr(set())
+ if (gdb_major_version, gdb_minor_version) < (7, 3):
+ self.skipTest("pretty-printing of sets needs gdb 7.3 or later")
+ self.assertGdbRepr(set(), 'set()')
self.assertGdbRepr(set(['a', 'b']), "{'a', 'b'}")
self.assertGdbRepr(set([4, 5, 6]), "{4, 5, 6}")
# Ensure that we handle sets containing the "dummy" key value,
# which happens on deletion:
gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
-s.pop()
+s.remove('a')
id(s)''')
self.assertEqual(gdb_repr, "{'b'}")
def test_frozensets(self):
'Verify the pretty-printing of frozensets'
- self.assertGdbRepr(frozenset())
+ if (gdb_major_version, gdb_minor_version) < (7, 3):
+ self.skipTest("pretty-printing of frozensets needs gdb 7.3 or later")
+ self.assertGdbRepr(frozenset(), 'frozenset()')
self.assertGdbRepr(frozenset(['a', 'b']), "frozenset({'a', 'b'})")
self.assertGdbRepr(frozenset([4, 5, 6]), "frozenset({4, 5, 6})")
@@ -841,6 +852,10 @@ class PyLocalsTests(DebuggerTests):
r".*\na = 1\nb = 2\nc = 3\n.*")
def test_main():
+ if support.verbose:
+ print("GDB version:")
+ for line in os.fsdecode(gdb_version).splitlines():
+ print(" " * 4 + line)
run_unittest(PrettyPrintTests,
PyListTests,
StackNavigationTests,
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index 958054aef5..4e921177a5 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -1,3 +1,55 @@
+import gc
+import sys
+import unittest
+import weakref
+
+from test import support
+
+
+class FinalizationTest(unittest.TestCase):
+
+ def test_frame_resurrect(self):
+ # A generator frame can be resurrected by a generator's finalization.
+ def gen():
+ nonlocal frame
+ try:
+ yield
+ finally:
+ frame = sys._getframe()
+
+ g = gen()
+ wr = weakref.ref(g)
+ next(g)
+ del g
+ support.gc_collect()
+ self.assertIs(wr(), None)
+ self.assertTrue(frame)
+ del frame
+ support.gc_collect()
+
+ def test_refcycle(self):
+ # A generator caught in a refcycle gets finalized anyway.
+ old_garbage = gc.garbage[:]
+ finalized = False
+ def gen():
+ nonlocal finalized
+ try:
+ g = yield
+ yield 1
+ finally:
+ finalized = True
+
+ g = gen()
+ next(g)
+ g.send(g)
+ self.assertGreater(sys.getrefcount(g), 2)
+ self.assertFalse(finalized)
+ del g
+ support.gc_collect()
+ self.assertTrue(finalized)
+ self.assertEqual(gc.garbage, old_garbage)
+
+
tutorial_tests = """
Let's try a simple generator:
@@ -1729,9 +1781,7 @@ Our ill-behaved code should be invoked during GC:
>>> g = f()
>>> next(g)
>>> del g
->>> sys.stderr.getvalue().startswith(
-... "Exception RuntimeError: 'generator ignored GeneratorExit' in "
-... )
+>>> "RuntimeError: generator ignored GeneratorExit" in sys.stderr.getvalue()
True
>>> sys.stderr = old
@@ -1841,22 +1891,23 @@ to test.
... sys.stderr = io.StringIO()
... class Leaker:
... def __del__(self):
-... raise RuntimeError
+... def invoke(message):
+... raise RuntimeError(message)
+... invoke("test")
...
... l = Leaker()
... del l
... err = sys.stderr.getvalue().strip()
-... err.startswith(
-... "Exception RuntimeError: RuntimeError() in <"
-... )
-... err.endswith("> ignored")
-... len(err.splitlines())
+... "Exception ignored in" in err
+... "RuntimeError: test" in err
+... "Traceback" in err
+... "in invoke" in err
... finally:
... sys.stderr = old
True
True
-1
-
+True
+True
These refleak tests should perhaps be in a testfile of their own,
@@ -1881,6 +1932,7 @@ __test__ = {"tut": tutorial_tests,
# so this works as expected in both ways of running regrtest.
def test_main(verbose=None):
from test import support, test_generators
+ support.run_unittest(__name__)
support.run_doctest(test_generators, verbose)
# This part isn't needed for regrtest, but for running the test directly.
diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
index fd8bc577ca..e967897d75 100644
--- a/Lib/test/test_genericpath.py
+++ b/Lib/test/test_genericpath.py
@@ -188,12 +188,93 @@ class GenericTest:
support.unlink(support.TESTFN)
safe_rmdir(support.TESTFN)
+ @staticmethod
+ def _create_file(filename):
+ with open(filename, 'wb') as f:
+ f.write(b'foo')
+
+ def test_samefile(self):
+ try:
+ test_fn = support.TESTFN + "1"
+ self._create_file(test_fn)
+ self.assertTrue(self.pathmodule.samefile(test_fn, test_fn))
+ self.assertRaises(TypeError, self.pathmodule.samefile)
+ finally:
+ os.remove(test_fn)
+
+ @support.skip_unless_symlink
+ def test_samefile_on_symlink(self):
+ self._test_samefile_on_link_func(os.symlink)
+
+ def test_samefile_on_link(self):
+ self._test_samefile_on_link_func(os.link)
+
+ def _test_samefile_on_link_func(self, func):
+ try:
+ test_fn1 = support.TESTFN + "1"
+ test_fn2 = support.TESTFN + "2"
+ self._create_file(test_fn1)
+
+ func(test_fn1, test_fn2)
+ self.assertTrue(self.pathmodule.samefile(test_fn1, test_fn2))
+ os.remove(test_fn2)
+
+ self._create_file(test_fn2)
+ self.assertFalse(self.pathmodule.samefile(test_fn1, test_fn2))
+ finally:
+ os.remove(test_fn1)
+ os.remove(test_fn2)
+
+ def test_samestat(self):
+ try:
+ test_fn = support.TESTFN + "1"
+ self._create_file(test_fn)
+ test_fns = [test_fn]*2
+ stats = map(os.stat, test_fns)
+ self.assertTrue(self.pathmodule.samestat(*stats))
+ finally:
+ os.remove(test_fn)
+
+ @support.skip_unless_symlink
+ def test_samestat_on_symlink(self):
+ self._test_samestat_on_link_func(os.symlink)
+
+ def test_samestat_on_link(self):
+ self._test_samestat_on_link_func(os.link)
+
+ def _test_samestat_on_link_func(self, func):
+ try:
+ test_fn1 = support.TESTFN + "1"
+ test_fn2 = support.TESTFN + "2"
+ self._create_file(test_fn1)
+ test_fns = (test_fn1, test_fn2)
+ func(*test_fns)
+ stats = map(os.stat, test_fns)
+ self.assertTrue(self.pathmodule.samestat(*stats))
+ os.remove(test_fn2)
+
+ self._create_file(test_fn2)
+ stats = map(os.stat, test_fns)
+ self.assertFalse(self.pathmodule.samestat(*stats))
+
+ self.assertRaises(TypeError, self.pathmodule.samestat)
+ finally:
+ os.remove(test_fn1)
+ os.remove(test_fn2)
+
+ def test_sameopenfile(self):
+ fname = support.TESTFN + "1"
+ with open(fname, "wb") as a, open(fname, "wb") as b:
+ self.assertTrue(self.pathmodule.sameopenfile(
+ a.fileno(), b.fileno()))
+
class TestGenericTest(GenericTest, unittest.TestCase):
# Issue 16852: GenericTest can't inherit from unittest.TestCase
# for test discovery purposes; CommonTest inherits from GenericTest
# and is only meant to be inherited by others.
pathmodule = genericpath
+
# Following TestCase is not supposed to be run from test_genericpath.
# It is inherited by other test modules (macpath, ntpath, posixpath).
@@ -322,7 +403,6 @@ class CommonTest(GenericTest):
else:
self.skipTest("need support.TESTFN_NONASCII")
- # Test non-ASCII, non-UTF8 bytes in the path.
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
with support.temp_cwd(name):
diff --git a/Lib/test/test_getargs2.py b/Lib/test/test_getargs2.py
index 48ca94ee38..beea76a920 100644
--- a/Lib/test/test_getargs2.py
+++ b/Lib/test/test_getargs2.py
@@ -1,38 +1,40 @@
import unittest
from test import support
from _testcapi import getargs_keywords, getargs_keyword_only
-
-"""
-> How about the following counterproposal. This also changes some of
-> the other format codes to be a little more regular.
->
-> Code C type Range check
->
-> b unsigned char 0..UCHAR_MAX
-> h signed short SHRT_MIN..SHRT_MAX
-> B unsigned char none **
-> H unsigned short none **
-> k * unsigned long none
-> I * unsigned int 0..UINT_MAX
-
-
-> i int INT_MIN..INT_MAX
-> l long LONG_MIN..LONG_MAX
-
-> K * unsigned long long none
-> L long long LLONG_MIN..LLONG_MAX
-
-> Notes:
->
-> * New format codes.
->
-> ** Changed from previous "range-and-a-half" to "none"; the
-> range-and-a-half checking wasn't particularly useful.
-
-Plus a C API or two, e.g. PyInt_AsLongMask() ->
-unsigned long and PyInt_AsLongLongMask() -> unsigned
-long long (if that exists).
-"""
+try:
+ from _testcapi import getargs_L, getargs_K
+except ImportError:
+ getargs_L = None # PY_LONG_LONG not available
+
+# > How about the following counterproposal. This also changes some of
+# > the other format codes to be a little more regular.
+# >
+# > Code C type Range check
+# >
+# > b unsigned char 0..UCHAR_MAX
+# > h signed short SHRT_MIN..SHRT_MAX
+# > B unsigned char none **
+# > H unsigned short none **
+# > k * unsigned long none
+# > I * unsigned int 0..UINT_MAX
+#
+#
+# > i int INT_MIN..INT_MAX
+# > l long LONG_MIN..LONG_MAX
+#
+# > K * unsigned long long none
+# > L long long LLONG_MIN..LLONG_MAX
+#
+# > Notes:
+# >
+# > * New format codes.
+# >
+# > ** Changed from previous "range-and-a-half" to "none"; the
+# > range-and-a-half checking wasn't particularly useful.
+#
+# Plus a C API or two, e.g. PyInt_AsLongMask() ->
+# unsigned long and PyInt_AsLongLongMask() -> unsigned
+# long long (if that exists).
LARGE = 0x7FFFFFFF
VERY_LARGE = 0xFF0000121212121212121242
@@ -184,6 +186,7 @@ class Signed_TestCase(unittest.TestCase):
self.assertRaises(OverflowError, getargs_n, VERY_LARGE)
+@unittest.skipIf(getargs_L is None, 'PY_LONG_LONG is not available')
class LongLong_TestCase(unittest.TestCase):
def test_L(self):
from _testcapi import getargs_L
@@ -536,24 +539,5 @@ class Unicode_TestCase(unittest.TestCase):
self.assertIsNone(getargs_Z_hash(None))
-def test_main():
- tests = [
- Signed_TestCase,
- Unsigned_TestCase,
- Boolean_TestCase,
- Tuple_TestCase,
- Keywords_TestCase,
- KeywordOnly_TestCase,
- Bytes_TestCase,
- Unicode_TestCase,
- ]
- try:
- from _testcapi import getargs_L, getargs_K
- except ImportError:
- pass # PY_LONG_LONG not available
- else:
- tests.append(LongLong_TestCase)
- support.run_unittest(*tests)
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_getpass.py b/Lib/test/test_getpass.py
new file mode 100644
index 0000000000..1731bd44a6
--- /dev/null
+++ b/Lib/test/test_getpass.py
@@ -0,0 +1,155 @@
+import getpass
+import os
+import unittest
+from io import BytesIO, StringIO
+from unittest import mock
+from test import support
+
+try:
+ import termios
+except ImportError:
+ termios = None
+try:
+ import pwd
+except ImportError:
+ pwd = None
+
+@mock.patch('os.environ')
+class GetpassGetuserTest(unittest.TestCase):
+
+ def test_username_takes_username_from_env(self, environ):
+ expected_name = 'some_name'
+ environ.get.return_value = expected_name
+ self.assertEqual(expected_name, getpass.getuser())
+
+ def test_username_priorities_of_env_values(self, environ):
+ environ.get.return_value = None
+ try:
+ getpass.getuser()
+ except ImportError: # in case there's no pwd module
+ pass
+ self.assertEqual(
+ environ.get.call_args_list,
+ [mock.call(x) for x in ('LOGNAME', 'USER', 'LNAME', 'USERNAME')])
+
+ def test_username_falls_back_to_pwd(self, environ):
+ expected_name = 'some_name'
+ environ.get.return_value = None
+ if pwd:
+ with mock.patch('os.getuid') as uid, \
+ mock.patch('pwd.getpwuid') as getpw:
+ uid.return_value = 42
+ getpw.return_value = [expected_name]
+ self.assertEqual(expected_name,
+ getpass.getuser())
+ getpw.assert_called_once_with(42)
+ else:
+ self.assertRaises(ImportError, getpass.getuser)
+
+
+class GetpassRawinputTest(unittest.TestCase):
+
+ def test_flushes_stream_after_prompt(self):
+ # see issue 1703
+ stream = mock.Mock(spec=StringIO)
+ input = StringIO('input_string')
+ getpass._raw_input('some_prompt', stream, input=input)
+ stream.flush.assert_called_once_with()
+
+ def test_uses_stderr_as_default(self):
+ input = StringIO('input_string')
+ prompt = 'some_prompt'
+ with mock.patch('sys.stderr') as stderr:
+ getpass._raw_input(prompt, input=input)
+ stderr.write.assert_called_once_with(prompt)
+
+ @mock.patch('sys.stdin')
+ def test_uses_stdin_as_default_input(self, mock_input):
+ mock_input.readline.return_value = 'input_string'
+ getpass._raw_input(stream=StringIO())
+ mock_input.readline.assert_called_once_with()
+
+ def test_raises_on_empty_input(self):
+ input = StringIO('')
+ self.assertRaises(EOFError, getpass._raw_input, input=input)
+
+ def test_trims_trailing_newline(self):
+ input = StringIO('test\n')
+ self.assertEqual('test', getpass._raw_input(input=input))
+
+
+# Some of these tests are a bit white-box. The functional requirement is that
+# the password input be taken directly from the tty, and that it not be echoed
+# on the screen, unless we are falling back to stderr/stdin.
+
+# Some of these might run on platforms without termios, but play it safe.
+@unittest.skipUnless(termios, 'tests require system with termios')
+class UnixGetpassTest(unittest.TestCase):
+
+ def test_uses_tty_directly(self):
+ with mock.patch('os.open') as open, \
+ mock.patch('io.FileIO') as fileio, \
+ mock.patch('io.TextIOWrapper') as textio:
+ # By setting open's return value to None the implementation will
+ # skip code we don't care about in this test. We can mock this out
+ # fully if an alternate implementation works differently.
+ open.return_value = None
+ getpass.unix_getpass()
+ open.assert_called_once_with('/dev/tty',
+ os.O_RDWR | os.O_NOCTTY)
+ fileio.assert_called_once_with(open.return_value, 'w+')
+ textio.assert_called_once_with(fileio.return_value)
+
+ def test_resets_termios(self):
+ with mock.patch('os.open') as open, \
+ mock.patch('io.FileIO'), \
+ mock.patch('io.TextIOWrapper'), \
+ mock.patch('termios.tcgetattr') as tcgetattr, \
+ mock.patch('termios.tcsetattr') as tcsetattr:
+ open.return_value = 3
+ fake_attrs = [255, 255, 255, 255, 255]
+ tcgetattr.return_value = list(fake_attrs)
+ getpass.unix_getpass()
+ tcsetattr.assert_called_with(3, mock.ANY, fake_attrs)
+
+ def test_falls_back_to_fallback_if_termios_raises(self):
+ with mock.patch('os.open') as open, \
+ mock.patch('io.FileIO') as fileio, \
+ mock.patch('io.TextIOWrapper') as textio, \
+ mock.patch('termios.tcgetattr'), \
+ mock.patch('termios.tcsetattr') as tcsetattr, \
+ mock.patch('getpass.fallback_getpass') as fallback:
+ open.return_value = 3
+ fileio.return_value = BytesIO()
+ tcsetattr.side_effect = termios.error
+ getpass.unix_getpass()
+ fallback.assert_called_once_with('Password: ',
+ textio.return_value)
+
+ def test_flushes_stream_after_input(self):
+ # issue 7208
+ with mock.patch('os.open') as open, \
+ mock.patch('io.FileIO'), \
+ mock.patch('io.TextIOWrapper'), \
+ mock.patch('termios.tcgetattr'), \
+ mock.patch('termios.tcsetattr'):
+ open.return_value = 3
+ mock_stream = mock.Mock(spec=StringIO)
+ getpass.unix_getpass(stream=mock_stream)
+ mock_stream.flush.assert_called_with()
+
+ def test_falls_back_to_stdin(self):
+ with mock.patch('os.open') as os_open, \
+ mock.patch('sys.stdin', spec=StringIO) as stdin:
+ os_open.side_effect = IOError
+ stdin.fileno.side_effect = AttributeError
+ with support.captured_stderr() as stderr:
+ with self.assertWarns(getpass.GetPassWarning):
+ getpass.unix_getpass()
+ stdin.readline.assert_called_once_with()
+ self.assertIn('Warning', stderr.getvalue())
+ self.assertIn('Password:', stderr.getvalue())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_glob.py b/Lib/test/test_glob.py
index eb9aeb5776..a5ab8d6c3e 100644
--- a/Lib/test/test_glob.py
+++ b/Lib/test/test_glob.py
@@ -169,6 +169,28 @@ class GlobTests(unittest.TestCase):
eq(glob.glob('\\\\*\\*\\'), [])
eq(glob.glob(b'\\\\*\\*\\'), [])
+ def check_escape(self, arg, expected):
+ self.assertEqual(glob.escape(arg), expected)
+ self.assertEqual(glob.escape(os.fsencode(arg)), os.fsencode(expected))
+
+ def test_escape(self):
+ check = self.check_escape
+ check('abc', 'abc')
+ check('[', '[[]')
+ check('?', '[?]')
+ check('*', '[*]')
+ check('[[_/*?*/_]]', '[[][[]_/[*][?][*]/_]]')
+ check('/[[_/*?*/_]]/', '/[[][[]_/[*][?][*]/_]]/')
+
+ @unittest.skipUnless(sys.platform == "win32", "Win32 specific test")
+ def test_escape_windows(self):
+ check = self.check_escape
+ check('?:?', '?:[?]')
+ check('*:*', '*:[*]')
+ check(r'\\?\c:\?', r'\\?\c:\[?]')
+ check(r'\\*\*\*', r'\\*\*\[*]')
+ check('//?/c:/?', '//?/c:/[?]')
+ check('//*/*/*', '//*/*/[*]')
def test_main():
run_unittest(GlobTests)
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index 5eac9217b2..aeafc6536d 100755..100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -131,6 +131,14 @@ class TestGzip(BaseTest):
if not ztxt: break
self.assertEqual(contents, b'a'*201)
+ def test_exclusive_write(self):
+ with gzip.GzipFile(self.filename, 'xb') as f:
+ f.write(data1 * 50)
+ with gzip.GzipFile(self.filename, 'rb') as f:
+ self.assertEqual(f.read(), data1 * 50)
+ with self.assertRaises(FileExistsError):
+ gzip.GzipFile(self.filename, 'xb')
+
def test_buffered_reader(self):
# Issue #7471: a GzipFile can be wrapped in a BufferedReader for
# performance.
@@ -206,6 +214,9 @@ class TestGzip(BaseTest):
self.test_write()
with gzip.GzipFile(self.filename, 'r') as f:
self.assertEqual(f.myfileobj.mode, 'rb')
+ support.unlink(self.filename)
+ with gzip.GzipFile(self.filename, 'x') as f:
+ self.assertEqual(f.myfileobj.mode, 'xb')
def test_1647484(self):
for mode in ('wb', 'rb'):
@@ -389,6 +400,20 @@ class TestGzip(BaseTest):
datac = gzip.compress(data)
self.assertEqual(gzip.decompress(datac), data)
+ def test_read_truncated(self):
+ data = data1*50
+ # Drop the CRC (4 bytes) and file size (4 bytes).
+ truncated = gzip.compress(data)[:-8]
+ with gzip.GzipFile(fileobj=io.BytesIO(truncated)) as f:
+ self.assertRaises(EOFError, f.read)
+ with gzip.GzipFile(fileobj=io.BytesIO(truncated)) as f:
+ self.assertEqual(f.read(len(data)), data)
+ self.assertRaises(EOFError, f.read, 1)
+ # Incomplete 10-byte header.
+ for i in range(2, 10):
+ with gzip.GzipFile(fileobj=io.BytesIO(truncated[:i])) as f:
+ self.assertRaises(EOFError, f.read, 1)
+
def test_read_with_extra(self):
# Gzip data with an extra field
gzdata = (b'\x1f\x8b\x08\x04\xb2\x17cQ\x02\xff'
@@ -400,35 +425,59 @@ class TestGzip(BaseTest):
class TestOpen(BaseTest):
def test_binary_modes(self):
uncompressed = data1 * 50
+
with gzip.open(self.filename, "wb") as f:
f.write(uncompressed)
with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed)
+
with gzip.open(self.filename, "rb") as f:
self.assertEqual(f.read(), uncompressed)
+
with gzip.open(self.filename, "ab") as f:
f.write(uncompressed)
with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed * 2)
+ with self.assertRaises(FileExistsError):
+ gzip.open(self.filename, "xb")
+ support.unlink(self.filename)
+ with gzip.open(self.filename, "xb") as f:
+ f.write(uncompressed)
+ with open(self.filename, "rb") as f:
+ file_data = gzip.decompress(f.read())
+ self.assertEqual(file_data, uncompressed)
+
def test_implicit_binary_modes(self):
# Test implicit binary modes (no "b" or "t" in mode string).
uncompressed = data1 * 50
+
with gzip.open(self.filename, "w") as f:
f.write(uncompressed)
with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed)
+
with gzip.open(self.filename, "r") as f:
self.assertEqual(f.read(), uncompressed)
+
with gzip.open(self.filename, "a") as f:
f.write(uncompressed)
with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed * 2)
+ with self.assertRaises(FileExistsError):
+ gzip.open(self.filename, "x")
+ support.unlink(self.filename)
+ with gzip.open(self.filename, "x") as f:
+ f.write(uncompressed)
+ with open(self.filename, "rb") as f:
+ file_data = gzip.decompress(f.read())
+ self.assertEqual(file_data, uncompressed)
+
def test_text_modes(self):
uncompressed = data1.decode("ascii") * 50
uncompressed_raw = uncompressed.replace("\n", os.linesep)
@@ -463,6 +512,8 @@ class TestOpen(BaseTest):
with self.assertRaises(ValueError):
gzip.open(self.filename, "wbt")
with self.assertRaises(ValueError):
+ gzip.open(self.filename, "xbt")
+ with self.assertRaises(ValueError):
gzip.open(self.filename, "rb", encoding="utf-8")
with self.assertRaises(ValueError):
gzip.open(self.filename, "rb", errors="ignore")
diff --git a/Lib/test/test_hash.py b/Lib/test/test_hash.py
index e3ab6e4385..f6657bd3cb 100644
--- a/Lib/test/test_hash.py
+++ b/Lib/test/test_hash.py
@@ -12,6 +12,40 @@ from collections import Hashable
IS_64BIT = sys.maxsize > 2**32
+def lcg(x, length=16):
+ """Linear congruential generator"""
+ if x == 0:
+ return bytes(length)
+ out = bytearray(length)
+ for i in range(length):
+ x = (214013 * x + 2531011) & 0x7fffffff
+ out[i] = (x >> 16) & 0xff
+ return bytes(out)
+
+def pysiphash(uint64):
+ """Convert SipHash24 output to Py_hash_t
+ """
+ assert 0 <= uint64 < (1 << 64)
+ # simple unsigned to signed int64
+ if uint64 > (1 << 63) - 1:
+ int64 = uint64 - (1 << 64)
+ else:
+ int64 = uint64
+ # mangle uint64 to uint32
+ uint32 = (uint64 ^ uint64 >> 32) & 0xffffffff
+ # simple unsigned to signed int32
+ if uint32 > (1 << 31) - 1:
+ int32 = uint32 - (1 << 32)
+ else:
+ int32 = uint32
+ return int32, int64
+
+def skip_unless_internalhash(test):
+ """Skip decorator for tests that depend on SipHash24 or FNV"""
+ ok = sys.hash_info.algorithm in {"fnv", "siphash24"}
+ msg = "Requires SipHash24 or FNV"
+ return test if ok else unittest.skip(msg)(test)
+
class HashEqualityTestCase(unittest.TestCase):
@@ -138,7 +172,7 @@ class HashRandomizationTests:
# an object to be tested
def get_hash_command(self, repr_):
- return 'print(hash(%s))' % repr_
+ return 'print(hash(eval(%s.decode("utf-8"))))' % repr_.encode("utf-8")
def get_hash(self, repr_, seed=None):
env = os.environ.copy()
@@ -161,12 +195,63 @@ class HashRandomizationTests:
self.assertNotEqual(run1, run2)
class StringlikeHashRandomizationTests(HashRandomizationTests):
+ repr_ = None
+ repr_long = None
+
+ # 32bit little, 64bit little, 32bit big, 64bit big
+ known_hashes = {
+ 'djba33x': [ # only used for small strings
+ # seed 0, 'abc'
+ [193485960, 193485960, 193485960, 193485960],
+ # seed 42, 'abc'
+ [-678966196, 573763426263223372, -820489388, -4282905804826039665],
+ ],
+ 'siphash24': [
+ # NOTE: PyUCS2 layout depends on endianess
+ # seed 0, 'abc'
+ [1198583518, 4596069200710135518, 1198583518, 4596069200710135518],
+ # seed 42, 'abc'
+ [273876886, -4501618152524544106, 273876886, -4501618152524544106],
+ # seed 42, 'abcdefghijk'
+ [-1745215313, 4436719588892876975, -1745215313, 4436719588892876975],
+ # seed 0, 'äú∑ℇ'
+ [493570806, 5749986484189612790, -1006381564, -5915111450199468540],
+ # seed 42, 'äú∑ℇ'
+ [-1677110816, -2947981342227738144, -1860207793, -4296699217652516017],
+ ],
+ 'fnv': [
+ # seed 0, 'abc'
+ [-1600925533, 1453079729188098211, -1600925533,
+ 1453079729188098211],
+ # seed 42, 'abc'
+ [-206076799, -4410911502303878509, -1024014457,
+ -3570150969479994130],
+ # seed 42, 'abcdefghijk'
+ [811136751, -5046230049376118746, -77208053 ,
+ -4779029615281019666],
+ # seed 0, 'äú∑ℇ'
+ [44402817, 8998297579845987431, -1956240331,
+ -782697888614047887],
+ # seed 42, 'äú∑ℇ'
+ [-283066365, -4576729883824601543, -271871407, None],
+ ]
+ }
+
+ def get_expected_hash(self, position, length):
+ if length < sys.hash_info.cutoff:
+ algorithm = "djba33x"
+ else:
+ algorithm = sys.hash_info.algorithm
+ if sys.byteorder == 'little':
+ platform = 1 if IS_64BIT else 0
+ else:
+ assert(sys.byteorder == 'big')
+ platform = 3 if IS_64BIT else 2
+ return self.known_hashes[algorithm][position][platform]
+
def test_null_hash(self):
# PYTHONHASHSEED=0 disables the randomized hash
- if IS_64BIT:
- known_hash_of_obj = 1453079729188098211
- else:
- known_hash_of_obj = -1600925533
+ known_hash_of_obj = self.get_expected_hash(0, 3)
# Randomization is enabled by default:
self.assertNotEqual(self.get_hash(self.repr_), known_hash_of_obj)
@@ -174,39 +259,53 @@ class StringlikeHashRandomizationTests(HashRandomizationTests):
# It can also be disabled by setting the seed to 0:
self.assertEqual(self.get_hash(self.repr_, seed=0), known_hash_of_obj)
+ @skip_unless_internalhash
def test_fixed_hash(self):
# test a fixed seed for the randomized hash
# Note that all types share the same values:
- if IS_64BIT:
- if sys.byteorder == 'little':
- h = -4410911502303878509
- else:
- h = -3570150969479994130
- else:
- if sys.byteorder == 'little':
- h = -206076799
- else:
- h = -1024014457
+ h = self.get_expected_hash(1, 3)
self.assertEqual(self.get_hash(self.repr_, seed=42), h)
+ @skip_unless_internalhash
+ def test_long_fixed_hash(self):
+ if self.repr_long is None:
+ return
+ h = self.get_expected_hash(2, 11)
+ self.assertEqual(self.get_hash(self.repr_long, seed=42), h)
+
+
class StrHashRandomizationTests(StringlikeHashRandomizationTests,
unittest.TestCase):
repr_ = repr('abc')
+ repr_long = repr('abcdefghijk')
+ repr_ucs2 = repr('äú∑ℇ')
+ @skip_unless_internalhash
def test_empty_string(self):
self.assertEqual(hash(""), 0)
+ @skip_unless_internalhash
+ def test_ucs2_string(self):
+ h = self.get_expected_hash(3, 6)
+ self.assertEqual(self.get_hash(self.repr_ucs2, seed=0), h)
+ h = self.get_expected_hash(4, 6)
+ self.assertEqual(self.get_hash(self.repr_ucs2, seed=42), h)
+
class BytesHashRandomizationTests(StringlikeHashRandomizationTests,
unittest.TestCase):
repr_ = repr(b'abc')
+ repr_long = repr(b'abcdefghijk')
+ @skip_unless_internalhash
def test_empty_string(self):
self.assertEqual(hash(b""), 0)
class MemoryviewHashRandomizationTests(StringlikeHashRandomizationTests,
unittest.TestCase):
repr_ = "memoryview(b'abc')"
+ repr_long = "memoryview(b'abcdefghijk')"
+ @skip_unless_internalhash
def test_empty_string(self):
self.assertEqual(hash(memoryview(b"")), 0)
@@ -224,5 +323,22 @@ class DatetimeTimeTests(DatetimeTests, unittest.TestCase):
repr_ = repr(datetime.time(0))
+class HashDistributionTestCase(unittest.TestCase):
+
+ def test_hash_distribution(self):
+ # check for hash collision
+ base = "abcdefghabcdefg"
+ for i in range(1, len(base)):
+ prefix = base[:i]
+ s15 = set()
+ s255 = set()
+ for c in range(256):
+ h = hash(prefix + chr(c))
+ s15.add(h & 0xf)
+ s255.add(h & 0xff)
+ # SipHash24 distribution depends on key, usually > 60%
+ self.assertGreater(len(s15), 8, prefix)
+ self.assertGreater(len(s255), 128, prefix)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py
index f3385f6d6f..4a5ea7f1a9 100644
--- a/Lib/test/test_hashlib.py
+++ b/Lib/test/test_hashlib.py
@@ -18,11 +18,13 @@ except ImportError:
import unittest
import warnings
from test import support
-from test.support import _4G, bigmemtest
+from test.support import _4G, bigmemtest, import_fresh_module
# Were we compiled --with-pydebug or with #define Py_DEBUG?
COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount')
+c_hashlib = import_fresh_module('hashlib', fresh=['_hashlib'])
+py_hashlib = import_fresh_module('hashlib', blocked=['_hashlib'])
def hexstr(s):
assert isinstance(s, bytes), repr(s)
@@ -36,7 +38,10 @@ def hexstr(s):
class HashLibTestCase(unittest.TestCase):
supported_hash_names = ( 'md5', 'MD5', 'sha1', 'SHA1',
'sha224', 'SHA224', 'sha256', 'SHA256',
- 'sha384', 'SHA384', 'sha512', 'SHA512' )
+ 'sha384', 'SHA384', 'sha512', 'SHA512',
+ 'sha3_224', 'sha3_256', 'sha3_384',
+ 'sha3_512', 'SHA3_224', 'SHA3_256',
+ 'SHA3_384', 'SHA3_512' )
# Issue #14693: fallback modules are always compiled under POSIX
_warn_on_extension_import = os.name == 'posix' or COMPILED_WITH_PYDEBUG
@@ -72,27 +77,37 @@ class HashLibTestCase(unittest.TestCase):
if _hashlib:
# These two algorithms should always be present when this module
# is compiled. If not, something was compiled wrong.
- assert hasattr(_hashlib, 'openssl_md5')
- assert hasattr(_hashlib, 'openssl_sha1')
+ self.assertTrue(hasattr(_hashlib, 'openssl_md5'))
+ self.assertTrue(hasattr(_hashlib, 'openssl_sha1'))
for algorithm, constructors in self.constructors_to_test.items():
constructor = getattr(_hashlib, 'openssl_'+algorithm, None)
if constructor:
constructors.add(constructor)
+ def add_builtin_constructor(name):
+ constructor = getattr(hashlib, "__get_builtin_constructor")(name)
+ self.constructors_to_test[name].add(constructor)
+
_md5 = self._conditional_import_module('_md5')
if _md5:
- self.constructors_to_test['md5'].add(_md5.md5)
+ add_builtin_constructor('md5')
_sha1 = self._conditional_import_module('_sha1')
if _sha1:
- self.constructors_to_test['sha1'].add(_sha1.sha1)
+ add_builtin_constructor('sha1')
_sha256 = self._conditional_import_module('_sha256')
if _sha256:
- self.constructors_to_test['sha224'].add(_sha256.sha224)
- self.constructors_to_test['sha256'].add(_sha256.sha256)
+ add_builtin_constructor('sha224')
+ add_builtin_constructor('sha256')
_sha512 = self._conditional_import_module('_sha512')
if _sha512:
- self.constructors_to_test['sha384'].add(_sha512.sha384)
- self.constructors_to_test['sha512'].add(_sha512.sha512)
+ add_builtin_constructor('sha384')
+ add_builtin_constructor('sha512')
+ _sha3 = self._conditional_import_module('_sha3')
+ if _sha3:
+ add_builtin_constructor('sha3_224')
+ add_builtin_constructor('sha3_256')
+ add_builtin_constructor('sha3_384')
+ add_builtin_constructor('sha3_512')
super(HashLibTestCase, self).__init__(*args, **kwargs)
@@ -121,8 +136,10 @@ class HashLibTestCase(unittest.TestCase):
self.assertRaises(TypeError, hashlib.new, 1)
def test_get_builtin_constructor(self):
- get_builtin_constructor = hashlib.__dict__[
- '__get_builtin_constructor']
+ get_builtin_constructor = getattr(hashlib,
+ '__get_builtin_constructor')
+ builtin_constructor_cache = getattr(hashlib,
+ '__builtin_constructor_cache')
self.assertRaises(ValueError, get_builtin_constructor, 'test')
try:
import _md5
@@ -130,6 +147,8 @@ class HashLibTestCase(unittest.TestCase):
pass
# This forces an ImportError for "import _md5" statements
sys.modules['_md5'] = None
+ # clear the cache
+ builtin_constructor_cache.clear()
try:
self.assertRaises(ValueError, get_builtin_constructor, 'md5')
finally:
@@ -138,13 +157,23 @@ class HashLibTestCase(unittest.TestCase):
else:
del sys.modules['_md5']
self.assertRaises(TypeError, get_builtin_constructor, 3)
+ constructor = get_builtin_constructor('md5')
+ self.assertIs(constructor, _md5.md5)
+ self.assertEqual(sorted(builtin_constructor_cache), ['MD5', 'md5'])
def test_hexdigest(self):
for cons in self.hash_constructors:
h = cons()
- assert isinstance(h.digest(), bytes), name
+ self.assertIsInstance(h.digest(), bytes)
self.assertEqual(hexstr(h.digest()), h.hexdigest())
+ def test_name_attribute(self):
+ for cons in self.hash_constructors:
+ h = cons()
+ self.assertIsInstance(h.name, str)
+ self.assertIn(h.name, self.supported_hash_names)
+ self.assertEqual(h.name, hashlib.new(h.name).name)
+
def test_large_update(self):
aas = b'a' * 128
bees = b'b' * 127
@@ -205,6 +234,10 @@ class HashLibTestCase(unittest.TestCase):
self.check_no_unicode('sha256')
self.check_no_unicode('sha384')
self.check_no_unicode('sha512')
+ self.check_no_unicode('sha3_224')
+ self.check_no_unicode('sha3_256')
+ self.check_no_unicode('sha3_384')
+ self.check_no_unicode('sha3_512')
def check_blocksize_name(self, name, block_size=0, digest_size=0):
constructors = self.constructors_to_test[name]
@@ -213,8 +246,9 @@ class HashLibTestCase(unittest.TestCase):
self.assertEqual(m.block_size, block_size)
self.assertEqual(m.digest_size, digest_size)
self.assertEqual(len(m.digest()), digest_size)
- self.assertEqual(m.name.lower(), name.lower())
- self.assertIn(name.split("_")[0], repr(m).lower())
+ self.assertEqual(m.name, name)
+ # split for sha3_512 / _sha3.sha3 object
+ self.assertIn(name.split("_")[0], repr(m))
def test_blocksize_name(self):
self.check_blocksize_name('md5', 64, 16)
@@ -223,6 +257,10 @@ class HashLibTestCase(unittest.TestCase):
self.check_blocksize_name('sha256', 64, 32)
self.check_blocksize_name('sha384', 128, 48)
self.check_blocksize_name('sha512', 128, 64)
+ self.check_blocksize_name('sha3_224', NotImplemented, 28)
+ self.check_blocksize_name('sha3_256', NotImplemented, 32)
+ self.check_blocksize_name('sha3_384', NotImplemented, 48)
+ self.check_blocksize_name('sha3_512', NotImplemented, 64)
def test_case_md5_0(self):
self.check('md5', b'', 'd41d8cd98f00b204e9800998ecf8427e')
@@ -358,6 +396,108 @@ class HashLibTestCase(unittest.TestCase):
"e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973eb"+
"de0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b")
+ # SHA-3 family
+ def test_case_sha3_224_0(self):
+ self.check('sha3_224', b"",
+ "F71837502BA8E10837BDD8D365ADB85591895602FC552B48B7390ABD")
+
+ def test_case_sha3_224_1(self):
+ self.check('sha3_224', bytes.fromhex("CC"),
+ "A9CAB59EB40A10B246290F2D6086E32E3689FAF1D26B470C899F2802")
+
+ def test_case_sha3_224_2(self):
+ self.check('sha3_224', bytes.fromhex("41FB"),
+ "615BA367AFDC35AAC397BC7EB5D58D106A734B24986D5D978FEFD62C")
+
+ def test_case_sha3_224_3(self):
+ self.check('sha3_224', bytes.fromhex(
+ "433C5303131624C0021D868A30825475E8D0BD3052A022180398F4CA4423B9"+
+ "8214B6BEAAC21C8807A2C33F8C93BD42B092CC1B06CEDF3224D5ED1EC29784"+
+ "444F22E08A55AA58542B524B02CD3D5D5F6907AFE71C5D7462224A3F9D9E53"+
+ "E7E0846DCBB4CE"),
+ "62B10F1B6236EBC2DA72957742A8D4E48E213B5F8934604BFD4D2C3A")
+
+ @bigmemtest(size=_4G + 5, memuse=1)
+ def test_case_sha3_224_huge(self, size):
+ if size == _4G + 5:
+ try:
+ self.check('sha3_224', b'A'*size,
+ '58ef60057c9dddb6a87477e9ace5a26f0d9db01881cf9b10a9f8c224')
+ except OverflowError:
+ pass # 32-bit arch
+
+
+ def test_case_sha3_256_0(self):
+ self.check('sha3_256', b"",
+ "C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470")
+
+ def test_case_sha3_256_1(self):
+ self.check('sha3_256', bytes.fromhex("CC"),
+ "EEAD6DBFC7340A56CAEDC044696A168870549A6A7F6F56961E84A54BD9970B8A")
+
+ def test_case_sha3_256_2(self):
+ self.check('sha3_256', bytes.fromhex("41FB"),
+ "A8EACEDA4D47B3281A795AD9E1EA2122B407BAF9AABCB9E18B5717B7873537D2")
+
+ def test_case_sha3_256_3(self):
+ self.check('sha3_256', bytes.fromhex(
+ "433C5303131624C0021D868A30825475E8D0BD3052A022180398F4CA4423B9"+
+ "8214B6BEAAC21C8807A2C33F8C93BD42B092CC1B06CEDF3224D5ED1EC29784"+
+ "444F22E08A55AA58542B524B02CD3D5D5F6907AFE71C5D7462224A3F9D9E53"+
+ "E7E0846DCBB4CE"),
+ "CE87A5173BFFD92399221658F801D45C294D9006EE9F3F9D419C8D427748DC41")
+
+
+ def test_case_sha3_384_0(self):
+ self.check('sha3_384', b"",
+ "2C23146A63A29ACF99E73B88F8C24EAA7DC60AA771780CCC006AFBFA8FE2479B"+
+ "2DD2B21362337441AC12B515911957FF")
+
+ def test_case_sha3_384_1(self):
+ self.check('sha3_384', bytes.fromhex("CC"),
+ "1B84E62A46E5A201861754AF5DC95C4A1A69CAF4A796AE405680161E29572641"+
+ "F5FA1E8641D7958336EE7B11C58F73E9")
+
+ def test_case_sha3_384_2(self):
+ self.check('sha3_384', bytes.fromhex("41FB"),
+ "495CCE2714CD72C8C53C3363D22C58B55960FE26BE0BF3BBC7A3316DD563AD1D"+
+ "B8410E75EEFEA655E39D4670EC0B1792")
+
+ def test_case_sha3_384_3(self):
+ self.check('sha3_384', bytes.fromhex(
+ "433C5303131624C0021D868A30825475E8D0BD3052A022180398F4CA4423B9"+
+ "8214B6BEAAC21C8807A2C33F8C93BD42B092CC1B06CEDF3224D5ED1EC29784"+
+ "444F22E08A55AA58542B524B02CD3D5D5F6907AFE71C5D7462224A3F9D9E53"+
+ "E7E0846DCBB4CE"),
+ "135114508DD63E279E709C26F7817C0482766CDE49132E3EDF2EEDD8996F4E35"+
+ "96D184100B384868249F1D8B8FDAA2C9")
+
+
+ def test_case_sha3_512_0(self):
+ self.check('sha3_512', b"",
+ "0EAB42DE4C3CEB9235FC91ACFFE746B29C29A8C366B7C60E4E67C466F36A4304"+
+ "C00FA9CAF9D87976BA469BCBE06713B435F091EF2769FB160CDAB33D3670680E")
+
+ def test_case_sha3_512_1(self):
+ self.check('sha3_512', bytes.fromhex("CC"),
+ "8630C13CBD066EA74BBE7FE468FEC1DEE10EDC1254FB4C1B7C5FD69B646E4416"+
+ "0B8CE01D05A0908CA790DFB080F4B513BC3B6225ECE7A810371441A5AC666EB9")
+
+ def test_case_sha3_512_2(self):
+ self.check('sha3_512', bytes.fromhex("41FB"),
+ "551DA6236F8B96FCE9F97F1190E901324F0B45E06DBBB5CDB8355D6ED1DC34B3"+
+ "F0EAE7DCB68622FF232FA3CECE0D4616CDEB3931F93803662A28DF1CD535B731")
+
+ def test_case_sha3_512_3(self):
+ self.check('sha3_512', bytes.fromhex(
+ "433C5303131624C0021D868A30825475E8D0BD3052A022180398F4CA4423B9"+
+ "8214B6BEAAC21C8807A2C33F8C93BD42B092CC1B06CEDF3224D5ED1EC29784"+
+ "444F22E08A55AA58542B524B02CD3D5D5F6907AFE71C5D7462224A3F9D9E53"+
+ "E7E0846DCBB4CE"),
+ "527D28E341E6B14F4684ADB4B824C496C6482E51149565D3D17226828884306B"+
+ "51D6148A72622C2B75F5D3510B799D8BDC03EAEDE453676A6EC8FE03A1AD0EAB")
+
+
def test_gil(self):
# Check things work fine with an input larger than the size required
# for multithreaded operation (which is hardwired to 2048).
@@ -406,8 +546,8 @@ class HashLibTestCase(unittest.TestCase):
events = []
for threadnum in range(num_threads):
chunk_size = len(data) // (10**threadnum)
- assert chunk_size > 0
- assert chunk_size % len(smallest_data) == 0
+ self.assertGreater(chunk_size, 0)
+ self.assertEqual(chunk_size % len(smallest_data), 0)
event = threading.Event()
events.append(event)
threading.Thread(target=hash_in_chunks,
@@ -418,8 +558,95 @@ class HashLibTestCase(unittest.TestCase):
self.assertEqual(expected_hash, hasher.hexdigest())
-def test_main():
- support.run_unittest(HashLibTestCase)
+
+class KDFTests(unittest.TestCase):
+
+ pbkdf2_test_vectors = [
+ (b'password', b'salt', 1, None),
+ (b'password', b'salt', 2, None),
+ (b'password', b'salt', 4096, None),
+ # too slow, it takes over a minute on a fast CPU.
+ #(b'password', b'salt', 16777216, None),
+ (b'passwordPASSWORDpassword', b'saltSALTsaltSALTsaltSALTsaltSALTsalt',
+ 4096, -1),
+ (b'pass\0word', b'sa\0lt', 4096, 16),
+ ]
+
+ pbkdf2_results = {
+ "sha1": [
+ # offical test vectors from RFC 6070
+ (bytes.fromhex('0c60c80f961f0e71f3a9b524af6012062fe037a6'), None),
+ (bytes.fromhex('ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957'), None),
+ (bytes.fromhex('4b007901b765489abead49d926f721d065a429c1'), None),
+ #(bytes.fromhex('eefe3d61cd4da4e4e9945b3d6ba2158c2634e984'), None),
+ (bytes.fromhex('3d2eec4fe41c849b80c8d83662c0e44a8b291a964c'
+ 'f2f07038'), 25),
+ (bytes.fromhex('56fa6aa75548099dcc37d7f03425e0c3'), None),],
+ "sha256": [
+ (bytes.fromhex('120fb6cffcf8b32c43e7225256c4f837'
+ 'a86548c92ccc35480805987cb70be17b'), None),
+ (bytes.fromhex('ae4d0c95af6b46d32d0adff928f06dd0'
+ '2a303f8ef3c251dfd6e2d85a95474c43'), None),
+ (bytes.fromhex('c5e478d59288c841aa530db6845c4c8d'
+ '962893a001ce4e11a4963873aa98134a'), None),
+ #(bytes.fromhex('cf81c66fe8cfc04d1f31ecb65dab4089'
+ # 'f7f179e89b3b0bcb17ad10e3ac6eba46'), None),
+ (bytes.fromhex('348c89dbcbd32b2f32d814b8116e84cf2b17'
+ '347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9'), 40),
+ (bytes.fromhex('89b69d0516f829893c696226650a8687'), None),],
+ "sha512": [
+ (bytes.fromhex('867f70cf1ade02cff3752599a3a53dc4af34c7a669815ae5'
+ 'd513554e1c8cf252c02d470a285a0501bad999bfe943c08f'
+ '050235d7d68b1da55e63f73b60a57fce'), None),
+ (bytes.fromhex('e1d9c16aa681708a45f5c7c4e215ceb66e011a2e9f004071'
+ '3f18aefdb866d53cf76cab2868a39b9f7840edce4fef5a82'
+ 'be67335c77a6068e04112754f27ccf4e'), None),
+ (bytes.fromhex('d197b1b33db0143e018b12f3d1d1479e6cdebdcc97c5c0f8'
+ '7f6902e072f457b5143f30602641b3d55cd335988cb36b84'
+ '376060ecd532e039b742a239434af2d5'), None),
+ (bytes.fromhex('8c0511f4c6e597c6ac6315d8f0362e225f3c501495ba23b8'
+ '68c005174dc4ee71115b59f9e60cd9532fa33e0f75aefe30'
+ '225c583a186cd82bd4daea9724a3d3b8'), 64),
+ (bytes.fromhex('9d9e9c4cd21fe4be24d5b8244c759665'), None),],
+ }
+
+ def _test_pbkdf2_hmac(self, pbkdf2):
+ for digest_name, results in self.pbkdf2_results.items():
+ for i, vector in enumerate(self.pbkdf2_test_vectors):
+ password, salt, rounds, dklen = vector
+ expected, overwrite_dklen = results[i]
+ if overwrite_dklen:
+ dklen = overwrite_dklen
+ out = pbkdf2(digest_name, password, salt, rounds, dklen)
+ self.assertEqual(out, expected,
+ (digest_name, password, salt, rounds, dklen))
+ out = pbkdf2(digest_name, memoryview(password),
+ memoryview(salt), rounds, dklen)
+ out = pbkdf2(digest_name, bytearray(password),
+ bytearray(salt), rounds, dklen)
+ self.assertEqual(out, expected)
+ if dklen is None:
+ out = pbkdf2(digest_name, password, salt, rounds)
+ self.assertEqual(out, expected,
+ (digest_name, password, salt, rounds))
+
+ self.assertRaises(TypeError, pbkdf2, b'sha1', b'pass', b'salt', 1)
+ self.assertRaises(TypeError, pbkdf2, 'sha1', 'pass', 'salt', 1)
+ self.assertRaises(ValueError, pbkdf2, 'sha1', b'pass', b'salt', 0)
+ self.assertRaises(ValueError, pbkdf2, 'sha1', b'pass', b'salt', -1)
+ self.assertRaises(ValueError, pbkdf2, 'sha1', b'pass', b'salt', 1, 0)
+ self.assertRaises(ValueError, pbkdf2, 'sha1', b'pass', b'salt', 1, -1)
+ with self.assertRaisesRegex(ValueError, 'unsupported hash type'):
+ pbkdf2('unknown', b'pass', b'salt', 1)
+
+ def test_pbkdf2_hmac_py(self):
+ self._test_pbkdf2_hmac(py_hashlib.pbkdf2_hmac)
+
+ @unittest.skipUnless(hasattr(c_hashlib, 'pbkdf2_hmac'),
+ ' test requires OpenSSL > 1.0')
+ def test_pbkdf2_hmac_c(self):
+ self._test_pbkdf2_hmac(c_hashlib.pbkdf2_hmac)
+
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index 4ca7cec44c..cde56fd76f 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -1,17 +1,39 @@
+import functools
import hmac
import hashlib
import unittest
import warnings
from test import support
+
+def ignore_warning(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore",
+ category=PendingDeprecationWarning)
+ return func(*args, **kwargs)
+ return wrapper
+
+
class TestVectorsTestCase(unittest.TestCase):
def test_md5_vectors(self):
# Test the HMAC module against test vectors from the RFC.
def md5test(key, data, digest):
- h = hmac.HMAC(key, data)
+ h = hmac.HMAC(key, data, digestmod=hashlib.md5)
self.assertEqual(h.hexdigest().upper(), digest.upper())
+ self.assertEqual(h.name, "hmac-md5")
+ self.assertEqual(h.digest_size, 16)
+ self.assertEqual(h.block_size, 64)
+
+ h = hmac.HMAC(key, data, digestmod='md5')
+ self.assertEqual(h.hexdigest().upper(), digest.upper())
+ self.assertEqual(h.name, "hmac-md5")
+ self.assertEqual(h.digest_size, 16)
+ self.assertEqual(h.block_size, 64)
+
md5test(b"\x0b" * 16,
b"Hi There",
@@ -46,6 +68,16 @@ class TestVectorsTestCase(unittest.TestCase):
def shatest(key, data, digest):
h = hmac.HMAC(key, data, digestmod=hashlib.sha1)
self.assertEqual(h.hexdigest().upper(), digest.upper())
+ self.assertEqual(h.name, "hmac-sha1")
+ self.assertEqual(h.digest_size, 20)
+ self.assertEqual(h.block_size, 64)
+
+ h = hmac.HMAC(key, data, digestmod='sha1')
+ self.assertEqual(h.hexdigest().upper(), digest.upper())
+ self.assertEqual(h.name, "hmac-sha1")
+ self.assertEqual(h.digest_size, 20)
+ self.assertEqual(h.block_size, 64)
+
shatest(b"\x0b" * 20,
b"Hi There",
@@ -76,10 +108,21 @@ class TestVectorsTestCase(unittest.TestCase):
b"and Larger Than One Block-Size Data"),
"e8e99d0f45237d786d6bbaa7965c7808bbff1a91")
- def _rfc4231_test_cases(self, hashfunc):
+ def _rfc4231_test_cases(self, hashfunc, hash_name, digest_size, block_size):
def hmactest(key, data, hexdigests):
+ hmac_name = "hmac-" + hash_name
h = hmac.HMAC(key, data, digestmod=hashfunc)
self.assertEqual(h.hexdigest().lower(), hexdigests[hashfunc])
+ self.assertEqual(h.name, hmac_name)
+ self.assertEqual(h.digest_size, digest_size)
+ self.assertEqual(h.block_size, block_size)
+
+ h = hmac.HMAC(key, data, digestmod=hash_name)
+ self.assertEqual(h.hexdigest().lower(), hexdigests[hashfunc])
+ self.assertEqual(h.name, hmac_name)
+ self.assertEqual(h.digest_size, digest_size)
+ self.assertEqual(h.block_size, block_size)
+
# 4.2. Test Case 1
hmactest(key = b'\x0b'*20,
@@ -189,16 +232,16 @@ class TestVectorsTestCase(unittest.TestCase):
})
def test_sha224_rfc4231(self):
- self._rfc4231_test_cases(hashlib.sha224)
+ self._rfc4231_test_cases(hashlib.sha224, 'sha224', 28, 64)
def test_sha256_rfc4231(self):
- self._rfc4231_test_cases(hashlib.sha256)
+ self._rfc4231_test_cases(hashlib.sha256, 'sha256', 32, 64)
def test_sha384_rfc4231(self):
- self._rfc4231_test_cases(hashlib.sha384)
+ self._rfc4231_test_cases(hashlib.sha384, 'sha384', 48, 128)
def test_sha512_rfc4231(self):
- self._rfc4231_test_cases(hashlib.sha512)
+ self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128)
def test_legacy_block_size_warnings(self):
class MockCrazyHash(object):
@@ -222,46 +265,74 @@ class TestVectorsTestCase(unittest.TestCase):
hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
self.fail('Expected warning about small block_size')
+ def test_with_digestmod_warning(self):
+ with self.assertWarns(PendingDeprecationWarning):
+ key = b"\x0b" * 16
+ data = b"Hi There"
+ digest = "9294727A3638BB1C13F48EF8158BFC9D"
+ h = hmac.HMAC(key, data)
+ self.assertEqual(h.hexdigest().upper(), digest)
class ConstructorTestCase(unittest.TestCase):
+ @ignore_warning
def test_normal(self):
# Standard constructor call.
failed = 0
try:
h = hmac.HMAC(b"key")
- except:
+ except Exception:
self.fail("Standard constructor call raised exception.")
+ @ignore_warning
def test_with_str_key(self):
# Pass a key of type str, which is an error, because it expects a key
# of type bytes
with self.assertRaises(TypeError):
h = hmac.HMAC("key")
+ @ignore_warning
def test_dot_new_with_str_key(self):
# Pass a key of type str, which is an error, because it expects a key
# of type bytes
with self.assertRaises(TypeError):
h = hmac.new("key")
+ @ignore_warning
def test_withtext(self):
# Constructor call with text.
try:
h = hmac.HMAC(b"key", b"hash this!")
- except:
+ except Exception:
self.fail("Constructor call with text argument raised exception.")
+ self.assertEqual(h.hexdigest(), '34325b639da4cfd95735b381e28cb864')
+
+ def test_with_bytearray(self):
+ try:
+ h = hmac.HMAC(bytearray(b"key"), bytearray(b"hash this!"),
+ digestmod="md5")
+ except Exception:
+ self.fail("Constructor call with bytearray arguments raised exception.")
+ self.assertEqual(h.hexdigest(), '34325b639da4cfd95735b381e28cb864')
+
+ def test_with_memoryview_msg(self):
+ try:
+ h = hmac.HMAC(b"key", memoryview(b"hash this!"), digestmod="md5")
+ except Exception:
+ self.fail("Constructor call with memoryview msg raised exception.")
+ self.assertEqual(h.hexdigest(), '34325b639da4cfd95735b381e28cb864')
def test_withmodule(self):
# Constructor call with text and digest module.
try:
h = hmac.HMAC(b"key", b"", hashlib.sha1)
- except:
+ except Exception:
self.fail("Constructor call with hashlib.sha1 raised exception.")
class SanityTestCase(unittest.TestCase):
+ @ignore_warning
def test_default_is_md5(self):
# Testing if HMAC defaults to MD5 algorithm.
# NOTE: this whitebox test depends on the hmac class internals
@@ -272,19 +343,19 @@ class SanityTestCase(unittest.TestCase):
# Exercising all methods once.
# This must not raise any exceptions
try:
- h = hmac.HMAC(b"my secret key")
+ h = hmac.HMAC(b"my secret key", digestmod="md5")
h.update(b"compute the hash of this text!")
dig = h.digest()
dig = h.hexdigest()
h2 = h.copy()
- except:
+ except Exception:
self.fail("Exception raised during normal usage of HMAC class.")
class CopyTestCase(unittest.TestCase):
def test_attributes(self):
# Testing if attributes are of same type.
- h1 = hmac.HMAC(b"key")
+ h1 = hmac.HMAC(b"key", digestmod="md5")
h2 = h1.copy()
self.assertTrue(h1.digest_cons == h2.digest_cons,
"digest constructors don't match.")
@@ -295,7 +366,7 @@ class CopyTestCase(unittest.TestCase):
def test_realcopy(self):
# Testing if the copy method created a real copy.
- h1 = hmac.HMAC(b"key")
+ h1 = hmac.HMAC(b"key", digestmod="md5")
h2 = h1.copy()
# Using id() in case somebody has overridden __eq__/__ne__.
self.assertTrue(id(h1) != id(h2), "No real copy of the HMAC instance.")
@@ -306,7 +377,7 @@ class CopyTestCase(unittest.TestCase):
def test_equality(self):
# Testing if the copy has the same digests.
- h1 = hmac.HMAC(b"key")
+ h1 = hmac.HMAC(b"key", digestmod="md5")
h1.update(b"some random text")
h2 = h1.copy()
self.assertEqual(h1.digest(), h2.digest(),
diff --git a/Lib/test/test_html.py b/Lib/test/test_html.py
index 30dac58e6d..5e9f382a2d 100644
--- a/Lib/test/test_html.py
+++ b/Lib/test/test_html.py
@@ -16,9 +16,89 @@ class HtmlTests(unittest.TestCase):
html.escape('\'<script>"&foo;"</script>\'', False),
'\'&lt;script&gt;"&amp;foo;"&lt;/script&gt;\'')
+ def test_unescape(self):
+ numeric_formats = ['&#%d', '&#%d;', '&#x%x', '&#x%x;']
+ errmsg = 'unescape(%r) should have returned %r'
+ def check(text, expected):
+ self.assertEqual(html.unescape(text), expected,
+ msg=errmsg % (text, expected))
+ def check_num(num, expected):
+ for format in numeric_formats:
+ text = format % num
+ self.assertEqual(html.unescape(text), expected,
+ msg=errmsg % (text, expected))
+ # check text with no character references
+ check('no character references', 'no character references')
+ # check & followed by invalid chars
+ check('&\n&\t& &&', '&\n&\t& &&')
+ # check & followed by numbers and letters
+ check('&0 &9 &a &0; &9; &a;', '&0 &9 &a &0; &9; &a;')
+ # check incomplete entities at the end of the string
+ for x in ['&', '&#', '&#x', '&#X', '&#y', '&#xy', '&#Xy']:
+ check(x, x)
+ check(x+';', x+';')
+ # check several combinations of numeric character references,
+ # possibly followed by different characters
+ formats = ['&#%d', '&#%07d', '&#%d;', '&#%07d;',
+ '&#x%x', '&#x%06x', '&#x%x;', '&#x%06x;',
+ '&#x%X', '&#x%06X', '&#X%x;', '&#X%06x;']
+ for num, char in zip([65, 97, 34, 38, 0x2603, 0x101234],
+ ['A', 'a', '"', '&', '\u2603', '\U00101234']):
+ for s in formats:
+ check(s % num, char)
+ for end in [' ', 'X']:
+ check((s+end) % num, char+end)
+ # check invalid codepoints
+ for cp in [0xD800, 0xDB00, 0xDC00, 0xDFFF, 0x110000]:
+ check_num(cp, '\uFFFD')
+ # check more invalid codepoints
+ for cp in [0x1, 0xb, 0xe, 0x7f, 0xfffe, 0xffff, 0x10fffe, 0x10ffff]:
+ check_num(cp, '')
+ # check invalid numbers
+ for num, ch in zip([0x0d, 0x80, 0x95, 0x9d], '\r\u20ac\u2022\x9d'):
+ check_num(num, ch)
+ # check small numbers
+ check_num(0, '\uFFFD')
+ check_num(9, '\t')
+ # check a big number
+ check_num(1000000000000000000, '\uFFFD')
+ # check that multiple trailing semicolons are handled correctly
+ for e in ['&quot;;', '&#34;;', '&#x22;;', '&#X22;;']:
+ check(e, '";')
+ # check that semicolons in the middle don't create problems
+ for e in ['&quot;quot;', '&#34;quot;', '&#x22;quot;', '&#X22;quot;']:
+ check(e, '"quot;')
+ # check triple adjacent charrefs
+ for e in ['&quot', '&#34', '&#x22', '&#X22']:
+ check(e*3, '"""')
+ check((e+';')*3, '"""')
+ # check that the case is respected
+ for e in ['&amp', '&amp;', '&AMP', '&AMP;']:
+ check(e, '&')
+ for e in ['&Amp', '&Amp;']:
+ check(e, e)
+ # check that non-existent named entities are returned unchanged
+ check('&svadilfari;', '&svadilfari;')
+ # the following examples are in the html5 specs
+ check('&notit', '¬it')
+ check('&notit;', '¬it;')
+ check('&notin', '¬in')
+ check('&notin;', '∉')
+ # a similar example with a long name
+ check('&notReallyAnExistingNamedCharacterReference;',
+ '¬ReallyAnExistingNamedCharacterReference;')
+ # longest valid name
+ check('&CounterClockwiseContourIntegral;', '∳')
+ # check a charref that maps to two unicode chars
+ check('&acE;', '\u223E\u0333')
+ check('&acE', '&acE')
+ # see #12888
+ check('&#123; ' * 1050, '{ ' * 1050)
+ # see #15156
+ check('&Eacuteric&Eacute;ric&alphacentauri&alpha;centauri',
+ 'ÉricÉric&alphacentauriαcentauri')
+ check('&co;', '&co;')
-def test_main():
- run_unittest(HtmlTests)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_htmlparser.py b/Lib/test/test_htmlparser.py
index c977a9dd4d..1a480c8187 100644
--- a/Lib/test/test_htmlparser.py
+++ b/Lib/test/test_htmlparser.py
@@ -70,6 +70,18 @@ class EventCollectorExtra(EventCollector):
self.append(("starttag_text", self.get_starttag_text()))
+class EventCollectorCharrefs(EventCollector):
+
+ def get_events(self):
+ return self.events
+
+ def handle_charref(self, data):
+ self.fail('This should never be called with convert_charrefs=True')
+
+ def handle_entityref(self, data):
+ self.fail('This should never be called with convert_charrefs=True')
+
+
class TestCaseBase(unittest.TestCase):
def get_collector(self):
@@ -84,26 +96,30 @@ class TestCaseBase(unittest.TestCase):
parser.close()
events = parser.get_events()
if events != expected_events:
- self.fail("received events did not match expected events\n"
- "Expected:\n" + pprint.pformat(expected_events) +
+ self.fail("received events did not match expected events" +
+ "\nSource:\n" + repr(source) +
+ "\nExpected:\n" + pprint.pformat(expected_events) +
"\nReceived:\n" + pprint.pformat(events))
def _run_check_extra(self, source, events):
- self._run_check(source, events, EventCollectorExtra())
+ self._run_check(source, events,
+ EventCollectorExtra(convert_charrefs=False))
def _parse_error(self, source):
def parse(source=source):
parser = self.get_collector()
parser.feed(source)
parser.close()
- self.assertRaises(html.parser.HTMLParseError, parse)
+ with self.assertRaises(html.parser.HTMLParseError):
+ with self.assertWarns(DeprecationWarning):
+ parse()
class HTMLParserStrictTestCase(TestCaseBase):
def get_collector(self):
with support.check_warnings(("", DeprecationWarning), quite=False):
- return EventCollector(strict=True)
+ return EventCollector(strict=True, convert_charrefs=False)
def test_processing_instruction_only(self):
self._run_check("<?processing instruction>", [
@@ -333,7 +349,7 @@ text
self._run_check(s, [("starttag", element_lower, []),
("data", content),
("endtag", element_lower)],
- collector=Collector())
+ collector=Collector(convert_charrefs=False))
def test_comments(self):
html = ("<!-- I'm a valid comment -->"
@@ -361,11 +377,60 @@ text
('comment', '[if lte IE 7]>pretty?<![endif]')]
self._run_check(html, expected)
+ def test_convert_charrefs(self):
+ collector = lambda: EventCollectorCharrefs(convert_charrefs=True)
+ self.assertTrue(collector().convert_charrefs)
+ charrefs = ['&quot;', '&#34;', '&#x22;', '&quot', '&#34', '&#x22']
+ # check charrefs in the middle of the text/attributes
+ expected = [('starttag', 'a', [('href', 'foo"zar')]),
+ ('data', 'a"z'), ('endtag', 'a')]
+ for charref in charrefs:
+ self._run_check('<a href="foo{0}zar">a{0}z</a>'.format(charref),
+ expected, collector=collector())
+ # check charrefs at the beginning/end of the text/attributes
+ expected = [('data', '"'),
+ ('starttag', 'a', [('x', '"'), ('y', '"X'), ('z', 'X"')]),
+ ('data', '"'), ('endtag', 'a'), ('data', '"')]
+ for charref in charrefs:
+ self._run_check('{0}<a x="{0}" y="{0}X" z="X{0}">'
+ '{0}</a>{0}'.format(charref),
+ expected, collector=collector())
+ # check charrefs in <script>/<style> elements
+ for charref in charrefs:
+ text = 'X'.join([charref]*3)
+ expected = [('data', '"'),
+ ('starttag', 'script', []), ('data', text),
+ ('endtag', 'script'), ('data', '"'),
+ ('starttag', 'style', []), ('data', text),
+ ('endtag', 'style'), ('data', '"')]
+ self._run_check('{1}<script>{0}</script>{1}'
+ '<style>{0}</style>{1}'.format(text, charref),
+ expected, collector=collector())
+ # check truncated charrefs at the end of the file
+ html = '&quo &# &#x'
+ for x in range(1, len(html)):
+ self._run_check(html[:x], [('data', html[:x])],
+ collector=collector())
+ # check a string with no charrefs
+ self._run_check('no charrefs here', [('data', 'no charrefs here')],
+ collector=collector())
+
class HTMLParserTolerantTestCase(HTMLParserStrictTestCase):
def get_collector(self):
- return EventCollector(strict=False)
+ return EventCollector(convert_charrefs=False)
+
+ def test_deprecation_warnings(self):
+ with self.assertWarns(DeprecationWarning):
+ EventCollector() # convert_charrefs not passed explicitly
+ with self.assertWarns(DeprecationWarning):
+ EventCollector(strict=True)
+ with self.assertWarns(DeprecationWarning):
+ EventCollector(strict=False)
+ with self.assertRaises(html.parser.HTMLParseError):
+ with self.assertWarns(DeprecationWarning):
+ EventCollector().error('test')
def test_tolerant_parsing(self):
self._run_check('<html <html>te>>xt&a<<bc</a></html>\n'
@@ -558,17 +623,12 @@ class HTMLParserTolerantTestCase(HTMLParserStrictTestCase):
for html, expected in data:
self._run_check(html, expected)
- def test_unescape_function(self):
+ def test_unescape_method(self):
+ from html import unescape
p = self.get_collector()
- self.assertEqual(p.unescape('&#bad;'),'&#bad;')
- self.assertEqual(p.unescape('&#0038;'),'&')
- # see #12888
- self.assertEqual(p.unescape('&#123; ' * 1050), '{ ' * 1050)
- # see #15156
- self.assertEqual(p.unescape('&Eacuteric&Eacute;ric'
- '&alphacentauri&alpha;centauri'),
- 'ÉricÉric&alphacentauriαcentauri')
- self.assertEqual(p.unescape('&co;'), '&co;')
+ with self.assertWarns(DeprecationWarning):
+ s = '&quot;&#34;&#x22;&quot&#34&#x22&#bad;'
+ self.assertEqual(p.unescape(s), unescape(s))
def test_broken_comments(self):
html = ('<! not really a comment >'
@@ -624,7 +684,7 @@ class AttributesStrictTestCase(TestCaseBase):
def get_collector(self):
with support.check_warnings(("", DeprecationWarning), quite=False):
- return EventCollector(strict=True)
+ return EventCollector(strict=True, convert_charrefs=False)
def test_attr_syntax(self):
output = [
@@ -685,7 +745,7 @@ class AttributesStrictTestCase(TestCaseBase):
class AttributesTolerantTestCase(AttributesStrictTestCase):
def get_collector(self):
- return EventCollector(strict=False)
+ return EventCollector(convert_charrefs=False)
def test_attr_funky_names2(self):
self._run_check(
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
index f3c27c2df6..31c0b6a6ca 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -27,8 +27,10 @@ class FakeSocket:
self.text = text
self.fileclass = fileclass
self.data = b''
+ self.sendall_calls = 0
def sendall(self, data):
+ self.sendall_calls += 1
self.data += data
def makefile(self, mode, bufsize=None):
@@ -45,7 +47,7 @@ class EPipeSocket(FakeSocket):
def sendall(self, data):
if self.pipe_trigger in data:
- raise socket.error(errno.EPIPE, "gotcha")
+ raise OSError(errno.EPIPE, "gotcha")
self.data += data
def close(self):
@@ -597,7 +599,7 @@ class BasicTest(TestCase):
b"Content-Length")
conn = client.HTTPConnection("example.com")
conn.sock = sock
- self.assertRaises(socket.error,
+ self.assertRaises(OSError,
lambda: conn.request("PUT", "/url", "body"))
resp = conn.getresponse()
self.assertEqual(401, resp.status)
@@ -643,6 +645,28 @@ class BasicTest(TestCase):
resp.close()
self.assertTrue(resp.closed)
+ def test_delayed_ack_opt(self):
+ # Test that Nagle/delayed_ack optimistaion works correctly.
+
+ # For small payloads, it should coalesce the body with
+ # headers, resulting in a single sendall() call
+ conn = client.HTTPConnection('example.com')
+ sock = FakeSocket(None)
+ conn.sock = sock
+ body = b'x' * (conn.mss - 1)
+ conn.request('POST', '/', body)
+ self.assertEqual(sock.sendall_calls, 1)
+
+ # For large payloads, it should send the headers and
+ # then the body, resulting in more than one sendall()
+ # call
+ conn = client.HTTPConnection('example.com')
+ sock = FakeSocket(None)
+ conn.sock = sock
+ body = b'x' * conn.mss
+ conn.request('POST', '/', body)
+ self.assertGreater(sock.sendall_calls, 1)
+
class OfflineTest(TestCase):
def test_responses(self):
self.assertEqual(client.responses[client.NOT_FOUND], "Not Found")
@@ -733,7 +757,7 @@ class HTTPSTest(TestCase):
def make_server(self, certfile):
from test.ssl_servers import make_https_server
- return make_https_server(self, certfile)
+ return make_https_server(self, certfile=certfile)
def test_attributes(self):
# simple test to check it's storing the timeout
diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
index 9dd27788f7..b2bce1c27a 100644
--- a/Lib/test/test_httpservers.py
+++ b/Lib/test/test_httpservers.py
@@ -92,6 +92,13 @@ class BaseHTTPServerTestCase(BaseTestCase):
def do_KEYERROR(self):
self.send_error(999)
+ def do_NOTFOUND(self):
+ self.send_error(404)
+
+ def do_EXPLAINERROR(self):
+ self.send_error(999, "Short Message",
+ "This is a long \n explaination")
+
def do_CUSTOM(self):
self.send_response(999)
self.send_header('Content-Type', 'text/html')
@@ -203,6 +210,12 @@ class BaseHTTPServerTestCase(BaseTestCase):
res = self.con.getresponse()
self.assertEqual(res.status, 999)
+ def test_return_explain_error(self):
+ self.con.request('EXPLAINERROR', '/')
+ res = self.con.getresponse()
+ self.assertEqual(res.status, 999)
+ self.assertTrue(int(res.getheader('Content-Length')))
+
def test_latin1_header(self):
self.con.request('LATINONEHEADER', '/', headers={
'X-Special-Incoming': 'Ärger mit Unicode'
@@ -211,6 +224,14 @@ class BaseHTTPServerTestCase(BaseTestCase):
self.assertEqual(res.getheader('X-Special'), 'Dängerous Mind')
self.assertEqual(res.read(), 'Ärger mit Unicode'.encode('utf-8'))
+ def test_error_content_length(self):
+ # Issue #16088: standard error responses should have a content-length
+ self.con.request('NOTFOUND', '/')
+ res = self.con.getresponse()
+ self.assertEqual(res.status, 404)
+ data = res.read()
+ self.assertEqual(int(res.getheader('Content-Length')), len(data))
+
class SimpleHTTPServerTestCase(BaseTestCase):
class request_handler(NoLogRequestHandler, SimpleHTTPRequestHandler):
diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py
index daa8afeec5..81bfd1fbc2 100644
--- a/Lib/test/test_imaplib.py
+++ b/Lib/test/test_imaplib.py
@@ -125,7 +125,7 @@ class SimpleIMAPHandler(socketserver.StreamRequestHandler):
# Naked sockets return empty strings..
return
line += part
- except IOError:
+ except OSError:
# ..but SSLSockets raise exceptions.
return
if line.endswith(b'\r\n'):
diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py
index b56efe3bea..78f573407c 100644
--- a/Lib/test/test_imp.py
+++ b/Lib/test/test_imp.py
@@ -1,14 +1,29 @@
-import imp
+try:
+ import _thread
+except ImportError:
+ _thread = None
import importlib
import os
import os.path
import shutil
import sys
from test import support
-from test.test_importlib import util
import unittest
import warnings
+with warnings.catch_warnings():
+ warnings.simplefilter('ignore', PendingDeprecationWarning)
+ import imp
+
+def requires_load_dynamic(meth):
+ """Decorator to skip a test if not running under CPython or lacking
+ imp.load_dynamic()."""
+ meth = support.cpython_only(meth)
+ return unittest.skipIf(not hasattr(imp, 'load_dynamic'),
+ 'imp.load_dynamic() required')(meth)
+
+
+@unittest.skipIf(_thread is None, '_thread module is required')
class LockTests(unittest.TestCase):
"""Very basic test of import lock functions."""
@@ -150,7 +165,7 @@ class ImportTests(unittest.TestCase):
self.assertIsNotNone(file)
self.assertTrue(filename[:-3].endswith(temp_mod_name))
self.assertEqual(info[0], '.py')
- self.assertEqual(info[1], 'U')
+ self.assertEqual(info[1], 'r')
self.assertEqual(info[2], imp.PY_SOURCE)
mod = imp.load_module(temp_mod_name, file, filename, info)
@@ -208,9 +223,7 @@ class ImportTests(unittest.TestCase):
self.assertIs(orig_path, new_os.path)
self.assertIsNot(orig_getenv, new_os.getenv)
- @support.cpython_only
- @unittest.skipIf(not hasattr(imp, 'load_dynamic'),
- 'imp.load_dynamic() required')
+ @requires_load_dynamic
def test_issue15828_load_extensions(self):
# Issue 15828 picked up that the adapter between the old imp API
# and importlib couldn't handle C extensions
@@ -222,6 +235,22 @@ class ImportTests(unittest.TestCase):
mod = imp.load_module(example, *x)
self.assertEqual(mod.__name__, example)
+ @requires_load_dynamic
+ def test_issue16421_multiple_modules_in_one_dll(self):
+ # Issue 16421: loading several modules from the same compiled file fails
+ m = '_testimportmultiple'
+ fileobj, pathname, description = imp.find_module(m)
+ fileobj.close()
+ mod0 = imp.load_dynamic(m, pathname)
+ mod1 = imp.load_dynamic('_testimportmultiple_foo', pathname)
+ mod2 = imp.load_dynamic('_testimportmultiple_bar', pathname)
+ self.assertEqual(mod0.__name__, m)
+ self.assertEqual(mod1.__name__, '_testimportmultiple_foo')
+ self.assertEqual(mod2.__name__, '_testimportmultiple_bar')
+ with self.assertRaises(ImportError):
+ imp.load_dynamic('nonexistent', pathname)
+
+ @requires_load_dynamic
def test_load_dynamic_ImportError_path(self):
# Issue #1559549 added `name` and `path` attributes to ImportError
# in order to provide better detail. Issue #10854 implemented those
@@ -233,14 +262,12 @@ class ImportTests(unittest.TestCase):
self.assertIn(path, err.exception.path)
self.assertEqual(name, err.exception.name)
- @support.cpython_only
- @unittest.skipIf(not hasattr(imp, 'load_dynamic'),
- 'imp.load_dynamic() required')
+ @requires_load_dynamic
def test_load_module_extension_file_is_None(self):
# When loading an extension module and the file is None, open one
# on the behalf of imp.load_dynamic().
# Issue #15902
- name = '_heapq'
+ name = '_testimportmultiple'
found = imp.find_module(name)
if found[0] is not None:
found[0].close()
@@ -248,6 +275,15 @@ class ImportTests(unittest.TestCase):
return
imp.load_module(name, None, *found[1:])
+ @unittest.skipIf(sys.dont_write_bytecode,
+ "test meaningful only when writing bytecode")
+ def test_bug7732(self):
+ with support.temp_cwd():
+ source = support.TESTFN + '.py'
+ os.mkdir(source)
+ self.assertRaisesRegex(ImportError, '^No module',
+ imp.find_module, support.TESTFN, ["."])
+
def test_multiple_calls_to_get_data(self):
# Issue #18755: make sure multiple calls to get_data() can succeed.
loader = imp._LoadSourceCompatibility('imp', imp.__file__,
@@ -293,22 +329,6 @@ class ReloadTests(unittest.TestCase):
with self.assertRaisesRegex(ImportError, 'html'):
imp.reload(parser)
- def test_module_replaced(self):
- # see #18698
- def code():
- module = type(sys)('top_level')
- module.spam = 3
- sys.modules['top_level'] = module
- mock = util.mock_modules('top_level',
- module_code={'top_level': code})
- with mock:
- with util.import_state(meta_path=[mock]):
- module = importlib.import_module('top_level')
- reloaded = imp.reload(module)
- actual = sys.modules['top_level']
- self.assertEqual(actual.spam, 3)
- self.assertEqual(reloaded.spam, 3)
-
class PEP3147Tests(unittest.TestCase):
"""Tests of PEP 3147."""
@@ -461,20 +481,5 @@ class NullImporterTests(unittest.TestCase):
os.rmdir(name)
-def test_main():
- tests = [
- ImportTests,
- PEP3147Tests,
- ReloadTests,
- NullImporterTests,
- ]
- try:
- import _thread
- except ImportError:
- pass
- else:
- tests.append(LockTests)
- support.run_unittest(*tests)
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py
index df08e6a86f..bda1541fdb 100644
--- a/Lib/test/test_import.py
+++ b/Lib/test/test_import.py
@@ -1,9 +1,8 @@
# We import importlib *ASAP* in order to test #15386
import importlib
+import importlib.util
from importlib._bootstrap import _get_sourcefile
import builtins
-import imp
-from test.test_importlib.import_ import util as importlib_util
import marshal
import os
import platform
@@ -22,7 +21,7 @@ import test.support
from test.support import (
EnvironmentVarGuard, TESTFN, check_warnings, forget, is_jython,
make_legacy_pyc, rmtree, run_unittest, swap_attr, swap_item, temp_umask,
- unlink, unload, create_empty_file, cpython_only)
+ unlink, unload, create_empty_file, cpython_only, TESTFN_UNENCODABLE)
from test import script_helper
@@ -70,8 +69,6 @@ class ImportTests(unittest.TestCase):
def tearDown(self):
unload(TESTFN)
- setUp = tearDown
-
def test_case_sensitivity(self):
# Brief digression to test that import is case-sensitive: if we got
# this far, we know for sure that "random" exists.
@@ -129,16 +126,6 @@ class ImportTests(unittest.TestCase):
finally:
del sys.path[0]
- @skip_if_dont_write_bytecode
- def test_bug7732(self):
- source = TESTFN + '.py'
- os.mkdir(source)
- try:
- self.assertRaisesRegex(ImportError, '^No module',
- imp.find_module, TESTFN, ["."])
- finally:
- os.rmdir(source)
-
def test_module_with_large_stack(self, module='longlist'):
# Regression test for http://bugs.python.org/issue561858.
filename = module + '.py'
@@ -161,16 +148,24 @@ class ImportTests(unittest.TestCase):
sys.path.append('')
importlib.invalidate_caches()
+ namespace = {}
try:
make_legacy_pyc(filename)
# This used to crash.
- exec('import ' + module)
+ exec('import ' + module, None, namespace)
finally:
# Cleanup.
del sys.path[-1]
unlink(filename + 'c')
unlink(filename + 'o')
+ # Remove references to the module (unload the module)
+ namespace.clear()
+ try:
+ del sys.modules[module]
+ except KeyError:
+ pass
+
def test_failing_import_sticks(self):
source = TESTFN + ".py"
with open(source, "w") as f:
@@ -225,7 +220,7 @@ class ImportTests(unittest.TestCase):
with open(source, "w") as f:
f.write("a = 10\nb=20//0\n")
- self.assertRaises(ZeroDivisionError, imp.reload, mod)
+ self.assertRaises(ZeroDivisionError, importlib.reload, mod)
# But we still expect the module to be in sys.modules.
mod = sys.modules.get(TESTFN)
self.assertIsNot(mod, None, "expected module to be in sys.modules")
@@ -280,7 +275,7 @@ class ImportTests(unittest.TestCase):
import sys
class C:
def __del__(self):
- import imp
+ import importlib
sys.argv.insert(0, C())
"""))
script_helper.assert_python_ok(testfn)
@@ -291,7 +286,7 @@ class ImportTests(unittest.TestCase):
sys.path.insert(0, os.curdir)
try:
source = TESTFN + ".py"
- compiled = imp.cache_from_source(source)
+ compiled = importlib.util.cache_from_source(source)
with open(source, 'w') as f:
pass
try:
@@ -322,6 +317,14 @@ class ImportTests(unittest.TestCase):
stdout, stderr = popen.communicate()
self.assertIn(b"ImportError", stdout)
+ def test_from_import_message_for_nonexistent_module(self):
+ with self.assertRaisesRegex(ImportError, "^No module named 'bogus'"):
+ from bogus import foo
+
+ def test_from_import_message_for_existing_module(self):
+ with self.assertRaisesRegex(ImportError, "^cannot import name 'bogus'"):
+ from re import bogus
+
@skip_if_dont_write_bytecode
class FilePermissionTests(unittest.TestCase):
@@ -332,7 +335,7 @@ class FilePermissionTests(unittest.TestCase):
def test_creation_mode(self):
mask = 0o022
with temp_umask(mask), _ready_to_import() as (name, path):
- cached_path = imp.cache_from_source(path)
+ cached_path = importlib.util.cache_from_source(path)
module = __import__(name)
if not os.path.exists(cached_path):
self.fail("__import__ did not result in creation of "
@@ -350,7 +353,7 @@ class FilePermissionTests(unittest.TestCase):
# permissions of .pyc should match those of .py, regardless of mask
mode = 0o600
with temp_umask(0o022), _ready_to_import() as (name, path):
- cached_path = imp.cache_from_source(path)
+ cached_path = importlib.util.cache_from_source(path)
os.chmod(path, mode)
__import__(name)
if not os.path.exists(cached_path):
@@ -365,7 +368,7 @@ class FilePermissionTests(unittest.TestCase):
def test_cached_readonly(self):
mode = 0o400
with temp_umask(0o022), _ready_to_import() as (name, path):
- cached_path = imp.cache_from_source(path)
+ cached_path = importlib.util.cache_from_source(path)
os.chmod(path, mode)
__import__(name)
if not os.path.exists(cached_path):
@@ -405,7 +408,7 @@ class FilePermissionTests(unittest.TestCase):
bytecode_only = path + "c"
else:
bytecode_only = path + "o"
- os.rename(imp.cache_from_source(path), bytecode_only)
+ os.rename(importlib.util.cache_from_source(path), bytecode_only)
m = __import__(name)
self.assertEqual(m.x, 'rewritten')
@@ -427,7 +430,7 @@ func_filename = func.__code__.co_filename
"""
dir_name = os.path.abspath(TESTFN)
file_name = os.path.join(dir_name, module_name) + os.extsep + "py"
- compiled_name = imp.cache_from_source(file_name)
+ compiled_name = importlib.util.cache_from_source(file_name)
def setUp(self):
self.sys_path = sys.path[:]
@@ -488,7 +491,7 @@ func_filename = func.__code__.co_filename
header = f.read(12)
code = marshal.load(f)
constants = list(code.co_consts)
- foreign_code = test_main.__code__
+ foreign_code = importlib.import_module.__code__
pos = constants.index(1)
constants[pos] = foreign_code
code = type(code)(code.co_argcount, code.co_kwonlyargcount,
@@ -630,7 +633,7 @@ class OverridingImportBuiltinTests(unittest.TestCase):
class PycacheTests(unittest.TestCase):
# Test the various PEP 3147 related behaviors.
- tag = imp.get_tag()
+ tag = sys.implementation.cache_tag
def _clean(self):
forget(TESTFN)
@@ -678,10 +681,11 @@ class PycacheTests(unittest.TestCase):
# With PEP 3147 cache layout, removing the source but leaving the pyc
# file does not satisfy the import.
__import__(TESTFN)
- pyc_file = imp.cache_from_source(self.source)
+ pyc_file = importlib.util.cache_from_source(self.source)
self.assertTrue(os.path.exists(pyc_file))
os.remove(self.source)
forget(TESTFN)
+ importlib.invalidate_caches()
self.assertRaises(ImportError, __import__, TESTFN)
@skip_if_dont_write_bytecode
@@ -703,7 +707,7 @@ class PycacheTests(unittest.TestCase):
def test___cached__(self):
# Modules now also have an __cached__ that points to the pyc file.
m = __import__(TESTFN)
- pyc_file = imp.cache_from_source(TESTFN + '.py')
+ pyc_file = importlib.util.cache_from_source(TESTFN + '.py')
self.assertEqual(m.__cached__, os.path.join(os.curdir, pyc_file))
@skip_if_dont_write_bytecode
@@ -738,10 +742,10 @@ class PycacheTests(unittest.TestCase):
pass
importlib.invalidate_caches()
m = __import__('pep3147.foo')
- init_pyc = imp.cache_from_source(
+ init_pyc = importlib.util.cache_from_source(
os.path.join('pep3147', '__init__.py'))
self.assertEqual(m.__cached__, os.path.join(os.curdir, init_pyc))
- foo_pyc = imp.cache_from_source(os.path.join('pep3147', 'foo.py'))
+ foo_pyc = importlib.util.cache_from_source(os.path.join('pep3147', 'foo.py'))
self.assertEqual(sys.modules['pep3147.foo'].__cached__,
os.path.join(os.curdir, foo_pyc))
@@ -765,10 +769,10 @@ class PycacheTests(unittest.TestCase):
unload('pep3147')
importlib.invalidate_caches()
m = __import__('pep3147.foo')
- init_pyc = imp.cache_from_source(
+ init_pyc = importlib.util.cache_from_source(
os.path.join('pep3147', '__init__.py'))
self.assertEqual(m.__cached__, os.path.join(os.curdir, init_pyc))
- foo_pyc = imp.cache_from_source(os.path.join('pep3147', 'foo.py'))
+ foo_pyc = importlib.util.cache_from_source(os.path.join('pep3147', 'foo.py'))
self.assertEqual(sys.modules['pep3147.foo'].__cached__,
os.path.join(os.curdir, foo_pyc))
@@ -852,7 +856,6 @@ class ImportlibBootstrapTests(unittest.TestCase):
from importlib import machinery
mod = sys.modules['_frozen_importlib']
self.assertIs(machinery.FileFinder, mod.FileFinder)
- self.assertIs(imp.new_module, mod.new_module)
@cpython_only
@@ -1033,11 +1036,14 @@ class ImportTracebackTests(unittest.TestCase):
# away from the traceback.
self.create_module("foo", "")
importlib = sys.modules['_frozen_importlib']
- old_load_module = importlib.SourceLoader.load_module
+ if 'load_module' in vars(importlib.SourceLoader):
+ old_exec_module = importlib.SourceLoader.exec_module
+ else:
+ old_exec_module = None
try:
- def load_module(*args):
+ def exec_module(*args):
1/0
- importlib.SourceLoader.load_module = load_module
+ importlib.SourceLoader.exec_module = exec_module
try:
import foo
except ZeroDivisionError as e:
@@ -1046,19 +1052,21 @@ class ImportTracebackTests(unittest.TestCase):
self.fail("ZeroDivisionError should have been raised")
self.assert_traceback(tb, [__file__, '<frozen importlib', __file__])
finally:
- importlib.SourceLoader.load_module = old_load_module
-
+ if old_exec_module is None:
+ del importlib.SourceLoader.exec_module
+ else:
+ importlib.SourceLoader.exec_module = old_exec_module
-def test_main(verbose=None):
- run_unittest(ImportTests, PycacheTests, FilePermissionTests,
- PycRewritingTests, PathsTests, RelativeImportTests,
- OverridingImportBuiltinTests,
- ImportlibBootstrapTests, GetSourcefileTests,
- TestSymbolicallyLinkedPackage,
- ImportTracebackTests)
+ @unittest.skipUnless(TESTFN_UNENCODABLE, 'need TESTFN_UNENCODABLE')
+ def test_unencodable_filename(self):
+ # Issue #11619: The Python parser and the import machinery must not
+ # encode filenames, especially on Windows
+ pyname = script_helper.make_script('', TESTFN_UNENCODABLE, 'pass')
+ name = pyname[:-3]
+ script_helper.assert_python_ok("-c", "mod = __import__(%a)" % name,
+ __isolated=False)
if __name__ == '__main__':
# Test needs to be a package, so we can do relative imports.
- from test.test_import import test_main
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importhooks.py b/Lib/test/test_importhooks.py
deleted file mode 100644
index 2a22d1a186..0000000000
--- a/Lib/test/test_importhooks.py
+++ /dev/null
@@ -1,250 +0,0 @@
-import sys
-import imp
-import os
-import unittest
-from test import support
-
-
-test_src = """\
-def get_name():
- return __name__
-def get_file():
- return __file__
-"""
-
-absimp = "import sub\n"
-relimp = "from . import sub\n"
-deeprelimp = "from .... import sub\n"
-futimp = "from __future__ import absolute_import\n"
-
-reload_src = test_src+"""\
-reloaded = True
-"""
-
-test_co = compile(test_src, "<???>", "exec")
-reload_co = compile(reload_src, "<???>", "exec")
-
-test2_oldabs_co = compile(absimp + test_src, "<???>", "exec")
-test2_newabs_co = compile(futimp + absimp + test_src, "<???>", "exec")
-test2_newrel_co = compile(relimp + test_src, "<???>", "exec")
-test2_deeprel_co = compile(deeprelimp + test_src, "<???>", "exec")
-test2_futrel_co = compile(futimp + relimp + test_src, "<???>", "exec")
-
-test_path = "!!!_test_!!!"
-
-
-class TestImporter:
-
- modules = {
- "hooktestmodule": (False, test_co),
- "hooktestpackage": (True, test_co),
- "hooktestpackage.sub": (True, test_co),
- "hooktestpackage.sub.subber": (True, test_co),
- "hooktestpackage.oldabs": (False, test2_oldabs_co),
- "hooktestpackage.newabs": (False, test2_newabs_co),
- "hooktestpackage.newrel": (False, test2_newrel_co),
- "hooktestpackage.sub.subber.subest": (True, test2_deeprel_co),
- "hooktestpackage.futrel": (False, test2_futrel_co),
- "sub": (False, test_co),
- "reloadmodule": (False, test_co),
- }
-
- def __init__(self, path=test_path):
- if path != test_path:
- # if our class is on sys.path_hooks, we must raise
- # ImportError for any path item that we can't handle.
- raise ImportError
- self.path = path
-
- def _get__path__(self):
- raise NotImplementedError
-
- def find_module(self, fullname, path=None):
- if fullname in self.modules:
- return self
- else:
- return None
-
- def load_module(self, fullname):
- ispkg, code = self.modules[fullname]
- mod = sys.modules.setdefault(fullname,imp.new_module(fullname))
- mod.__file__ = "<%s>" % self.__class__.__name__
- mod.__loader__ = self
- if ispkg:
- mod.__path__ = self._get__path__()
- exec(code, mod.__dict__)
- return mod
-
-
-class MetaImporter(TestImporter):
- def _get__path__(self):
- return []
-
-class PathImporter(TestImporter):
- def _get__path__(self):
- return [self.path]
-
-
-class ImportBlocker:
- """Place an ImportBlocker instance on sys.meta_path and you
- can be sure the modules you specified can't be imported, even
- if it's a builtin."""
- def __init__(self, *namestoblock):
- self.namestoblock = dict.fromkeys(namestoblock)
- def find_module(self, fullname, path=None):
- if fullname in self.namestoblock:
- return self
- return None
- def load_module(self, fullname):
- raise ImportError("I dare you")
-
-
-class ImpWrapper:
-
- def __init__(self, path=None):
- if path is not None and not os.path.isdir(path):
- raise ImportError
- self.path = path
-
- def find_module(self, fullname, path=None):
- subname = fullname.split(".")[-1]
- if subname != fullname and self.path is None:
- return None
- if self.path is None:
- path = None
- else:
- path = [self.path]
- try:
- file, filename, stuff = imp.find_module(subname, path)
- except ImportError:
- return None
- return ImpLoader(file, filename, stuff)
-
-
-class ImpLoader:
-
- def __init__(self, file, filename, stuff):
- self.file = file
- self.filename = filename
- self.stuff = stuff
-
- def load_module(self, fullname):
- mod = imp.load_module(fullname, self.file, self.filename, self.stuff)
- if self.file:
- self.file.close()
- mod.__loader__ = self # for introspection
- return mod
-
-
-class ImportHooksBaseTestCase(unittest.TestCase):
-
- def setUp(self):
- self.path = sys.path[:]
- self.meta_path = sys.meta_path[:]
- self.path_hooks = sys.path_hooks[:]
- sys.path_importer_cache.clear()
- self.modules_before = support.modules_setup()
-
- def tearDown(self):
- sys.path[:] = self.path
- sys.meta_path[:] = self.meta_path
- sys.path_hooks[:] = self.path_hooks
- sys.path_importer_cache.clear()
- support.modules_cleanup(*self.modules_before)
-
-
-class ImportHooksTestCase(ImportHooksBaseTestCase):
-
- def doTestImports(self, importer=None):
- import hooktestmodule
- import hooktestpackage
- import hooktestpackage.sub
- import hooktestpackage.sub.subber
- self.assertEqual(hooktestmodule.get_name(),
- "hooktestmodule")
- self.assertEqual(hooktestpackage.get_name(),
- "hooktestpackage")
- self.assertEqual(hooktestpackage.sub.get_name(),
- "hooktestpackage.sub")
- self.assertEqual(hooktestpackage.sub.subber.get_name(),
- "hooktestpackage.sub.subber")
- if importer:
- self.assertEqual(hooktestmodule.__loader__, importer)
- self.assertEqual(hooktestpackage.__loader__, importer)
- self.assertEqual(hooktestpackage.sub.__loader__, importer)
- self.assertEqual(hooktestpackage.sub.subber.__loader__, importer)
-
- TestImporter.modules['reloadmodule'] = (False, test_co)
- import reloadmodule
- self.assertFalse(hasattr(reloadmodule,'reloaded'))
-
- import hooktestpackage.newrel
- self.assertEqual(hooktestpackage.newrel.get_name(),
- "hooktestpackage.newrel")
- self.assertEqual(hooktestpackage.newrel.sub,
- hooktestpackage.sub)
-
- import hooktestpackage.sub.subber.subest as subest
- self.assertEqual(subest.get_name(),
- "hooktestpackage.sub.subber.subest")
- self.assertEqual(subest.sub,
- hooktestpackage.sub)
-
- import hooktestpackage.futrel
- self.assertEqual(hooktestpackage.futrel.get_name(),
- "hooktestpackage.futrel")
- self.assertEqual(hooktestpackage.futrel.sub,
- hooktestpackage.sub)
-
- import sub
- self.assertEqual(sub.get_name(), "sub")
-
- import hooktestpackage.oldabs
- self.assertEqual(hooktestpackage.oldabs.get_name(),
- "hooktestpackage.oldabs")
- self.assertEqual(hooktestpackage.oldabs.sub, sub)
-
- import hooktestpackage.newabs
- self.assertEqual(hooktestpackage.newabs.get_name(),
- "hooktestpackage.newabs")
- self.assertEqual(hooktestpackage.newabs.sub, sub)
-
- def testMetaPath(self):
- i = MetaImporter()
- sys.meta_path.append(i)
- self.doTestImports(i)
-
- def testPathHook(self):
- sys.path_hooks.insert(0, PathImporter)
- sys.path.append(test_path)
- self.doTestImports()
-
- def testBlocker(self):
- mname = "exceptions" # an arbitrary harmless builtin module
- support.unload(mname)
- sys.meta_path.append(ImportBlocker(mname))
- self.assertRaises(ImportError, __import__, mname)
-
- def testImpWrapper(self):
- i = ImpWrapper()
- sys.meta_path.append(i)
- sys.path_hooks.insert(0, ImpWrapper)
- mnames = (
- "colorsys", "urllib.parse", "distutils.core", "sys",
- )
- for mname in mnames:
- parent = mname.split(".")[0]
- for n in list(sys.modules):
- if n.startswith(parent):
- del sys.modules[n]
- for mname in mnames:
- m = __import__(mname, globals(), locals(), ["__dummy__"])
- # to make sure we actually handled the import
- self.assertTrue(hasattr(m, "__loader__"))
-
-
-def test_main():
- support.run_unittest(ImportHooksTestCase)
-
-if __name__ == "__main__":
- test_main()
diff --git a/Lib/test/test_importlib/__main__.py b/Lib/test/test_importlib/__main__.py
index c39712871f..14bd5bc017 100644
--- a/Lib/test/test_importlib/__main__.py
+++ b/Lib/test/test_importlib/__main__.py
@@ -4,17 +4,6 @@ Specifying the ``--builtin`` flag will run tests, where applicable, with
builtins.__import__ instead of importlib.__import__.
"""
-from . import test_main
-
-
if __name__ == '__main__':
- import argparse
-
- parser = argparse.ArgumentParser(description='Execute the importlib test '
- 'suite')
- parser.add_argument('-b', '--builtin', action='store_true', default=False,
- help='use builtins.__import__() instead of importlib')
- args = parser.parse_args()
- if args.builtin:
- util.using___import__ = True
+ from . import test_main
test_main()
diff --git a/Lib/test/test_importlib/abc.py b/Lib/test/test_importlib/abc.py
index 2c17ac329b..2070dadc23 100644
--- a/Lib/test/test_importlib/abc.py
+++ b/Lib/test/test_importlib/abc.py
@@ -2,7 +2,7 @@ import abc
import unittest
-class FinderTests(unittest.TestCase, metaclass=abc.ABCMeta):
+class FinderTests(metaclass=abc.ABCMeta):
"""Basic tests for a finder to pass."""
@@ -39,7 +39,7 @@ class FinderTests(unittest.TestCase, metaclass=abc.ABCMeta):
pass
-class LoaderTests(unittest.TestCase, metaclass=abc.ABCMeta):
+class LoaderTests(metaclass=abc.ABCMeta):
@abc.abstractmethod
def test_module(self):
@@ -81,11 +81,6 @@ class LoaderTests(unittest.TestCase, metaclass=abc.ABCMeta):
pass
@abc.abstractmethod
- def test_module_reuse(self):
- """If a module is already in sys.modules, it should be reused."""
- pass
-
- @abc.abstractmethod
def test_state_after_failure(self):
"""If a module is already in sys.modules and a reload fails
(e.g. a SyntaxError), the module should be in the state it was before
diff --git a/Lib/test/test_importlib/builtin/test_finder.py b/Lib/test/test_importlib/builtin/test_finder.py
index da0ef28589..934562feb6 100644
--- a/Lib/test/test_importlib/builtin/test_finder.py
+++ b/Lib/test/test_importlib/builtin/test_finder.py
@@ -1,11 +1,53 @@
-from importlib import machinery
from .. import abc
from .. import util
from . import util as builtin_util
+frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
+
import sys
import unittest
+
+class FindSpecTests(abc.FinderTests):
+
+ """Test find_spec() for built-in modules."""
+
+ def test_module(self):
+ # Common case.
+ with util.uncache(builtin_util.NAME):
+ found = self.machinery.BuiltinImporter.find_spec(builtin_util.NAME)
+ self.assertTrue(found)
+ self.assertEqual(found.origin, 'built-in')
+
+ # Built-in modules cannot be a package.
+ test_package = None
+
+ # Built-in modules cannobt be in a package.
+ test_module_in_package = None
+
+ # Built-in modules cannot be a package.
+ test_package_in_package = None
+
+ # Built-in modules cannot be a package.
+ test_package_over_module = None
+
+ def test_failure(self):
+ name = 'importlib'
+ assert name not in sys.builtin_module_names
+ spec = self.machinery.BuiltinImporter.find_spec(name)
+ self.assertIsNone(spec)
+
+ def test_ignore_path(self):
+ # The value for 'path' should always trigger a failed import.
+ with util.uncache(builtin_util.NAME):
+ spec = self.machinery.BuiltinImporter.find_spec(builtin_util.NAME,
+ ['pkg'])
+ self.assertIsNone(spec)
+
+Frozen_FindSpecTests, Source_FindSpecTests = util.test_both(FindSpecTests,
+ machinery=[frozen_machinery, source_machinery])
+
+
class FinderTests(abc.FinderTests):
"""Test find_module() for built-in modules."""
@@ -13,8 +55,9 @@ class FinderTests(abc.FinderTests):
def test_module(self):
# Common case.
with util.uncache(builtin_util.NAME):
- found = machinery.BuiltinImporter.find_module(builtin_util.NAME)
+ found = self.machinery.BuiltinImporter.find_module(builtin_util.NAME)
self.assertTrue(found)
+ self.assertTrue(hasattr(found, 'load_module'))
# Built-in modules cannot be a package.
test_package = test_package_in_package = test_package_over_module = None
@@ -24,22 +67,19 @@ class FinderTests(abc.FinderTests):
def test_failure(self):
assert 'importlib' not in sys.builtin_module_names
- loader = machinery.BuiltinImporter.find_module('importlib')
+ loader = self.machinery.BuiltinImporter.find_module('importlib')
self.assertIsNone(loader)
def test_ignore_path(self):
# The value for 'path' should always trigger a failed import.
with util.uncache(builtin_util.NAME):
- loader = machinery.BuiltinImporter.find_module(builtin_util.NAME,
+ loader = self.machinery.BuiltinImporter.find_module(builtin_util.NAME,
['pkg'])
self.assertIsNone(loader)
-
-
-def test_main():
- from test.support import run_unittest
- run_unittest(FinderTests)
+Frozen_FinderTests, Source_FinderTests = util.test_both(FinderTests,
+ machinery=[frozen_machinery, source_machinery])
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/builtin/test_loader.py b/Lib/test/test_importlib/builtin/test_loader.py
index 12a79c6ba9..a636f778fa 100644
--- a/Lib/test/test_importlib/builtin/test_loader.py
+++ b/Lib/test/test_importlib/builtin/test_loader.py
@@ -1,9 +1,9 @@
-import importlib
-from importlib import machinery
from .. import abc
from .. import util
from . import util as builtin_util
+frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
+
import sys
import types
import unittest
@@ -13,8 +13,9 @@ class LoaderTests(abc.LoaderTests):
"""Test load_module() for built-in modules."""
- verification = {'__name__': 'errno', '__package__': '',
- '__loader__': machinery.BuiltinImporter}
+ def setUp(self):
+ self.verification = {'__name__': 'errno', '__package__': '',
+ '__loader__': self.machinery.BuiltinImporter}
def verify(self, module):
"""Verify that the module matches against what it should have."""
@@ -23,8 +24,8 @@ class LoaderTests(abc.LoaderTests):
self.assertEqual(getattr(module, attr), value)
self.assertIn(module.__name__, sys.modules)
- load_module = staticmethod(lambda name:
- machinery.BuiltinImporter.load_module(name))
+ def load_module(self, name):
+ return self.machinery.BuiltinImporter.load_module(name)
def test_module(self):
# Common case.
@@ -55,45 +56,51 @@ class LoaderTests(abc.LoaderTests):
def test_already_imported(self):
# Using the name of a module already imported but not a built-in should
# still fail.
- assert hasattr(importlib, '__file__') # Not a built-in.
+ module_name = 'builtin_reload_test'
+ assert module_name not in sys.builtin_module_names
+ with util.uncache(module_name):
+ module = types.ModuleType(module_name)
+ sys.modules[module_name] = module
with self.assertRaises(ImportError) as cm:
- self.load_module('importlib')
- self.assertEqual(cm.exception.name, 'importlib')
+ self.load_module(module_name)
+ self.assertEqual(cm.exception.name, module_name)
+
+
+Frozen_LoaderTests, Source_LoaderTests = util.test_both(LoaderTests,
+ machinery=[frozen_machinery, source_machinery])
-class InspectLoaderTests(unittest.TestCase):
+class InspectLoaderTests:
"""Tests for InspectLoader methods for BuiltinImporter."""
def test_get_code(self):
# There is no code object.
- result = machinery.BuiltinImporter.get_code(builtin_util.NAME)
+ result = self.machinery.BuiltinImporter.get_code(builtin_util.NAME)
self.assertIsNone(result)
def test_get_source(self):
# There is no source.
- result = machinery.BuiltinImporter.get_source(builtin_util.NAME)
+ result = self.machinery.BuiltinImporter.get_source(builtin_util.NAME)
self.assertIsNone(result)
def test_is_package(self):
# Cannot be a package.
- result = machinery.BuiltinImporter.is_package(builtin_util.NAME)
+ result = self.machinery.BuiltinImporter.is_package(builtin_util.NAME)
self.assertTrue(not result)
def test_not_builtin(self):
# Modules not built-in should raise ImportError.
for meth_name in ('get_code', 'get_source', 'is_package'):
- method = getattr(machinery.BuiltinImporter, meth_name)
+ method = getattr(self.machinery.BuiltinImporter, meth_name)
with self.assertRaises(ImportError) as cm:
method(builtin_util.BAD_NAME)
self.assertRaises(builtin_util.BAD_NAME)
-
-
-def test_main():
- from test.support import run_unittest
- run_unittest(LoaderTests, InspectLoaderTests)
+Frozen_InspectLoaderTests, Source_InspectLoaderTests = util.test_both(
+ InspectLoaderTests,
+ machinery=[frozen_machinery, source_machinery])
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/extension/test_case_sensitivity.py b/Lib/test/test_importlib/extension/test_case_sensitivity.py
index 76c53e4fd6..bb2528e626 100644
--- a/Lib/test/test_importlib/extension/test_case_sensitivity.py
+++ b/Lib/test/test_importlib/extension/test_case_sensitivity.py
@@ -1,22 +1,27 @@
-import imp
+from importlib import _bootstrap
import sys
from test import support
import unittest
-from importlib import _bootstrap
+
from .. import util
from . import util as ext_util
+frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
+
+# XXX find_spec tests
+
+@unittest.skipIf(ext_util.FILENAME is None, '_testcapi not available')
@util.case_insensitive_tests
-class ExtensionModuleCaseSensitivityTest(unittest.TestCase):
+class ExtensionModuleCaseSensitivityTest:
def find_module(self):
good_name = ext_util.NAME
bad_name = good_name.upper()
assert good_name != bad_name
- finder = _bootstrap.FileFinder(ext_util.PATH,
- (_bootstrap.ExtensionFileLoader,
- _bootstrap.EXTENSION_SUFFIXES))
+ finder = self.machinery.FileFinder(ext_util.PATH,
+ (self.machinery.ExtensionFileLoader,
+ self.machinery.EXTENSION_SUFFIXES))
return finder.find_module(bad_name)
def test_case_sensitive(self):
@@ -37,14 +42,10 @@ class ExtensionModuleCaseSensitivityTest(unittest.TestCase):
loader = self.find_module()
self.assertTrue(hasattr(loader, 'load_module'))
-
-
-
-def test_main():
- if ext_util.FILENAME is None:
- return
- support.run_unittest(ExtensionModuleCaseSensitivityTest)
+Frozen_ExtensionCaseSensitivity, Source_ExtensionCaseSensitivity = util.test_both(
+ ExtensionModuleCaseSensitivityTest,
+ machinery=[frozen_machinery, source_machinery])
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/extension/test_finder.py b/Lib/test/test_importlib/extension/test_finder.py
index 37f67727b0..d0c8bb0994 100644
--- a/Lib/test/test_importlib/extension/test_finder.py
+++ b/Lib/test/test_importlib/extension/test_finder.py
@@ -1,17 +1,21 @@
-from importlib import machinery
from .. import abc
+from .. import util as test_util
from . import util
+machinery = test_util.import_importlib('importlib.machinery')
+
import unittest
+# XXX find_spec tests
+
class FinderTests(abc.FinderTests):
"""Test the finder for extension modules."""
def find_module(self, fullname):
- importer = machinery.FileFinder(util.PATH,
- (machinery.ExtensionFileLoader,
- machinery.EXTENSION_SUFFIXES))
+ importer = self.machinery.FileFinder(util.PATH,
+ (self.machinery.ExtensionFileLoader,
+ self.machinery.EXTENSION_SUFFIXES))
return importer.find_module(fullname)
def test_module(self):
@@ -29,11 +33,9 @@ class FinderTests(abc.FinderTests):
def test_failure(self):
self.assertIsNone(self.find_module('asdfjkl;'))
-
-def test_main():
- from test.support import run_unittest
- run_unittest(FinderTests)
+Frozen_FinderTests, Source_FinderTests = test_util.test_both(
+ FinderTests, machinery=machinery)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py
index e790675f58..d04dff5959 100644
--- a/Lib/test/test_importlib/extension/test_loader.py
+++ b/Lib/test/test_importlib/extension/test_loader.py
@@ -1,10 +1,12 @@
-from importlib import machinery
from . import util as ext_util
from .. import abc
from .. import util
+machinery = util.import_importlib('importlib.machinery')
+
import os.path
import sys
+import types
import unittest
@@ -13,8 +15,8 @@ class LoaderTests(abc.LoaderTests):
"""Test load_module() for extension modules."""
def setUp(self):
- self.loader = machinery.ExtensionFileLoader(ext_util.NAME,
- ext_util.FILEPATH)
+ self.loader = self.machinery.ExtensionFileLoader(ext_util.NAME,
+ ext_util.FILEPATH)
def load_module(self, fullname):
return self.loader.load_module(fullname)
@@ -36,7 +38,7 @@ class LoaderTests(abc.LoaderTests):
self.assertEqual(getattr(module, attr), value)
self.assertIn(ext_util.NAME, sys.modules)
self.assertIsInstance(module.__loader__,
- machinery.ExtensionFileLoader)
+ self.machinery.ExtensionFileLoader)
# No extension module as __init__ available for testing.
test_package = None
@@ -61,16 +63,15 @@ class LoaderTests(abc.LoaderTests):
def test_is_package(self):
self.assertFalse(self.loader.is_package(ext_util.NAME))
- for suffix in machinery.EXTENSION_SUFFIXES:
+ for suffix in self.machinery.EXTENSION_SUFFIXES:
path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
- loader = machinery.ExtensionFileLoader('pkg', path)
+ loader = self.machinery.ExtensionFileLoader('pkg', path)
self.assertTrue(loader.is_package('pkg'))
+Frozen_LoaderTests, Source_LoaderTests = util.test_both(
+ LoaderTests, machinery=machinery)
-def test_main():
- from test.support import run_unittest
- run_unittest(LoaderTests)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/extension/test_path_hook.py b/Lib/test/test_importlib/extension/test_path_hook.py
index 1d969a1b28..49d6734711 100644
--- a/Lib/test/test_importlib/extension/test_path_hook.py
+++ b/Lib/test/test_importlib/extension/test_path_hook.py
@@ -1,32 +1,32 @@
-from importlib import machinery
+from .. import util as test_util
from . import util
+machinery = test_util.import_importlib('importlib.machinery')
+
import collections
-import imp
import sys
import unittest
-class PathHookTests(unittest.TestCase):
+class PathHookTests:
"""Test the path hook for extension modules."""
# XXX Should it only succeed for pre-existing directories?
# XXX Should it only work for directories containing an extension module?
def hook(self, entry):
- return machinery.FileFinder.path_hook((machinery.ExtensionFileLoader,
- machinery.EXTENSION_SUFFIXES))(entry)
+ return self.machinery.FileFinder.path_hook(
+ (self.machinery.ExtensionFileLoader,
+ self.machinery.EXTENSION_SUFFIXES))(entry)
def test_success(self):
# Path hook should handle a directory where a known extension module
# exists.
self.assertTrue(hasattr(self.hook(util.PATH), 'find_module'))
-
-def test_main():
- from test.support import run_unittest
- run_unittest(PathHookTests)
+Frozen_PathHooksTests, Source_PathHooksTests = test_util.test_both(
+ PathHookTests, machinery=machinery)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/extension/util.py b/Lib/test/test_importlib/extension/util.py
index a266dd98c8..8d089f0d7c 100644
--- a/Lib/test/test_importlib/extension/util.py
+++ b/Lib/test/test_importlib/extension/util.py
@@ -1,4 +1,3 @@
-import imp
from importlib import machinery
import os
import sys
diff --git a/Lib/test/test_importlib/frozen/test_finder.py b/Lib/test/test_importlib/frozen/test_finder.py
index f0abe0e989..f9f97f3851 100644
--- a/Lib/test/test_importlib/frozen/test_finder.py
+++ b/Lib/test/test_importlib/frozen/test_finder.py
@@ -1,15 +1,52 @@
-from importlib import machinery
from .. import abc
+from .. import util
+
+machinery = util.import_importlib('importlib.machinery')
import unittest
+class FindSpecTests(abc.FinderTests):
+
+ """Test finding frozen modules."""
+
+ def find(self, name, path=None):
+ finder = self.machinery.FrozenImporter
+ return finder.find_spec(name, path)
+
+ def test_module(self):
+ name = '__hello__'
+ spec = self.find(name)
+ self.assertEqual(spec.origin, 'frozen')
+
+ def test_package(self):
+ spec = self.find('__phello__')
+ self.assertIsNotNone(spec)
+
+ def test_module_in_package(self):
+ spec = self.find('__phello__.spam', ['__phello__'])
+ self.assertIsNotNone(spec)
+
+ # No frozen package within another package to test with.
+ test_package_in_package = None
+
+ # No easy way to test.
+ test_package_over_module = None
+
+ def test_failure(self):
+ spec = self.find('<not real>')
+ self.assertIsNone(spec)
+
+Frozen_FindSpecTests, Source_FindSpecTests = util.test_both(FindSpecTests,
+ machinery=machinery)
+
+
class FinderTests(abc.FinderTests):
"""Test finding frozen modules."""
def find(self, name, path=None):
- finder = machinery.FrozenImporter
+ finder = self.machinery.FrozenImporter
return finder.find_module(name, path)
def test_module(self):
@@ -35,11 +72,9 @@ class FinderTests(abc.FinderTests):
loader = self.find('<not real>')
self.assertIsNone(loader)
-
-def test_main():
- from test.support import run_unittest
- run_unittest(FinderTests)
+Frozen_FinderTests, Source_FinderTests = util.test_both(FinderTests,
+ machinery=machinery)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/frozen/test_loader.py b/Lib/test/test_importlib/frozen/test_loader.py
index f3a8bf67c7..2abbe929bc 100644
--- a/Lib/test/test_importlib/frozen/test_loader.py
+++ b/Lib/test/test_importlib/frozen/test_loader.py
@@ -1,18 +1,93 @@
-from importlib import machinery
-import imp
-import unittest
from .. import abc
from .. import util
+
+machinery = util.import_importlib('importlib.machinery')
+
+
+import sys
from test.support import captured_stdout
+import types
+import unittest
+
+
+class ExecModuleTests(abc.LoaderTests):
+
+ def exec_module(self, name):
+ with util.uncache(name), captured_stdout() as stdout:
+ spec = self.machinery.ModuleSpec(
+ name, self.machinery.FrozenImporter, origin='frozen',
+ is_package=self.machinery.FrozenImporter.is_package(name))
+ module = types.ModuleType(name)
+ module.__spec__ = spec
+ assert not hasattr(module, 'initialized')
+ self.machinery.FrozenImporter.exec_module(module)
+ self.assertTrue(module.initialized)
+ self.assertTrue(hasattr(module, '__spec__'))
+ self.assertEqual(module.__spec__.origin, 'frozen')
+ return module, stdout.getvalue()
+
+ def test_module(self):
+ name = '__hello__'
+ module, output = self.exec_module(name)
+ check = {'__name__': name}
+ for attr, value in check.items():
+ self.assertEqual(getattr(module, attr), value)
+ self.assertEqual(output, 'Hello world!\n')
+ self.assertTrue(hasattr(module, '__spec__'))
+
+ def test_package(self):
+ name = '__phello__'
+ module, output = self.exec_module(name)
+ check = {'__name__': name}
+ for attr, value in check.items():
+ attr_value = getattr(module, attr)
+ self.assertEqual(attr_value, value,
+ 'for {name}.{attr}, {given!r} != {expected!r}'.format(
+ name=name, attr=attr, given=attr_value,
+ expected=value))
+ self.assertEqual(output, 'Hello world!\n')
+
+ def test_lacking_parent(self):
+ name = '__phello__.spam'
+ with util.uncache('__phello__'):
+ module, output = self.exec_module(name)
+ check = {'__name__': name}
+ for attr, value in check.items():
+ attr_value = getattr(module, attr)
+ self.assertEqual(attr_value, value,
+ 'for {name}.{attr}, {given} != {expected!r}'.format(
+ name=name, attr=attr, given=attr_value,
+ expected=value))
+ self.assertEqual(output, 'Hello world!\n')
+
+
+ def test_module_repr(self):
+ name = '__hello__'
+ module, output = self.exec_module(name)
+ self.assertEqual(repr(module),
+ "<module '__hello__' (frozen)>")
+
+ # No way to trigger an error in a frozen module.
+ test_state_after_failure = None
+
+ def test_unloadable(self):
+ assert self.machinery.FrozenImporter.find_module('_not_real') is None
+ with self.assertRaises(ImportError) as cm:
+ self.exec_module('_not_real')
+ self.assertEqual(cm.exception.name, '_not_real')
+
+Frozen_ExecModuleTests, Source_ExecModuleTests = util.test_both(ExecModuleTests,
+ machinery=machinery)
+
class LoaderTests(abc.LoaderTests):
def test_module(self):
with util.uncache('__hello__'), captured_stdout() as stdout:
- module = machinery.FrozenImporter.load_module('__hello__')
+ module = self.machinery.FrozenImporter.load_module('__hello__')
check = {'__name__': '__hello__',
'__package__': '',
- '__loader__': machinery.FrozenImporter,
+ '__loader__': self.machinery.FrozenImporter,
}
for attr, value in check.items():
self.assertEqual(getattr(module, attr), value)
@@ -21,11 +96,11 @@ class LoaderTests(abc.LoaderTests):
def test_package(self):
with util.uncache('__phello__'), captured_stdout() as stdout:
- module = machinery.FrozenImporter.load_module('__phello__')
+ module = self.machinery.FrozenImporter.load_module('__phello__')
check = {'__name__': '__phello__',
'__package__': '__phello__',
- '__path__': ['__phello__'],
- '__loader__': machinery.FrozenImporter,
+ '__path__': [],
+ '__loader__': self.machinery.FrozenImporter,
}
for attr, value in check.items():
attr_value = getattr(module, attr)
@@ -38,10 +113,10 @@ class LoaderTests(abc.LoaderTests):
def test_lacking_parent(self):
with util.uncache('__phello__', '__phello__.spam'), \
captured_stdout() as stdout:
- module = machinery.FrozenImporter.load_module('__phello__.spam')
+ module = self.machinery.FrozenImporter.load_module('__phello__.spam')
check = {'__name__': '__phello__.spam',
'__package__': '__phello__',
- '__loader__': machinery.FrozenImporter,
+ '__loader__': self.machinery.FrozenImporter,
}
for attr, value in check.items():
attr_value = getattr(module, attr)
@@ -53,15 +128,15 @@ class LoaderTests(abc.LoaderTests):
def test_module_reuse(self):
with util.uncache('__hello__'), captured_stdout() as stdout:
- module1 = machinery.FrozenImporter.load_module('__hello__')
- module2 = machinery.FrozenImporter.load_module('__hello__')
+ module1 = self.machinery.FrozenImporter.load_module('__hello__')
+ module2 = self.machinery.FrozenImporter.load_module('__hello__')
self.assertIs(module1, module2)
self.assertEqual(stdout.getvalue(),
'Hello world!\nHello world!\n')
def test_module_repr(self):
with util.uncache('__hello__'), captured_stdout():
- module = machinery.FrozenImporter.load_module('__hello__')
+ module = self.machinery.FrozenImporter.load_module('__hello__')
self.assertEqual(repr(module),
"<module '__hello__' (frozen)>")
@@ -69,13 +144,16 @@ class LoaderTests(abc.LoaderTests):
test_state_after_failure = None
def test_unloadable(self):
- assert machinery.FrozenImporter.find_module('_not_real') is None
+ assert self.machinery.FrozenImporter.find_module('_not_real') is None
with self.assertRaises(ImportError) as cm:
- machinery.FrozenImporter.load_module('_not_real')
+ self.machinery.FrozenImporter.load_module('_not_real')
self.assertEqual(cm.exception.name, '_not_real')
+Frozen_LoaderTests, Source_LoaderTests = util.test_both(LoaderTests,
+ machinery=machinery)
-class InspectLoaderTests(unittest.TestCase):
+
+class InspectLoaderTests:
"""Tests for the InspectLoader methods for FrozenImporter."""
@@ -83,15 +161,15 @@ class InspectLoaderTests(unittest.TestCase):
# Make sure that the code object is good.
name = '__hello__'
with captured_stdout() as stdout:
- code = machinery.FrozenImporter.get_code(name)
- mod = imp.new_module(name)
+ code = self.machinery.FrozenImporter.get_code(name)
+ mod = types.ModuleType(name)
exec(code, mod.__dict__)
self.assertTrue(hasattr(mod, 'initialized'))
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
def test_get_source(self):
# Should always return None.
- result = machinery.FrozenImporter.get_source('__hello__')
+ result = self.machinery.FrozenImporter.get_source('__hello__')
self.assertIsNone(result)
def test_is_package(self):
@@ -99,22 +177,20 @@ class InspectLoaderTests(unittest.TestCase):
test_for = (('__hello__', False), ('__phello__', True),
('__phello__.spam', False))
for name, is_package in test_for:
- result = machinery.FrozenImporter.is_package(name)
+ result = self.machinery.FrozenImporter.is_package(name)
self.assertEqual(bool(result), is_package)
def test_failure(self):
# Raise ImportError for modules that are not frozen.
for meth_name in ('get_code', 'get_source', 'is_package'):
- method = getattr(machinery.FrozenImporter, meth_name)
+ method = getattr(self.machinery.FrozenImporter, meth_name)
with self.assertRaises(ImportError) as cm:
method('importlib')
self.assertEqual(cm.exception.name, 'importlib')
-
-def test_main():
- from test.support import run_unittest
- run_unittest(LoaderTests, InspectLoaderTests)
+Frozen_ILTests, Source_ILTests = util.test_both(InspectLoaderTests,
+ machinery=machinery)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test___loader__.py b/Lib/test/test_importlib/import_/test___loader__.py
new file mode 100644
index 0000000000..6df80101fa
--- /dev/null
+++ b/Lib/test/test_importlib/import_/test___loader__.py
@@ -0,0 +1,70 @@
+from importlib import machinery
+import sys
+import types
+import unittest
+
+from .. import util
+from . import util as import_util
+
+
+class SpecLoaderMock:
+
+ def find_spec(self, fullname, path=None, target=None):
+ return machinery.ModuleSpec(fullname, self)
+
+ def exec_module(self, module):
+ pass
+
+
+class SpecLoaderAttributeTests:
+
+ def test___loader__(self):
+ loader = SpecLoaderMock()
+ with util.uncache('blah'), util.import_state(meta_path=[loader]):
+ module = self.__import__('blah')
+ self.assertEqual(loader, module.__loader__)
+
+Frozen_SpecTests, Source_SpecTests = util.test_both(
+ SpecLoaderAttributeTests, __import__=import_util.__import__)
+
+
+class LoaderMock:
+
+ def find_module(self, fullname, path=None):
+ return self
+
+ def load_module(self, fullname):
+ sys.modules[fullname] = self.module
+ return self.module
+
+
+class LoaderAttributeTests:
+
+ def test___loader___missing(self):
+ module = types.ModuleType('blah')
+ try:
+ del module.__loader__
+ except AttributeError:
+ pass
+ loader = LoaderMock()
+ loader.module = module
+ with util.uncache('blah'), util.import_state(meta_path=[loader]):
+ module = self.__import__('blah')
+ self.assertEqual(loader, module.__loader__)
+
+ def test___loader___is_None(self):
+ module = types.ModuleType('blah')
+ module.__loader__ = None
+ loader = LoaderMock()
+ loader.module = module
+ with util.uncache('blah'), util.import_state(meta_path=[loader]):
+ returned_module = self.__import__('blah')
+ self.assertEqual(loader, module.__loader__)
+
+
+Frozen_Tests, Source_Tests = util.test_both(LoaderAttributeTests,
+ __import__=import_util.__import__)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test___package__.py b/Lib/test/test_importlib/import_/test___package__.py
index 783cde1729..5fa432f2e3 100644
--- a/Lib/test/test_importlib/import_/test___package__.py
+++ b/Lib/test/test_importlib/import_/test___package__.py
@@ -9,7 +9,7 @@ from .. import util
from . import util as import_util
-class Using__package__(unittest.TestCase):
+class Using__package__:
"""Use of __package__ supercedes the use of __name__/__path__ to calculate
what package a module belongs to. The basic algorithm is [__package__]::
@@ -38,8 +38,8 @@ class Using__package__(unittest.TestCase):
# [__package__]
with util.mock_modules('pkg.__init__', 'pkg.fake') as importer:
with util.import_state(meta_path=[importer]):
- import_util.import_('pkg.fake')
- module = import_util.import_('',
+ self.__import__('pkg.fake')
+ module = self.__import__('',
globals={'__package__': 'pkg.fake'},
fromlist=['attr'], level=2)
self.assertEqual(module.__name__, 'pkg')
@@ -51,8 +51,8 @@ class Using__package__(unittest.TestCase):
globals_['__package__'] = None
with util.mock_modules('pkg.__init__', 'pkg.fake') as importer:
with util.import_state(meta_path=[importer]):
- import_util.import_('pkg.fake')
- module = import_util.import_('', globals= globals_,
+ self.__import__('pkg.fake')
+ module = self.__import__('', globals= globals_,
fromlist=['attr'], level=2)
self.assertEqual(module.__name__, 'pkg')
@@ -63,15 +63,17 @@ class Using__package__(unittest.TestCase):
def test_bad__package__(self):
globals = {'__package__': '<not real>'}
with self.assertRaises(SystemError):
- import_util.import_('', globals, {}, ['relimport'], 1)
+ self.__import__('', globals, {}, ['relimport'], 1)
def test_bunk__package__(self):
globals = {'__package__': 42}
with self.assertRaises(TypeError):
- import_util.import_('', globals, {}, ['relimport'], 1)
+ self.__import__('', globals, {}, ['relimport'], 1)
+
+Frozen_UsingPackage, Source_UsingPackage = util.test_both(
+ Using__package__, __import__=import_util.__import__)
-@import_util.importlib_only
class Setting__package__(unittest.TestCase):
"""Because __package__ is a new feature, it is not always set by a loader.
@@ -84,12 +86,14 @@ class Setting__package__(unittest.TestCase):
"""
+ __import__ = import_util.__import__[1]
+
# [top-level]
def test_top_level(self):
with util.mock_modules('top_level') as mock:
with util.import_state(meta_path=[mock]):
del mock['top_level'].__package__
- module = import_util.import_('top_level')
+ module = self.__import__('top_level')
self.assertEqual(module.__package__, '')
# [package]
@@ -97,7 +101,7 @@ class Setting__package__(unittest.TestCase):
with util.mock_modules('pkg.__init__') as mock:
with util.import_state(meta_path=[mock]):
del mock['pkg'].__package__
- module = import_util.import_('pkg')
+ module = self.__import__('pkg')
self.assertEqual(module.__package__, 'pkg')
# [submodule]
@@ -105,15 +109,10 @@ class Setting__package__(unittest.TestCase):
with util.mock_modules('pkg.__init__', 'pkg.mod') as mock:
with util.import_state(meta_path=[mock]):
del mock['pkg.mod'].__package__
- pkg = import_util.import_('pkg.mod')
+ pkg = self.__import__('pkg.mod')
module = getattr(pkg, 'mod')
self.assertEqual(module.__package__, 'pkg')
-def test_main():
- from test.support import run_unittest
- run_unittest(Using__package__, Setting__package__)
-
-
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test_api.py b/Lib/test/test_importlib/import_/test_api.py
index 3d4cd94889..439c105e9b 100644
--- a/Lib/test/test_importlib/import_/test_api.py
+++ b/Lib/test/test_importlib/import_/test_api.py
@@ -1,23 +1,41 @@
-from .. import util as importlib_test_util
-from . import util
-import imp
+from .. import util
+from . import util as import_util
+
+from importlib import machinery
import sys
+import types
import unittest
+PKG_NAME = 'fine'
+SUBMOD_NAME = 'fine.bogus'
+
+
+class BadSpecFinderLoader:
+ @classmethod
+ def find_spec(cls, fullname, path=None, target=None):
+ if fullname == SUBMOD_NAME:
+ spec = machinery.ModuleSpec(fullname, cls)
+ return spec
+
+ @staticmethod
+ def exec_module(module):
+ if module.__name__ == SUBMOD_NAME:
+ raise ImportError('I cannot be loaded!')
+
class BadLoaderFinder:
- bad = 'fine.bogus'
@classmethod
def find_module(cls, fullname, path):
- if fullname == cls.bad:
+ if fullname == SUBMOD_NAME:
return cls
+
@classmethod
def load_module(cls, fullname):
- if fullname == cls.bad:
+ if fullname == SUBMOD_NAME:
raise ImportError('I cannot be loaded!')
-class APITest(unittest.TestCase):
+class APITest:
"""Test API-specific details for __import__ (e.g. raising the right
exception when passing in an int for the module name)."""
@@ -25,43 +43,52 @@ class APITest(unittest.TestCase):
def test_name_requires_rparition(self):
# Raise TypeError if a non-string is passed in for the module name.
with self.assertRaises(TypeError):
- util.import_(42)
+ self.__import__(42)
def test_negative_level(self):
# Raise ValueError when a negative level is specified.
# PEP 328 did away with sys.module None entries and the ambiguity of
# absolute/relative imports.
with self.assertRaises(ValueError):
- util.import_('os', globals(), level=-1)
+ self.__import__('os', globals(), level=-1)
def test_nonexistent_fromlist_entry(self):
# If something in fromlist doesn't exist, that's okay.
# issue15715
- mod = imp.new_module('fine')
+ mod = types.ModuleType(PKG_NAME)
mod.__path__ = ['XXX']
- with importlib_test_util.import_state(meta_path=[BadLoaderFinder]):
- with importlib_test_util.uncache('fine'):
- sys.modules['fine'] = mod
- util.import_('fine', fromlist=['not here'])
+ with util.import_state(meta_path=[self.bad_finder_loader]):
+ with util.uncache(PKG_NAME):
+ sys.modules[PKG_NAME] = mod
+ self.__import__(PKG_NAME, fromlist=['not here'])
def test_fromlist_load_error_propagates(self):
# If something in fromlist triggers an exception not related to not
# existing, let that exception propagate.
# issue15316
- mod = imp.new_module('fine')
+ mod = types.ModuleType(PKG_NAME)
mod.__path__ = ['XXX']
- with importlib_test_util.import_state(meta_path=[BadLoaderFinder]):
- with importlib_test_util.uncache('fine'):
- sys.modules['fine'] = mod
+ with util.import_state(meta_path=[self.bad_finder_loader]):
+ with util.uncache(PKG_NAME):
+ sys.modules[PKG_NAME] = mod
with self.assertRaises(ImportError):
- util.import_('fine', fromlist=['bogus'])
+ self.__import__(PKG_NAME,
+ fromlist=[SUBMOD_NAME.rpartition('.')[-1]])
+
+
+class OldAPITests(APITest):
+ bad_finder_loader = BadLoaderFinder
+
+Frozen_OldAPITests, Source_OldAPITests = util.test_both(
+ OldAPITests, __import__=import_util.__import__)
+class SpecAPITests(APITest):
+ bad_finder_loader = BadSpecFinderLoader
-def test_main():
- from test.support import run_unittest
- run_unittest(APITest)
+Frozen_SpecAPITests, Source_SpecAPITests = util.test_both(
+ SpecAPITests, __import__=import_util.__import__)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test_caching.py b/Lib/test/test_importlib/import_/test_caching.py
index 207378a6fb..4d082b8eeb 100644
--- a/Lib/test/test_importlib/import_/test_caching.py
+++ b/Lib/test/test_importlib/import_/test_caching.py
@@ -6,7 +6,7 @@ from types import MethodType
import unittest
-class UseCache(unittest.TestCase):
+class UseCache:
"""When it comes to sys.modules, import prefers it over anything else.
@@ -21,12 +21,13 @@ class UseCache(unittest.TestCase):
ImportError is raised [None in cache].
"""
+
def test_using_cache(self):
# [use cache]
module_to_use = "some module found!"
with util.uncache('some_module'):
sys.modules['some_module'] = module_to_use
- module = import_util.import_('some_module')
+ module = self.__import__('some_module')
self.assertEqual(id(module_to_use), id(module))
def test_None_in_cache(self):
@@ -35,7 +36,7 @@ class UseCache(unittest.TestCase):
with util.uncache(name):
sys.modules[name] = None
with self.assertRaises(ImportError) as cm:
- import_util.import_(name)
+ self.__import__(name)
self.assertEqual(cm.exception.name, name)
def create_mock(self, *names, return_=None):
@@ -47,42 +48,43 @@ class UseCache(unittest.TestCase):
mock.load_module = MethodType(load_module, mock)
return mock
+Frozen_UseCache, Source_UseCache = util.test_both(
+ UseCache, __import__=import_util.__import__)
+
+
+class ImportlibUseCache(UseCache, unittest.TestCase):
+
+ __import__ = import_util.__import__[1]
+
# __import__ inconsistent between loaders and built-in import when it comes
# to when to use the module in sys.modules and when not to.
- @import_util.importlib_only
def test_using_cache_after_loader(self):
# [from cache on return]
with self.create_mock('module') as mock:
with util.import_state(meta_path=[mock]):
- module = import_util.import_('module')
+ module = self.__import__('module')
self.assertEqual(id(module), id(sys.modules['module']))
# See test_using_cache_after_loader() for reasoning.
- @import_util.importlib_only
def test_using_cache_for_assigning_to_attribute(self):
# [from cache to attribute]
with self.create_mock('pkg.__init__', 'pkg.module') as importer:
with util.import_state(meta_path=[importer]):
- module = import_util.import_('pkg.module')
+ module = self.__import__('pkg.module')
self.assertTrue(hasattr(module, 'module'))
self.assertEqual(id(module.module),
id(sys.modules['pkg.module']))
# See test_using_cache_after_loader() for reasoning.
- @import_util.importlib_only
def test_using_cache_for_fromlist(self):
# [from cache for fromlist]
with self.create_mock('pkg.__init__', 'pkg.module') as importer:
with util.import_state(meta_path=[importer]):
- module = import_util.import_('pkg', fromlist=['module'])
+ module = self.__import__('pkg', fromlist=['module'])
self.assertTrue(hasattr(module, 'module'))
self.assertEqual(id(module.module),
id(sys.modules['pkg.module']))
-def test_main():
- from test.support import run_unittest
- run_unittest(UseCache)
-
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test_fromlist.py b/Lib/test/test_importlib/import_/test_fromlist.py
index c16c33710f..fc29ce667d 100644
--- a/Lib/test/test_importlib/import_/test_fromlist.py
+++ b/Lib/test/test_importlib/import_/test_fromlist.py
@@ -1,10 +1,10 @@
"""Test that the semantics relating to the 'fromlist' argument are correct."""
from .. import util
from . import util as import_util
-import imp
import unittest
-class ReturnValue(unittest.TestCase):
+
+class ReturnValue:
"""The use of fromlist influences what import returns.
@@ -19,18 +19,21 @@ class ReturnValue(unittest.TestCase):
# [import return]
with util.mock_modules('pkg.__init__', 'pkg.module') as importer:
with util.import_state(meta_path=[importer]):
- module = import_util.import_('pkg.module')
+ module = self.__import__('pkg.module')
self.assertEqual(module.__name__, 'pkg')
def test_return_from_from_import(self):
# [from return]
with util.mock_modules('pkg.__init__', 'pkg.module')as importer:
with util.import_state(meta_path=[importer]):
- module = import_util.import_('pkg.module', fromlist=['attr'])
+ module = self.__import__('pkg.module', fromlist=['attr'])
self.assertEqual(module.__name__, 'pkg.module')
+Frozen_ReturnValue, Source_ReturnValue = util.test_both(
+ ReturnValue, __import__=import_util.__import__)
+
-class HandlingFromlist(unittest.TestCase):
+class HandlingFromlist:
"""Using fromlist triggers different actions based on what is being asked
of it.
@@ -49,14 +52,14 @@ class HandlingFromlist(unittest.TestCase):
# [object case]
with util.mock_modules('module') as importer:
with util.import_state(meta_path=[importer]):
- module = import_util.import_('module', fromlist=['attr'])
+ module = self.__import__('module', fromlist=['attr'])
self.assertEqual(module.__name__, 'module')
def test_nonexistent_object(self):
# [bad object]
with util.mock_modules('module') as importer:
with util.import_state(meta_path=[importer]):
- module = import_util.import_('module', fromlist=['non_existent'])
+ module = self.__import__('module', fromlist=['non_existent'])
self.assertEqual(module.__name__, 'module')
self.assertTrue(not hasattr(module, 'non_existent'))
@@ -64,7 +67,7 @@ class HandlingFromlist(unittest.TestCase):
# [module]
with util.mock_modules('pkg.__init__', 'pkg.module') as importer:
with util.import_state(meta_path=[importer]):
- module = import_util.import_('pkg', fromlist=['module'])
+ module = self.__import__('pkg', fromlist=['module'])
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module'))
self.assertEqual(module.module.__name__, 'pkg.module')
@@ -79,13 +82,13 @@ class HandlingFromlist(unittest.TestCase):
module_code={'pkg.mod': module_code}) as importer:
with util.import_state(meta_path=[importer]):
with self.assertRaises(ImportError) as exc:
- import_util.import_('pkg', fromlist=['mod'])
+ self.__import__('pkg', fromlist=['mod'])
self.assertEqual('i_do_not_exist', exc.exception.name)
def test_empty_string(self):
with util.mock_modules('pkg.__init__', 'pkg.mod') as importer:
with util.import_state(meta_path=[importer]):
- module = import_util.import_('pkg.mod', fromlist=[''])
+ module = self.__import__('pkg.mod', fromlist=[''])
self.assertEqual(module.__name__, 'pkg.mod')
def basic_star_test(self, fromlist=['*']):
@@ -93,7 +96,7 @@ class HandlingFromlist(unittest.TestCase):
with util.mock_modules('pkg.__init__', 'pkg.module') as mock:
with util.import_state(meta_path=[mock]):
mock['pkg'].__all__ = ['module']
- module = import_util.import_('pkg', fromlist=fromlist)
+ module = self.__import__('pkg', fromlist=fromlist)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module'))
self.assertEqual(module.module.__name__, 'pkg.module')
@@ -111,17 +114,16 @@ class HandlingFromlist(unittest.TestCase):
with context as mock:
with util.import_state(meta_path=[mock]):
mock['pkg'].__all__ = ['module1']
- module = import_util.import_('pkg', fromlist=['module2', '*'])
+ module = self.__import__('pkg', fromlist=['module2', '*'])
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module1'))
self.assertTrue(hasattr(module, 'module2'))
self.assertEqual(module.module1.__name__, 'pkg.module1')
self.assertEqual(module.module2.__name__, 'pkg.module2')
+Frozen_FromList, Source_FromList = util.test_both(
+ HandlingFromlist, __import__=import_util.__import__)
-def test_main():
- from test.support import run_unittest
- run_unittest(ReturnValue, HandlingFromlist)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test_meta_path.py b/Lib/test/test_importlib/import_/test_meta_path.py
index 4d85f80e67..a56253ddde 100644
--- a/Lib/test/test_importlib/import_/test_meta_path.py
+++ b/Lib/test/test_importlib/import_/test_meta_path.py
@@ -7,7 +7,7 @@ import unittest
import warnings
-class CallingOrder(unittest.TestCase):
+class CallingOrder:
"""Calls to the importers on sys.meta_path happen in order that they are
specified in the sequence, starting with the first importer
@@ -24,7 +24,7 @@ class CallingOrder(unittest.TestCase):
first.modules[mod] = 42
second.modules[mod] = -13
with util.import_state(meta_path=[first, second]):
- self.assertEqual(import_util.import_(mod), 42)
+ self.assertEqual(self.__import__(mod), 42)
def test_continuing(self):
# [continuing]
@@ -34,7 +34,7 @@ class CallingOrder(unittest.TestCase):
first.find_module = lambda self, fullname, path=None: None
second.modules[mod_name] = 42
with util.import_state(meta_path=[first, second]):
- self.assertEqual(import_util.import_(mod_name), 42)
+ self.assertEqual(self.__import__(mod_name), 42)
def test_empty(self):
# Raise an ImportWarning if sys.meta_path is empty.
@@ -46,13 +46,16 @@ class CallingOrder(unittest.TestCase):
with util.import_state(meta_path=[]):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
- self.assertIsNone(importlib._bootstrap._find_module('nothing',
- None))
+ self.assertIsNone(importlib._bootstrap._find_spec('nothing',
+ None))
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, ImportWarning))
+Frozen_CallingOrder, Source_CallingOrder = util.test_both(
+ CallingOrder, __import__=import_util.__import__)
-class CallSignature(unittest.TestCase):
+
+class CallSignature:
"""If there is no __path__ entry on the parent module, then 'path' is None
[no path]. Otherwise, the value for __path__ is passed in for the 'path'
@@ -74,7 +77,7 @@ class CallSignature(unittest.TestCase):
log, wrapped_call = self.log(importer.find_module)
importer.find_module = MethodType(wrapped_call, importer)
with util.import_state(meta_path=[importer]):
- import_util.import_(mod_name)
+ self.__import__(mod_name)
assert len(log) == 1
args = log[0][0]
kwargs = log[0][1]
@@ -95,7 +98,7 @@ class CallSignature(unittest.TestCase):
log, wrapped_call = self.log(importer.find_module)
importer.find_module = MethodType(wrapped_call, importer)
with util.import_state(meta_path=[importer]):
- import_util.import_(mod_name)
+ self.__import__(mod_name)
assert len(log) == 2
args = log[1][0]
kwargs = log[1][1]
@@ -104,12 +107,9 @@ class CallSignature(unittest.TestCase):
self.assertEqual(args[0], mod_name)
self.assertIs(args[1], path)
-
-
-def test_main():
- from test.support import run_unittest
- run_unittest(CallingOrder, CallSignature)
+Frozen_CallSignature, Source_CallSignature = util.test_both(
+ CallSignature, __import__=import_util.__import__)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test_packages.py b/Lib/test/test_importlib/import_/test_packages.py
index bfa18dc217..2df421d78e 100644
--- a/Lib/test/test_importlib/import_/test_packages.py
+++ b/Lib/test/test_importlib/import_/test_packages.py
@@ -6,21 +6,21 @@ import importlib
from test import support
-class ParentModuleTests(unittest.TestCase):
+class ParentModuleTests:
"""Importing a submodule should import the parent modules."""
def test_import_parent(self):
with util.mock_modules('pkg.__init__', 'pkg.module') as mock:
with util.import_state(meta_path=[mock]):
- module = import_util.import_('pkg.module')
+ module = self.__import__('pkg.module')
self.assertIn('pkg', sys.modules)
def test_bad_parent(self):
with util.mock_modules('pkg.module') as mock:
with util.import_state(meta_path=[mock]):
with self.assertRaises(ImportError) as cm:
- import_util.import_('pkg.module')
+ self.__import__('pkg.module')
self.assertEqual(cm.exception.name, 'pkg')
def test_raising_parent_after_importing_child(self):
@@ -32,11 +32,11 @@ class ParentModuleTests(unittest.TestCase):
with mock:
with util.import_state(meta_path=[mock]):
with self.assertRaises(ZeroDivisionError):
- import_util.import_('pkg')
+ self.__import__('pkg')
self.assertNotIn('pkg', sys.modules)
self.assertIn('pkg.module', sys.modules)
with self.assertRaises(ZeroDivisionError):
- import_util.import_('pkg.module')
+ self.__import__('pkg.module')
self.assertNotIn('pkg', sys.modules)
self.assertIn('pkg.module', sys.modules)
@@ -51,10 +51,10 @@ class ParentModuleTests(unittest.TestCase):
with self.assertRaises((ZeroDivisionError, ImportError)):
# This raises ImportError on the "from . import module"
# line, not sure why.
- import_util.import_('pkg')
+ self.__import__('pkg')
self.assertNotIn('pkg', sys.modules)
with self.assertRaises((ZeroDivisionError, ImportError)):
- import_util.import_('pkg.module')
+ self.__import__('pkg.module')
self.assertNotIn('pkg', sys.modules)
# XXX False
#self.assertIn('pkg.module', sys.modules)
@@ -71,10 +71,10 @@ class ParentModuleTests(unittest.TestCase):
with self.assertRaises((ZeroDivisionError, ImportError)):
# This raises ImportError on the "from ..subpkg import module"
# line, not sure why.
- import_util.import_('pkg.subpkg')
+ self.__import__('pkg.subpkg')
self.assertNotIn('pkg.subpkg', sys.modules)
with self.assertRaises((ZeroDivisionError, ImportError)):
- import_util.import_('pkg.subpkg.module')
+ self.__import__('pkg.subpkg.module')
self.assertNotIn('pkg.subpkg', sys.modules)
# XXX False
#self.assertIn('pkg.subpkg.module', sys.modules)
@@ -83,7 +83,7 @@ class ParentModuleTests(unittest.TestCase):
# Try to import a submodule from a non-package should raise ImportError.
assert not hasattr(sys, '__path__')
with self.assertRaises(ImportError) as cm:
- import_util.import_('sys.no_submodules_here')
+ self.__import__('sys.no_submodules_here')
self.assertEqual(cm.exception.name, 'sys.no_submodules_here')
def test_module_not_package_but_side_effects(self):
@@ -98,15 +98,13 @@ class ParentModuleTests(unittest.TestCase):
with mock_modules as mock:
with util.import_state(meta_path=[mock]):
try:
- submodule = import_util.import_(subname)
+ submodule = self.__import__(subname)
finally:
support.unload(subname)
-
-def test_main():
- from test.support import run_unittest
- run_unittest(ParentModuleTests)
+Frozen_ParentTests, Source_ParentTests = util.test_both(
+ ParentModuleTests, __import__=import_util.__import__)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test_path.py b/Lib/test/test_importlib/import_/test_path.py
index d82b7f6b0c..2e5033eb9e 100644
--- a/Lib/test/test_importlib/import_/test_path.py
+++ b/Lib/test/test_importlib/import_/test_path.py
@@ -1,8 +1,9 @@
-from importlib import _bootstrap
-from importlib import machinery
-from importlib import import_module
from .. import util
from . import util as import_util
+
+importlib = util.import_importlib('importlib')
+machinery = util.import_importlib('importlib.machinery')
+
import os
import sys
from types import ModuleType
@@ -11,15 +12,15 @@ import warnings
import zipimport
-class FinderTests(unittest.TestCase):
+class FinderTests:
"""Tests for PathFinder."""
def test_failure(self):
- # Test None returned upon not finding a suitable finder.
+ # Test None returned upon not finding a suitable loader.
module = '<test module>'
with util.import_state():
- self.assertIsNone(machinery.PathFinder.find_module(module))
+ self.assertIsNone(self.machinery.PathFinder.find_module(module))
def test_sys_path(self):
# Test that sys.path is used when 'path' is None.
@@ -29,7 +30,7 @@ class FinderTests(unittest.TestCase):
importer = util.mock_modules(module)
with util.import_state(path_importer_cache={path: importer},
path=[path]):
- loader = machinery.PathFinder.find_module(module)
+ loader = self.machinery.PathFinder.find_module(module)
self.assertIs(loader, importer)
def test_path(self):
@@ -39,7 +40,7 @@ class FinderTests(unittest.TestCase):
path = '<test path>'
importer = util.mock_modules(module)
with util.import_state(path_importer_cache={path: importer}):
- loader = machinery.PathFinder.find_module(module, [path])
+ loader = self.machinery.PathFinder.find_module(module, [path])
self.assertIs(loader, importer)
def test_empty_list(self):
@@ -49,7 +50,7 @@ class FinderTests(unittest.TestCase):
importer = util.mock_modules(module)
with util.import_state(path_importer_cache={path: importer},
path=[path]):
- self.assertIsNone(machinery.PathFinder.find_module('module', []))
+ self.assertIsNone(self.machinery.PathFinder.find_module('module', []))
def test_path_hooks(self):
# Test that sys.path_hooks is used.
@@ -59,7 +60,7 @@ class FinderTests(unittest.TestCase):
importer = util.mock_modules(module)
hook = import_util.mock_path_hook(path, importer=importer)
with util.import_state(path_hooks=[hook]):
- loader = machinery.PathFinder.find_module(module, [path])
+ loader = self.machinery.PathFinder.find_module(module, [path])
self.assertIs(loader, importer)
self.assertIn(path, sys.path_importer_cache)
self.assertIs(sys.path_importer_cache[path], importer)
@@ -72,7 +73,7 @@ class FinderTests(unittest.TestCase):
path=[path_entry]):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
- self.assertIsNone(machinery.PathFinder.find_module('os'))
+ self.assertIsNone(self.machinery.PathFinder.find_module('os'))
self.assertIsNone(sys.path_importer_cache[path_entry])
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, ImportWarning))
@@ -82,11 +83,11 @@ class FinderTests(unittest.TestCase):
path = ''
module = '<test module>'
importer = util.mock_modules(module)
- hook = import_util.mock_path_hook(os.curdir, importer=importer)
+ hook = import_util.mock_path_hook(os.getcwd(), importer=importer)
with util.import_state(path=[path], path_hooks=[hook]):
- loader = machinery.PathFinder.find_module(module)
+ loader = self.machinery.PathFinder.find_module(module)
self.assertIs(loader, importer)
- self.assertIn(os.curdir, sys.path_importer_cache)
+ self.assertIn(os.getcwd(), sys.path_importer_cache)
def test_None_on_sys_path(self):
# Putting None in sys.path[0] caused an import regression from Python
@@ -96,8 +97,8 @@ class FinderTests(unittest.TestCase):
new_path_importer_cache = sys.path_importer_cache.copy()
new_path_importer_cache.pop(None, None)
new_path_hooks = [zipimport.zipimporter,
- _bootstrap.FileFinder.path_hook(
- *_bootstrap._get_supported_file_loaders())]
+ self.machinery.FileFinder.path_hook(
+ *self.importlib._bootstrap._get_supported_file_loaders())]
missing = object()
email = sys.modules.pop('email', missing)
try:
@@ -105,16 +106,15 @@ class FinderTests(unittest.TestCase):
path=new_path,
path_importer_cache=new_path_importer_cache,
path_hooks=new_path_hooks):
- module = import_module('email')
+ module = self.importlib.import_module('email')
self.assertIsInstance(module, ModuleType)
finally:
if email is not missing:
sys.modules['email'] = email
+Frozen_FinderTests, Source_FinderTests = util.test_both(
+ FinderTests, importlib=importlib, machinery=machinery)
-def test_main():
- from test.support import run_unittest
- run_unittest(FinderTests)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/test_relative_imports.py b/Lib/test/test_importlib/import_/test_relative_imports.py
index 4569c26424..fc6d25a5e5 100644
--- a/Lib/test/test_importlib/import_/test_relative_imports.py
+++ b/Lib/test/test_importlib/import_/test_relative_imports.py
@@ -4,7 +4,7 @@ from . import util as import_util
import sys
import unittest
-class RelativeImports(unittest.TestCase):
+class RelativeImports:
"""PEP 328 introduced relative imports. This allows for imports to occur
from within a package without having to specify the actual package name.
@@ -76,8 +76,8 @@ class RelativeImports(unittest.TestCase):
create = 'pkg.__init__', 'pkg.mod2'
globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.mod1'}
def callback(global_):
- import_util.import_('pkg') # For __import__().
- module = import_util.import_('', global_, fromlist=['mod2'], level=1)
+ self.__import__('pkg') # For __import__().
+ module = self.__import__('', global_, fromlist=['mod2'], level=1)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'mod2'))
self.assertEqual(module.mod2.attr, 'pkg.mod2')
@@ -88,8 +88,8 @@ class RelativeImports(unittest.TestCase):
create = 'pkg.__init__', 'pkg.mod2'
globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.mod1'}
def callback(global_):
- import_util.import_('pkg') # For __import__().
- module = import_util.import_('mod2', global_, fromlist=['attr'],
+ self.__import__('pkg') # For __import__().
+ module = self.__import__('mod2', global_, fromlist=['attr'],
level=1)
self.assertEqual(module.__name__, 'pkg.mod2')
self.assertEqual(module.attr, 'pkg.mod2')
@@ -101,8 +101,8 @@ class RelativeImports(unittest.TestCase):
globals_ = ({'__package__': 'pkg'},
{'__name__': 'pkg', '__path__': ['blah']})
def callback(global_):
- import_util.import_('pkg') # For __import__().
- module = import_util.import_('', global_, fromlist=['module'],
+ self.__import__('pkg') # For __import__().
+ module = self.__import__('', global_, fromlist=['module'],
level=1)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module'))
@@ -114,8 +114,8 @@ class RelativeImports(unittest.TestCase):
create = 'pkg.__init__', 'pkg.module'
globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
def callback(global_):
- import_util.import_('pkg') # For __import__().
- module = import_util.import_('', global_, fromlist=['attr'], level=1)
+ self.__import__('pkg') # For __import__().
+ module = self.__import__('', global_, fromlist=['attr'], level=1)
self.assertEqual(module.__name__, 'pkg')
self.relative_import_test(create, globals_, callback)
@@ -126,7 +126,7 @@ class RelativeImports(unittest.TestCase):
globals_ = ({'__package__': 'pkg.subpkg1'},
{'__name__': 'pkg.subpkg1', '__path__': ['blah']})
def callback(global_):
- module = import_util.import_('', global_, fromlist=['subpkg2'],
+ module = self.__import__('', global_, fromlist=['subpkg2'],
level=2)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'subpkg2'))
@@ -142,8 +142,8 @@ class RelativeImports(unittest.TestCase):
{'__name__': 'pkg.pkg1.pkg2.pkg3.pkg4.pkg5',
'__path__': ['blah']})
def callback(global_):
- import_util.import_(globals_[0]['__package__'])
- module = import_util.import_('', global_, fromlist=['attr'], level=6)
+ self.__import__(globals_[0]['__package__'])
+ module = self.__import__('', global_, fromlist=['attr'], level=6)
self.assertEqual(module.__name__, 'pkg')
self.relative_import_test(create, globals_, callback)
@@ -153,9 +153,9 @@ class RelativeImports(unittest.TestCase):
globals_ = ({'__package__': 'pkg'},
{'__name__': 'pkg', '__path__': ['blah']})
def callback(global_):
- import_util.import_('pkg')
+ self.__import__('pkg')
with self.assertRaises(ValueError):
- import_util.import_('', global_, fromlist=['top_level'],
+ self.__import__('', global_, fromlist=['top_level'],
level=2)
self.relative_import_test(create, globals_, callback)
@@ -164,16 +164,16 @@ class RelativeImports(unittest.TestCase):
create = ['top_level', 'pkg.__init__', 'pkg.module']
globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
def callback(global_):
- import_util.import_('pkg')
+ self.__import__('pkg')
with self.assertRaises(ValueError):
- import_util.import_('', global_, fromlist=['top_level'],
+ self.__import__('', global_, fromlist=['top_level'],
level=2)
self.relative_import_test(create, globals_, callback)
def test_empty_name_w_level_0(self):
# [empty name]
with self.assertRaises(ValueError):
- import_util.import_('')
+ self.__import__('')
def test_import_from_different_package(self):
# Test importing from a different package than the caller.
@@ -186,8 +186,8 @@ class RelativeImports(unittest.TestCase):
'__runpy_pkg__.uncle.cousin.nephew']
globals_ = {'__package__': '__runpy_pkg__.__runpy_pkg__'}
def callback(global_):
- import_util.import_('__runpy_pkg__.__runpy_pkg__')
- module = import_util.import_('uncle.cousin', globals_, {},
+ self.__import__('__runpy_pkg__.__runpy_pkg__')
+ module = self.__import__('uncle.cousin', globals_, {},
fromlist=['nephew'],
level=2)
self.assertEqual(module.__name__, '__runpy_pkg__.uncle.cousin')
@@ -198,20 +198,19 @@ class RelativeImports(unittest.TestCase):
create = ['crash.__init__', 'crash.mod']
globals_ = [{'__package__': 'crash', '__name__': 'crash'}]
def callback(global_):
- import_util.import_('crash')
- mod = import_util.import_('mod', global_, {}, [], 1)
+ self.__import__('crash')
+ mod = self.__import__('mod', global_, {}, [], 1)
self.assertEqual(mod.__name__, 'crash.mod')
self.relative_import_test(create, globals_, callback)
def test_relative_import_no_globals(self):
# No globals for a relative import is an error.
with self.assertRaises(KeyError):
- import_util.import_('sys', level=1)
+ self.__import__('sys', level=1)
+Frozen_RelativeImports, Source_RelativeImports = util.test_both(
+ RelativeImports, __import__=import_util.__import__)
-def test_main():
- from test.support import run_unittest
- run_unittest(RelativeImports)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/import_/util.py b/Lib/test/test_importlib/import_/util.py
index 86ac065e64..dcb490f967 100644
--- a/Lib/test/test_importlib/import_/util.py
+++ b/Lib/test/test_importlib/import_/util.py
@@ -1,22 +1,14 @@
+from .. import util
+
+frozen_importlib, source_importlib = util.import_importlib('importlib')
+
+import builtins
import functools
import importlib
import unittest
-using___import__ = False
-
-
-def import_(*args, **kwargs):
- """Delegate to allow for injecting different implementations of import."""
- if using___import__:
- return __import__(*args, **kwargs)
- else:
- return importlib.__import__(*args, **kwargs)
-
-
-def importlib_only(fxn):
- """Decorator to skip a test if using __builtins__.__import__."""
- return unittest.skipIf(using___import__, "importlib-specific test")(fxn)
+__import__ = staticmethod(builtins.__import__), staticmethod(source_importlib.__import__)
def mock_path_hook(*entries, importer):
diff --git a/Lib/test/test_importlib/source/test_abc_loader.py b/Lib/test/test_importlib/source/test_abc_loader.py
deleted file mode 100644
index 0d912b6469..0000000000
--- a/Lib/test/test_importlib/source/test_abc_loader.py
+++ /dev/null
@@ -1,906 +0,0 @@
-import importlib
-from importlib import abc
-
-from .. import abc as testing_abc
-from .. import util
-from . import util as source_util
-
-import imp
-import inspect
-import io
-import marshal
-import os
-import sys
-import types
-import unittest
-import warnings
-
-
-class SourceOnlyLoaderMock(abc.SourceLoader):
-
- # Globals that should be defined for all modules.
- source = (b"_ = '::'.join([__name__, __file__, __cached__, __package__, "
- b"repr(__loader__)])")
-
- def __init__(self, path):
- self.path = path
-
- def get_data(self, path):
- assert self.path == path
- return self.source
-
- def get_filename(self, fullname):
- return self.path
-
- def module_repr(self, module):
- return '<module>'
-
-
-class SourceLoaderMock(SourceOnlyLoaderMock):
-
- source_mtime = 1
-
- def __init__(self, path, magic=imp.get_magic()):
- super().__init__(path)
- self.bytecode_path = imp.cache_from_source(self.path)
- self.source_size = len(self.source)
- data = bytearray(magic)
- data.extend(importlib._w_long(self.source_mtime))
- data.extend(importlib._w_long(self.source_size))
- code_object = compile(self.source, self.path, 'exec',
- dont_inherit=True)
- data.extend(marshal.dumps(code_object))
- self.bytecode = bytes(data)
- self.written = {}
-
- def get_data(self, path):
- if path == self.path:
- return super().get_data(path)
- elif path == self.bytecode_path:
- return self.bytecode
- else:
- raise IOError
-
- def path_stats(self, path):
- assert path == self.path
- return {'mtime': self.source_mtime, 'size': self.source_size}
-
- def set_data(self, path, data):
- self.written[path] = bytes(data)
- return path == self.bytecode_path
-
-
-class PyLoaderMock(abc.PyLoader):
-
- # Globals that should be defined for all modules.
- source = (b"_ = '::'.join([__name__, __file__, __package__, "
- b"repr(__loader__)])")
-
- def __init__(self, data):
- """Take a dict of 'module_name: path' pairings.
-
- Paths should have no file extension, allowing packages to be denoted by
- ending in '__init__'.
-
- """
- self.module_paths = data
- self.path_to_module = {val:key for key,val in data.items()}
-
- def get_data(self, path):
- if path not in self.path_to_module:
- raise IOError
- return self.source
-
- def is_package(self, name):
- filename = os.path.basename(self.get_filename(name))
- return os.path.splitext(filename)[0] == '__init__'
-
- def source_path(self, name):
- try:
- return self.module_paths[name]
- except KeyError:
- raise ImportError
-
- def get_filename(self, name):
- """Silence deprecation warning."""
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- path = super().get_filename(name)
- assert len(w) == 1
- assert issubclass(w[0].category, DeprecationWarning)
- return path
-
- def module_repr(self):
- return '<module>'
-
-
-class PyLoaderCompatMock(PyLoaderMock):
-
- """Mock that matches what is suggested to have a loader that is compatible
- from Python 3.1 onwards."""
-
- def get_filename(self, fullname):
- try:
- return self.module_paths[fullname]
- except KeyError:
- raise ImportError
-
- def source_path(self, fullname):
- try:
- return self.get_filename(fullname)
- except ImportError:
- return None
-
-
-class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock):
-
- default_mtime = 1
-
- def __init__(self, source, bc={}):
- """Initialize mock.
-
- 'bc' is a dict keyed on a module's name. The value is dict with
- possible keys of 'path', 'mtime', 'magic', and 'bc'. Except for 'path',
- each of those keys control if any part of created bytecode is to
- deviate from default values.
-
- """
- super().__init__(source)
- self.module_bytecode = {}
- self.path_to_bytecode = {}
- self.bytecode_to_path = {}
- for name, data in bc.items():
- self.path_to_bytecode[data['path']] = name
- self.bytecode_to_path[name] = data['path']
- magic = data.get('magic', imp.get_magic())
- mtime = importlib._w_long(data.get('mtime', self.default_mtime))
- source_size = importlib._w_long(len(self.source) & 0xFFFFFFFF)
- if 'bc' in data:
- bc = data['bc']
- else:
- bc = self.compile_bc(name)
- self.module_bytecode[name] = magic + mtime + source_size + bc
-
- def compile_bc(self, name):
- source_path = self.module_paths.get(name, '<test>') or '<test>'
- code = compile(self.source, source_path, 'exec')
- return marshal.dumps(code)
-
- def source_mtime(self, name):
- if name in self.module_paths:
- return self.default_mtime
- elif name in self.module_bytecode:
- return None
- else:
- raise ImportError
-
- def bytecode_path(self, name):
- try:
- return self.bytecode_to_path[name]
- except KeyError:
- if name in self.module_paths:
- return None
- else:
- raise ImportError
-
- def write_bytecode(self, name, bytecode):
- self.module_bytecode[name] = bytecode
- return True
-
- def get_data(self, path):
- if path in self.path_to_module:
- return super().get_data(path)
- elif path in self.path_to_bytecode:
- name = self.path_to_bytecode[path]
- return self.module_bytecode[name]
- else:
- raise IOError
-
- def is_package(self, name):
- try:
- return super().is_package(name)
- except TypeError:
- return '__init__' in self.bytecode_to_path[name]
-
- def get_code(self, name):
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- code_object = super().get_code(name)
- assert len(w) == 1
- assert issubclass(w[0].category, DeprecationWarning)
- return code_object
-
-class PyLoaderTests(testing_abc.LoaderTests):
-
- """Tests for importlib.abc.PyLoader."""
-
- mocker = PyLoaderMock
-
- def eq_attrs(self, ob, **kwargs):
- for attr, val in kwargs.items():
- found = getattr(ob, attr)
- self.assertEqual(found, val,
- "{} attribute: {} != {}".format(attr, found, val))
-
- def test_module(self):
- name = '<module>'
- path = os.path.join('', 'path', 'to', 'module')
- mock = self.mocker({name: path})
- with util.uncache(name):
- module = mock.load_module(name)
- self.assertIn(name, sys.modules)
- self.eq_attrs(module, __name__=name, __file__=path, __package__='',
- __loader__=mock)
- self.assertTrue(not hasattr(module, '__path__'))
- return mock, name
-
- def test_package(self):
- name = '<pkg>'
- path = os.path.join('path', 'to', name, '__init__')
- mock = self.mocker({name: path})
- with util.uncache(name):
- module = mock.load_module(name)
- self.assertIn(name, sys.modules)
- self.eq_attrs(module, __name__=name, __file__=path,
- __path__=[os.path.dirname(path)], __package__=name,
- __loader__=mock)
- return mock, name
-
- def test_lacking_parent(self):
- name = 'pkg.mod'
- path = os.path.join('path', 'to', 'pkg', 'mod')
- mock = self.mocker({name: path})
- with util.uncache(name):
- module = mock.load_module(name)
- self.assertIn(name, sys.modules)
- self.eq_attrs(module, __name__=name, __file__=path, __package__='pkg',
- __loader__=mock)
- self.assertFalse(hasattr(module, '__path__'))
- return mock, name
-
- def test_module_reuse(self):
- name = 'mod'
- path = os.path.join('path', 'to', 'mod')
- module = imp.new_module(name)
- mock = self.mocker({name: path})
- with util.uncache(name):
- sys.modules[name] = module
- loaded_module = mock.load_module(name)
- self.assertIs(loaded_module, module)
- self.assertIs(sys.modules[name], module)
- return mock, name
-
- def test_state_after_failure(self):
- name = "mod"
- module = imp.new_module(name)
- module.blah = None
- mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
- mock.source = b"1/0"
- with util.uncache(name):
- sys.modules[name] = module
- with self.assertRaises(ZeroDivisionError):
- mock.load_module(name)
- self.assertIs(sys.modules[name], module)
- self.assertTrue(hasattr(module, 'blah'))
- return mock
-
- def test_unloadable(self):
- name = "mod"
- mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
- mock.source = b"1/0"
- with util.uncache(name):
- with self.assertRaises(ZeroDivisionError):
- mock.load_module(name)
- self.assertNotIn(name, sys.modules)
- return mock
-
-
-class PyLoaderCompatTests(PyLoaderTests):
-
- """Test that the suggested code to make a loader that is compatible from
- Python 3.1 forward works."""
-
- mocker = PyLoaderCompatMock
-
-
-class PyLoaderInterfaceTests(unittest.TestCase):
-
- """Tests for importlib.abc.PyLoader to make sure that when source_path()
- doesn't return a path everything works as expected."""
-
- def test_no_source_path(self):
- # No source path should lead to ImportError.
- name = 'mod'
- mock = PyLoaderMock({})
- with util.uncache(name), self.assertRaises(ImportError):
- mock.load_module(name)
-
- def test_source_path_is_None(self):
- name = 'mod'
- mock = PyLoaderMock({name: None})
- with util.uncache(name), self.assertRaises(ImportError):
- mock.load_module(name)
-
- def test_get_filename_with_source_path(self):
- # get_filename() should return what source_path() returns.
- name = 'mod'
- path = os.path.join('path', 'to', 'source')
- mock = PyLoaderMock({name: path})
- with util.uncache(name):
- self.assertEqual(mock.get_filename(name), path)
-
- def test_get_filename_no_source_path(self):
- # get_filename() should raise ImportError if source_path returns None.
- name = 'mod'
- mock = PyLoaderMock({name: None})
- with util.uncache(name), self.assertRaises(ImportError):
- mock.get_filename(name)
-
-
-class PyPycLoaderTests(PyLoaderTests):
-
- """Tests for importlib.abc.PyPycLoader."""
-
- mocker = PyPycLoaderMock
-
- @source_util.writes_bytecode_files
- def verify_bytecode(self, mock, name):
- assert name in mock.module_paths
- self.assertIn(name, mock.module_bytecode)
- magic = mock.module_bytecode[name][:4]
- self.assertEqual(magic, imp.get_magic())
- mtime = importlib._r_long(mock.module_bytecode[name][4:8])
- self.assertEqual(mtime, 1)
- source_size = mock.module_bytecode[name][8:12]
- self.assertEqual(len(mock.source) & 0xFFFFFFFF,
- importlib._r_long(source_size))
- bc = mock.module_bytecode[name][12:]
- self.assertEqual(bc, mock.compile_bc(name))
-
- def test_module(self):
- mock, name = super().test_module()
- self.verify_bytecode(mock, name)
-
- def test_package(self):
- mock, name = super().test_package()
- self.verify_bytecode(mock, name)
-
- def test_lacking_parent(self):
- mock, name = super().test_lacking_parent()
- self.verify_bytecode(mock, name)
-
- def test_module_reuse(self):
- mock, name = super().test_module_reuse()
- self.verify_bytecode(mock, name)
-
- def test_state_after_failure(self):
- super().test_state_after_failure()
-
- def test_unloadable(self):
- super().test_unloadable()
-
-
-class PyPycLoaderInterfaceTests(unittest.TestCase):
-
- """Test for the interface of importlib.abc.PyPycLoader."""
-
- def get_filename_check(self, src_path, bc_path, expect):
- name = 'mod'
- mock = PyPycLoaderMock({name: src_path}, {name: {'path': bc_path}})
- with util.uncache(name):
- assert mock.source_path(name) == src_path
- assert mock.bytecode_path(name) == bc_path
- self.assertEqual(mock.get_filename(name), expect)
-
- def test_filename_with_source_bc(self):
- # When source and bytecode paths present, return the source path.
- self.get_filename_check('source_path', 'bc_path', 'source_path')
-
- def test_filename_with_source_no_bc(self):
- # With source but no bc, return source path.
- self.get_filename_check('source_path', None, 'source_path')
-
- def test_filename_with_no_source_bc(self):
- # With not source but bc, return the bc path.
- self.get_filename_check(None, 'bc_path', 'bc_path')
-
- def test_filename_with_no_source_or_bc(self):
- # With no source or bc, raise ImportError.
- name = 'mod'
- mock = PyPycLoaderMock({name: None}, {name: {'path': None}})
- with util.uncache(name), self.assertRaises(ImportError):
- mock.get_filename(name)
-
-
-class SkipWritingBytecodeTests(unittest.TestCase):
-
- """Test that bytecode is properly handled based on
- sys.dont_write_bytecode."""
-
- @source_util.writes_bytecode_files
- def run_test(self, dont_write_bytecode):
- name = 'mod'
- mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
- sys.dont_write_bytecode = dont_write_bytecode
- with util.uncache(name):
- mock.load_module(name)
- self.assertIsNot(name in mock.module_bytecode, dont_write_bytecode)
-
- def test_no_bytecode_written(self):
- self.run_test(True)
-
- def test_bytecode_written(self):
- self.run_test(False)
-
-
-class RegeneratedBytecodeTests(unittest.TestCase):
-
- """Test that bytecode is regenerated as expected."""
-
- @source_util.writes_bytecode_files
- def test_different_magic(self):
- # A different magic number should lead to new bytecode.
- name = 'mod'
- bad_magic = b'\x00\x00\x00\x00'
- assert bad_magic != imp.get_magic()
- mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')},
- {name: {'path': os.path.join('path', 'to',
- 'mod.bytecode'),
- 'magic': bad_magic}})
- with util.uncache(name):
- mock.load_module(name)
- self.assertIn(name, mock.module_bytecode)
- magic = mock.module_bytecode[name][:4]
- self.assertEqual(magic, imp.get_magic())
-
- @source_util.writes_bytecode_files
- def test_old_mtime(self):
- # Bytecode with an older mtime should be regenerated.
- name = 'mod'
- old_mtime = PyPycLoaderMock.default_mtime - 1
- mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')},
- {name: {'path': 'path/to/mod.bytecode', 'mtime': old_mtime}})
- with util.uncache(name):
- mock.load_module(name)
- self.assertIn(name, mock.module_bytecode)
- mtime = importlib._r_long(mock.module_bytecode[name][4:8])
- self.assertEqual(mtime, PyPycLoaderMock.default_mtime)
-
-
-class BadBytecodeFailureTests(unittest.TestCase):
-
- """Test import failures when there is no source and parts of the bytecode
- is bad."""
-
- def test_bad_magic(self):
- # A bad magic number should lead to an ImportError.
- name = 'mod'
- bad_magic = b'\x00\x00\x00\x00'
- bc = {name:
- {'path': os.path.join('path', 'to', 'mod'),
- 'magic': bad_magic}}
- mock = PyPycLoaderMock({name: None}, bc)
- with util.uncache(name), self.assertRaises(ImportError) as cm:
- mock.load_module(name)
- self.assertEqual(cm.exception.name, name)
-
- def test_no_bytecode(self):
- # Missing code object bytecode should lead to an EOFError.
- name = 'mod'
- bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b''}}
- mock = PyPycLoaderMock({name: None}, bc)
- with util.uncache(name), self.assertRaises(EOFError):
- mock.load_module(name)
-
- def test_bad_bytecode(self):
- # Malformed code object bytecode should lead to a ValueError.
- name = 'mod'
- bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b'1234'}}
- mock = PyPycLoaderMock({name: None}, bc)
- with util.uncache(name), self.assertRaises(ValueError):
- mock.load_module(name)
-
-
-def raise_ImportError(*args, **kwargs):
- raise ImportError
-
-class MissingPathsTests(unittest.TestCase):
-
- """Test what happens when a source or bytecode path does not exist (either
- from *_path returning None or raising ImportError)."""
-
- def test_source_path_None(self):
- # Bytecode should be used when source_path returns None, along with
- # __file__ being set to the bytecode path.
- name = 'mod'
- bytecode_path = 'path/to/mod'
- mock = PyPycLoaderMock({name: None}, {name: {'path': bytecode_path}})
- with util.uncache(name):
- module = mock.load_module(name)
- self.assertEqual(module.__file__, bytecode_path)
-
- # Testing for bytecode_path returning None handled by all tests where no
- # bytecode initially exists.
-
- def test_all_paths_None(self):
- # If all *_path methods return None, raise ImportError.
- name = 'mod'
- mock = PyPycLoaderMock({name: None})
- with util.uncache(name), self.assertRaises(ImportError) as cm:
- mock.load_module(name)
- self.assertEqual(cm.exception.name, name)
-
- def test_source_path_ImportError(self):
- # An ImportError from source_path should trigger an ImportError.
- name = 'mod'
- mock = PyPycLoaderMock({}, {name: {'path': os.path.join('path', 'to',
- 'mod')}})
- with util.uncache(name), self.assertRaises(ImportError):
- mock.load_module(name)
-
- def test_bytecode_path_ImportError(self):
- # An ImportError from bytecode_path should trigger an ImportError.
- name = 'mod'
- mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
- bad_meth = types.MethodType(raise_ImportError, mock)
- mock.bytecode_path = bad_meth
- with util.uncache(name), self.assertRaises(ImportError) as cm:
- mock.load_module(name)
-
-
-class SourceLoaderTestHarness(unittest.TestCase):
-
- def setUp(self, *, is_package=True, **kwargs):
- self.package = 'pkg'
- if is_package:
- self.path = os.path.join(self.package, '__init__.py')
- self.name = self.package
- else:
- module_name = 'mod'
- self.path = os.path.join(self.package, '.'.join(['mod', 'py']))
- self.name = '.'.join([self.package, module_name])
- self.cached = imp.cache_from_source(self.path)
- self.loader = self.loader_mock(self.path, **kwargs)
-
- def verify_module(self, module):
- self.assertEqual(module.__name__, self.name)
- self.assertEqual(module.__file__, self.path)
- self.assertEqual(module.__cached__, self.cached)
- self.assertEqual(module.__package__, self.package)
- self.assertEqual(module.__loader__, self.loader)
- values = module._.split('::')
- self.assertEqual(values[0], self.name)
- self.assertEqual(values[1], self.path)
- self.assertEqual(values[2], self.cached)
- self.assertEqual(values[3], self.package)
- self.assertEqual(values[4], repr(self.loader))
-
- def verify_code(self, code_object):
- module = imp.new_module(self.name)
- module.__file__ = self.path
- module.__cached__ = self.cached
- module.__package__ = self.package
- module.__loader__ = self.loader
- module.__path__ = []
- exec(code_object, module.__dict__)
- self.verify_module(module)
-
-
-class SourceOnlyLoaderTests(SourceLoaderTestHarness):
-
- """Test importlib.abc.SourceLoader for source-only loading.
-
- Reload testing is subsumed by the tests for
- importlib.util.module_for_loader.
-
- """
-
- loader_mock = SourceOnlyLoaderMock
-
- def test_get_source(self):
- # Verify the source code is returned as a string.
- # If an IOError is raised by get_data then raise ImportError.
- expected_source = self.loader.source.decode('utf-8')
- self.assertEqual(self.loader.get_source(self.name), expected_source)
- def raise_IOError(path):
- raise IOError
- self.loader.get_data = raise_IOError
- with self.assertRaises(ImportError) as cm:
- self.loader.get_source(self.name)
- self.assertEqual(cm.exception.name, self.name)
-
- def test_is_package(self):
- # Properly detect when loading a package.
- self.setUp(is_package=False)
- self.assertFalse(self.loader.is_package(self.name))
- self.setUp(is_package=True)
- self.assertTrue(self.loader.is_package(self.name))
- self.assertFalse(self.loader.is_package(self.name + '.__init__'))
-
- def test_get_code(self):
- # Verify the code object is created.
- code_object = self.loader.get_code(self.name)
- self.verify_code(code_object)
-
- def test_load_module(self):
- # Loading a module should set __name__, __loader__, __package__,
- # __path__ (for packages), __file__, and __cached__.
- # The module should also be put into sys.modules.
- with util.uncache(self.name):
- module = self.loader.load_module(self.name)
- self.verify_module(module)
- self.assertEqual(module.__path__, [os.path.dirname(self.path)])
- self.assertIn(self.name, sys.modules)
-
- def test_package_settings(self):
- # __package__ needs to be set, while __path__ is set on if the module
- # is a package.
- # Testing the values for a package are covered by test_load_module.
- self.setUp(is_package=False)
- with util.uncache(self.name):
- module = self.loader.load_module(self.name)
- self.verify_module(module)
- self.assertTrue(not hasattr(module, '__path__'))
-
- def test_get_source_encoding(self):
- # Source is considered encoded in UTF-8 by default unless otherwise
- # specified by an encoding line.
- source = "_ = 'ü'"
- self.loader.source = source.encode('utf-8')
- returned_source = self.loader.get_source(self.name)
- self.assertEqual(returned_source, source)
- source = "# coding: latin-1\n_ = ü"
- self.loader.source = source.encode('latin-1')
- returned_source = self.loader.get_source(self.name)
- self.assertEqual(returned_source, source)
-
-
-@unittest.skipIf(sys.dont_write_bytecode, "sys.dont_write_bytecode is true")
-class SourceLoaderBytecodeTests(SourceLoaderTestHarness):
-
- """Test importlib.abc.SourceLoader's use of bytecode.
-
- Source-only testing handled by SourceOnlyLoaderTests.
-
- """
-
- loader_mock = SourceLoaderMock
-
- def verify_code(self, code_object, *, bytecode_written=False):
- super().verify_code(code_object)
- if bytecode_written:
- self.assertIn(self.cached, self.loader.written)
- data = bytearray(imp.get_magic())
- data.extend(importlib._w_long(self.loader.source_mtime))
- data.extend(importlib._w_long(self.loader.source_size))
- data.extend(marshal.dumps(code_object))
- self.assertEqual(self.loader.written[self.cached], bytes(data))
-
- def test_code_with_everything(self):
- # When everything should work.
- code_object = self.loader.get_code(self.name)
- self.verify_code(code_object)
-
- def test_no_bytecode(self):
- # If no bytecode exists then move on to the source.
- self.loader.bytecode_path = "<does not exist>"
- # Sanity check
- with self.assertRaises(IOError):
- bytecode_path = imp.cache_from_source(self.path)
- self.loader.get_data(bytecode_path)
- code_object = self.loader.get_code(self.name)
- self.verify_code(code_object, bytecode_written=True)
-
- def test_code_bad_timestamp(self):
- # Bytecode is only used when the timestamp matches the source EXACTLY.
- for source_mtime in (0, 2):
- assert source_mtime != self.loader.source_mtime
- original = self.loader.source_mtime
- self.loader.source_mtime = source_mtime
- # If bytecode is used then EOFError would be raised by marshal.
- self.loader.bytecode = self.loader.bytecode[8:]
- code_object = self.loader.get_code(self.name)
- self.verify_code(code_object, bytecode_written=True)
- self.loader.source_mtime = original
-
- def test_code_bad_magic(self):
- # Skip over bytecode with a bad magic number.
- self.setUp(magic=b'0000')
- # If bytecode is used then EOFError would be raised by marshal.
- self.loader.bytecode = self.loader.bytecode[8:]
- code_object = self.loader.get_code(self.name)
- self.verify_code(code_object, bytecode_written=True)
-
- def test_dont_write_bytecode(self):
- # Bytecode is not written if sys.dont_write_bytecode is true.
- # Can assume it is false already thanks to the skipIf class decorator.
- try:
- sys.dont_write_bytecode = True
- self.loader.bytecode_path = "<does not exist>"
- code_object = self.loader.get_code(self.name)
- self.assertNotIn(self.cached, self.loader.written)
- finally:
- sys.dont_write_bytecode = False
-
- def test_no_set_data(self):
- # If set_data is not defined, one can still read bytecode.
- self.setUp(magic=b'0000')
- original_set_data = self.loader.__class__.set_data
- try:
- del self.loader.__class__.set_data
- code_object = self.loader.get_code(self.name)
- self.verify_code(code_object)
- finally:
- self.loader.__class__.set_data = original_set_data
-
- def test_set_data_raises_exceptions(self):
- # Raising NotImplementedError or IOError is okay for set_data.
- def raise_exception(exc):
- def closure(*args, **kwargs):
- raise exc
- return closure
-
- self.setUp(magic=b'0000')
- self.loader.set_data = raise_exception(NotImplementedError)
- code_object = self.loader.get_code(self.name)
- self.verify_code(code_object)
-
-
-class SourceLoaderGetSourceTests(unittest.TestCase):
-
- """Tests for importlib.abc.SourceLoader.get_source()."""
-
- def test_default_encoding(self):
- # Should have no problems with UTF-8 text.
- name = 'mod'
- mock = SourceOnlyLoaderMock('mod.file')
- source = 'x = "ü"'
- mock.source = source.encode('utf-8')
- returned_source = mock.get_source(name)
- self.assertEqual(returned_source, source)
-
- def test_decoded_source(self):
- # Decoding should work.
- name = 'mod'
- mock = SourceOnlyLoaderMock("mod.file")
- source = "# coding: Latin-1\nx='ü'"
- assert source.encode('latin-1') != source.encode('utf-8')
- mock.source = source.encode('latin-1')
- returned_source = mock.get_source(name)
- self.assertEqual(returned_source, source)
-
- def test_universal_newlines(self):
- # PEP 302 says universal newlines should be used.
- name = 'mod'
- mock = SourceOnlyLoaderMock('mod.file')
- source = "x = 42\r\ny = -13\r\n"
- mock.source = source.encode('utf-8')
- expect = io.IncrementalNewlineDecoder(None, True).decode(source)
- self.assertEqual(mock.get_source(name), expect)
-
-
-class AbstractMethodImplTests(unittest.TestCase):
-
- """Test the concrete abstractmethod implementations."""
-
- class MetaPathFinder(abc.MetaPathFinder):
- def find_module(self, fullname, path):
- super().find_module(fullname, path)
-
- class PathEntryFinder(abc.PathEntryFinder):
- def find_module(self, _):
- super().find_module(_)
-
- def find_loader(self, _):
- super().find_loader(_)
-
- class Finder(abc.Finder):
- def find_module(self, fullname, path):
- super().find_module(fullname, path)
-
- class Loader(abc.Loader):
- def load_module(self, fullname):
- super().load_module(fullname)
- def module_repr(self, module):
- super().module_repr(module)
-
- class ResourceLoader(Loader, abc.ResourceLoader):
- def get_data(self, _):
- super().get_data(_)
-
- class InspectLoader(Loader, abc.InspectLoader):
- def is_package(self, _):
- super().is_package(_)
-
- def get_code(self, _):
- super().get_code(_)
-
- def get_source(self, _):
- super().get_source(_)
-
- class ExecutionLoader(InspectLoader, abc.ExecutionLoader):
- def get_filename(self, _):
- super().get_filename(_)
-
- class SourceLoader(ResourceLoader, ExecutionLoader, abc.SourceLoader):
- pass
-
- class PyLoader(ResourceLoader, InspectLoader, abc.PyLoader):
- def source_path(self, _):
- super().source_path(_)
-
- class PyPycLoader(PyLoader, abc.PyPycLoader):
- def bytecode_path(self, _):
- super().bytecode_path(_)
-
- def source_mtime(self, _):
- super().source_mtime(_)
-
- def write_bytecode(self, _, _2):
- super().write_bytecode(_, _2)
-
- def raises_NotImplementedError(self, ins, *args):
- for method_name in args:
- method = getattr(ins, method_name)
- arg_count = len(inspect.getfullargspec(method)[0]) - 1
- args = [''] * arg_count
- try:
- method(*args)
- except NotImplementedError:
- pass
- else:
- msg = "{}.{} did not raise NotImplementedError"
- self.fail(msg.format(ins.__class__.__name__, method_name))
-
- def test_Loader(self):
- self.raises_NotImplementedError(self.Loader(), 'load_module')
-
- # XXX misplaced; should be somewhere else
- def test_Finder(self):
- self.raises_NotImplementedError(self.Finder(), 'find_module')
-
- def test_ResourceLoader(self):
- self.raises_NotImplementedError(self.ResourceLoader(), 'load_module',
- 'get_data')
-
- def test_InspectLoader(self):
- self.raises_NotImplementedError(self.InspectLoader(), 'load_module',
- 'is_package', 'get_code', 'get_source')
-
- def test_ExecutionLoader(self):
- self.raises_NotImplementedError(self.ExecutionLoader(), 'load_module',
- 'is_package', 'get_code', 'get_source',
- 'get_filename')
-
- def test_SourceLoader(self):
- ins = self.SourceLoader()
- # Required abstractmethods.
- self.raises_NotImplementedError(ins, 'get_filename', 'get_data')
- # Optional abstractmethods.
- self.raises_NotImplementedError(ins,'path_stats', 'set_data')
-
- def test_PyLoader(self):
- self.raises_NotImplementedError(self.PyLoader(), 'source_path',
- 'get_data', 'is_package')
-
- def test_PyPycLoader(self):
- self.raises_NotImplementedError(self.PyPycLoader(), 'source_path',
- 'source_mtime', 'bytecode_path',
- 'write_bytecode')
-
-
-def test_main():
- from test.support import run_unittest
- run_unittest(PyLoaderTests, PyLoaderCompatTests,
- PyLoaderInterfaceTests,
- PyPycLoaderTests, PyPycLoaderInterfaceTests,
- SkipWritingBytecodeTests, RegeneratedBytecodeTests,
- BadBytecodeFailureTests, MissingPathsTests,
- SourceOnlyLoaderTests,
- SourceLoaderBytecodeTests,
- SourceLoaderGetSourceTests,
- AbstractMethodImplTests)
-
-
-if __name__ == '__main__':
- test_main()
diff --git a/Lib/test/test_importlib/source/test_case_sensitivity.py b/Lib/test/test_importlib/source/test_case_sensitivity.py
index 241173fb44..b3e9d25bb2 100644
--- a/Lib/test/test_importlib/source/test_case_sensitivity.py
+++ b/Lib/test/test_importlib/source/test_case_sensitivity.py
@@ -1,9 +1,10 @@
"""Test case-sensitivity (PEP 235)."""
-from importlib import _bootstrap
-from importlib import machinery
from .. import util
from . import util as source_util
-import imp
+
+importlib = util.import_importlib('importlib')
+machinery = util.import_importlib('importlib.machinery')
+
import os
import sys
from test import support as test_support
@@ -11,7 +12,7 @@ import unittest
@util.case_insensitive_tests
-class CaseSensitivityTest(unittest.TestCase):
+class CaseSensitivityTest:
"""PEP 235 dictates that on case-preserving, case-insensitive file systems
that imports are case-sensitive unless the PYTHONCASEOK environment
@@ -21,11 +22,11 @@ class CaseSensitivityTest(unittest.TestCase):
assert name != name.lower()
def find(self, path):
- finder = machinery.FileFinder(path,
- (machinery.SourceFileLoader,
- machinery.SOURCE_SUFFIXES),
- (machinery.SourcelessFileLoader,
- machinery.BYTECODE_SUFFIXES))
+ finder = self.machinery.FileFinder(path,
+ (self.machinery.SourceFileLoader,
+ self.machinery.SOURCE_SUFFIXES),
+ (self.machinery.SourcelessFileLoader,
+ self.machinery.BYTECODE_SUFFIXES))
return finder.find_module(self.name)
def sensitivity_test(self):
@@ -41,7 +42,7 @@ class CaseSensitivityTest(unittest.TestCase):
def test_sensitive(self):
with test_support.EnvironmentVarGuard() as env:
env.unset('PYTHONCASEOK')
- if b'PYTHONCASEOK' in _bootstrap._os.environ:
+ if b'PYTHONCASEOK' in self.importlib._bootstrap._os.environ:
self.skipTest('os.environ changes not reflected in '
'_os.environ')
sensitive, insensitive = self.sensitivity_test()
@@ -52,7 +53,7 @@ class CaseSensitivityTest(unittest.TestCase):
def test_insensitive(self):
with test_support.EnvironmentVarGuard() as env:
env.set('PYTHONCASEOK', '1')
- if b'PYTHONCASEOK' not in _bootstrap._os.environ:
+ if b'PYTHONCASEOK' not in self.importlib._bootstrap._os.environ:
self.skipTest('os.environ changes not reflected in '
'_os.environ')
sensitive, insensitive = self.sensitivity_test()
@@ -61,10 +62,9 @@ class CaseSensitivityTest(unittest.TestCase):
self.assertTrue(hasattr(insensitive, 'load_module'))
self.assertIn(self.name, insensitive.get_filename(self.name))
-
-def test_main():
- test_support.run_unittest(CaseSensitivityTest)
+Frozen_CaseSensitivityTest, Source_CaseSensitivityTest = util.test_both(
+ CaseSensitivityTest, importlib=importlib, machinery=machinery)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/source/test_file_loader.py b/Lib/test/test_importlib/source/test_file_loader.py
index d59969a8ce..f1e2713a97 100644
--- a/Lib/test/test_importlib/source/test_file_loader.py
+++ b/Lib/test/test_importlib/source/test_file_loader.py
@@ -1,24 +1,26 @@
-from importlib import machinery
-import importlib
-import importlib.abc
from .. import abc
from .. import util
from . import util as source_util
+importlib = util.import_importlib('importlib')
+importlib_abc = util.import_importlib('importlib.abc')
+machinery = util.import_importlib('importlib.machinery')
+importlib_util = util.import_importlib('importlib.util')
+
import errno
-import imp
import marshal
import os
import py_compile
import shutil
import stat
import sys
+import types
import unittest
-from test.support import make_legacy_pyc
+from test.support import make_legacy_pyc, unload
-class SimpleTest(unittest.TestCase):
+class SimpleTest(abc.LoaderTests):
"""Should have no issue importing a source module [basic]. And if there is
a syntax error, it should raise a SyntaxError [syntax error].
@@ -26,27 +28,17 @@ class SimpleTest(unittest.TestCase):
"""
def test_load_module_API(self):
- # If fullname is not specified that assume self.name is desired.
- class TesterMixin(importlib.abc.Loader):
- def load_module(self, fullname): return fullname
- def module_repr(self, module): return '<module>'
-
- class Tester(importlib.abc.FileLoader, TesterMixin):
- def get_code(self, _): pass
- def get_source(self, _): pass
- def is_package(self, _): pass
+ class Tester(self.abc.FileLoader):
+ def get_source(self, _): return 'attr = 42'
+ def is_package(self, _): return False
- name = 'mod_name'
- loader = Tester(name, 'some_path')
- self.assertEqual(name, loader.load_module())
- self.assertEqual(name, loader.load_module(None))
- self.assertEqual(name, loader.load_module(name))
- with self.assertRaises(ImportError):
- loader.load_module(loader.name + 'XXX')
+ loader = Tester('blah', 'blah.py')
+ self.addCleanup(unload, 'blah')
+ module = loader.load_module() # Should not raise an exception.
def test_get_filename_API(self):
# If fullname is not set then assume self.path is desired.
- class Tester(importlib.abc.FileLoader):
+ class Tester(self.abc.FileLoader):
def get_code(self, _): pass
def get_source(self, _): pass
def is_package(self, _): pass
@@ -64,7 +56,7 @@ class SimpleTest(unittest.TestCase):
# [basic]
def test_module(self):
with source_util.create_modules('_temp') as mapping:
- loader = machinery.SourceFileLoader('_temp', mapping['_temp'])
+ loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
module = loader.load_module('_temp')
self.assertIn('_temp', sys.modules)
check = {'__name__': '_temp', '__file__': mapping['_temp'],
@@ -74,7 +66,7 @@ class SimpleTest(unittest.TestCase):
def test_package(self):
with source_util.create_modules('_pkg.__init__') as mapping:
- loader = machinery.SourceFileLoader('_pkg',
+ loader = self.machinery.SourceFileLoader('_pkg',
mapping['_pkg.__init__'])
module = loader.load_module('_pkg')
self.assertIn('_pkg', sys.modules)
@@ -87,7 +79,7 @@ class SimpleTest(unittest.TestCase):
def test_lacking_parent(self):
with source_util.create_modules('_pkg.__init__', '_pkg.mod')as mapping:
- loader = machinery.SourceFileLoader('_pkg.mod',
+ loader = self.machinery.SourceFileLoader('_pkg.mod',
mapping['_pkg.mod'])
module = loader.load_module('_pkg.mod')
self.assertIn('_pkg.mod', sys.modules)
@@ -102,7 +94,7 @@ class SimpleTest(unittest.TestCase):
def test_module_reuse(self):
with source_util.create_modules('_temp') as mapping:
- loader = machinery.SourceFileLoader('_temp', mapping['_temp'])
+ loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
module = loader.load_module('_temp')
module_id = id(module)
module_dict_id = id(module.__dict__)
@@ -122,12 +114,12 @@ class SimpleTest(unittest.TestCase):
value = '<test>'
name = '_temp'
with source_util.create_modules(name) as mapping:
- orig_module = imp.new_module(name)
+ orig_module = types.ModuleType(name)
for attr in attributes:
setattr(orig_module, attr, value)
with open(mapping[name], 'w') as file:
file.write('+++ bad syntax +++')
- loader = machinery.SourceFileLoader('_temp', mapping['_temp'])
+ loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
with self.assertRaises(SyntaxError):
loader.load_module(name)
for attr in attributes:
@@ -138,7 +130,7 @@ class SimpleTest(unittest.TestCase):
with source_util.create_modules('_temp') as mapping:
with open(mapping['_temp'], 'w') as file:
file.write('=')
- loader = machinery.SourceFileLoader('_temp', mapping['_temp'])
+ loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
with self.assertRaises(SyntaxError):
loader.load_module('_temp')
self.assertNotIn('_temp', sys.modules)
@@ -151,14 +143,14 @@ class SimpleTest(unittest.TestCase):
file.write("# test file for importlib")
try:
with util.uncache('_temp'):
- loader = machinery.SourceFileLoader('_temp', file_path)
+ loader = self.machinery.SourceFileLoader('_temp', file_path)
mod = loader.load_module('_temp')
self.assertEqual(file_path, mod.__file__)
- self.assertEqual(imp.cache_from_source(file_path),
+ self.assertEqual(self.util.cache_from_source(file_path),
mod.__cached__)
finally:
os.unlink(file_path)
- pycache = os.path.dirname(imp.cache_from_source(file_path))
+ pycache = os.path.dirname(self.util.cache_from_source(file_path))
if os.path.exists(pycache):
shutil.rmtree(pycache)
@@ -167,7 +159,7 @@ class SimpleTest(unittest.TestCase):
# truncated rather than raise an OverflowError.
with source_util.create_modules('_temp') as mapping:
source = mapping['_temp']
- compiled = imp.cache_from_source(source)
+ compiled = self.util.cache_from_source(source)
with open(source, 'w') as f:
f.write("x = 5")
try:
@@ -178,7 +170,7 @@ class SimpleTest(unittest.TestCase):
if e.errno != getattr(errno, 'EOVERFLOW', None):
raise
self.skipTest("cannot set modification time to large integer ({})".format(e))
- loader = machinery.SourceFileLoader('_temp', mapping['_temp'])
+ loader = self.machinery.SourceFileLoader('_temp', mapping['_temp'])
mod = loader.load_module('_temp')
# Sanity checks.
self.assertEqual(mod.__cached__, compiled)
@@ -186,8 +178,17 @@ class SimpleTest(unittest.TestCase):
# The pyc file was created.
os.stat(compiled)
+ def test_unloadable(self):
+ loader = self.machinery.SourceFileLoader('good name', {})
+ with self.assertRaises(ImportError):
+ loader.load_module('bad name')
+
+Frozen_SimpleTest, Source_SimpleTest = util.test_both(
+ SimpleTest, importlib=importlib, machinery=machinery, abc=importlib_abc,
+ util=importlib_util)
+
-class BadBytecodeTest(unittest.TestCase):
+class BadBytecodeTest:
def import_(self, file, module_name):
loader = self.loader(module_name, file)
@@ -204,7 +205,7 @@ class BadBytecodeTest(unittest.TestCase):
pass
py_compile.compile(mapping[name])
if not del_source:
- bytecode_path = imp.cache_from_source(mapping[name])
+ bytecode_path = self.util.cache_from_source(mapping[name])
else:
os.unlink(mapping[name])
bytecode_path = make_legacy_pyc(mapping[name])
@@ -293,7 +294,9 @@ class BadBytecodeTest(unittest.TestCase):
class SourceLoaderBadBytecodeTest(BadBytecodeTest):
- loader = machinery.SourceFileLoader
+ @classmethod
+ def setUpClass(cls):
+ cls.loader = cls.machinery.SourceFileLoader
@source_util.writes_bytecode_files
def test_empty_file(self):
@@ -332,7 +335,8 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
def test(name, mapping, bytecode_path):
self.import_(mapping[name], name)
with open(bytecode_path, 'rb') as bytecode_file:
- self.assertEqual(bytecode_file.read(4), imp.get_magic())
+ self.assertEqual(bytecode_file.read(4),
+ self.util.MAGIC_NUMBER)
self._test_bad_magic(test)
@@ -382,13 +386,13 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
zeros = b'\x00\x00\x00\x00'
with source_util.create_modules('_temp') as mapping:
py_compile.compile(mapping['_temp'])
- bytecode_path = imp.cache_from_source(mapping['_temp'])
+ bytecode_path = self.util.cache_from_source(mapping['_temp'])
with open(bytecode_path, 'r+b') as bytecode_file:
bytecode_file.seek(4)
bytecode_file.write(zeros)
self.import_(mapping['_temp'], '_temp')
source_mtime = os.path.getmtime(mapping['_temp'])
- source_timestamp = importlib._w_long(source_mtime)
+ source_timestamp = self.importlib._w_long(source_mtime)
with open(bytecode_path, 'rb') as bytecode_file:
bytecode_file.seek(4)
self.assertEqual(bytecode_file.read(4), source_timestamp)
@@ -400,7 +404,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
with source_util.create_modules('_temp') as mapping:
# Create bytecode that will need to be re-created.
py_compile.compile(mapping['_temp'])
- bytecode_path = imp.cache_from_source(mapping['_temp'])
+ bytecode_path = self.util.cache_from_source(mapping['_temp'])
with open(bytecode_path, 'r+b') as bytecode_file:
bytecode_file.seek(0)
bytecode_file.write(b'\x00\x00\x00\x00')
@@ -408,16 +412,22 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
os.chmod(bytecode_path,
stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
try:
- # Should not raise IOError!
+ # Should not raise OSError!
self.import_(mapping['_temp'], '_temp')
finally:
# Make writable for eventual clean-up.
os.chmod(bytecode_path, stat.S_IWUSR)
+Frozen_SourceBadBytecode, Source_SourceBadBytecode = util.test_both(
+ SourceLoaderBadBytecodeTest, importlib=importlib, machinery=machinery,
+ abc=importlib_abc, util=importlib_util)
+
class SourcelessLoaderBadBytecodeTest(BadBytecodeTest):
- loader = machinery.SourcelessFileLoader
+ @classmethod
+ def setUpClass(cls):
+ cls.loader = cls.machinery.SourcelessFileLoader
def test_empty_file(self):
def test(name, mapping, bytecode_path):
@@ -472,14 +482,10 @@ class SourcelessLoaderBadBytecodeTest(BadBytecodeTest):
def test_non_code_marshal(self):
self._test_non_code_marshal(del_source=True)
-
-def test_main():
- from test.support import run_unittest
- run_unittest(SimpleTest,
- SourceLoaderBadBytecodeTest,
- SourcelessLoaderBadBytecodeTest
- )
+Frozen_SourcelessBadBytecode, Source_SourcelessBadBytecode = util.test_both(
+ SourcelessLoaderBadBytecodeTest, importlib=importlib,
+ machinery=machinery, abc=importlib_abc, util=importlib_util)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/source/test_finder.py b/Lib/test/test_importlib/source/test_finder.py
index 8e4986835d..8bf8cb7880 100644
--- a/Lib/test/test_importlib/source/test_finder.py
+++ b/Lib/test/test_importlib/source/test_finder.py
@@ -1,9 +1,10 @@
from .. import abc
+from .. import util
from . import util as source_util
-from importlib import machinery
+machinery = util.import_importlib('importlib.machinery')
+
import errno
-import imp
import os
import py_compile
import stat
@@ -39,11 +40,11 @@ class FinderTests(abc.FinderTests):
"""
def get_finder(self, root):
- loader_details = [(machinery.SourceFileLoader,
- machinery.SOURCE_SUFFIXES),
- (machinery.SourcelessFileLoader,
- machinery.BYTECODE_SUFFIXES)]
- return machinery.FileFinder(root, *loader_details)
+ loader_details = [(self.machinery.SourceFileLoader,
+ self.machinery.SOURCE_SUFFIXES),
+ (self.machinery.SourcelessFileLoader,
+ self.machinery.BYTECODE_SUFFIXES)]
+ return self.machinery.FileFinder(root, *loader_details)
def import_(self, root, module):
return self.get_finder(root).find_module(module)
@@ -124,8 +125,8 @@ class FinderTests(abc.FinderTests):
def test_empty_string_for_dir(self):
# The empty string from sys.path means to search in the cwd.
- finder = machinery.FileFinder('', (machinery.SourceFileLoader,
- machinery.SOURCE_SUFFIXES))
+ finder = self.machinery.FileFinder('', (self.machinery.SourceFileLoader,
+ self.machinery.SOURCE_SUFFIXES))
with open('mod.py', 'w') as file:
file.write("# test file for importlib")
try:
@@ -136,8 +137,8 @@ class FinderTests(abc.FinderTests):
def test_invalidate_caches(self):
# invalidate_caches() should reset the mtime.
- finder = machinery.FileFinder('', (machinery.SourceFileLoader,
- machinery.SOURCE_SUFFIXES))
+ finder = self.machinery.FileFinder('', (self.machinery.SourceFileLoader,
+ self.machinery.SOURCE_SUFFIXES))
finder._path_mtime = 42
finder.invalidate_caches()
self.assertEqual(finder._path_mtime, -1)
@@ -181,11 +182,9 @@ class FinderTests(abc.FinderTests):
finder = self.get_finder(file_obj.name)
self.assertEqual((None, []), finder.find_loader('doesnotexist'))
+Frozen_FinderTests, Source_FinderTests = util.test_both(FinderTests, machinery=machinery)
-def test_main():
- from test.support import run_unittest
- run_unittest(FinderTests)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/source/test_path_hook.py b/Lib/test/test_importlib/source/test_path_hook.py
index 6a78792f07..92da77265b 100644
--- a/Lib/test/test_importlib/source/test_path_hook.py
+++ b/Lib/test/test_importlib/source/test_path_hook.py
@@ -1,17 +1,18 @@
+from .. import util
from . import util as source_util
-from importlib import machinery
-import imp
+machinery = util.import_importlib('importlib.machinery')
+
import unittest
-class PathHookTest(unittest.TestCase):
+class PathHookTest:
"""Test the path hook for source."""
def path_hook(self):
- return machinery.FileFinder.path_hook((machinery.SourceFileLoader,
- machinery.SOURCE_SUFFIXES))
+ return self.machinery.FileFinder.path_hook((self.machinery.SourceFileLoader,
+ self.machinery.SOURCE_SUFFIXES))
def test_success(self):
with source_util.create_modules('dummy') as mapping:
@@ -22,11 +23,8 @@ class PathHookTest(unittest.TestCase):
# The empty string represents the cwd.
self.assertTrue(hasattr(self.path_hook()(''), 'find_module'))
-
-def test_main():
- from test.support import run_unittest
- run_unittest(PathHookTest)
+Frozen_PathHookTest, Source_PathHooktest = util.test_both(PathHookTest, machinery=machinery)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/source/test_source_encoding.py b/Lib/test/test_importlib/source/test_source_encoding.py
index ba02b44274..654f4c2b2f 100644
--- a/Lib/test/test_importlib/source/test_source_encoding.py
+++ b/Lib/test/test_importlib/source/test_source_encoding.py
@@ -1,6 +1,8 @@
+from .. import util
from . import util as source_util
-from importlib import _bootstrap
+machinery = util.import_importlib('importlib.machinery')
+
import codecs
import re
import sys
@@ -13,7 +15,7 @@ import unittest
CODING_RE = re.compile(r'^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)', re.ASCII)
-class EncodingTest(unittest.TestCase):
+class EncodingTest:
"""PEP 3120 makes UTF-8 the default encoding for source code
[default encoding].
@@ -35,7 +37,7 @@ class EncodingTest(unittest.TestCase):
with source_util.create_modules(self.module_name) as mapping:
with open(mapping[self.module_name], 'wb') as file:
file.write(source)
- loader = _bootstrap.SourceFileLoader(self.module_name,
+ loader = self.machinery.SourceFileLoader(self.module_name,
mapping[self.module_name])
return loader.load_module(self.module_name)
@@ -84,8 +86,10 @@ class EncodingTest(unittest.TestCase):
with self.assertRaises(SyntaxError):
self.run_test(source)
+Frozen_EncodingTest, Source_EncodingTest = util.test_both(EncodingTest, machinery=machinery)
+
-class LineEndingTest(unittest.TestCase):
+class LineEndingTest:
r"""Source written with the three types of line endings (\n, \r\n, \r)
need to be readable [cr][crlf][lf]."""
@@ -97,7 +101,7 @@ class LineEndingTest(unittest.TestCase):
with source_util.create_modules(module_name) as mapping:
with open(mapping[module_name], 'wb') as file:
file.write(source)
- loader = _bootstrap.SourceFileLoader(module_name,
+ loader = self.machinery.SourceFileLoader(module_name,
mapping[module_name])
return loader.load_module(module_name)
@@ -113,11 +117,9 @@ class LineEndingTest(unittest.TestCase):
def test_lf(self):
self.run_test(b'\n')
+Frozen_LineEndings, Source_LineEndings = util.test_both(LineEndingTest, machinery=machinery)
-def test_main():
- from test.support import run_unittest
- run_unittest(EncodingTest, LineEndingTest)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/source/util.py b/Lib/test/test_importlib/source/util.py
index ae65663a67..63cd25adc4 100644
--- a/Lib/test/test_importlib/source/util.py
+++ b/Lib/test/test_importlib/source/util.py
@@ -2,7 +2,6 @@ from .. import util
import contextlib
import errno
import functools
-import imp
import os
import os.path
import sys
diff --git a/Lib/test/test_importlib/test_abc.py b/Lib/test/test_importlib/test_abc.py
index c620c3771b..ba8d60542b 100644
--- a/Lib/test/test_importlib/test_abc.py
+++ b/Lib/test/test_importlib/test_abc.py
@@ -1,9 +1,21 @@
-from importlib import abc
-from importlib import machinery
+import contextlib
import inspect
+import io
+import marshal
+import os
+import sys
+from test import support
+import types
import unittest
+from unittest import mock
+from . import util
+frozen_init, source_init = util.import_importlib('importlib')
+frozen_abc, source_abc = util.import_importlib('importlib.abc')
+frozen_util, source_util = util.import_importlib('importlib.util')
+
+##### Inheritance ##############################################################
class InheritanceTests:
"""Test that the specified class is a subclass/superclass of the expected
@@ -14,8 +26,19 @@ class InheritanceTests:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ self.superclasses = [getattr(self.abc, class_name)
+ for class_name in self.superclass_names]
+ if hasattr(self, 'subclass_names'):
+ # Because test.support.import_fresh_module() creates a new
+ # importlib._bootstrap per module, inheritance checks fail when
+ # checking across module boundaries (i.e. the _bootstrap in abc is
+ # not the same as the one in machinery). That means stealing one of
+ # the modules from the other to make sure the same instance is used.
+ self.subclasses = [getattr(self.abc.machinery, class_name)
+ for class_name in self.subclass_names]
assert self.subclasses or self.superclasses, self.__class__
- self.__test = getattr(abc, self.__class__.__name__)
+ testing = self.__class__.__name__.partition('_')[2]
+ self.__test = getattr(self.abc, testing)
def test_subclasses(self):
# Test that the expected subclasses inherit.
@@ -29,75 +52,761 @@ class InheritanceTests:
self.assertTrue(issubclass(self.__test, superclass),
"{0} is not a superclass of {1}".format(superclass, self.__test))
+def create_inheritance_tests(base_class):
+ def set_frozen(ns):
+ ns['abc'] = frozen_abc
+ def set_source(ns):
+ ns['abc'] = source_abc
-class MetaPathFinder(InheritanceTests, unittest.TestCase):
+ classes = []
+ for prefix, ns_set in [('Frozen', set_frozen), ('Source', set_source)]:
+ classes.append(types.new_class('_'.join([prefix, base_class.__name__]),
+ (base_class, unittest.TestCase),
+ exec_body=ns_set))
+ return classes
- superclasses = [abc.Finder]
- subclasses = [machinery.BuiltinImporter, machinery.FrozenImporter,
- machinery.PathFinder, machinery.WindowsRegistryFinder]
+class MetaPathFinder(InheritanceTests):
+ superclass_names = ['Finder']
+ subclass_names = ['BuiltinImporter', 'FrozenImporter', 'PathFinder',
+ 'WindowsRegistryFinder']
-class PathEntryFinder(InheritanceTests, unittest.TestCase):
+tests = create_inheritance_tests(MetaPathFinder)
+Frozen_MetaPathFinderInheritanceTests, Source_MetaPathFinderInheritanceTests = tests
- superclasses = [abc.Finder]
- subclasses = [machinery.FileFinder]
+class PathEntryFinder(InheritanceTests):
+ superclass_names = ['Finder']
+ subclass_names = ['FileFinder']
-class Loader(InheritanceTests, unittest.TestCase):
+tests = create_inheritance_tests(PathEntryFinder)
+Frozen_PathEntryFinderInheritanceTests, Source_PathEntryFinderInheritanceTests = tests
- subclasses = [abc.PyLoader]
+class ResourceLoader(InheritanceTests):
+ superclass_names = ['Loader']
-class ResourceLoader(InheritanceTests, unittest.TestCase):
+tests = create_inheritance_tests(ResourceLoader)
+Frozen_ResourceLoaderInheritanceTests, Source_ResourceLoaderInheritanceTests = tests
- superclasses = [abc.Loader]
+class InspectLoader(InheritanceTests):
+ superclass_names = ['Loader']
+ subclass_names = ['BuiltinImporter', 'FrozenImporter', 'ExtensionFileLoader']
-class InspectLoader(InheritanceTests, unittest.TestCase):
+tests = create_inheritance_tests(InspectLoader)
+Frozen_InspectLoaderInheritanceTests, Source_InspectLoaderInheritanceTests = tests
- superclasses = [abc.Loader]
- subclasses = [abc.PyLoader, machinery.BuiltinImporter,
- machinery.FrozenImporter, machinery.ExtensionFileLoader]
+class ExecutionLoader(InheritanceTests):
+ superclass_names = ['InspectLoader']
+ subclass_names = ['ExtensionFileLoader']
-class ExecutionLoader(InheritanceTests, unittest.TestCase):
+tests = create_inheritance_tests(ExecutionLoader)
+Frozen_ExecutionLoaderInheritanceTests, Source_ExecutionLoaderInheritanceTests = tests
- superclasses = [abc.InspectLoader]
- subclasses = [abc.PyLoader]
+class FileLoader(InheritanceTests):
+ superclass_names = ['ResourceLoader', 'ExecutionLoader']
+ subclass_names = ['SourceFileLoader', 'SourcelessFileLoader']
-class FileLoader(InheritanceTests, unittest.TestCase):
+tests = create_inheritance_tests(FileLoader)
+Frozen_FileLoaderInheritanceTests, Source_FileLoaderInheritanceTests = tests
- superclasses = [abc.ResourceLoader, abc.ExecutionLoader]
- subclasses = [machinery.SourceFileLoader, machinery.SourcelessFileLoader]
+class SourceLoader(InheritanceTests):
+ superclass_names = ['ResourceLoader', 'ExecutionLoader']
+ subclass_names = ['SourceFileLoader']
-class SourceLoader(InheritanceTests, unittest.TestCase):
+tests = create_inheritance_tests(SourceLoader)
+Frozen_SourceLoaderInheritanceTests, Source_SourceLoaderInheritanceTests = tests
- superclasses = [abc.ResourceLoader, abc.ExecutionLoader]
- subclasses = [machinery.SourceFileLoader]
+##### Default return values ####################################################
+def make_abc_subclasses(base_class):
+ classes = []
+ for kind, abc in [('Frozen', frozen_abc), ('Source', source_abc)]:
+ name = '_'.join([kind, base_class.__name__])
+ base_classes = base_class, getattr(abc, base_class.__name__)
+ classes.append(types.new_class(name, base_classes))
+ return classes
+def make_return_value_tests(base_class, test_class):
+ frozen_class, source_class = make_abc_subclasses(base_class)
+ tests = []
+ for prefix, class_in_test in [('Frozen', frozen_class), ('Source', source_class)]:
+ def set_ns(ns):
+ ns['ins'] = class_in_test()
+ tests.append(types.new_class('_'.join([prefix, test_class.__name__]),
+ (test_class, unittest.TestCase),
+ exec_body=set_ns))
+ return tests
-class PyLoader(InheritanceTests, unittest.TestCase):
- superclasses = [abc.Loader, abc.ResourceLoader, abc.ExecutionLoader]
+class MetaPathFinder:
+ def find_module(self, fullname, path):
+ return super().find_module(fullname, path)
-class PyPycLoader(InheritanceTests, unittest.TestCase):
+Frozen_MPF, Source_MPF = make_abc_subclasses(MetaPathFinder)
- superclasses = [abc.PyLoader]
+class MetaPathFinderDefaultsTests:
-def test_main():
- from test.support import run_unittest
- classes = []
- for class_ in globals().values():
- if (inspect.isclass(class_) and
- issubclass(class_, unittest.TestCase) and
- issubclass(class_, InheritanceTests)):
- classes.append(class_)
- run_unittest(*classes)
+ def test_find_module(self):
+ # Default should return None.
+ self.assertIsNone(self.ins.find_module('something', None))
+
+ def test_invalidate_caches(self):
+ # Calling the method is a no-op.
+ self.ins.invalidate_caches()
+
+
+tests = make_return_value_tests(MetaPathFinder, MetaPathFinderDefaultsTests)
+Frozen_MPFDefaultTests, Source_MPFDefaultTests = tests
+
+
+class PathEntryFinder:
+
+ def find_loader(self, fullname):
+ return super().find_loader(fullname)
+
+Frozen_PEF, Source_PEF = make_abc_subclasses(PathEntryFinder)
+
+
+class PathEntryFinderDefaultsTests:
+
+ def test_find_loader(self):
+ self.assertEqual((None, []), self.ins.find_loader('something'))
+
+ def find_module(self):
+ self.assertEqual(None, self.ins.find_module('something'))
+
+ def test_invalidate_caches(self):
+ # Should be a no-op.
+ self.ins.invalidate_caches()
+
+
+tests = make_return_value_tests(PathEntryFinder, PathEntryFinderDefaultsTests)
+Frozen_PEFDefaultTests, Source_PEFDefaultTests = tests
+
+
+class Loader:
+
+ def load_module(self, fullname):
+ return super().load_module(fullname)
+
+
+Frozen_L, Source_L = make_abc_subclasses(Loader)
+
+
+class LoaderDefaultsTests:
+
+ def test_load_module(self):
+ with self.assertRaises(ImportError):
+ self.ins.load_module('something')
+
+ def test_module_repr(self):
+ mod = types.ModuleType('blah')
+ with self.assertRaises(NotImplementedError):
+ self.ins.module_repr(mod)
+ original_repr = repr(mod)
+ mod.__loader__ = self.ins
+ # Should still return a proper repr.
+ self.assertTrue(repr(mod))
+
+
+tests = make_return_value_tests(Loader, LoaderDefaultsTests)
+Frozen_LDefaultTests, SourceLDefaultTests = tests
+
+
+class ResourceLoader(Loader):
+
+ def get_data(self, path):
+ return super().get_data(path)
+
+
+Frozen_RL, Source_RL = make_abc_subclasses(ResourceLoader)
+
+
+class ResourceLoaderDefaultsTests:
+
+ def test_get_data(self):
+ with self.assertRaises(IOError):
+ self.ins.get_data('/some/path')
+
+
+tests = make_return_value_tests(ResourceLoader, ResourceLoaderDefaultsTests)
+Frozen_RLDefaultTests, Source_RLDefaultTests = tests
+
+
+class InspectLoader(Loader):
+
+ def is_package(self, fullname):
+ return super().is_package(fullname)
+
+ def get_source(self, fullname):
+ return super().get_source(fullname)
+
+
+Frozen_IL, Source_IL = make_abc_subclasses(InspectLoader)
+
+
+class InspectLoaderDefaultsTests:
+
+ def test_is_package(self):
+ with self.assertRaises(ImportError):
+ self.ins.is_package('blah')
+
+ def test_get_source(self):
+ with self.assertRaises(ImportError):
+ self.ins.get_source('blah')
+
+
+tests = make_return_value_tests(InspectLoader, InspectLoaderDefaultsTests)
+Frozen_ILDefaultTests, Source_ILDefaultTests = tests
+
+
+class ExecutionLoader(InspectLoader):
+
+ def get_filename(self, fullname):
+ return super().get_filename(fullname)
+
+Frozen_EL, Source_EL = make_abc_subclasses(ExecutionLoader)
+
+
+class ExecutionLoaderDefaultsTests:
+
+ def test_get_filename(self):
+ with self.assertRaises(ImportError):
+ self.ins.get_filename('blah')
+
+
+tests = make_return_value_tests(ExecutionLoader, InspectLoaderDefaultsTests)
+Frozen_ELDefaultTests, Source_ELDefaultsTests = tests
+
+
+##### InspectLoader concrete methods ###########################################
+class InspectLoaderSourceToCodeTests:
+
+ def source_to_module(self, data, path=None):
+ """Help with source_to_code() tests."""
+ module = types.ModuleType('blah')
+ loader = self.InspectLoaderSubclass()
+ if path is None:
+ code = loader.source_to_code(data)
+ else:
+ code = loader.source_to_code(data, path)
+ exec(code, module.__dict__)
+ return module
+
+ def test_source_to_code_source(self):
+ # Since compile() can handle strings, so should source_to_code().
+ source = 'attr = 42'
+ module = self.source_to_module(source)
+ self.assertTrue(hasattr(module, 'attr'))
+ self.assertEqual(module.attr, 42)
+
+ def test_source_to_code_bytes(self):
+ # Since compile() can handle bytes, so should source_to_code().
+ source = b'attr = 42'
+ module = self.source_to_module(source)
+ self.assertTrue(hasattr(module, 'attr'))
+ self.assertEqual(module.attr, 42)
+
+ def test_source_to_code_path(self):
+ # Specifying a path should set it for the code object.
+ path = 'path/to/somewhere'
+ loader = self.InspectLoaderSubclass()
+ code = loader.source_to_code('', path)
+ self.assertEqual(code.co_filename, path)
+
+ def test_source_to_code_no_path(self):
+ # Not setting a path should still work and be set to <string> since that
+ # is a pre-existing practice as a default to compile().
+ loader = self.InspectLoaderSubclass()
+ code = loader.source_to_code('')
+ self.assertEqual(code.co_filename, '<string>')
+
+
+class Frozen_ILSourceToCodeTests(InspectLoaderSourceToCodeTests, unittest.TestCase):
+ InspectLoaderSubclass = Frozen_IL
+
+class Source_ILSourceToCodeTests(InspectLoaderSourceToCodeTests, unittest.TestCase):
+ InspectLoaderSubclass = Source_IL
+
+
+class InspectLoaderGetCodeTests:
+
+ def test_get_code(self):
+ # Test success.
+ module = types.ModuleType('blah')
+ with mock.patch.object(self.InspectLoaderSubclass, 'get_source') as mocked:
+ mocked.return_value = 'attr = 42'
+ loader = self.InspectLoaderSubclass()
+ code = loader.get_code('blah')
+ exec(code, module.__dict__)
+ self.assertEqual(module.attr, 42)
+
+ def test_get_code_source_is_None(self):
+ # If get_source() is None then this should be None.
+ with mock.patch.object(self.InspectLoaderSubclass, 'get_source') as mocked:
+ mocked.return_value = None
+ loader = self.InspectLoaderSubclass()
+ code = loader.get_code('blah')
+ self.assertIsNone(code)
+
+ def test_get_code_source_not_found(self):
+ # If there is no source then there is no code object.
+ loader = self.InspectLoaderSubclass()
+ with self.assertRaises(ImportError):
+ loader.get_code('blah')
+
+
+class Frozen_ILGetCodeTests(InspectLoaderGetCodeTests, unittest.TestCase):
+ InspectLoaderSubclass = Frozen_IL
+
+class Source_ILGetCodeTests(InspectLoaderGetCodeTests, unittest.TestCase):
+ InspectLoaderSubclass = Source_IL
+
+
+class InspectLoaderLoadModuleTests:
+
+ """Test InspectLoader.load_module()."""
+
+ module_name = 'blah'
+
+ def setUp(self):
+ support.unload(self.module_name)
+ self.addCleanup(support.unload, self.module_name)
+
+ def mock_get_code(self):
+ return mock.patch.object(self.InspectLoaderSubclass, 'get_code')
+
+ def test_get_code_ImportError(self):
+ # If get_code() raises ImportError, it should propagate.
+ with self.mock_get_code() as mocked_get_code:
+ mocked_get_code.side_effect = ImportError
+ with self.assertRaises(ImportError):
+ loader = self.InspectLoaderSubclass()
+ loader.load_module(self.module_name)
+
+ def test_get_code_None(self):
+ # If get_code() returns None, raise ImportError.
+ with self.mock_get_code() as mocked_get_code:
+ mocked_get_code.return_value = None
+ with self.assertRaises(ImportError):
+ loader = self.InspectLoaderSubclass()
+ loader.load_module(self.module_name)
+
+ def test_module_returned(self):
+ # The loaded module should be returned.
+ code = compile('attr = 42', '<string>', 'exec')
+ with self.mock_get_code() as mocked_get_code:
+ mocked_get_code.return_value = code
+ loader = self.InspectLoaderSubclass()
+ module = loader.load_module(self.module_name)
+ self.assertEqual(module, sys.modules[self.module_name])
+
+
+class Frozen_ILLoadModuleTests(InspectLoaderLoadModuleTests, unittest.TestCase):
+ InspectLoaderSubclass = Frozen_IL
+
+class Source_ILLoadModuleTests(InspectLoaderLoadModuleTests, unittest.TestCase):
+ InspectLoaderSubclass = Source_IL
+
+
+##### ExecutionLoader concrete methods #########################################
+class ExecutionLoaderGetCodeTests:
+
+ def mock_methods(self, *, get_source=False, get_filename=False):
+ source_mock_context, filename_mock_context = None, None
+ if get_source:
+ source_mock_context = mock.patch.object(self.ExecutionLoaderSubclass,
+ 'get_source')
+ if get_filename:
+ filename_mock_context = mock.patch.object(self.ExecutionLoaderSubclass,
+ 'get_filename')
+ return source_mock_context, filename_mock_context
+
+ def test_get_code(self):
+ path = 'blah.py'
+ source_mock_context, filename_mock_context = self.mock_methods(
+ get_source=True, get_filename=True)
+ with source_mock_context as source_mock, filename_mock_context as name_mock:
+ source_mock.return_value = 'attr = 42'
+ name_mock.return_value = path
+ loader = self.ExecutionLoaderSubclass()
+ code = loader.get_code('blah')
+ self.assertEqual(code.co_filename, path)
+ module = types.ModuleType('blah')
+ exec(code, module.__dict__)
+ self.assertEqual(module.attr, 42)
+
+ def test_get_code_source_is_None(self):
+ # If get_source() is None then this should be None.
+ source_mock_context, _ = self.mock_methods(get_source=True)
+ with source_mock_context as mocked:
+ mocked.return_value = None
+ loader = self.ExecutionLoaderSubclass()
+ code = loader.get_code('blah')
+ self.assertIsNone(code)
+
+ def test_get_code_source_not_found(self):
+ # If there is no source then there is no code object.
+ loader = self.ExecutionLoaderSubclass()
+ with self.assertRaises(ImportError):
+ loader.get_code('blah')
+
+ def test_get_code_no_path(self):
+ # If get_filename() raises ImportError then simply skip setting the path
+ # on the code object.
+ source_mock_context, filename_mock_context = self.mock_methods(
+ get_source=True, get_filename=True)
+ with source_mock_context as source_mock, filename_mock_context as name_mock:
+ source_mock.return_value = 'attr = 42'
+ name_mock.side_effect = ImportError
+ loader = self.ExecutionLoaderSubclass()
+ code = loader.get_code('blah')
+ self.assertEqual(code.co_filename, '<string>')
+ module = types.ModuleType('blah')
+ exec(code, module.__dict__)
+ self.assertEqual(module.attr, 42)
+
+
+class Frozen_ELGetCodeTests(ExecutionLoaderGetCodeTests, unittest.TestCase):
+ ExecutionLoaderSubclass = Frozen_EL
+
+class Source_ELGetCodeTests(ExecutionLoaderGetCodeTests, unittest.TestCase):
+ ExecutionLoaderSubclass = Source_EL
+
+
+##### SourceLoader concrete methods ############################################
+class SourceLoader:
+
+ # Globals that should be defined for all modules.
+ source = (b"_ = '::'.join([__name__, __file__, __cached__, __package__, "
+ b"repr(__loader__)])")
+
+ def __init__(self, path):
+ self.path = path
+
+ def get_data(self, path):
+ if path != self.path:
+ raise IOError
+ return self.source
+
+ def get_filename(self, fullname):
+ return self.path
+
+ def module_repr(self, module):
+ return '<module>'
+
+
+Frozen_SourceOnlyL, Source_SourceOnlyL = make_abc_subclasses(SourceLoader)
+
+
+class SourceLoader(SourceLoader):
+
+ source_mtime = 1
+
+ def __init__(self, path, magic=None):
+ super().__init__(path)
+ self.bytecode_path = self.util.cache_from_source(self.path)
+ self.source_size = len(self.source)
+ if magic is None:
+ magic = self.util.MAGIC_NUMBER
+ data = bytearray(magic)
+ data.extend(self.init._w_long(self.source_mtime))
+ data.extend(self.init._w_long(self.source_size))
+ code_object = compile(self.source, self.path, 'exec',
+ dont_inherit=True)
+ data.extend(marshal.dumps(code_object))
+ self.bytecode = bytes(data)
+ self.written = {}
+
+ def get_data(self, path):
+ if path == self.path:
+ return super().get_data(path)
+ elif path == self.bytecode_path:
+ return self.bytecode
+ else:
+ raise OSError
+
+ def path_stats(self, path):
+ if path != self.path:
+ raise IOError
+ return {'mtime': self.source_mtime, 'size': self.source_size}
+
+ def set_data(self, path, data):
+ self.written[path] = bytes(data)
+ return path == self.bytecode_path
+
+
+Frozen_SL, Source_SL = make_abc_subclasses(SourceLoader)
+Frozen_SL.util = frozen_util
+Source_SL.util = source_util
+Frozen_SL.init = frozen_init
+Source_SL.init = source_init
+
+
+class SourceLoaderTestHarness:
+
+ def setUp(self, *, is_package=True, **kwargs):
+ self.package = 'pkg'
+ if is_package:
+ self.path = os.path.join(self.package, '__init__.py')
+ self.name = self.package
+ else:
+ module_name = 'mod'
+ self.path = os.path.join(self.package, '.'.join(['mod', 'py']))
+ self.name = '.'.join([self.package, module_name])
+ self.cached = self.util.cache_from_source(self.path)
+ self.loader = self.loader_mock(self.path, **kwargs)
+
+ def verify_module(self, module):
+ self.assertEqual(module.__name__, self.name)
+ self.assertEqual(module.__file__, self.path)
+ self.assertEqual(module.__cached__, self.cached)
+ self.assertEqual(module.__package__, self.package)
+ self.assertEqual(module.__loader__, self.loader)
+ values = module._.split('::')
+ self.assertEqual(values[0], self.name)
+ self.assertEqual(values[1], self.path)
+ self.assertEqual(values[2], self.cached)
+ self.assertEqual(values[3], self.package)
+ self.assertEqual(values[4], repr(self.loader))
+
+ def verify_code(self, code_object):
+ module = types.ModuleType(self.name)
+ module.__file__ = self.path
+ module.__cached__ = self.cached
+ module.__package__ = self.package
+ module.__loader__ = self.loader
+ module.__path__ = []
+ exec(code_object, module.__dict__)
+ self.verify_module(module)
+
+
+class SourceOnlyLoaderTests(SourceLoaderTestHarness):
+
+ """Test importlib.abc.SourceLoader for source-only loading.
+
+ Reload testing is subsumed by the tests for
+ importlib.util.module_for_loader.
+
+ """
+
+ def test_get_source(self):
+ # Verify the source code is returned as a string.
+ # If an OSError is raised by get_data then raise ImportError.
+ expected_source = self.loader.source.decode('utf-8')
+ self.assertEqual(self.loader.get_source(self.name), expected_source)
+ def raise_OSError(path):
+ raise OSError
+ self.loader.get_data = raise_OSError
+ with self.assertRaises(ImportError) as cm:
+ self.loader.get_source(self.name)
+ self.assertEqual(cm.exception.name, self.name)
+
+ def test_is_package(self):
+ # Properly detect when loading a package.
+ self.setUp(is_package=False)
+ self.assertFalse(self.loader.is_package(self.name))
+ self.setUp(is_package=True)
+ self.assertTrue(self.loader.is_package(self.name))
+ self.assertFalse(self.loader.is_package(self.name + '.__init__'))
+
+ def test_get_code(self):
+ # Verify the code object is created.
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+ def test_source_to_code(self):
+ # Verify the compiled code object.
+ code = self.loader.source_to_code(self.loader.source, self.path)
+ self.verify_code(code)
+
+ def test_load_module(self):
+ # Loading a module should set __name__, __loader__, __package__,
+ # __path__ (for packages), __file__, and __cached__.
+ # The module should also be put into sys.modules.
+ with util.uncache(self.name):
+ module = self.loader.load_module(self.name)
+ self.verify_module(module)
+ self.assertEqual(module.__path__, [os.path.dirname(self.path)])
+ self.assertIn(self.name, sys.modules)
+
+ def test_package_settings(self):
+ # __package__ needs to be set, while __path__ is set on if the module
+ # is a package.
+ # Testing the values for a package are covered by test_load_module.
+ self.setUp(is_package=False)
+ with util.uncache(self.name):
+ module = self.loader.load_module(self.name)
+ self.verify_module(module)
+ self.assertTrue(not hasattr(module, '__path__'))
+
+ def test_get_source_encoding(self):
+ # Source is considered encoded in UTF-8 by default unless otherwise
+ # specified by an encoding line.
+ source = "_ = 'ü'"
+ self.loader.source = source.encode('utf-8')
+ returned_source = self.loader.get_source(self.name)
+ self.assertEqual(returned_source, source)
+ source = "# coding: latin-1\n_ = ü"
+ self.loader.source = source.encode('latin-1')
+ returned_source = self.loader.get_source(self.name)
+ self.assertEqual(returned_source, source)
+
+
+class Frozen_SourceOnlyLTests(SourceOnlyLoaderTests, unittest.TestCase):
+ loader_mock = Frozen_SourceOnlyL
+ util = frozen_util
+
+class Source_SourceOnlyLTests(SourceOnlyLoaderTests, unittest.TestCase):
+ loader_mock = Source_SourceOnlyL
+ util = source_util
+
+
+@unittest.skipIf(sys.dont_write_bytecode, "sys.dont_write_bytecode is true")
+class SourceLoaderBytecodeTests(SourceLoaderTestHarness):
+
+ """Test importlib.abc.SourceLoader's use of bytecode.
+
+ Source-only testing handled by SourceOnlyLoaderTests.
+
+ """
+
+ def verify_code(self, code_object, *, bytecode_written=False):
+ super().verify_code(code_object)
+ if bytecode_written:
+ self.assertIn(self.cached, self.loader.written)
+ data = bytearray(self.util.MAGIC_NUMBER)
+ data.extend(self.init._w_long(self.loader.source_mtime))
+ data.extend(self.init._w_long(self.loader.source_size))
+ data.extend(marshal.dumps(code_object))
+ self.assertEqual(self.loader.written[self.cached], bytes(data))
+
+ def test_code_with_everything(self):
+ # When everything should work.
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+ def test_no_bytecode(self):
+ # If no bytecode exists then move on to the source.
+ self.loader.bytecode_path = "<does not exist>"
+ # Sanity check
+ with self.assertRaises(OSError):
+ bytecode_path = self.util.cache_from_source(self.path)
+ self.loader.get_data(bytecode_path)
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+
+ def test_code_bad_timestamp(self):
+ # Bytecode is only used when the timestamp matches the source EXACTLY.
+ for source_mtime in (0, 2):
+ assert source_mtime != self.loader.source_mtime
+ original = self.loader.source_mtime
+ self.loader.source_mtime = source_mtime
+ # If bytecode is used then EOFError would be raised by marshal.
+ self.loader.bytecode = self.loader.bytecode[8:]
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+ self.loader.source_mtime = original
+
+ def test_code_bad_magic(self):
+ # Skip over bytecode with a bad magic number.
+ self.setUp(magic=b'0000')
+ # If bytecode is used then EOFError would be raised by marshal.
+ self.loader.bytecode = self.loader.bytecode[8:]
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+
+ def test_dont_write_bytecode(self):
+ # Bytecode is not written if sys.dont_write_bytecode is true.
+ # Can assume it is false already thanks to the skipIf class decorator.
+ try:
+ sys.dont_write_bytecode = True
+ self.loader.bytecode_path = "<does not exist>"
+ code_object = self.loader.get_code(self.name)
+ self.assertNotIn(self.cached, self.loader.written)
+ finally:
+ sys.dont_write_bytecode = False
+
+ def test_no_set_data(self):
+ # If set_data is not defined, one can still read bytecode.
+ self.setUp(magic=b'0000')
+ original_set_data = self.loader.__class__.mro()[1].set_data
+ try:
+ del self.loader.__class__.mro()[1].set_data
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+ finally:
+ self.loader.__class__.mro()[1].set_data = original_set_data
+
+ def test_set_data_raises_exceptions(self):
+ # Raising NotImplementedError or OSError is okay for set_data.
+ def raise_exception(exc):
+ def closure(*args, **kwargs):
+ raise exc
+ return closure
+
+ self.setUp(magic=b'0000')
+ self.loader.set_data = raise_exception(NotImplementedError)
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+
+class Frozen_SLBytecodeTests(SourceLoaderBytecodeTests, unittest.TestCase):
+ loader_mock = Frozen_SL
+ init = frozen_init
+ util = frozen_util
+
+class SourceSLBytecodeTests(SourceLoaderBytecodeTests, unittest.TestCase):
+ loader_mock = Source_SL
+ init = source_init
+ util = source_util
+
+
+class SourceLoaderGetSourceTests:
+
+ """Tests for importlib.abc.SourceLoader.get_source()."""
+
+ def test_default_encoding(self):
+ # Should have no problems with UTF-8 text.
+ name = 'mod'
+ mock = self.SourceOnlyLoaderMock('mod.file')
+ source = 'x = "ü"'
+ mock.source = source.encode('utf-8')
+ returned_source = mock.get_source(name)
+ self.assertEqual(returned_source, source)
+
+ def test_decoded_source(self):
+ # Decoding should work.
+ name = 'mod'
+ mock = self.SourceOnlyLoaderMock("mod.file")
+ source = "# coding: Latin-1\nx='ü'"
+ assert source.encode('latin-1') != source.encode('utf-8')
+ mock.source = source.encode('latin-1')
+ returned_source = mock.get_source(name)
+ self.assertEqual(returned_source, source)
+
+ def test_universal_newlines(self):
+ # PEP 302 says universal newlines should be used.
+ name = 'mod'
+ mock = self.SourceOnlyLoaderMock('mod.file')
+ source = "x = 42\r\ny = -13\r\n"
+ mock.source = source.encode('utf-8')
+ expect = io.IncrementalNewlineDecoder(None, True).decode(source)
+ self.assertEqual(mock.get_source(name), expect)
+
+
+class Frozen_SourceOnlyLGetSourceTests(SourceLoaderGetSourceTests, unittest.TestCase):
+ SourceOnlyLoaderMock = Frozen_SourceOnlyL
+
+class Source_SourceOnlyLGetSourceTests(SourceLoaderGetSourceTests, unittest.TestCase):
+ SourceOnlyLoaderMock = Source_SourceOnlyL
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py
index b1a58944f3..c6c2d47051 100644
--- a/Lib/test/test_importlib/test_api.py
+++ b/Lib/test/test_importlib/test_api.py
@@ -1,14 +1,17 @@
from . import util
-import imp
-import importlib
-from importlib import machinery
+
+frozen_init, source_init = util.import_importlib('importlib')
+frozen_util, source_util = util.import_importlib('importlib.util')
+frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
+
+import os.path
import sys
from test import support
import types
import unittest
-class ImportModuleTests(unittest.TestCase):
+class ImportModuleTests:
"""Test importlib.import_module."""
@@ -16,7 +19,7 @@ class ImportModuleTests(unittest.TestCase):
# Test importing a top-level module.
with util.mock_modules('top_level') as mock:
with util.import_state(meta_path=[mock]):
- module = importlib.import_module('top_level')
+ module = self.init.import_module('top_level')
self.assertEqual(module.__name__, 'top_level')
def test_absolute_package_import(self):
@@ -26,7 +29,7 @@ class ImportModuleTests(unittest.TestCase):
name = '{0}.mod'.format(pkg_name)
with util.mock_modules(pkg_long_name, name) as mock:
with util.import_state(meta_path=[mock]):
- module = importlib.import_module(name)
+ module = self.init.import_module(name)
self.assertEqual(module.__name__, name)
def test_shallow_relative_package_import(self):
@@ -38,17 +41,17 @@ class ImportModuleTests(unittest.TestCase):
relative_name = '.{0}'.format(module_name)
with util.mock_modules(pkg_long_name, absolute_name) as mock:
with util.import_state(meta_path=[mock]):
- importlib.import_module(pkg_name)
- module = importlib.import_module(relative_name, pkg_name)
+ self.init.import_module(pkg_name)
+ module = self.init.import_module(relative_name, pkg_name)
self.assertEqual(module.__name__, absolute_name)
def test_deep_relative_package_import(self):
modules = ['a.__init__', 'a.b.__init__', 'a.c']
with util.mock_modules(*modules) as mock:
with util.import_state(meta_path=[mock]):
- importlib.import_module('a')
- importlib.import_module('a.b')
- module = importlib.import_module('..c', 'a.b')
+ self.init.import_module('a')
+ self.init.import_module('a.b')
+ module = self.init.import_module('..c', 'a.b')
self.assertEqual(module.__name__, 'a.c')
def test_absolute_import_with_package(self):
@@ -59,15 +62,15 @@ class ImportModuleTests(unittest.TestCase):
name = '{0}.mod'.format(pkg_name)
with util.mock_modules(pkg_long_name, name) as mock:
with util.import_state(meta_path=[mock]):
- importlib.import_module(pkg_name)
- module = importlib.import_module(name, pkg_name)
+ self.init.import_module(pkg_name)
+ module = self.init.import_module(name, pkg_name)
self.assertEqual(module.__name__, name)
def test_relative_import_wo_package(self):
# Relative imports cannot happen without the 'package' argument being
# set.
with self.assertRaises(TypeError):
- importlib.import_module('.support')
+ self.init.import_module('.support')
def test_loaded_once(self):
@@ -76,7 +79,7 @@ class ImportModuleTests(unittest.TestCase):
# module currently being imported.
b_load_count = 0
def load_a():
- importlib.import_module('a.b')
+ self.init.import_module('a.b')
def load_b():
nonlocal b_load_count
b_load_count += 1
@@ -84,11 +87,17 @@ class ImportModuleTests(unittest.TestCase):
modules = ['a.__init__', 'a.b']
with util.mock_modules(*modules, module_code=code) as mock:
with util.import_state(meta_path=[mock]):
- importlib.import_module('a.b')
+ self.init.import_module('a.b')
self.assertEqual(b_load_count, 1)
+class Frozen_ImportModuleTests(ImportModuleTests, unittest.TestCase):
+ init = frozen_init
+
+class Source_ImportModuleTests(ImportModuleTests, unittest.TestCase):
+ init = source_init
-class FindLoaderTests(unittest.TestCase):
+
+class FindLoaderTests:
class FakeMetaFinder:
@staticmethod
@@ -98,29 +107,43 @@ class FindLoaderTests(unittest.TestCase):
# If a module with __loader__ is in sys.modules, then return it.
name = 'some_mod'
with util.uncache(name):
- module = imp.new_module(name)
+ module = types.ModuleType(name)
loader = 'a loader!'
module.__loader__ = loader
sys.modules[name] = module
- found = importlib.find_loader(name)
+ found = self.init.find_loader(name)
self.assertEqual(loader, found)
def test_sys_modules_loader_is_None(self):
# If sys.modules[name].__loader__ is None, raise ValueError.
name = 'some_mod'
with util.uncache(name):
- module = imp.new_module(name)
+ module = types.ModuleType(name)
module.__loader__ = None
sys.modules[name] = module
with self.assertRaises(ValueError):
- importlib.find_loader(name)
+ self.init.find_loader(name)
+
+ def test_sys_modules_loader_is_not_set(self):
+ # Should raise ValueError
+ # Issue #17099
+ name = 'some_mod'
+ with util.uncache(name):
+ module = types.ModuleType(name)
+ try:
+ del module.__loader__
+ except AttributeError:
+ pass
+ sys.modules[name] = module
+ with self.assertRaises(ValueError):
+ self.init.find_loader(name)
def test_success(self):
# Return the loader found on sys.meta_path.
name = 'some_mod'
with util.uncache(name):
with util.import_state(meta_path=[self.FakeMetaFinder]):
- self.assertEqual((name, None), importlib.find_loader(name))
+ self.assertEqual((name, None), self.init.find_loader(name))
def test_success_path(self):
# Searching on a path should work.
@@ -129,14 +152,275 @@ class FindLoaderTests(unittest.TestCase):
with util.uncache(name):
with util.import_state(meta_path=[self.FakeMetaFinder]):
self.assertEqual((name, path),
- importlib.find_loader(name, path))
+ self.init.find_loader(name, path))
+
+ def test_nothing(self):
+ # None is returned upon failure to find a loader.
+ self.assertIsNone(self.init.find_loader('nevergoingtofindthismodule'))
+
+class Frozen_FindLoaderTests(FindLoaderTests, unittest.TestCase):
+ init = frozen_init
+
+class Source_FindLoaderTests(FindLoaderTests, unittest.TestCase):
+ init = source_init
+
+
+class FindSpecTests:
+
+ class FakeMetaFinder:
+ @staticmethod
+ def find_spec(name, path=None, target=None): return name, path, target
+
+ def test_sys_modules(self):
+ name = 'some_mod'
+ with util.uncache(name):
+ module = types.ModuleType(name)
+ loader = 'a loader!'
+ spec = self.machinery.ModuleSpec(name, loader)
+ module.__loader__ = loader
+ module.__spec__ = spec
+ sys.modules[name] = module
+ found = self.init.find_spec(name)
+ self.assertEqual(found, spec)
+
+ def test_sys_modules_without___loader__(self):
+ name = 'some_mod'
+ with util.uncache(name):
+ module = types.ModuleType(name)
+ del module.__loader__
+ loader = 'a loader!'
+ spec = self.machinery.ModuleSpec(name, loader)
+ module.__spec__ = spec
+ sys.modules[name] = module
+ found = self.init.find_spec(name)
+ self.assertEqual(found, spec)
+
+ def test_sys_modules_spec_is_None(self):
+ name = 'some_mod'
+ with util.uncache(name):
+ module = types.ModuleType(name)
+ module.__spec__ = None
+ sys.modules[name] = module
+ with self.assertRaises(ValueError):
+ self.init.find_spec(name)
+
+ def test_sys_modules_loader_is_None(self):
+ name = 'some_mod'
+ with util.uncache(name):
+ module = types.ModuleType(name)
+ spec = self.machinery.ModuleSpec(name, None)
+ module.__spec__ = spec
+ sys.modules[name] = module
+ found = self.init.find_spec(name)
+ self.assertEqual(found, spec)
+
+ def test_sys_modules_spec_is_not_set(self):
+ name = 'some_mod'
+ with util.uncache(name):
+ module = types.ModuleType(name)
+ try:
+ del module.__spec__
+ except AttributeError:
+ pass
+ sys.modules[name] = module
+ with self.assertRaises(ValueError):
+ self.init.find_spec(name)
+
+ def test_success(self):
+ name = 'some_mod'
+ with util.uncache(name):
+ with util.import_state(meta_path=[self.FakeMetaFinder]):
+ self.assertEqual((name, None, None),
+ self.init.find_spec(name))
+
+ def test_success_path(self):
+ # Searching on a path should work.
+ name = 'some_mod'
+ path = 'path to some place'
+ with util.uncache(name):
+ with util.import_state(meta_path=[self.FakeMetaFinder]):
+ self.assertEqual((name, path, None),
+ self.init.find_spec(name, path))
def test_nothing(self):
# None is returned upon failure to find a loader.
- self.assertIsNone(importlib.find_loader('nevergoingtofindthismodule'))
+ self.assertIsNone(self.init.find_spec('nevergoingtofindthismodule'))
+
+class Frozen_FindSpecTests(FindSpecTests, unittest.TestCase):
+ init = frozen_init
+ machinery = frozen_machinery
+
+class Source_FindSpecTests(FindSpecTests, unittest.TestCase):
+ init = source_init
+ machinery = source_machinery
+
+
+class ReloadTests:
+ """Test module reloading for builtin and extension modules."""
-class InvalidateCacheTests(unittest.TestCase):
+ def test_reload_modules(self):
+ for mod in ('tokenize', 'time', 'marshal'):
+ with self.subTest(module=mod):
+ with support.CleanImport(mod):
+ module = self.init.import_module(mod)
+ self.init.reload(module)
+
+ def test_module_replaced(self):
+ def code():
+ import sys
+ module = type(sys)('top_level')
+ module.spam = 3
+ sys.modules['top_level'] = module
+ mock = util.mock_modules('top_level',
+ module_code={'top_level': code})
+ with mock:
+ with util.import_state(meta_path=[mock]):
+ module = self.init.import_module('top_level')
+ reloaded = self.init.reload(module)
+ actual = sys.modules['top_level']
+ self.assertEqual(actual.spam, 3)
+ self.assertEqual(reloaded.spam, 3)
+
+ def test_reload_missing_loader(self):
+ with support.CleanImport('types'):
+ import types
+ loader = types.__loader__
+ del types.__loader__
+ reloaded = self.init.reload(types)
+
+ self.assertIs(reloaded, types)
+ self.assertIs(sys.modules['types'], types)
+ self.assertEqual(reloaded.__loader__.path, loader.path)
+
+ def test_reload_loader_replaced(self):
+ with support.CleanImport('types'):
+ import types
+ types.__loader__ = None
+ self.init.invalidate_caches()
+ reloaded = self.init.reload(types)
+
+ self.assertIsNot(reloaded.__loader__, None)
+ self.assertIs(reloaded, types)
+ self.assertIs(sys.modules['types'], types)
+
+ def test_reload_location_changed(self):
+ name = 'spam'
+ with support.temp_cwd(None) as cwd:
+ with util.uncache('spam'):
+ with support.DirsOnSysPath(cwd):
+ # Start as a plain module.
+ self.init.invalidate_caches()
+ path = os.path.join(cwd, name + '.py')
+ cached = self.util.cache_from_source(path)
+ expected = {'__name__': name,
+ '__package__': '',
+ '__file__': path,
+ '__cached__': cached,
+ '__doc__': None,
+ '__builtins__': __builtins__,
+ }
+ support.create_empty_file(path)
+ module = self.init.import_module(name)
+ ns = vars(module)
+ loader = ns.pop('__loader__')
+ spec = ns.pop('__spec__')
+ self.assertEqual(spec.name, name)
+ self.assertEqual(spec.loader, loader)
+ self.assertEqual(loader.path, path)
+ self.assertEqual(ns, expected)
+
+ # Change to a package.
+ self.init.invalidate_caches()
+ init_path = os.path.join(cwd, name, '__init__.py')
+ cached = self.util.cache_from_source(init_path)
+ expected = {'__name__': name,
+ '__package__': name,
+ '__file__': init_path,
+ '__cached__': cached,
+ '__path__': [os.path.dirname(init_path)],
+ '__doc__': None,
+ '__builtins__': __builtins__,
+ }
+ os.mkdir(name)
+ os.rename(path, init_path)
+ reloaded = self.init.reload(module)
+ ns = vars(reloaded)
+ loader = ns.pop('__loader__')
+ spec = ns.pop('__spec__')
+ self.assertEqual(spec.name, name)
+ self.assertEqual(spec.loader, loader)
+ self.assertIs(reloaded, module)
+ self.assertEqual(loader.path, init_path)
+ self.maxDiff = None
+ self.assertEqual(ns, expected)
+
+ def test_reload_namespace_changed(self):
+ name = 'spam'
+ with support.temp_cwd(None) as cwd:
+ with util.uncache('spam'):
+ with support.DirsOnSysPath(cwd):
+ # Start as a namespace package.
+ self.init.invalidate_caches()
+ bad_path = os.path.join(cwd, name, '__init.py')
+ cached = self.util.cache_from_source(bad_path)
+ expected = {'__name__': name,
+ '__package__': name,
+ '__doc__': None,
+ }
+ os.mkdir(name)
+ with open(bad_path, 'w') as init_file:
+ init_file.write('eggs = None')
+ module = self.init.import_module(name)
+ ns = vars(module)
+ loader = ns.pop('__loader__')
+ path = ns.pop('__path__')
+ spec = ns.pop('__spec__')
+ self.assertEqual(spec.name, name)
+ self.assertIs(spec.loader, None)
+ self.assertIsNot(loader, None)
+ self.assertEqual(set(path),
+ set([os.path.dirname(bad_path)]))
+ with self.assertRaises(AttributeError):
+ # a NamespaceLoader
+ loader.path
+ self.assertEqual(ns, expected)
+
+ # Change to a regular package.
+ self.init.invalidate_caches()
+ init_path = os.path.join(cwd, name, '__init__.py')
+ cached = self.util.cache_from_source(init_path)
+ expected = {'__name__': name,
+ '__package__': name,
+ '__file__': init_path,
+ '__cached__': cached,
+ '__path__': [os.path.dirname(init_path)],
+ '__doc__': None,
+ '__builtins__': __builtins__,
+ 'eggs': None,
+ }
+ os.rename(bad_path, init_path)
+ reloaded = self.init.reload(module)
+ ns = vars(reloaded)
+ loader = ns.pop('__loader__')
+ spec = ns.pop('__spec__')
+ self.assertEqual(spec.name, name)
+ self.assertEqual(spec.loader, loader)
+ self.assertIs(reloaded, module)
+ self.assertEqual(loader.path, init_path)
+ self.assertEqual(ns, expected)
+
+
+class Frozen_ReloadTests(ReloadTests, unittest.TestCase):
+ init = frozen_init
+ util = frozen_util
+
+class Source_ReloadTests(ReloadTests, unittest.TestCase):
+ init = source_init
+ util = source_util
+
+
+class InvalidateCacheTests:
def test_method_called(self):
# If defined the method should be called.
@@ -155,48 +439,65 @@ class InvalidateCacheTests(unittest.TestCase):
self.addCleanup(lambda: sys.path_importer_cache.__delitem__(key))
sys.path_importer_cache[key] = path_ins
self.addCleanup(lambda: sys.meta_path.remove(meta_ins))
- importlib.invalidate_caches()
+ self.init.invalidate_caches()
self.assertTrue(meta_ins.called)
self.assertTrue(path_ins.called)
def test_method_lacking(self):
# There should be no issues if the method is not defined.
key = 'gobbledeegook'
- sys.path_importer_cache[key] = imp.NullImporter('abc')
+ sys.path_importer_cache[key] = None
self.addCleanup(lambda: sys.path_importer_cache.__delitem__(key))
- importlib.invalidate_caches() # Shouldn't trigger an exception.
+ self.init.invalidate_caches() # Shouldn't trigger an exception.
+
+class Frozen_InvalidateCacheTests(InvalidateCacheTests, unittest.TestCase):
+ init = frozen_init
+
+class Source_InvalidateCacheTests(InvalidateCacheTests, unittest.TestCase):
+ init = source_init
class FrozenImportlibTests(unittest.TestCase):
def test_no_frozen_importlib(self):
# Should be able to import w/o _frozen_importlib being defined.
- module = support.import_fresh_module('importlib', blocked=['_frozen_importlib'])
- self.assertFalse(isinstance(module.__loader__,
- machinery.FrozenImporter))
+ # Can't do an isinstance() check since separate copies of importlib
+ # may have been used for import, so just check the name is not for the
+ # frozen loader.
+ self.assertNotEqual(source_init.__loader__.__class__.__name__,
+ 'FrozenImporter')
-class StartupTests(unittest.TestCase):
+class StartupTests:
def test_everyone_has___loader__(self):
# Issue #17098: all modules should have __loader__ defined.
for name, module in sys.modules.items():
if isinstance(module, types.ModuleType):
- if name in sys.builtin_module_names:
- self.assertEqual(importlib.machinery.BuiltinImporter,
- module.__loader__)
- elif imp.is_frozen(name):
- self.assertEqual(importlib.machinery.FrozenImporter,
- module.__loader__)
-
-def test_main():
- from test.support import run_unittest
- run_unittest(ImportModuleTests,
- FindLoaderTests,
- InvalidateCacheTests,
- FrozenImportlibTests,
- StartupTests)
+ with self.subTest(name=name):
+ self.assertTrue(hasattr(module, '__loader__'),
+ '{!r} lacks a __loader__ attribute'.format(name))
+ if self.machinery.BuiltinImporter.find_module(name):
+ self.assertIsNot(module.__loader__, None)
+ elif self.machinery.FrozenImporter.find_module(name):
+ self.assertIsNot(module.__loader__, None)
+
+ def test_everyone_has___spec__(self):
+ for name, module in sys.modules.items():
+ if isinstance(module, types.ModuleType):
+ with self.subTest(name=name):
+ self.assertTrue(hasattr(module, '__spec__'))
+ if self.machinery.BuiltinImporter.find_module(name):
+ self.assertIsNot(module.__spec__, None)
+ elif self.machinery.FrozenImporter.find_module(name):
+ self.assertIsNot(module.__spec__, None)
+
+class Frozen_StartupTests(StartupTests, unittest.TestCase):
+ machinery = frozen_machinery
+
+class Source_StartupTests(StartupTests, unittest.TestCase):
+ machinery = source_machinery
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/test_locks.py b/Lib/test/test_importlib/test_locks.py
index c373b11256..dc97ba1567 100644
--- a/Lib/test/test_importlib/test_locks.py
+++ b/Lib/test/test_importlib/test_locks.py
@@ -1,4 +1,8 @@
-from importlib import _bootstrap
+from . import util
+frozen_init, source_init = util.import_importlib('importlib')
+frozen_bootstrap = frozen_init._bootstrap
+source_bootstrap = source_init._bootstrap
+
import sys
import time
import unittest
@@ -13,14 +17,9 @@ except ImportError:
else:
from test import lock_tests
-
-LockType = _bootstrap._ModuleLock
-DeadlockError = _bootstrap._DeadlockError
-
-
if threading is not None:
- class ModuleLockAsRLockTests(lock_tests.RLockTests):
- locktype = staticmethod(lambda: LockType("some_lock"))
+ class ModuleLockAsRLockTests:
+ locktype = classmethod(lambda cls: cls.LockType("some_lock"))
# _is_owned() unsupported
test__is_owned = None
@@ -34,13 +33,21 @@ if threading is not None:
# _release_save() unsupported
test_release_save_unacquired = None
+ class Frozen_ModuleLockAsRLockTests(ModuleLockAsRLockTests, lock_tests.RLockTests):
+ LockType = frozen_bootstrap._ModuleLock
+
+ class Source_ModuleLockAsRLockTests(ModuleLockAsRLockTests, lock_tests.RLockTests):
+ LockType = source_bootstrap._ModuleLock
+
else:
- class ModuleLockAsRLockTests(unittest.TestCase):
+ class Frozen_ModuleLockAsRLockTests(unittest.TestCase):
pass
+ class Source_ModuleLockAsRLockTests(unittest.TestCase):
+ pass
-@unittest.skipUnless(threading, "threads needed for this test")
-class DeadlockAvoidanceTests(unittest.TestCase):
+
+class DeadlockAvoidanceTests:
def setUp(self):
try:
@@ -55,7 +62,7 @@ class DeadlockAvoidanceTests(unittest.TestCase):
def run_deadlock_avoidance_test(self, create_deadlock):
NLOCKS = 10
- locks = [LockType(str(i)) for i in range(NLOCKS)]
+ locks = [self.LockType(str(i)) for i in range(NLOCKS)]
pairs = [(locks[i], locks[(i+1)%NLOCKS]) for i in range(NLOCKS)]
if create_deadlock:
NTHREADS = NLOCKS
@@ -67,7 +74,7 @@ class DeadlockAvoidanceTests(unittest.TestCase):
"""Try to acquire the lock. Return True on success, False on deadlock."""
try:
lock.acquire()
- except DeadlockError:
+ except self.DeadlockError:
return False
else:
return True
@@ -99,30 +106,50 @@ class DeadlockAvoidanceTests(unittest.TestCase):
self.assertEqual(results.count((True, False)), 0)
self.assertEqual(results.count((True, True)), len(results))
+@unittest.skipUnless(threading, "threads needed for this test")
+class Frozen_DeadlockAvoidanceTests(DeadlockAvoidanceTests, unittest.TestCase):
+ LockType = frozen_bootstrap._ModuleLock
+ DeadlockError = frozen_bootstrap._DeadlockError
+
+@unittest.skipUnless(threading, "threads needed for this test")
+class Source_DeadlockAvoidanceTests(DeadlockAvoidanceTests, unittest.TestCase):
+ LockType = source_bootstrap._ModuleLock
+ DeadlockError = source_bootstrap._DeadlockError
-class LifetimeTests(unittest.TestCase):
+
+class LifetimeTests:
def test_lock_lifetime(self):
name = "xyzzy"
- self.assertNotIn(name, _bootstrap._module_locks)
- lock = _bootstrap._get_module_lock(name)
- self.assertIn(name, _bootstrap._module_locks)
+ self.assertNotIn(name, self.bootstrap._module_locks)
+ lock = self.bootstrap._get_module_lock(name)
+ self.assertIn(name, self.bootstrap._module_locks)
wr = weakref.ref(lock)
del lock
support.gc_collect()
- self.assertNotIn(name, _bootstrap._module_locks)
+ self.assertNotIn(name, self.bootstrap._module_locks)
self.assertIsNone(wr())
def test_all_locks(self):
support.gc_collect()
- self.assertEqual(0, len(_bootstrap._module_locks), _bootstrap._module_locks)
+ self.assertEqual(0, len(self.bootstrap._module_locks),
+ self.bootstrap._module_locks)
+
+class Frozen_LifetimeTests(LifetimeTests, unittest.TestCase):
+ bootstrap = frozen_bootstrap
+
+class Source_LifetimeTests(LifetimeTests, unittest.TestCase):
+ bootstrap = source_bootstrap
@support.reap_threads
def test_main():
- support.run_unittest(ModuleLockAsRLockTests,
- DeadlockAvoidanceTests,
- LifetimeTests)
+ support.run_unittest(Frozen_ModuleLockAsRLockTests,
+ Source_ModuleLockAsRLockTests,
+ Frozen_DeadlockAvoidanceTests,
+ Source_DeadlockAvoidanceTests,
+ Frozen_LifetimeTests,
+ Source_LifetimeTests)
if __name__ == '__main__':
diff --git a/Lib/test/test_importlib/test_spec.py b/Lib/test/test_importlib/test_spec.py
new file mode 100644
index 0000000000..e4ef39fd29
--- /dev/null
+++ b/Lib/test/test_importlib/test_spec.py
@@ -0,0 +1,968 @@
+from . import util
+
+frozen_init, source_init = util.import_importlib('importlib')
+frozen_bootstrap = frozen_init._bootstrap
+source_bootstrap = source_init._bootstrap
+frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
+frozen_util, source_util = util.import_importlib('importlib.util')
+
+import os.path
+from test.support import CleanImport
+import unittest
+import sys
+
+
+
+class TestLoader:
+
+ def __init__(self, path=None, is_package=None):
+# if path:
+# if is_package:
+# if not path.endswith('.py'):
+# path = os.path.join(path, '__init__.py')
+# elif is_package is None:
+# is_package = path.endswith('__init__.py')
+
+ self.path = path
+ self.package = is_package
+
+ def __repr__(self):
+ return '<TestLoader object>'
+
+ def __getattr__(self, name):
+ if name == 'get_filename' and self.path is not None:
+ return self._get_filename
+ if name == 'is_package':
+ return self._is_package
+ raise AttributeError(name)
+
+ def _get_filename(self, name):
+ return self.path
+
+ def _is_package(self, name):
+ return self.package
+
+
+class NewLoader(TestLoader):
+
+ EGGS = 1
+
+ def exec_module(self, module):
+ module.eggs = self.EGGS
+
+
+class LegacyLoader(TestLoader):
+
+ HAM = -1
+
+ @frozen_util.module_for_loader
+ def load_module(self, module):
+ module.ham = self.HAM
+ return module
+
+
+class ModuleSpecTests:
+
+ def setUp(self):
+ self.name = 'spam'
+ self.path = 'spam.py'
+ self.cached = self.util.cache_from_source(self.path)
+ self.loader = TestLoader()
+ self.spec = self.machinery.ModuleSpec(self.name, self.loader)
+ self.loc_spec = self.machinery.ModuleSpec(self.name, self.loader,
+ origin=self.path)
+ self.loc_spec._set_fileattr = True
+
+ def test_default(self):
+ spec = self.machinery.ModuleSpec(self.name, self.loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_default_no_loader(self):
+ spec = self.machinery.ModuleSpec(self.name, None)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertIs(spec.loader, None)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_default_is_package_false(self):
+ spec = self.machinery.ModuleSpec(self.name, self.loader,
+ is_package=False)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_default_is_package_true(self):
+ spec = self.machinery.ModuleSpec(self.name, self.loader,
+ is_package=True)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertEqual(spec.submodule_search_locations, [])
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_equality(self):
+ other = type(sys.implementation)(name=self.name,
+ loader=self.loader,
+ origin=None,
+ submodule_search_locations=None,
+ has_location=False,
+ cached=None,
+ )
+
+ self.assertTrue(self.spec == other)
+
+ def test_equality_location(self):
+ other = type(sys.implementation)(name=self.name,
+ loader=self.loader,
+ origin=self.path,
+ submodule_search_locations=None,
+ has_location=True,
+ cached=self.cached,
+ )
+
+ self.assertEqual(self.loc_spec, other)
+
+ def test_inequality(self):
+ other = type(sys.implementation)(name='ham',
+ loader=self.loader,
+ origin=None,
+ submodule_search_locations=None,
+ has_location=False,
+ cached=None,
+ )
+
+ self.assertNotEqual(self.spec, other)
+
+ def test_inequality_incomplete(self):
+ other = type(sys.implementation)(name=self.name,
+ loader=self.loader,
+ )
+
+ self.assertNotEqual(self.spec, other)
+
+ def test_package(self):
+ spec = self.machinery.ModuleSpec('spam.eggs', self.loader)
+
+ self.assertEqual(spec.parent, 'spam')
+
+ def test_package_is_package(self):
+ spec = self.machinery.ModuleSpec('spam.eggs', self.loader,
+ is_package=True)
+
+ self.assertEqual(spec.parent, 'spam.eggs')
+
+ # cached
+
+ def test_cached_set(self):
+ before = self.spec.cached
+ self.spec.cached = 'there'
+ after = self.spec.cached
+
+ self.assertIs(before, None)
+ self.assertEqual(after, 'there')
+
+ def test_cached_no_origin(self):
+ spec = self.machinery.ModuleSpec(self.name, self.loader)
+
+ self.assertIs(spec.cached, None)
+
+ def test_cached_with_origin_not_location(self):
+ spec = self.machinery.ModuleSpec(self.name, self.loader,
+ origin=self.path)
+
+ self.assertIs(spec.cached, None)
+
+ def test_cached_source(self):
+ expected = self.util.cache_from_source(self.path)
+
+ self.assertEqual(self.loc_spec.cached, expected)
+
+ def test_cached_source_unknown_suffix(self):
+ self.loc_spec.origin = 'spam.spamspamspam'
+
+ self.assertIs(self.loc_spec.cached, None)
+
+ def test_cached_source_missing_cache_tag(self):
+ original = sys.implementation.cache_tag
+ sys.implementation.cache_tag = None
+ try:
+ cached = self.loc_spec.cached
+ finally:
+ sys.implementation.cache_tag = original
+
+ self.assertIs(cached, None)
+
+ def test_cached_sourceless(self):
+ self.loc_spec.origin = 'spam.pyc'
+
+ self.assertEqual(self.loc_spec.cached, 'spam.pyc')
+
+
+class Frozen_ModuleSpecTests(ModuleSpecTests, unittest.TestCase):
+ util = frozen_util
+ machinery = frozen_machinery
+
+
+class Source_ModuleSpecTests(ModuleSpecTests, unittest.TestCase):
+ util = source_util
+ machinery = source_machinery
+
+
+class ModuleSpecMethodsTests:
+
+ def setUp(self):
+ self.name = 'spam'
+ self.path = 'spam.py'
+ self.cached = self.util.cache_from_source(self.path)
+ self.loader = TestLoader()
+ self.spec = self.machinery.ModuleSpec(self.name, self.loader)
+ self.loc_spec = self.machinery.ModuleSpec(self.name, self.loader,
+ origin=self.path)
+ self.loc_spec._set_fileattr = True
+
+ # init_module_attrs
+
+ def test_init_module_attrs(self):
+ module = type(sys)(self.name)
+ spec = self.machinery.ModuleSpec(self.name, self.loader)
+ self.bootstrap._SpecMethods(spec).init_module_attrs(module)
+
+ self.assertEqual(module.__name__, spec.name)
+ self.assertIs(module.__loader__, spec.loader)
+ self.assertEqual(module.__package__, spec.parent)
+ self.assertIs(module.__spec__, spec)
+ self.assertFalse(hasattr(module, '__path__'))
+ self.assertFalse(hasattr(module, '__file__'))
+ self.assertFalse(hasattr(module, '__cached__'))
+
+ def test_init_module_attrs_package(self):
+ module = type(sys)(self.name)
+ spec = self.machinery.ModuleSpec(self.name, self.loader)
+ spec.submodule_search_locations = ['spam', 'ham']
+ self.bootstrap._SpecMethods(spec).init_module_attrs(module)
+
+ self.assertEqual(module.__name__, spec.name)
+ self.assertIs(module.__loader__, spec.loader)
+ self.assertEqual(module.__package__, spec.parent)
+ self.assertIs(module.__spec__, spec)
+ self.assertIs(module.__path__, spec.submodule_search_locations)
+ self.assertFalse(hasattr(module, '__file__'))
+ self.assertFalse(hasattr(module, '__cached__'))
+
+ def test_init_module_attrs_location(self):
+ module = type(sys)(self.name)
+ spec = self.loc_spec
+ self.bootstrap._SpecMethods(spec).init_module_attrs(module)
+
+ self.assertEqual(module.__name__, spec.name)
+ self.assertIs(module.__loader__, spec.loader)
+ self.assertEqual(module.__package__, spec.parent)
+ self.assertIs(module.__spec__, spec)
+ self.assertFalse(hasattr(module, '__path__'))
+ self.assertEqual(module.__file__, spec.origin)
+ self.assertEqual(module.__cached__,
+ self.util.cache_from_source(spec.origin))
+
+ def test_init_module_attrs_different_name(self):
+ module = type(sys)('eggs')
+ spec = self.machinery.ModuleSpec(self.name, self.loader)
+ self.bootstrap._SpecMethods(spec).init_module_attrs(module)
+
+ self.assertEqual(module.__name__, spec.name)
+
+ def test_init_module_attrs_different_spec(self):
+ module = type(sys)(self.name)
+ module.__spec__ = self.machinery.ModuleSpec('eggs', object())
+ spec = self.machinery.ModuleSpec(self.name, self.loader)
+ self.bootstrap._SpecMethods(spec).init_module_attrs(module)
+
+ self.assertEqual(module.__name__, spec.name)
+ self.assertIs(module.__loader__, spec.loader)
+ self.assertEqual(module.__package__, spec.parent)
+ self.assertIs(module.__spec__, spec)
+
+ def test_init_module_attrs_already_set(self):
+ module = type(sys)('ham.eggs')
+ module.__loader__ = object()
+ module.__package__ = 'ham'
+ module.__path__ = ['eggs']
+ module.__file__ = 'ham/eggs/__init__.py'
+ module.__cached__ = self.util.cache_from_source(module.__file__)
+ original = vars(module).copy()
+ spec = self.loc_spec
+ spec.submodule_search_locations = ['']
+ self.bootstrap._SpecMethods(spec).init_module_attrs(module)
+
+ self.assertIs(module.__loader__, original['__loader__'])
+ self.assertEqual(module.__package__, original['__package__'])
+ self.assertIs(module.__path__, original['__path__'])
+ self.assertEqual(module.__file__, original['__file__'])
+ self.assertEqual(module.__cached__, original['__cached__'])
+
+ def test_init_module_attrs_immutable(self):
+ module = object()
+ spec = self.loc_spec
+ spec.submodule_search_locations = ['']
+ self.bootstrap._SpecMethods(spec).init_module_attrs(module)
+
+ self.assertFalse(hasattr(module, '__name__'))
+ self.assertFalse(hasattr(module, '__loader__'))
+ self.assertFalse(hasattr(module, '__package__'))
+ self.assertFalse(hasattr(module, '__spec__'))
+ self.assertFalse(hasattr(module, '__path__'))
+ self.assertFalse(hasattr(module, '__file__'))
+ self.assertFalse(hasattr(module, '__cached__'))
+
+ # create()
+
+ def test_create(self):
+ created = self.bootstrap._SpecMethods(self.spec).create()
+
+ self.assertEqual(created.__name__, self.spec.name)
+ self.assertIs(created.__loader__, self.spec.loader)
+ self.assertEqual(created.__package__, self.spec.parent)
+ self.assertIs(created.__spec__, self.spec)
+ self.assertFalse(hasattr(created, '__path__'))
+ self.assertFalse(hasattr(created, '__file__'))
+ self.assertFalse(hasattr(created, '__cached__'))
+
+ def test_create_from_loader(self):
+ module = type(sys.implementation)()
+ class CreatingLoader(TestLoader):
+ def create_module(self, spec):
+ return module
+ self.spec.loader = CreatingLoader()
+ created = self.bootstrap._SpecMethods(self.spec).create()
+
+ self.assertIs(created, module)
+ self.assertEqual(created.__name__, self.spec.name)
+ self.assertIs(created.__loader__, self.spec.loader)
+ self.assertEqual(created.__package__, self.spec.parent)
+ self.assertIs(created.__spec__, self.spec)
+ self.assertFalse(hasattr(created, '__path__'))
+ self.assertFalse(hasattr(created, '__file__'))
+ self.assertFalse(hasattr(created, '__cached__'))
+
+ def test_create_from_loader_not_handled(self):
+ class CreatingLoader(TestLoader):
+ def create_module(self, spec):
+ return None
+ self.spec.loader = CreatingLoader()
+ created = self.bootstrap._SpecMethods(self.spec).create()
+
+ self.assertEqual(created.__name__, self.spec.name)
+ self.assertIs(created.__loader__, self.spec.loader)
+ self.assertEqual(created.__package__, self.spec.parent)
+ self.assertIs(created.__spec__, self.spec)
+ self.assertFalse(hasattr(created, '__path__'))
+ self.assertFalse(hasattr(created, '__file__'))
+ self.assertFalse(hasattr(created, '__cached__'))
+
+ # exec()
+
+ def test_exec(self):
+ self.spec.loader = NewLoader()
+ module = self.bootstrap._SpecMethods(self.spec).create()
+ sys.modules[self.name] = module
+ self.assertFalse(hasattr(module, 'eggs'))
+ self.bootstrap._SpecMethods(self.spec).exec(module)
+
+ self.assertEqual(module.eggs, 1)
+
+ # load()
+
+ def test_load(self):
+ self.spec.loader = NewLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ installed = sys.modules[self.spec.name]
+
+ self.assertEqual(loaded.eggs, 1)
+ self.assertIs(loaded, installed)
+
+ def test_load_replaced(self):
+ replacement = object()
+ class ReplacingLoader(TestLoader):
+ def exec_module(self, module):
+ sys.modules[module.__name__] = replacement
+ self.spec.loader = ReplacingLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ installed = sys.modules[self.spec.name]
+
+ self.assertIs(loaded, replacement)
+ self.assertIs(installed, replacement)
+
+ def test_load_failed(self):
+ class FailedLoader(TestLoader):
+ def exec_module(self, module):
+ raise RuntimeError
+ self.spec.loader = FailedLoader()
+ with CleanImport(self.spec.name):
+ with self.assertRaises(RuntimeError):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ self.assertNotIn(self.spec.name, sys.modules)
+
+ def test_load_failed_removed(self):
+ class FailedLoader(TestLoader):
+ def exec_module(self, module):
+ del sys.modules[module.__name__]
+ raise RuntimeError
+ self.spec.loader = FailedLoader()
+ with CleanImport(self.spec.name):
+ with self.assertRaises(RuntimeError):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ self.assertNotIn(self.spec.name, sys.modules)
+
+ def test_load_existing(self):
+ existing = type(sys)('ham')
+ existing.count = 5
+ self.spec.loader = NewLoader()
+ with CleanImport(self.name):
+ sys.modules[self.name] = existing
+ assert self.spec.name == self.name
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+
+ self.assertEqual(loaded.eggs, 1)
+ self.assertFalse(hasattr(loaded, 'ham'))
+
+ def test_load_legacy(self):
+ self.spec.loader = LegacyLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+
+ self.assertEqual(loaded.ham, -1)
+
+ def test_load_legacy_attributes(self):
+ self.spec.loader = LegacyLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+
+ self.assertIs(loaded.__loader__, self.spec.loader)
+ self.assertEqual(loaded.__package__, self.spec.parent)
+ self.assertIs(loaded.__spec__, self.spec)
+
+ def test_load_legacy_attributes_immutable(self):
+ module = object()
+ class ImmutableLoader(TestLoader):
+ def load_module(self, name):
+ sys.modules[name] = module
+ return module
+ self.spec.loader = ImmutableLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+
+ self.assertIs(sys.modules[self.spec.name], module)
+
+ # reload()
+
+ def test_reload(self):
+ self.spec.loader = NewLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded)
+ installed = sys.modules[self.spec.name]
+
+ self.assertEqual(loaded.eggs, 1)
+ self.assertIs(reloaded, loaded)
+ self.assertIs(installed, loaded)
+
+ def test_reload_modified(self):
+ self.spec.loader = NewLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ loaded.eggs = 2
+ reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded)
+
+ self.assertEqual(loaded.eggs, 1)
+ self.assertIs(reloaded, loaded)
+
+ def test_reload_extra_attributes(self):
+ self.spec.loader = NewLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ loaded.available = False
+ reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded)
+
+ self.assertFalse(loaded.available)
+ self.assertIs(reloaded, loaded)
+
+ def test_reload_init_module_attrs(self):
+ self.spec.loader = NewLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ loaded.__name__ = 'ham'
+ del loaded.__loader__
+ del loaded.__package__
+ del loaded.__spec__
+ self.bootstrap._SpecMethods(self.spec).exec(loaded)
+
+ self.assertEqual(loaded.__name__, self.spec.name)
+ self.assertIs(loaded.__loader__, self.spec.loader)
+ self.assertEqual(loaded.__package__, self.spec.parent)
+ self.assertIs(loaded.__spec__, self.spec)
+ self.assertFalse(hasattr(loaded, '__path__'))
+ self.assertFalse(hasattr(loaded, '__file__'))
+ self.assertFalse(hasattr(loaded, '__cached__'))
+
+ def test_reload_legacy(self):
+ self.spec.loader = LegacyLoader()
+ with CleanImport(self.spec.name):
+ loaded = self.bootstrap._SpecMethods(self.spec).load()
+ reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded)
+ installed = sys.modules[self.spec.name]
+
+ self.assertEqual(loaded.ham, -1)
+ self.assertIs(reloaded, loaded)
+ self.assertIs(installed, loaded)
+
+
+class Frozen_ModuleSpecMethodsTests(ModuleSpecMethodsTests, unittest.TestCase):
+ bootstrap = frozen_bootstrap
+ machinery = frozen_machinery
+ util = frozen_util
+
+
+class Source_ModuleSpecMethodsTests(ModuleSpecMethodsTests, unittest.TestCase):
+ bootstrap = source_bootstrap
+ machinery = source_machinery
+ util = source_util
+
+
+class ModuleReprTests:
+
+ # XXX Add more tests for repr(module) once ModuleSpec._module_repr()
+ # is in place?
+
+ def setUp(self):
+ self.module = type(os)('spam')
+ self.spec = self.machinery.ModuleSpec('spam', TestLoader())
+
+ def test_module___loader___module_repr(self):
+ class Loader:
+ def module_repr(self, module):
+ return '<delicious {}>'.format(module.__name__)
+ self.module.__loader__ = Loader()
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr, '<delicious spam>')
+
+ def test_module___loader___module_repr_bad(self):
+ class Loader(TestLoader):
+ def module_repr(self, module):
+ raise Exception
+ self.module.__loader__ = Loader()
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr,
+ '<module {!r} (<TestLoader object>)>'.format('spam'))
+
+ def test_module___spec__(self):
+ origin = 'in a hole, in the ground'
+ self.spec.origin = origin
+ self.module.__spec__ = self.spec
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr, '<module {!r} ({})>'.format('spam', origin))
+
+ def test_module___spec___location(self):
+ location = 'in_a_galaxy_far_far_away.py'
+ self.spec.origin = location
+ self.spec._set_fileattr = True
+ self.module.__spec__ = self.spec
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr,
+ '<module {!r} from {!r}>'.format('spam', location))
+
+ def test_module___spec___no_origin(self):
+ self.spec.loader = TestLoader()
+ self.module.__spec__ = self.spec
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr,
+ '<module {!r} (<TestLoader object>)>'.format('spam'))
+
+ def test_module___spec___no_origin_no_loader(self):
+ self.spec.loader = None
+ self.module.__spec__ = self.spec
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr, '<module {!r}>'.format('spam'))
+
+ def test_module_no_name(self):
+ del self.module.__name__
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr, '<module {!r}>'.format('?'))
+
+ def test_module_with_file(self):
+ filename = 'e/i/e/i/o/spam.py'
+ self.module.__file__ = filename
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr,
+ '<module {!r} from {!r}>'.format('spam', filename))
+
+ def test_module_no_file(self):
+ self.module.__loader__ = TestLoader()
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr,
+ '<module {!r} (<TestLoader object>)>'.format('spam'))
+
+ def test_module_no_file_no_loader(self):
+ modrepr = self.bootstrap._module_repr(self.module)
+
+ self.assertEqual(modrepr, '<module {!r}>'.format('spam'))
+
+
+class Frozen_ModuleReprTests(ModuleReprTests, unittest.TestCase):
+ bootstrap = frozen_bootstrap
+ machinery = frozen_machinery
+ util = frozen_util
+
+
+class Source_ModuleReprTests(ModuleReprTests, unittest.TestCase):
+ bootstrap = source_bootstrap
+ machinery = source_machinery
+ util = source_util
+
+
+class FactoryTests:
+
+ def setUp(self):
+ self.name = 'spam'
+ self.path = 'spam.py'
+ self.cached = self.util.cache_from_source(self.path)
+ self.loader = TestLoader()
+ self.fileloader = TestLoader(self.path)
+ self.pkgloader = TestLoader(self.path, True)
+
+ # spec_from_loader()
+
+ def test_spec_from_loader_default(self):
+ spec = self.util.spec_from_loader(self.name, self.loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_spec_from_loader_default_with_bad_is_package(self):
+ class Loader:
+ def is_package(self, name):
+ raise ImportError
+ loader = Loader()
+ spec = self.util.spec_from_loader(self.name, loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_spec_from_loader_origin(self):
+ origin = 'somewhere over the rainbow'
+ spec = self.util.spec_from_loader(self.name, self.loader,
+ origin=origin)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertIs(spec.origin, origin)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_spec_from_loader_is_package_false(self):
+ spec = self.util.spec_from_loader(self.name, self.loader,
+ is_package=False)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_spec_from_loader_is_package_true(self):
+ spec = self.util.spec_from_loader(self.name, self.loader,
+ is_package=True)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertEqual(spec.submodule_search_locations, [])
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_spec_from_loader_origin_and_is_package(self):
+ origin = 'where the streets have no name'
+ spec = self.util.spec_from_loader(self.name, self.loader,
+ origin=origin, is_package=True)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertIs(spec.origin, origin)
+ self.assertIs(spec.loader_state, None)
+ self.assertEqual(spec.submodule_search_locations, [])
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_spec_from_loader_is_package_with_loader_false(self):
+ loader = TestLoader(is_package=False)
+ spec = self.util.spec_from_loader(self.name, loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_spec_from_loader_is_package_with_loader_true(self):
+ loader = TestLoader(is_package=True)
+ spec = self.util.spec_from_loader(self.name, loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, loader)
+ self.assertIs(spec.origin, None)
+ self.assertIs(spec.loader_state, None)
+ self.assertEqual(spec.submodule_search_locations, [])
+ self.assertIs(spec.cached, None)
+ self.assertFalse(spec.has_location)
+
+ def test_spec_from_loader_default_with_file_loader(self):
+ spec = self.util.spec_from_loader(self.name, self.fileloader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.fileloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_loader_is_package_false_with_fileloader(self):
+ spec = self.util.spec_from_loader(self.name, self.fileloader,
+ is_package=False)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.fileloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_loader_is_package_true_with_fileloader(self):
+ spec = self.util.spec_from_loader(self.name, self.fileloader,
+ is_package=True)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.fileloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertEqual(spec.submodule_search_locations, [''])
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ # spec_from_file_location()
+
+ def test_spec_from_file_location_default(self):
+ if self.machinery is source_machinery:
+ raise unittest.SkipTest('not sure why this is breaking...')
+ spec = self.util.spec_from_file_location(self.name, self.path)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertIsInstance(spec.loader,
+ self.machinery.SourceFileLoader)
+ self.assertEqual(spec.loader.name, self.name)
+ self.assertEqual(spec.loader.path, self.path)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_default_without_location(self):
+ spec = self.util.spec_from_file_location(self.name)
+
+ self.assertIs(spec, None)
+
+ def test_spec_from_file_location_default_bad_suffix(self):
+ spec = self.util.spec_from_file_location(self.name, 'spam.eggs')
+
+ self.assertIs(spec, None)
+
+ def test_spec_from_file_location_loader_no_location(self):
+ spec = self.util.spec_from_file_location(self.name,
+ loader=self.fileloader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.fileloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_loader_no_location_no_get_filename(self):
+ spec = self.util.spec_from_file_location(self.name,
+ loader=self.loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.loader)
+ self.assertEqual(spec.origin, '<unknown>')
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_loader_no_location_bad_get_filename(self):
+ class Loader:
+ def get_filename(self, name):
+ raise ImportError
+ loader = Loader()
+ spec = self.util.spec_from_file_location(self.name, loader=loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, loader)
+ self.assertEqual(spec.origin, '<unknown>')
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertIs(spec.cached, None)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_smsl_none(self):
+ spec = self.util.spec_from_file_location(self.name, self.path,
+ loader=self.fileloader,
+ submodule_search_locations=None)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.fileloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_smsl_empty(self):
+ spec = self.util.spec_from_file_location(self.name, self.path,
+ loader=self.fileloader,
+ submodule_search_locations=[])
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.fileloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertEqual(spec.submodule_search_locations, [''])
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_smsl_not_empty(self):
+ spec = self.util.spec_from_file_location(self.name, self.path,
+ loader=self.fileloader,
+ submodule_search_locations=['eggs'])
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.fileloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertEqual(spec.submodule_search_locations, ['eggs'])
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_smsl_default(self):
+ spec = self.util.spec_from_file_location(self.name, self.path,
+ loader=self.pkgloader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.pkgloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertEqual(spec.submodule_search_locations, [''])
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_smsl_default_not_package(self):
+ class Loader:
+ def is_package(self, name):
+ return False
+ loader = Loader()
+ spec = self.util.spec_from_file_location(self.name, self.path,
+ loader=loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, loader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_smsl_default_no_is_package(self):
+ spec = self.util.spec_from_file_location(self.name, self.path,
+ loader=self.fileloader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, self.fileloader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+ def test_spec_from_file_location_smsl_default_bad_is_package(self):
+ class Loader:
+ def is_package(self, name):
+ raise ImportError
+ loader = Loader()
+ spec = self.util.spec_from_file_location(self.name, self.path,
+ loader=loader)
+
+ self.assertEqual(spec.name, self.name)
+ self.assertEqual(spec.loader, loader)
+ self.assertEqual(spec.origin, self.path)
+ self.assertIs(spec.loader_state, None)
+ self.assertIs(spec.submodule_search_locations, None)
+ self.assertEqual(spec.cached, self.cached)
+ self.assertTrue(spec.has_location)
+
+
+class Frozen_FactoryTests(FactoryTests, unittest.TestCase):
+ util = frozen_util
+ machinery = frozen_machinery
+
+
+class Source_FactoryTests(FactoryTests, unittest.TestCase):
+ util = source_util
+ machinery = source_machinery
diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
index efc8977fb4..604e44d1b3 100644
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -1,23 +1,64 @@
from importlib import util
from . import util as test_util
-import imp
+frozen_util, source_util = test_util.import_importlib('importlib.util')
+
+import os
import sys
+from test import support
import types
import unittest
+import warnings
+
+
+class DecodeSourceBytesTests:
+
+ source = "string ='ü'"
+
+ def test_ut8_default(self):
+ source_bytes = self.source.encode('utf-8')
+ self.assertEqual(self.util.decode_source(source_bytes), self.source)
+
+ def test_specified_encoding(self):
+ source = '# coding=latin-1\n' + self.source
+ source_bytes = source.encode('latin-1')
+ assert source_bytes != source.encode('utf-8')
+ self.assertEqual(self.util.decode_source(source_bytes), source)
+
+ def test_universal_newlines(self):
+ source = '\r\n'.join([self.source, self.source])
+ source_bytes = source.encode('utf-8')
+ self.assertEqual(self.util.decode_source(source_bytes),
+ '\n'.join([self.source, self.source]))
+
+Frozen_DecodeSourceBytesTests, Source_DecodeSourceBytesTests = test_util.test_both(
+ DecodeSourceBytesTests, util=[frozen_util, source_util])
-class ModuleForLoaderTests(unittest.TestCase):
+class ModuleForLoaderTests:
"""Tests for importlib.util.module_for_loader."""
+ @classmethod
+ def module_for_loader(cls, func):
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', PendingDeprecationWarning)
+ return cls.util.module_for_loader(func)
+
+ def test_warning(self):
+ # Should raise a PendingDeprecationWarning when used.
+ with warnings.catch_warnings():
+ warnings.simplefilter('error', PendingDeprecationWarning)
+ with self.assertRaises(PendingDeprecationWarning):
+ func = self.util.module_for_loader(lambda x: x)
+
def return_module(self, name):
- fxn = util.module_for_loader(lambda self, module: module)
+ fxn = self.module_for_loader(lambda self, module: module)
return fxn(self, name)
def raise_exception(self, name):
def to_wrap(self, module):
raise ImportError
- fxn = util.module_for_loader(to_wrap)
+ fxn = self.module_for_loader(to_wrap)
try:
fxn(self, name)
except ImportError:
@@ -35,12 +76,23 @@ class ModuleForLoaderTests(unittest.TestCase):
def test_reload(self):
# Test that a module is reused if already in sys.modules.
+ class FakeLoader:
+ def is_package(self, name):
+ return True
+ @self.module_for_loader
+ def load_module(self, module):
+ return module
name = 'a.b.c'
- module = imp.new_module('a.b.c')
+ module = types.ModuleType('a.b.c')
+ module.__loader__ = 42
+ module.__package__ = 42
with test_util.uncache(name):
sys.modules[name] = module
- returned_module = self.return_module(name)
+ loader = FakeLoader()
+ returned_module = loader.load_module(name)
self.assertIs(returned_module, sys.modules[name])
+ self.assertEqual(module.__loader__, loader)
+ self.assertEqual(module.__package__, name)
def test_new_module_failure(self):
# Test that a module is removed from sys.modules if added but an
@@ -53,7 +105,7 @@ class ModuleForLoaderTests(unittest.TestCase):
def test_reload_failure(self):
# Test that a failure on reload leaves the module in-place.
name = 'a.b.c'
- module = imp.new_module(name)
+ module = types.ModuleType(name)
with test_util.uncache(name):
sys.modules[name] = module
self.raise_exception(name)
@@ -61,7 +113,7 @@ class ModuleForLoaderTests(unittest.TestCase):
def test_decorator_attrs(self):
def fxn(self, module): pass
- wrapped = util.module_for_loader(fxn)
+ wrapped = self.module_for_loader(fxn)
self.assertEqual(wrapped.__name__, fxn.__name__)
self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
@@ -87,7 +139,7 @@ class ModuleForLoaderTests(unittest.TestCase):
self._pkg = is_package
def is_package(self, name):
return self._pkg
- @util.module_for_loader
+ @self.module_for_loader
def load_module(self, module):
return module
@@ -107,8 +159,11 @@ class ModuleForLoaderTests(unittest.TestCase):
self.assertIs(module.__loader__, loader)
self.assertEqual(module.__package__, name)
+Frozen_ModuleForLoaderTests, Source_ModuleForLoaderTests = test_util.test_both(
+ ModuleForLoaderTests, util=[frozen_util, source_util])
-class SetPackageTests(unittest.TestCase):
+
+class SetPackageTests:
"""Tests for importlib.util.set_package."""
@@ -116,7 +171,7 @@ class SetPackageTests(unittest.TestCase):
"""Verify the module has the expected value for __package__ after
passing through set_package."""
fxn = lambda: module
- wrapped = util.set_package(fxn)
+ wrapped = self.util.set_package(fxn)
wrapped()
self.assertTrue(hasattr(module, '__package__'))
self.assertEqual(expect, module.__package__)
@@ -124,26 +179,26 @@ class SetPackageTests(unittest.TestCase):
def test_top_level(self):
# __package__ should be set to the empty string if a top-level module.
# Implicitly tests when package is set to None.
- module = imp.new_module('module')
+ module = types.ModuleType('module')
module.__package__ = None
self.verify(module, '')
def test_package(self):
# Test setting __package__ for a package.
- module = imp.new_module('pkg')
+ module = types.ModuleType('pkg')
module.__path__ = ['<path>']
module.__package__ = None
self.verify(module, 'pkg')
def test_submodule(self):
# Test __package__ for a module in a package.
- module = imp.new_module('pkg.mod')
+ module = types.ModuleType('pkg.mod')
module.__package__ = None
self.verify(module, 'pkg')
def test_setting_if_missing(self):
# __package__ should be set if it is missing.
- module = imp.new_module('mod')
+ module = types.ModuleType('mod')
if hasattr(module, '__package__'):
delattr(module, '__package__')
self.verify(module, '')
@@ -151,58 +206,230 @@ class SetPackageTests(unittest.TestCase):
def test_leaving_alone(self):
# If __package__ is set and not None then leave it alone.
for value in (True, False):
- module = imp.new_module('mod')
+ module = types.ModuleType('mod')
module.__package__ = value
self.verify(module, value)
def test_decorator_attrs(self):
def fxn(module): pass
- wrapped = util.set_package(fxn)
+ wrapped = self.util.set_package(fxn)
self.assertEqual(wrapped.__name__, fxn.__name__)
self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
+Frozen_SetPackageTests, Source_SetPackageTests = test_util.test_both(
+ SetPackageTests, util=[frozen_util, source_util])
+
+
+class SetLoaderTests:
+
+ """Tests importlib.util.set_loader()."""
+
+ class DummyLoader:
+ @util.set_loader
+ def load_module(self, module):
+ return self.module
+
+ def test_no_attribute(self):
+ loader = self.DummyLoader()
+ loader.module = types.ModuleType('blah')
+ try:
+ del loader.module.__loader__
+ except AttributeError:
+ pass
+ self.assertEqual(loader, loader.load_module('blah').__loader__)
-class ResolveNameTests(unittest.TestCase):
+ def test_attribute_is_None(self):
+ loader = self.DummyLoader()
+ loader.module = types.ModuleType('blah')
+ loader.module.__loader__ = None
+ self.assertEqual(loader, loader.load_module('blah').__loader__)
+
+ def test_not_reset(self):
+ loader = self.DummyLoader()
+ loader.module = types.ModuleType('blah')
+ loader.module.__loader__ = 42
+ self.assertEqual(42, loader.load_module('blah').__loader__)
+
+class Frozen_SetLoaderTests(SetLoaderTests, unittest.TestCase):
+ class DummyLoader:
+ @frozen_util.set_loader
+ def load_module(self, module):
+ return self.module
+
+class Source_SetLoaderTests(SetLoaderTests, unittest.TestCase):
+ class DummyLoader:
+ @source_util.set_loader
+ def load_module(self, module):
+ return self.module
+
+
+class ResolveNameTests:
"""Tests importlib.util.resolve_name()."""
def test_absolute(self):
# bacon
- self.assertEqual('bacon', util.resolve_name('bacon', None))
+ self.assertEqual('bacon', self.util.resolve_name('bacon', None))
def test_aboslute_within_package(self):
# bacon in spam
- self.assertEqual('bacon', util.resolve_name('bacon', 'spam'))
+ self.assertEqual('bacon', self.util.resolve_name('bacon', 'spam'))
def test_no_package(self):
# .bacon in ''
with self.assertRaises(ValueError):
- util.resolve_name('.bacon', '')
+ self.util.resolve_name('.bacon', '')
def test_in_package(self):
# .bacon in spam
self.assertEqual('spam.eggs.bacon',
- util.resolve_name('.bacon', 'spam.eggs'))
+ self.util.resolve_name('.bacon', 'spam.eggs'))
def test_other_package(self):
# ..bacon in spam.bacon
self.assertEqual('spam.bacon',
- util.resolve_name('..bacon', 'spam.eggs'))
+ self.util.resolve_name('..bacon', 'spam.eggs'))
def test_escape(self):
# ..bacon in spam
with self.assertRaises(ValueError):
- util.resolve_name('..bacon', 'spam')
-
-
-def test_main():
- from test import support
- support.run_unittest(
- ModuleForLoaderTests,
- SetPackageTests,
- ResolveNameTests
- )
+ self.util.resolve_name('..bacon', 'spam')
+
+Frozen_ResolveNameTests, Source_ResolveNameTests = test_util.test_both(
+ ResolveNameTests,
+ util=[frozen_util, source_util])
+
+
+class MagicNumberTests:
+
+ def test_length(self):
+ # Should be 4 bytes.
+ self.assertEqual(len(self.util.MAGIC_NUMBER), 4)
+
+ def test_incorporates_rn(self):
+ # The magic number uses \r\n to come out wrong when splitting on lines.
+ self.assertTrue(self.util.MAGIC_NUMBER.endswith(b'\r\n'))
+
+Frozen_MagicNumberTests, Source_MagicNumberTests = test_util.test_both(
+ MagicNumberTests, util=[frozen_util, source_util])
+
+
+class PEP3147Tests:
+
+ """Tests of PEP 3147-related functions: cache_from_source and source_from_cache."""
+
+ tag = sys.implementation.cache_tag
+
+ @unittest.skipUnless(sys.implementation.cache_tag is not None,
+ 'requires sys.implementation.cache_tag not be None')
+ def test_cache_from_source(self):
+ # Given the path to a .py file, return the path to its PEP 3147
+ # defined .pyc file (i.e. under __pycache__).
+ path = os.path.join('foo', 'bar', 'baz', 'qux.py')
+ expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
+ 'qux.{}.pyc'.format(self.tag))
+ self.assertEqual(self.util.cache_from_source(path, True), expect)
+
+ def test_cache_from_source_no_cache_tag(self):
+ # No cache tag means NotImplementedError.
+ with support.swap_attr(sys.implementation, 'cache_tag', None):
+ with self.assertRaises(NotImplementedError):
+ self.util.cache_from_source('whatever.py')
+
+ def test_cache_from_source_no_dot(self):
+ # Directory with a dot, filename without dot.
+ path = os.path.join('foo.bar', 'file')
+ expect = os.path.join('foo.bar', '__pycache__',
+ 'file{}.pyc'.format(self.tag))
+ self.assertEqual(self.util.cache_from_source(path, True), expect)
+
+ def test_cache_from_source_optimized(self):
+ # Given the path to a .py file, return the path to its PEP 3147
+ # defined .pyo file (i.e. under __pycache__).
+ path = os.path.join('foo', 'bar', 'baz', 'qux.py')
+ expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
+ 'qux.{}.pyo'.format(self.tag))
+ self.assertEqual(self.util.cache_from_source(path, False), expect)
+
+ def test_cache_from_source_cwd(self):
+ path = 'foo.py'
+ expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
+ self.assertEqual(self.util.cache_from_source(path, True), expect)
+
+ def test_cache_from_source_override(self):
+ # When debug_override is not None, it can be any true-ish or false-ish
+ # value.
+ path = os.path.join('foo', 'bar', 'baz.py')
+ partial_expect = os.path.join('foo', 'bar', '__pycache__',
+ 'baz.{}.py'.format(self.tag))
+ self.assertEqual(self.util.cache_from_source(path, []), partial_expect + 'o')
+ self.assertEqual(self.util.cache_from_source(path, [17]),
+ partial_expect + 'c')
+ # However if the bool-ishness can't be determined, the exception
+ # propagates.
+ class Bearish:
+ def __bool__(self): raise RuntimeError
+ with self.assertRaises(RuntimeError):
+ self.util.cache_from_source('/foo/bar/baz.py', Bearish())
+
+ @unittest.skipUnless(os.sep == '\\' and os.altsep == '/',
+ 'test meaningful only where os.altsep is defined')
+ def test_sep_altsep_and_sep_cache_from_source(self):
+ # Windows path and PEP 3147 where sep is right of altsep.
+ self.assertEqual(
+ self.util.cache_from_source('\\foo\\bar\\baz/qux.py', True),
+ '\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag))
+
+ @unittest.skipUnless(sys.implementation.cache_tag is not None,
+ 'requires sys.implementation.cache_tag to not be '
+ 'None')
+ def test_source_from_cache(self):
+ # Given the path to a PEP 3147 defined .pyc file, return the path to
+ # its source. This tests the good path.
+ path = os.path.join('foo', 'bar', 'baz', '__pycache__',
+ 'qux.{}.pyc'.format(self.tag))
+ expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
+ self.assertEqual(self.util.source_from_cache(path), expect)
+
+ def test_source_from_cache_no_cache_tag(self):
+ # If sys.implementation.cache_tag is None, raise NotImplementedError.
+ path = os.path.join('blah', '__pycache__', 'whatever.pyc')
+ with support.swap_attr(sys.implementation, 'cache_tag', None):
+ with self.assertRaises(NotImplementedError):
+ self.util.source_from_cache(path)
+
+ def test_source_from_cache_bad_path(self):
+ # When the path to a pyc file is not in PEP 3147 format, a ValueError
+ # is raised.
+ self.assertRaises(
+ ValueError, self.util.source_from_cache, '/foo/bar/bazqux.pyc')
+
+ def test_source_from_cache_no_slash(self):
+ # No slashes at all in path -> ValueError
+ self.assertRaises(
+ ValueError, self.util.source_from_cache, 'foo.cpython-32.pyc')
+
+ def test_source_from_cache_too_few_dots(self):
+ # Too few dots in final path component -> ValueError
+ self.assertRaises(
+ ValueError, self.util.source_from_cache, '__pycache__/foo.pyc')
+
+ def test_source_from_cache_too_many_dots(self):
+ # Too many dots in final path component -> ValueError
+ self.assertRaises(
+ ValueError, self.util.source_from_cache,
+ '__pycache__/foo.cpython-32.foo.pyc')
+
+ def test_source_from_cache_no__pycache__(self):
+ # Another problem with the path -> ValueError
+ self.assertRaises(
+ ValueError, self.util.source_from_cache,
+ '/foo/bar/foo.cpython-32.foo.pyc')
+
+Frozen_PEP3147Tests, Source_PEP3147Tests = test_util.test_both(
+ PEP3147Tests,
+ util=[frozen_util, source_util])
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py
index ef32f7d690..80d43b5baa 100644
--- a/Lib/test/test_importlib/util.py
+++ b/Lib/test/test_importlib/util.py
@@ -1,9 +1,30 @@
from contextlib import contextmanager
-import imp
import os.path
from test import support
import unittest
import sys
+import types
+
+
+def import_importlib(module_name):
+ """Import a module from importlib both w/ and w/o _frozen_importlib."""
+ fresh = ('importlib',) if '.' in module_name else ()
+ frozen = support.import_fresh_module(module_name)
+ source = support.import_fresh_module(module_name, fresh=fresh,
+ blocked=('_frozen_importlib',))
+ return frozen, source
+
+
+def test_both(test_class, **kwargs):
+ frozen_tests = types.new_class('Frozen_'+test_class.__name__,
+ (test_class, unittest.TestCase))
+ source_tests = types.new_class('Source_'+test_class.__name__,
+ (test_class, unittest.TestCase))
+ frozen_tests.__module__ = source_tests.__module__ = test_class.__module__
+ for attr, (frozen_value, source_value) in kwargs.items():
+ setattr(frozen_tests, attr, frozen_value)
+ setattr(source_tests, attr, source_value)
+ return frozen_tests, source_tests
CASE_INSENSITIVE_FS = True
@@ -98,7 +119,7 @@ class mock_modules:
package = name.rsplit('.', 1)[0]
else:
package = import_name
- module = imp.new_module(import_name)
+ module = types.ModuleType(import_name)
module.__loader__ = self
module.__file__ = '<mock __file__>'
module.__package__ = package
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 5cbec9bb17..365f59230a 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -8,10 +8,17 @@ import datetime
import collections
import os
import shutil
+import functools
+import importlib
from os.path import normcase
+try:
+ from concurrent.futures import ThreadPoolExecutor
+except ImportError:
+ ThreadPoolExecutor = None
from test.support import run_unittest, TESTFN, DirsOnSysPath
-
+from test.support import MISSING_C_DOCSTRINGS
+from test.script_helper import assert_python_ok, assert_python_failure
from test import inspect_fodder as mod
from test import inspect_fodder2 as mod2
@@ -120,7 +127,6 @@ class TestPredicates(IsTestBase):
def test_get_slot_members(self):
class C(object):
__slots__ = ("a", "b")
-
x = C()
x.a = 42
members = dict(inspect.getmembers(x))
@@ -418,14 +424,14 @@ class TestBuggyCases(GetSourceBase):
unicodedata.__file__[-4:] in (".pyc", ".pyo"),
"unicodedata is not an external binary module")
def test_findsource_binary(self):
- self.assertRaises(IOError, inspect.getsource, unicodedata)
- self.assertRaises(IOError, inspect.findsource, unicodedata)
+ self.assertRaises(OSError, inspect.getsource, unicodedata)
+ self.assertRaises(OSError, inspect.findsource, unicodedata)
def test_findsource_code_in_linecache(self):
lines = ["x=1"]
co = compile(lines[0], "_dynamically_created_file", "exec")
- self.assertRaises(IOError, inspect.findsource, co)
- self.assertRaises(IOError, inspect.getsource, co)
+ self.assertRaises(OSError, inspect.findsource, co)
+ self.assertRaises(OSError, inspect.getsource, co)
linecache.cache[co.co_filename] = (1, None, lines, co.co_filename)
try:
self.assertEqual(inspect.findsource(co), (lines,0))
@@ -463,13 +469,13 @@ class _BrokenDataDescriptor(object):
A broken data descriptor. See bug #1785.
"""
def __get__(*args):
- raise AssertionError("should not __get__ data descriptors")
+ raise AttributeError("broken data descriptor")
def __set__(*args):
raise RuntimeError
def __getattr__(*args):
- raise AssertionError("should not __getattr__ data descriptors")
+ raise AttributeError("broken data descriptor")
class _BrokenMethodDescriptor(object):
@@ -477,10 +483,10 @@ class _BrokenMethodDescriptor(object):
A broken method descriptor. See bug #1785.
"""
def __get__(*args):
- raise AssertionError("should not __get__ method descriptors")
+ raise AttributeError("broken method descriptor")
def __getattr__(*args):
- raise AssertionError("should not __getattr__ method descriptors")
+ raise AttributeError("broken method descriptor")
# Helper for testing classify_class_attrs.
@@ -650,6 +656,88 @@ class TestClassesAndFunctions(unittest.TestCase):
if isinstance(builtin, type):
inspect.classify_class_attrs(builtin)
+ def test_classify_DynamicClassAttribute(self):
+ class Meta(type):
+ def __getattr__(self, name):
+ if name == 'ham':
+ return 'spam'
+ return super().__getattr__(name)
+ class VA(metaclass=Meta):
+ @types.DynamicClassAttribute
+ def ham(self):
+ return 'eggs'
+ should_find_dca = inspect.Attribute('ham', 'data', VA, VA.__dict__['ham'])
+ self.assertIn(should_find_dca, inspect.classify_class_attrs(VA))
+ should_find_ga = inspect.Attribute('ham', 'data', Meta, 'spam')
+ self.assertIn(should_find_ga, inspect.classify_class_attrs(VA))
+
+ def test_classify_metaclass_class_attribute(self):
+ class Meta(type):
+ fish = 'slap'
+ def __dir__(self):
+ return ['__class__', '__modules__', '__name__', 'fish']
+ class Class(metaclass=Meta):
+ pass
+ should_find = inspect.Attribute('fish', 'data', Meta, 'slap')
+ self.assertIn(should_find, inspect.classify_class_attrs(Class))
+
+ def test_classify_VirtualAttribute(self):
+ class Meta(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'BOOM']
+ def __getattr__(self, name):
+ if name =='BOOM':
+ return 42
+ return super().__getattr(name)
+ class Class(metaclass=Meta):
+ pass
+ should_find = inspect.Attribute('BOOM', 'data', Meta, 42)
+ self.assertIn(should_find, inspect.classify_class_attrs(Class))
+
+ def test_classify_VirtualAttribute_multi_classes(self):
+ class Meta1(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'one']
+ def __getattr__(self, name):
+ if name =='one':
+ return 1
+ return super().__getattr__(name)
+ class Meta2(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'two']
+ def __getattr__(self, name):
+ if name =='two':
+ return 2
+ return super().__getattr__(name)
+ class Meta3(Meta1, Meta2):
+ def __dir__(cls):
+ return list(sorted(set(['__class__', '__module__', '__name__', 'three'] +
+ Meta1.__dir__(cls) + Meta2.__dir__(cls))))
+ def __getattr__(self, name):
+ if name =='three':
+ return 3
+ return super().__getattr__(name)
+ class Class1(metaclass=Meta1):
+ pass
+ class Class2(Class1, metaclass=Meta3):
+ pass
+
+ should_find1 = inspect.Attribute('one', 'data', Meta1, 1)
+ should_find2 = inspect.Attribute('two', 'data', Meta2, 2)
+ should_find3 = inspect.Attribute('three', 'data', Meta3, 3)
+ cca = inspect.classify_class_attrs(Class2)
+ for sf in (should_find1, should_find2, should_find3):
+ self.assertIn(sf, cca)
+
+ def test_classify_class_attrs_with_buggy_dir(self):
+ class M(type):
+ def __dir__(cls):
+ return ['__class__', '__name__', 'missing']
+ class C(metaclass=M):
+ pass
+ attrs = [a[0] for a in inspect.classify_class_attrs(C)]
+ self.assertNotIn('missing', attrs)
+
def test_getmembers_descriptors(self):
class A(object):
dd = _BrokenDataDescriptor()
@@ -693,6 +781,28 @@ class TestClassesAndFunctions(unittest.TestCase):
self.assertIn(('f', b.f), inspect.getmembers(b))
self.assertIn(('f', b.f), inspect.getmembers(b, inspect.ismethod))
+ def test_getmembers_VirtualAttribute(self):
+ class M(type):
+ def __getattr__(cls, name):
+ if name == 'eggs':
+ return 'scrambled'
+ return super().__getattr__(name)
+ class A(metaclass=M):
+ @types.DynamicClassAttribute
+ def eggs(self):
+ return 'spam'
+ self.assertIn(('eggs', 'scrambled'), inspect.getmembers(A))
+ self.assertIn(('eggs', 'spam'), inspect.getmembers(A()))
+
+ def test_getmembers_with_buggy_dir(self):
+ class M(type):
+ def __dir__(cls):
+ return ['__class__', '__name__', 'missing']
+ class C(metaclass=M):
+ pass
+ attrs = [a[0] for a in inspect.getmembers(C)]
+ self.assertNotIn('missing', attrs)
+
_global_ref = object()
class TestGetClosureVars(unittest.TestCase):
@@ -1080,6 +1190,15 @@ class TestGetattrStatic(unittest.TestCase):
self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.x)
+ def test_classVirtualAttribute(self):
+ class Thing(object):
+ @types.DynamicClassAttribute
+ def x(self):
+ return self._x
+ _x = object()
+
+ self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.__dict__['x'])
+
def test_inherited_classattribute(self):
class Thing(object):
x = object()
@@ -1461,7 +1580,7 @@ class TestSignatureObject(unittest.TestCase):
('kwargs', ..., int, "var_keyword")),
...))
- def test_signature_on_builtin_function(self):
+ def test_signature_on_unsupported_builtins(self):
with self.assertRaisesRegex(ValueError, 'not supported by signature'):
inspect.signature(type)
with self.assertRaisesRegex(ValueError, 'not supported by signature'):
@@ -1470,10 +1589,13 @@ class TestSignatureObject(unittest.TestCase):
with self.assertRaisesRegex(ValueError, 'not supported by signature'):
# support for 'method-wrapper'
inspect.signature(min.__call__)
- with self.assertRaisesRegex(ValueError,
- 'no signature found for builtin function'):
- # support for 'method-wrapper'
- inspect.signature(min)
+
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_signature_on_builtins(self):
+ self.assertEqual(inspect.signature(min), None)
+ signature = inspect.signature(os.stat)
+ self.assertTrue(isinstance(signature, inspect.Signature))
def test_signature_on_non_function(self):
with self.assertRaisesRegex(TypeError, 'is not a callable object'):
@@ -1736,6 +1858,17 @@ class TestSignatureObject(unittest.TestCase):
((('b', ..., ..., "positional_or_keyword"),),
...))
+ # Test we handle __signature__ partway down the wrapper stack
+ def wrapped_foo_call():
+ pass
+ wrapped_foo_call.__wrapped__ = Foo.__call__
+
+ self.assertEqual(self.signature(wrapped_foo_call),
+ ((('a', ..., ..., "positional_or_keyword"),
+ ('b', ..., ..., "positional_or_keyword")),
+ ...))
+
+
def test_signature_on_class(self):
class C:
def __init__(self, a):
@@ -1850,6 +1983,10 @@ class TestSignatureObject(unittest.TestCase):
self.assertEqual(self.signature(Wrapped),
((('a', ..., ..., "positional_or_keyword"),),
...))
+ # wrapper loop:
+ Wrapped.__wrapped__ = Wrapped
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ self.signature(Wrapped)
def test_signature_on_lambdas(self):
self.assertEqual(self.signature((lambda a=10: a)),
@@ -2301,6 +2438,103 @@ class TestBoundArguments(unittest.TestCase):
self.assertNotEqual(ba, ba4)
+class TestUnwrap(unittest.TestCase):
+
+ def test_unwrap_one(self):
+ def func(a, b):
+ return a + b
+ wrapper = functools.lru_cache(maxsize=20)(func)
+ self.assertIs(inspect.unwrap(wrapper), func)
+
+ def test_unwrap_several(self):
+ def func(a, b):
+ return a + b
+ wrapper = func
+ for __ in range(10):
+ @functools.wraps(wrapper)
+ def wrapper():
+ pass
+ self.assertIsNot(wrapper.__wrapped__, func)
+ self.assertIs(inspect.unwrap(wrapper), func)
+
+ def test_stop(self):
+ def func1(a, b):
+ return a + b
+ @functools.wraps(func1)
+ def func2():
+ pass
+ @functools.wraps(func2)
+ def wrapper():
+ pass
+ func2.stop_here = 1
+ unwrapped = inspect.unwrap(wrapper,
+ stop=(lambda f: hasattr(f, "stop_here")))
+ self.assertIs(unwrapped, func2)
+
+ def test_cycle(self):
+ def func1(): pass
+ func1.__wrapped__ = func1
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ inspect.unwrap(func1)
+
+ def func2(): pass
+ func2.__wrapped__ = func1
+ func1.__wrapped__ = func2
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ inspect.unwrap(func1)
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ inspect.unwrap(func2)
+
+ def test_unhashable(self):
+ def func(): pass
+ func.__wrapped__ = None
+ class C:
+ __hash__ = None
+ __wrapped__ = func
+ self.assertIsNone(inspect.unwrap(C()))
+
+class TestMain(unittest.TestCase):
+ def test_only_source(self):
+ module = importlib.import_module('unittest')
+ rc, out, err = assert_python_ok('-m', 'inspect',
+ 'unittest')
+ lines = out.decode().splitlines()
+ # ignore the final newline
+ self.assertEqual(lines[:-1], inspect.getsource(module).splitlines())
+ self.assertEqual(err, b'')
+
+ @unittest.skipIf(ThreadPoolExecutor is None,
+ 'threads required to test __qualname__ for source files')
+ def test_qualname_source(self):
+ rc, out, err = assert_python_ok('-m', 'inspect',
+ 'concurrent.futures:ThreadPoolExecutor')
+ lines = out.decode().splitlines()
+ # ignore the final newline
+ self.assertEqual(lines[:-1],
+ inspect.getsource(ThreadPoolExecutor).splitlines())
+ self.assertEqual(err, b'')
+
+ def test_builtins(self):
+ module = importlib.import_module('unittest')
+ _, out, err = assert_python_failure('-m', 'inspect',
+ 'sys')
+ lines = err.decode().splitlines()
+ self.assertEqual(lines, ["Can't get info for builtin modules."])
+
+ def test_details(self):
+ module = importlib.import_module('unittest')
+ rc, out, err = assert_python_ok('-m', 'inspect',
+ 'unittest', '--details')
+ output = out.decode()
+ # Just a quick sanity check on the output
+ self.assertIn(module.__name__, output)
+ self.assertIn(module.__file__, output)
+ self.assertIn(module.__cached__, output)
+ self.assertEqual(err, b'')
+
+
+
+
def test_main():
run_unittest(
TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases,
@@ -2308,7 +2542,7 @@ def test_main():
TestGetcallargsFunctions, TestGetcallargsMethods,
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
- TestBoundArguments, TestGetClosureVars
+ TestBoundArguments, TestGetClosureVars, TestUnwrap, TestMain
)
if __name__ == "__main__":
diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py
index c198bcc740..4e00622325 100644
--- a/Lib/test/test_int.py
+++ b/Lib/test/test_int.py
@@ -228,6 +228,47 @@ class IntTestCases(unittest.TestCase):
self.assertRaises(TypeError, int, base=10)
self.assertRaises(TypeError, int, base=0)
+ def test_int_base_limits(self):
+ """Testing the supported limits of the int() base parameter."""
+ self.assertEqual(int('0', 5), 0)
+ with self.assertRaises(ValueError):
+ int('0', 1)
+ with self.assertRaises(ValueError):
+ int('0', 37)
+ with self.assertRaises(ValueError):
+ int('0', -909) # An old magic value base from Python 2.
+ with self.assertRaises(ValueError):
+ int('0', base=0-(2**234))
+ with self.assertRaises(ValueError):
+ int('0', base=2**234)
+ # Bases 2 through 36 are supported.
+ for base in range(2,37):
+ self.assertEqual(int('0', base=base), 0)
+
+ def test_int_base_bad_types(self):
+ """Not integer types are not valid bases; issue16772."""
+ with self.assertRaises(TypeError):
+ int('0', 5.5)
+ with self.assertRaises(TypeError):
+ int('0', 5.0)
+
+ def test_int_base_indexable(self):
+ class MyIndexable(object):
+ def __init__(self, value):
+ self.value = value
+ def __index__(self):
+ return self.value
+
+ # Check out of range bases.
+ for base in 2**100, -2**100, 1, 37:
+ with self.assertRaises(ValueError):
+ int('43', base)
+
+ # Check in-range bases.
+ self.assertEqual(int('101', base=MyIndexable(2)), 5)
+ self.assertEqual(int('101', base=MyIndexable(10)), 101)
+ self.assertEqual(int('101', base=MyIndexable(36)), 1 + 36**2)
+
def test_non_numeric_input_types(self):
# Test possible non-numeric types for the argument x, including
# subclasses of the explicitly documented accepted types.
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 9b8920211e..57f3659667 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -165,7 +165,7 @@ class CloseFailureIO(MockRawIO):
def close(self):
if not self.closed:
self.closed = 1
- raise IOError
+ raise OSError
class CCloseFailureIO(CloseFailureIO, io.RawIOBase):
pass
@@ -601,9 +601,9 @@ class IOTest(unittest.TestCase):
def test_flush_error_on_close(self):
f = self.open(support.TESTFN, "wb", buffering=0)
def bad_flush():
- raise IOError()
+ raise OSError()
f.flush = bad_flush
- self.assertRaises(IOError, f.close) # exception not swallowed
+ self.assertRaises(OSError, f.close) # exception not swallowed
self.assertTrue(f.closed)
def test_multi_close(self):
@@ -762,7 +762,7 @@ class CommonBufferedTests:
if s:
# The destructor *may* have printed an unraisable error, check it
self.assertEqual(len(s.splitlines()), 1)
- self.assertTrue(s.startswith("Exception IOError: "), s)
+ self.assertTrue(s.startswith("Exception OSError: "), s)
self.assertTrue(s.endswith(" ignored"), s)
def test_repr(self):
@@ -778,22 +778,22 @@ class CommonBufferedTests:
def test_flush_error_on_close(self):
raw = self.MockRawIO()
def bad_flush():
- raise IOError()
+ raise OSError()
raw.flush = bad_flush
b = self.tp(raw)
- self.assertRaises(IOError, b.close) # exception not swallowed
+ self.assertRaises(OSError, b.close) # exception not swallowed
self.assertTrue(b.closed)
def test_close_error_on_close(self):
raw = self.MockRawIO()
def bad_flush():
- raise IOError('flush')
+ raise OSError('flush')
def bad_close():
- raise IOError('close')
+ raise OSError('close')
raw.close = bad_close
b = self.tp(raw)
b.flush = bad_flush
- with self.assertRaises(IOError) as err: # exception not swallowed
+ with self.assertRaises(OSError) as err: # exception not swallowed
b.close()
self.assertEqual(err.exception.args, ('close',))
self.assertEqual(err.exception.__context__.args, ('flush',))
@@ -833,6 +833,14 @@ class SizeofTest:
bufio = self.tp(rawio, buffer_size=bufsize2)
self.assertEqual(sys.getsizeof(bufio), size + bufsize2)
+ @support.cpython_only
+ def test_buffer_freeing(self) :
+ bufsize = 4096
+ rawio = self.MockRawIO()
+ bufio = self.tp(rawio, buffer_size=bufsize)
+ size = sys.getsizeof(bufio) - bufsize
+ bufio.close()
+ self.assertEqual(sys.getsizeof(bufio), size)
class BufferedReaderTest(unittest.TestCase, CommonBufferedTests):
read_mode = "rb"
@@ -1007,8 +1015,8 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests):
def test_misbehaved_io(self):
rawio = self.MisbehavedRawIO((b"abc", b"d", b"efg"))
bufio = self.tp(rawio)
- self.assertRaises(IOError, bufio.seek, 0)
- self.assertRaises(IOError, bufio.tell)
+ self.assertRaises(OSError, bufio.seek, 0)
+ self.assertRaises(OSError, bufio.tell)
def test_no_extraneous_read(self):
# Issue #9550; when the raw IO object has satisfied the read request,
@@ -1059,17 +1067,18 @@ class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
bufio = self.tp(rawio)
# _pyio.BufferedReader seems to implement reading different, so that
# checking this is not so easy.
- self.assertRaises(IOError, bufio.read, 10)
+ self.assertRaises(OSError, bufio.read, 10)
def test_garbage_collection(self):
# C BufferedReader objects are collected.
# The Python version has __del__, so it ends into gc.garbage instead
- rawio = self.FileIO(support.TESTFN, "w+b")
- f = self.tp(rawio)
- f.f = f
- wr = weakref.ref(f)
- del f
- support.gc_collect()
+ with support.check_warnings(('', ResourceWarning)):
+ rawio = self.FileIO(support.TESTFN, "w+b")
+ f = self.tp(rawio)
+ f.f = f
+ wr = weakref.ref(f)
+ del f
+ support.gc_collect()
self.assertTrue(wr() is None, wr)
def test_args_error(self):
@@ -1312,9 +1321,9 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
def test_misbehaved_io(self):
rawio = self.MisbehavedRawIO()
bufio = self.tp(rawio, 5)
- self.assertRaises(IOError, bufio.seek, 0)
- self.assertRaises(IOError, bufio.tell)
- self.assertRaises(IOError, bufio.write, b"abcdef")
+ self.assertRaises(OSError, bufio.seek, 0)
+ self.assertRaises(OSError, bufio.tell)
+ self.assertRaises(OSError, bufio.write, b"abcdef")
def test_max_buffer_size_removal(self):
with self.assertRaises(TypeError):
@@ -1323,11 +1332,11 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
def test_write_error_on_close(self):
raw = self.MockRawIO()
def bad_write(b):
- raise IOError()
+ raise OSError()
raw.write = bad_write
b = self.tp(raw)
b.write(b'spam')
- self.assertRaises(IOError, b.close) # exception not swallowed
+ self.assertRaises(OSError, b.close) # exception not swallowed
self.assertTrue(b.closed)
@@ -1358,13 +1367,14 @@ class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
# C BufferedWriter objects are collected, and collecting them flushes
# all data to disk.
# The Python version has __del__, so it ends into gc.garbage instead
- rawio = self.FileIO(support.TESTFN, "w+b")
- f = self.tp(rawio)
- f.write(b"123xxx")
- f.x = f
- wr = weakref.ref(f)
- del f
- support.gc_collect()
+ with support.check_warnings(('', ResourceWarning)):
+ rawio = self.FileIO(support.TESTFN, "w+b")
+ f = self.tp(rawio)
+ f.write(b"123xxx")
+ f.x = f
+ wr = weakref.ref(f)
+ del f
+ support.gc_collect()
self.assertTrue(wr() is None, wr)
with self.open(support.TESTFN, "rb") as f:
self.assertEqual(f.read(), b"123xxx")
@@ -1397,14 +1407,14 @@ class BufferedRWPairTest(unittest.TestCase):
def readable(self):
return False
- self.assertRaises(IOError, self.tp, NotReadable(), self.MockRawIO())
+ self.assertRaises(OSError, self.tp, NotReadable(), self.MockRawIO())
def test_constructor_with_not_writeable(self):
class NotWriteable(MockRawIO):
def writable(self):
return False
- self.assertRaises(IOError, self.tp, self.MockRawIO(), NotWriteable())
+ self.assertRaises(OSError, self.tp, self.MockRawIO(), NotWriteable())
def test_read(self):
pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO())
@@ -2165,7 +2175,7 @@ class TextIOWrapperTest(unittest.TestCase):
if s:
# The destructor *may* have printed an unraisable error, check it
self.assertEqual(len(s.splitlines()), 1)
- self.assertTrue(s.startswith("Exception IOError: "), s)
+ self.assertTrue(s.startswith("Exception OSError: "), s)
self.assertTrue(s.endswith(" ignored"), s)
# Systematic tests of the text I/O API
@@ -2237,7 +2247,7 @@ class TextIOWrapperTest(unittest.TestCase):
f.seek(0)
for line in f:
self.assertEqual(line, "\xff\n")
- self.assertRaises(IOError, f.tell)
+ self.assertRaises(OSError, f.tell)
self.assertEqual(f.tell(), p2)
f.close()
@@ -2341,7 +2351,7 @@ class TextIOWrapperTest(unittest.TestCase):
def readable(self):
return False
txt = self.TextIOWrapper(UnReadable())
- self.assertRaises(IOError, txt.read)
+ self.assertRaises(OSError, txt.read)
def test_read_one_by_one(self):
txt = self.TextIOWrapper(self.BytesIO(b"AA\r\nBB"))
@@ -2516,9 +2526,9 @@ class TextIOWrapperTest(unittest.TestCase):
def test_flush_error_on_close(self):
txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii")
def bad_flush():
- raise IOError()
+ raise OSError()
txt.flush = bad_flush
- self.assertRaises(IOError, txt.close) # exception not swallowed
+ self.assertRaises(OSError, txt.close) # exception not swallowed
self.assertTrue(txt.closed)
def test_multi_close(self):
@@ -2599,14 +2609,15 @@ class CTextIOWrapperTest(TextIOWrapperTest):
# C TextIOWrapper objects are collected, and collecting them flushes
# all data to disk.
# The Python version has __del__, so it ends in gc.garbage instead.
- rawio = io.FileIO(support.TESTFN, "wb")
- b = self.BufferedWriter(rawio)
- t = self.TextIOWrapper(b, encoding="ascii")
- t.write("456def")
- t.x = t
- wr = weakref.ref(t)
- del t
- support.gc_collect()
+ with support.check_warnings(('', ResourceWarning)):
+ rawio = io.FileIO(support.TESTFN, "wb")
+ b = self.BufferedWriter(rawio)
+ t = self.TextIOWrapper(b, encoding="ascii")
+ t.write("456def")
+ t.x = t
+ wr = weakref.ref(t)
+ del t
+ support.gc_collect()
self.assertTrue(wr() is None, wr)
with self.open(support.TESTFN, "rb") as f:
self.assertEqual(f.read(), b"456def")
@@ -2766,7 +2777,8 @@ class MiscIOTest(unittest.TestCase):
self.assertEqual(f.mode, "wb")
f.close()
- f = self.open(support.TESTFN, "U")
+ with support.check_warnings(('', DeprecationWarning)):
+ f = self.open(support.TESTFN, "U")
self.assertEqual(f.name, support.TESTFN)
self.assertEqual(f.buffer.name, support.TESTFN)
self.assertEqual(f.buffer.raw.name, support.TESTFN)
@@ -2896,7 +2908,7 @@ class MiscIOTest(unittest.TestCase):
for fd in fds:
try:
os.close(fd)
- except EnvironmentError as e:
+ except OSError as e:
if e.errno != errno.EBADF:
raise
self.addCleanup(cleanup_fds)
@@ -3062,15 +3074,18 @@ class SignalsTest(unittest.TestCase):
try:
wio = self.io.open(w, **fdopen_kwargs)
t.start()
- signal.alarm(1)
# Fill the pipe enough that the write will be blocking.
# It will be interrupted by the timer armed above. Since the
# other thread has read one byte, the low-level write will
# return with a successful (partial) result rather than an EINTR.
# The buffered IO layer must check for pending signal
# handlers, which in this case will invoke alarm_interrupt().
- self.assertRaises(ZeroDivisionError,
- wio.write, item * (support.PIPE_MAX_SIZE // len(item) + 1))
+ signal.alarm(1)
+ try:
+ self.assertRaises(ZeroDivisionError,
+ wio.write, item * (support.PIPE_MAX_SIZE // len(item) + 1))
+ finally:
+ signal.alarm(0)
t.join()
# We got one byte, get another one and check that it isn't a
# repeat of the first one.
@@ -3084,7 +3099,7 @@ class SignalsTest(unittest.TestCase):
# buffer, and block again.
try:
wio.close()
- except IOError as e:
+ except OSError as e:
if e.errno != errno.EBADF:
raise
@@ -3212,7 +3227,7 @@ class SignalsTest(unittest.TestCase):
# buffer, and could block (in case of failure).
try:
wio.close()
- except IOError as e:
+ except OSError as e:
if e.errno != errno.EBADF:
raise
diff --git a/Lib/test/test_ioctl.py b/Lib/test/test_ioctl.py
index 531c9afbb5..efe9f516cc 100644
--- a/Lib/test/test_ioctl.py
+++ b/Lib/test/test_ioctl.py
@@ -8,7 +8,7 @@ get_attribute(termios, 'TIOCGPGRP') #Can't run tests without this feature
try:
tty = open("/dev/tty", "rb")
-except IOError:
+except OSError:
raise unittest.SkipTest("Unable to open /dev/tty")
else:
# Skip if another process is in foreground
@@ -86,8 +86,6 @@ class IoctlTests(unittest.TestCase):
os.close(mfd)
os.close(sfd)
-def test_main():
- run_unittest(IoctlTests)
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index 99c54f1614..f3b1565744 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -1319,6 +1319,14 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(True, ipaddress.ip_network(
'127.42.0.0/16').is_loopback)
self.assertEqual(False, ipaddress.ip_network('128.0.0.0').is_loopback)
+ self.assertEqual(False,
+ ipaddress.ip_network('100.64.0.0/10').is_private)
+ self.assertEqual(False, ipaddress.ip_network('100.64.0.0/10').is_global)
+
+ self.assertEqual(True,
+ ipaddress.ip_network('192.0.2.128/25').is_private)
+ self.assertEqual(True,
+ ipaddress.ip_network('192.0.3.0/24').is_global)
# test addresses
self.assertEqual(True, ipaddress.ip_address('0.0.0.0').is_unspecified)
@@ -1384,6 +1392,10 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(False, ipaddress.ip_network('::1').is_unspecified)
self.assertEqual(False, ipaddress.ip_network('::/127').is_unspecified)
+ self.assertEqual(True,
+ ipaddress.ip_network('2001::1/128').is_private)
+ self.assertEqual(True,
+ ipaddress.ip_network('200::1/128').is_global)
# test addresses
self.assertEqual(True, ipaddress.ip_address('ffff::').is_multicast)
self.assertEqual(True, ipaddress.ip_address(2**128 - 1).is_multicast)
diff --git a/Lib/test/test_iterlen.py b/Lib/test/test_iterlen.py
index 9101f6c884..152f5fc0cb 100644
--- a/Lib/test/test_iterlen.py
+++ b/Lib/test/test_iterlen.py
@@ -45,31 +45,21 @@ import unittest
from test import support
from itertools import repeat
from collections import deque
-from builtins import len as _len
+from operator import length_hint
n = 10
-def len(obj):
- try:
- return _len(obj)
- except TypeError:
- try:
- # note: this is an internal undocumented API,
- # don't rely on it in your own programs
- return obj.__length_hint__()
- except AttributeError:
- raise TypeError
class TestInvariantWithoutMutations:
def test_invariant(self):
it = self.it
for i in reversed(range(1, n+1)):
- self.assertEqual(len(it), i)
+ self.assertEqual(length_hint(it), i)
next(it)
- self.assertEqual(len(it), 0)
+ self.assertEqual(length_hint(it), 0)
self.assertRaises(StopIteration, next, it)
- self.assertEqual(len(it), 0)
+ self.assertEqual(length_hint(it), 0)
class TestTemporarilyImmutable(TestInvariantWithoutMutations):
@@ -78,12 +68,12 @@ class TestTemporarilyImmutable(TestInvariantWithoutMutations):
# length immutability during iteration
it = self.it
- self.assertEqual(len(it), n)
+ self.assertEqual(length_hint(it), n)
next(it)
- self.assertEqual(len(it), n-1)
+ self.assertEqual(length_hint(it), n-1)
self.mutate()
self.assertRaises(RuntimeError, next, it)
- self.assertEqual(len(it), 0)
+ self.assertEqual(length_hint(it), 0)
## ------- Concrete Type Tests -------
@@ -92,10 +82,6 @@ class TestRepeat(TestInvariantWithoutMutations, unittest.TestCase):
def setUp(self):
self.it = repeat(None, n)
- def test_no_len_for_infinite_repeat(self):
- # The repeat() object can also be infinite
- self.assertRaises(TypeError, len, repeat(None))
-
class TestXrange(TestInvariantWithoutMutations, unittest.TestCase):
def setUp(self):
@@ -167,14 +153,15 @@ class TestList(TestInvariantWithoutMutations, unittest.TestCase):
it = iter(d)
next(it)
next(it)
- self.assertEqual(len(it), n-2)
+ self.assertEqual(length_hint(it), n - 2)
d.append(n)
- self.assertEqual(len(it), n-1) # grow with append
+ self.assertEqual(length_hint(it), n - 1) # grow with append
d[1:] = []
- self.assertEqual(len(it), 0)
+ self.assertEqual(length_hint(it), 0)
self.assertEqual(list(it), [])
d.extend(range(20))
- self.assertEqual(len(it), 0)
+ self.assertEqual(length_hint(it), 0)
+
class TestListReversed(TestInvariantWithoutMutations, unittest.TestCase):
@@ -186,32 +173,41 @@ class TestListReversed(TestInvariantWithoutMutations, unittest.TestCase):
it = reversed(d)
next(it)
next(it)
- self.assertEqual(len(it), n-2)
+ self.assertEqual(length_hint(it), n - 2)
d.append(n)
- self.assertEqual(len(it), n-2) # ignore append
+ self.assertEqual(length_hint(it), n - 2) # ignore append
d[1:] = []
- self.assertEqual(len(it), 0)
+ self.assertEqual(length_hint(it), 0)
self.assertEqual(list(it), []) # confirm invariant
d.extend(range(20))
- self.assertEqual(len(it), 0)
+ self.assertEqual(length_hint(it), 0)
## -- Check to make sure exceptions are not suppressed by __length_hint__()
class BadLen(object):
- def __iter__(self): return iter(range(10))
+ def __iter__(self):
+ return iter(range(10))
+
def __len__(self):
raise RuntimeError('hello')
+
class BadLengthHint(object):
- def __iter__(self): return iter(range(10))
+ def __iter__(self):
+ return iter(range(10))
+
def __length_hint__(self):
raise RuntimeError('hello')
+
class NoneLengthHint(object):
- def __iter__(self): return iter(range(10))
+ def __iter__(self):
+ return iter(range(10))
+
def __length_hint__(self):
- return None
+ return NotImplemented
+
class TestLengthHintExceptions(unittest.TestCase):
diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py
index 514a6b7a20..2672b00c56 100644
--- a/Lib/test/test_itertools.py
+++ b/Lib/test/test_itertools.py
@@ -1729,9 +1729,8 @@ class TestVariousIteratorArgs(unittest.TestCase):
class LengthTransparency(unittest.TestCase):
def test_repeat(self):
- from test.test_iterlen import len
- self.assertEqual(len(repeat(None, 50)), 50)
- self.assertRaises(TypeError, len, repeat(None))
+ self.assertEqual(operator.length_hint(repeat(None, 50)), 50)
+ self.assertEqual(operator.length_hint(repeat(None), 12), 12)
class RegressionTests(unittest.TestCase):
diff --git a/Lib/test/test_json/test_decode.py b/Lib/test/test_json/test_decode.py
index d23e285909..35c02de88c 100644
--- a/Lib/test/test_json/test_decode.py
+++ b/Lib/test/test_json/test_decode.py
@@ -1,5 +1,5 @@
import decimal
-from io import StringIO
+from io import StringIO, BytesIO
from collections import OrderedDict
from test.test_json import PyTest, CTest
@@ -70,5 +70,26 @@ class TestDecode:
msg = 'escape'
self.assertRaisesRegex(ValueError, msg, self.loads, s)
+ def test_invalid_input_type(self):
+ msg = 'the JSON object must be str'
+ for value in [1, 3.14, b'bytes', b'\xff\x00', [], {}, None]:
+ self.assertRaisesRegex(TypeError, msg, self.loads, value)
+ with self.assertRaisesRegex(TypeError, msg):
+ self.json.load(BytesIO(b'[1,2,3]'))
+
+ def test_string_with_utf8_bom(self):
+ # see #18958
+ bom_json = "[1,2,3]".encode('utf-8-sig').decode('utf-8')
+ with self.assertRaises(ValueError) as cm:
+ self.loads(bom_json)
+ self.assertIn('BOM', str(cm.exception))
+ with self.assertRaises(ValueError) as cm:
+ self.json.load(StringIO(bom_json))
+ self.assertIn('BOM', str(cm.exception))
+ # make sure that the BOM is not detected in the middle of a string
+ bom_in_str = '"{}"'.format(''.encode('utf-8-sig').decode('utf-8'))
+ self.assertEqual(self.loads(bom_in_str), '\ufeff')
+ self.assertEqual(self.json.load(StringIO(bom_in_str)), '\ufeff')
+
class TestPyDecode(TestDecode, PyTest): pass
class TestCDecode(TestDecode, CTest): pass
diff --git a/Lib/test/test_json/test_enum.py b/Lib/test/test_json/test_enum.py
new file mode 100644
index 0000000000..10f414898b
--- /dev/null
+++ b/Lib/test/test_json/test_enum.py
@@ -0,0 +1,120 @@
+from enum import Enum, IntEnum
+from math import isnan
+from test.test_json import PyTest, CTest
+
+SMALL = 1
+BIG = 1<<32
+HUGE = 1<<64
+REALLY_HUGE = 1<<96
+
+class BigNum(IntEnum):
+ small = SMALL
+ big = BIG
+ huge = HUGE
+ really_huge = REALLY_HUGE
+
+E = 2.718281
+PI = 3.141593
+TAU = 2 * PI
+
+class FloatNum(float, Enum):
+ e = E
+ pi = PI
+ tau = TAU
+
+INF = float('inf')
+NEG_INF = float('-inf')
+NAN = float('nan')
+
+class WierdNum(float, Enum):
+ inf = INF
+ neg_inf = NEG_INF
+ nan = NAN
+
+class TestEnum:
+
+ def test_floats(self):
+ for enum in FloatNum:
+ self.assertEqual(self.dumps(enum), repr(enum.value))
+ self.assertEqual(float(self.dumps(enum)), enum)
+ self.assertEqual(self.loads(self.dumps(enum)), enum)
+
+ def test_weird_floats(self):
+ for enum, expected in zip(WierdNum, ('Infinity', '-Infinity', 'NaN')):
+ self.assertEqual(self.dumps(enum), expected)
+ if not isnan(enum):
+ self.assertEqual(float(self.dumps(enum)), enum)
+ self.assertEqual(self.loads(self.dumps(enum)), enum)
+ else:
+ self.assertTrue(isnan(float(self.dumps(enum))))
+ self.assertTrue(isnan(self.loads(self.dumps(enum))))
+
+ def test_ints(self):
+ for enum in BigNum:
+ self.assertEqual(self.dumps(enum), str(enum.value))
+ self.assertEqual(int(self.dumps(enum)), enum)
+ self.assertEqual(self.loads(self.dumps(enum)), enum)
+
+ def test_list(self):
+ self.assertEqual(self.dumps(list(BigNum)),
+ str([SMALL, BIG, HUGE, REALLY_HUGE]))
+ self.assertEqual(self.loads(self.dumps(list(BigNum))),
+ list(BigNum))
+ self.assertEqual(self.dumps(list(FloatNum)),
+ str([E, PI, TAU]))
+ self.assertEqual(self.loads(self.dumps(list(FloatNum))),
+ list(FloatNum))
+ self.assertEqual(self.dumps(list(WierdNum)),
+ '[Infinity, -Infinity, NaN]')
+ self.assertEqual(self.loads(self.dumps(list(WierdNum)))[:2],
+ list(WierdNum)[:2])
+ self.assertTrue(isnan(self.loads(self.dumps(list(WierdNum)))[2]))
+
+ def test_dict_keys(self):
+ s, b, h, r = BigNum
+ e, p, t = FloatNum
+ i, j, n = WierdNum
+ d = {
+ s:'tiny', b:'large', h:'larger', r:'largest',
+ e:"Euler's number", p:'pi', t:'tau',
+ i:'Infinity', j:'-Infinity', n:'NaN',
+ }
+ nd = self.loads(self.dumps(d))
+ self.assertEqual(nd[str(SMALL)], 'tiny')
+ self.assertEqual(nd[str(BIG)], 'large')
+ self.assertEqual(nd[str(HUGE)], 'larger')
+ self.assertEqual(nd[str(REALLY_HUGE)], 'largest')
+ self.assertEqual(nd[repr(E)], "Euler's number")
+ self.assertEqual(nd[repr(PI)], 'pi')
+ self.assertEqual(nd[repr(TAU)], 'tau')
+ self.assertEqual(nd['Infinity'], 'Infinity')
+ self.assertEqual(nd['-Infinity'], '-Infinity')
+ self.assertEqual(nd['NaN'], 'NaN')
+
+ def test_dict_values(self):
+ d = dict(
+ tiny=BigNum.small,
+ large=BigNum.big,
+ larger=BigNum.huge,
+ largest=BigNum.really_huge,
+ e=FloatNum.e,
+ pi=FloatNum.pi,
+ tau=FloatNum.tau,
+ i=WierdNum.inf,
+ j=WierdNum.neg_inf,
+ n=WierdNum.nan,
+ )
+ nd = self.loads(self.dumps(d))
+ self.assertEqual(nd['tiny'], SMALL)
+ self.assertEqual(nd['large'], BIG)
+ self.assertEqual(nd['larger'], HUGE)
+ self.assertEqual(nd['largest'], REALLY_HUGE)
+ self.assertEqual(nd['e'], E)
+ self.assertEqual(nd['pi'], PI)
+ self.assertEqual(nd['tau'], TAU)
+ self.assertEqual(nd['i'], INF)
+ self.assertEqual(nd['j'], NEG_INF)
+ self.assertTrue(isnan(nd['n']))
+
+class TestPyEnum(TestEnum, PyTest): pass
+class TestCEnum(TestEnum, CTest): pass
diff --git a/Lib/test/test_json/test_fail.py b/Lib/test/test_json/test_fail.py
index 3dd877afdb..7caafdbddd 100644
--- a/Lib/test/test_json/test_fail.py
+++ b/Lib/test/test_json/test_fail.py
@@ -1,4 +1,5 @@
from test.test_json import PyTest, CTest
+import re
# 2007-10-05
JSONDOCS = [
@@ -100,6 +101,94 @@ class TestFail:
#This is for python encoder
self.assertRaises(TypeError, self.dumps, data, indent=True)
+ def test_truncated_input(self):
+ test_cases = [
+ ('', 'Expecting value', 0),
+ ('[', 'Expecting value', 1),
+ ('[42', "Expecting ',' delimiter", 3),
+ ('[42,', 'Expecting value', 4),
+ ('["', 'Unterminated string starting at', 1),
+ ('["spam', 'Unterminated string starting at', 1),
+ ('["spam"', "Expecting ',' delimiter", 7),
+ ('["spam",', 'Expecting value', 8),
+ ('{', 'Expecting property name enclosed in double quotes', 1),
+ ('{"', 'Unterminated string starting at', 1),
+ ('{"spam', 'Unterminated string starting at', 1),
+ ('{"spam"', "Expecting ':' delimiter", 7),
+ ('{"spam":', 'Expecting value', 8),
+ ('{"spam":42', "Expecting ',' delimiter", 10),
+ ('{"spam":42,', 'Expecting property name enclosed in double quotes', 11),
+ ]
+ test_cases += [
+ ('"', 'Unterminated string starting at', 0),
+ ('"spam', 'Unterminated string starting at', 0),
+ ]
+ for data, msg, idx in test_cases:
+ self.assertRaisesRegex(ValueError,
+ r'^{0}: line 1 column {1} \(char {2}\)'.format(
+ re.escape(msg), idx + 1, idx),
+ self.loads, data)
+
+ def test_unexpected_data(self):
+ test_cases = [
+ ('[,', 'Expecting value', 1),
+ ('{"spam":[}', 'Expecting value', 9),
+ ('[42:', "Expecting ',' delimiter", 3),
+ ('[42 "spam"', "Expecting ',' delimiter", 4),
+ ('[42,]', 'Expecting value', 4),
+ ('{"spam":[42}', "Expecting ',' delimiter", 11),
+ ('["]', 'Unterminated string starting at', 1),
+ ('["spam":', "Expecting ',' delimiter", 7),
+ ('["spam",]', 'Expecting value', 8),
+ ('{:', 'Expecting property name enclosed in double quotes', 1),
+ ('{,', 'Expecting property name enclosed in double quotes', 1),
+ ('{42', 'Expecting property name enclosed in double quotes', 1),
+ ('[{]', 'Expecting property name enclosed in double quotes', 2),
+ ('{"spam",', "Expecting ':' delimiter", 7),
+ ('{"spam"}', "Expecting ':' delimiter", 7),
+ ('[{"spam"]', "Expecting ':' delimiter", 8),
+ ('{"spam":}', 'Expecting value', 8),
+ ('[{"spam":]', 'Expecting value', 9),
+ ('{"spam":42 "ham"', "Expecting ',' delimiter", 11),
+ ('[{"spam":42]', "Expecting ',' delimiter", 11),
+ ('{"spam":42,}', 'Expecting property name enclosed in double quotes', 11),
+ ]
+ for data, msg, idx in test_cases:
+ self.assertRaisesRegex(ValueError,
+ r'^{0}: line 1 column {1} \(char {2}\)'.format(
+ re.escape(msg), idx + 1, idx),
+ self.loads, data)
+
+ def test_extra_data(self):
+ test_cases = [
+ ('[]]', 'Extra data', 2),
+ ('{}}', 'Extra data', 2),
+ ('[],[]', 'Extra data', 2),
+ ('{},{}', 'Extra data', 2),
+ ]
+ test_cases += [
+ ('42,"spam"', 'Extra data', 2),
+ ('"spam",42', 'Extra data', 6),
+ ]
+ for data, msg, idx in test_cases:
+ self.assertRaisesRegex(ValueError,
+ r'^{0}: line 1 column {1} - line 1 column {2}'
+ r' \(char {3} - {4}\)'.format(
+ re.escape(msg), idx + 1, len(data) + 1, idx, len(data)),
+ self.loads, data)
+
+ def test_linecol(self):
+ test_cases = [
+ ('!', 1, 1, 0),
+ (' !', 1, 2, 1),
+ ('\n!', 2, 1, 1),
+ ('\n \n\n !', 4, 6, 10),
+ ]
+ for data, line, col, idx in test_cases:
+ self.assertRaisesRegex(ValueError,
+ r'^Expecting value: line {0} column {1}'
+ r' \(char {2}\)$'.format(line, col, idx),
+ self.loads, data)
class TestPyFail(TestFail, PyTest): pass
class TestCFail(TestFail, CTest): pass
diff --git a/Lib/test/test_json/test_indent.py b/Lib/test/test_json/test_indent.py
index a4d4d20142..e07856f33c 100644
--- a/Lib/test/test_json/test_indent.py
+++ b/Lib/test/test_json/test_indent.py
@@ -32,6 +32,8 @@ class TestIndent:
d1 = self.dumps(h)
d2 = self.dumps(h, indent=2, sort_keys=True, separators=(',', ': '))
d3 = self.dumps(h, indent='\t', sort_keys=True, separators=(',', ': '))
+ d4 = self.dumps(h, indent=2, sort_keys=True)
+ d5 = self.dumps(h, indent='\t', sort_keys=True)
h1 = self.loads(d1)
h2 = self.loads(d2)
@@ -42,6 +44,8 @@ class TestIndent:
self.assertEqual(h3, h)
self.assertEqual(d2, expect.expandtabs(2))
self.assertEqual(d3, expect)
+ self.assertEqual(d4, d2)
+ self.assertEqual(d5, d3)
def test_indent0(self):
h = {3: 1}
diff --git a/Lib/test/test_keyword.py b/Lib/test/test_keyword.py
new file mode 100644
index 0000000000..af99f52c63
--- /dev/null
+++ b/Lib/test/test_keyword.py
@@ -0,0 +1,138 @@
+import keyword
+import unittest
+from test import support
+import filecmp
+import os
+import sys
+import subprocess
+import shutil
+import textwrap
+
+KEYWORD_FILE = support.findfile('keyword.py')
+GRAMMAR_FILE = os.path.join(os.path.split(__file__)[0],
+ '..', '..', 'Python', 'graminit.c')
+TEST_PY_FILE = 'keyword_test.py'
+GRAMMAR_TEST_FILE = 'graminit_test.c'
+PY_FILE_WITHOUT_KEYWORDS = 'minimal_keyword.py'
+NONEXISTENT_FILE = 'not_here.txt'
+
+
+class Test_iskeyword(unittest.TestCase):
+ def test_true_is_a_keyword(self):
+ self.assertTrue(keyword.iskeyword('True'))
+
+ def test_uppercase_true_is_not_a_keyword(self):
+ self.assertFalse(keyword.iskeyword('TRUE'))
+
+ def test_none_value_is_not_a_keyword(self):
+ self.assertFalse(keyword.iskeyword(None))
+
+ # This is probably an accident of the current implementation, but should be
+ # preserved for backward compatibility.
+ def test_changing_the_kwlist_does_not_affect_iskeyword(self):
+ oldlist = keyword.kwlist
+ self.addCleanup(setattr, keyword, 'kwlist', oldlist)
+ keyword.kwlist = ['its', 'all', 'eggs', 'beans', 'and', 'a', 'slice']
+ self.assertFalse(keyword.iskeyword('eggs'))
+
+
+class TestKeywordGeneration(unittest.TestCase):
+
+ def _copy_file_without_generated_keywords(self, source_file, dest_file):
+ with open(source_file, 'rb') as fp:
+ lines = fp.readlines()
+ nl = lines[0][len(lines[0].strip()):]
+ with open(dest_file, 'wb') as fp:
+ fp.writelines(lines[:lines.index(b"#--start keywords--" + nl) + 1])
+ fp.writelines(lines[lines.index(b"#--end keywords--" + nl):])
+
+ def _generate_keywords(self, grammar_file, target_keyword_py_file):
+ proc = subprocess.Popen([sys.executable,
+ KEYWORD_FILE,
+ grammar_file,
+ target_keyword_py_file], stderr=subprocess.PIPE)
+ stderr = proc.communicate()[1]
+ return proc.returncode, stderr
+
+ @unittest.skipIf(not os.path.exists(GRAMMAR_FILE),
+ 'test only works from source build directory')
+ def test_real_grammar_and_keyword_file(self):
+ self._copy_file_without_generated_keywords(KEYWORD_FILE, TEST_PY_FILE)
+ self.addCleanup(support.unlink, TEST_PY_FILE)
+ self.assertFalse(filecmp.cmp(KEYWORD_FILE, TEST_PY_FILE))
+ self.assertEqual((0, b''), self._generate_keywords(GRAMMAR_FILE,
+ TEST_PY_FILE))
+ self.assertTrue(filecmp.cmp(KEYWORD_FILE, TEST_PY_FILE))
+
+ def test_grammar(self):
+ self._copy_file_without_generated_keywords(KEYWORD_FILE, TEST_PY_FILE)
+ self.addCleanup(support.unlink, TEST_PY_FILE)
+ with open(GRAMMAR_TEST_FILE, 'w') as fp:
+ # Some of these are probably implementation accidents.
+ fp.writelines(textwrap.dedent("""\
+ {2, 1},
+ {11, "encoding_decl", 0, 2, states_79,
+ "\000\000\040\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000"},
+ {1, "jello"},
+ {326, 0},
+ {1, "turnip"},
+ \t{1, "This one is tab indented"
+ {278, 0},
+ {1, "crazy but legal"
+ "also legal" {1, "
+ {1, "continue"},
+ {1, "lemon"},
+ {1, "tomato"},
+ {1, "wigii"},
+ {1, 'no good'}
+ {283, 0},
+ {1, "too many spaces"}"""))
+ self.addCleanup(support.unlink, GRAMMAR_TEST_FILE)
+ self._generate_keywords(GRAMMAR_TEST_FILE, TEST_PY_FILE)
+ expected = [
+ " 'This one is tab indented',",
+ " 'also legal',",
+ " 'continue',",
+ " 'crazy but legal',",
+ " 'jello',",
+ " 'lemon',",
+ " 'tomato',",
+ " 'turnip',",
+ " 'wigii',",
+ ]
+ with open(TEST_PY_FILE) as fp:
+ lines = fp.read().splitlines()
+ start = lines.index("#--start keywords--") + 1
+ end = lines.index("#--end keywords--")
+ actual = lines[start:end]
+ self.assertEqual(actual, expected)
+
+ def test_empty_grammar_results_in_no_keywords(self):
+ self._copy_file_without_generated_keywords(KEYWORD_FILE,
+ PY_FILE_WITHOUT_KEYWORDS)
+ self.addCleanup(support.unlink, PY_FILE_WITHOUT_KEYWORDS)
+ shutil.copyfile(KEYWORD_FILE, TEST_PY_FILE)
+ self.addCleanup(support.unlink, TEST_PY_FILE)
+ self.assertEqual((0, b''), self._generate_keywords(os.devnull,
+ TEST_PY_FILE))
+ self.assertTrue(filecmp.cmp(TEST_PY_FILE, PY_FILE_WITHOUT_KEYWORDS))
+
+ def test_keywords_py_without_markers_produces_error(self):
+ rc, stderr = self._generate_keywords(os.devnull, os.devnull)
+ self.assertNotEqual(rc, 0)
+ self.assertRegex(stderr, b'does not contain format markers')
+
+ def test_missing_grammar_file_produces_error(self):
+ rc, stderr = self._generate_keywords(NONEXISTENT_FILE, KEYWORD_FILE)
+ self.assertNotEqual(rc, 0)
+ self.assertRegex(stderr, b'(?ms)' + NONEXISTENT_FILE.encode())
+
+ def test_missing_keywords_py_file_produces_error(self):
+ rc, stderr = self._generate_keywords(os.devnull, NONEXISTENT_FILE)
+ self.assertNotEqual(rc, 0)
+ self.assertRegex(stderr, b'(?ms)' + NONEXISTENT_FILE.encode())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_keywordonlyarg.py b/Lib/test/test_keywordonlyarg.py
index 0503a7fc6a..e4a44c1d8f 100644
--- a/Lib/test/test_keywordonlyarg.py
+++ b/Lib/test/test_keywordonlyarg.py
@@ -176,6 +176,18 @@ class KeywordOnlyArgTestCase(unittest.TestCase):
return __a
self.assertEqual(X().f(), 42)
+ def test_default_evaluation_order(self):
+ # See issue 16967
+ a = 42
+ with self.assertRaises(NameError) as err:
+ def f(v=a, x=b, *, y=c, z=d):
+ pass
+ self.assertEqual(str(err.exception), "name 'b' is not defined")
+ with self.assertRaises(NameError) as err:
+ f = lambda v=a, x=b, *, y=c, z=d: None
+ self.assertEqual(str(err.exception), "name 'b' is not defined")
+
+
def test_main():
run_unittest(KeywordOnlyArgTestCase)
diff --git a/Lib/test/test_kqueue.py b/Lib/test/test_kqueue.py
index e5e6058334..bafdeba6ff 100644
--- a/Lib/test/test_kqueue.py
+++ b/Lib/test/test_kqueue.py
@@ -94,7 +94,7 @@ class TestKQueue(unittest.TestCase):
client.setblocking(False)
try:
client.connect(('127.0.0.1', serverSocket.getsockname()[1]))
- except socket.error as e:
+ except OSError as e:
self.assertEqual(e.args[0], errno.EINPROGRESS)
else:
#raise AssertionError("Connect should have raised EINPROGRESS")
@@ -185,6 +185,33 @@ class TestKQueue(unittest.TestCase):
b.close()
kq.close()
+ def test_close(self):
+ open_file = open(__file__, "rb")
+ self.addCleanup(open_file.close)
+ fd = open_file.fileno()
+ kqueue = select.kqueue()
+
+ # test fileno() method and closed attribute
+ self.assertIsInstance(kqueue.fileno(), int)
+ self.assertFalse(kqueue.closed)
+
+ # test close()
+ kqueue.close()
+ self.assertTrue(kqueue.closed)
+ self.assertRaises(ValueError, kqueue.fileno)
+
+ # close() can be called more than once
+ kqueue.close()
+
+ # operations must fail with ValueError("I/O operation on closed ...")
+ self.assertRaises(ValueError, kqueue.control, None, 4)
+
+ def test_fd_non_inheritable(self):
+ kqueue = select.kqueue()
+ self.addCleanup(kqueue.close)
+ self.assertEqual(os.get_inheritable(kqueue.fileno()), False)
+
+
def test_main():
support.run_unittest(TestKQueue)
diff --git a/Lib/test/test_largefile.py b/Lib/test/test_largefile.py
index 63ee697250..5b276e76ff 100644
--- a/Lib/test/test_largefile.py
+++ b/Lib/test/test_largefile.py
@@ -159,7 +159,7 @@ def setUpModule():
# Seeking is not enough of a test: you must write and flush, too!
f.write(b'x')
f.flush()
- except (IOError, OverflowError):
+ except (OSError, OverflowError):
raise unittest.SkipTest("filesystem does not have "
"largefile support")
finally:
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index ae4ca18444..ffd0c8bf2b 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -26,6 +26,7 @@ import logging.handlers
import logging.config
import codecs
+import configparser
import datetime
import pickle
import io
@@ -58,7 +59,9 @@ try:
import smtpd
from urllib.parse import urlparse, parse_qs
from socketserver import (ThreadingUDPServer, DatagramRequestHandler,
- ThreadingTCPServer, StreamRequestHandler)
+ ThreadingTCPServer, StreamRequestHandler,
+ ThreadingUnixStreamServer,
+ ThreadingUnixDatagramServer)
except ImportError:
threading = None
try:
@@ -75,13 +78,12 @@ try:
except ImportError:
pass
-
class BaseTest(unittest.TestCase):
"""Base class for logging tests."""
log_format = "%(name)s -> %(levelname)s: %(message)s"
- expected_log_pat = r"^([\w.]+) -> ([\w]+): ([\d]+)$"
+ expected_log_pat = r"^([\w.]+) -> (\w+): (\d+)$"
message_num = 0
def setUp(self):
@@ -93,7 +95,8 @@ class BaseTest(unittest.TestCase):
self.saved_handlers = logging._handlers.copy()
self.saved_handler_list = logging._handlerList[:]
self.saved_loggers = saved_loggers = logger_dict.copy()
- self.saved_level_names = logging._levelNames.copy()
+ self.saved_name_to_level = logging._nameToLevel.copy()
+ self.saved_level_to_name = logging._levelToName.copy()
self.logger_states = logger_states = {}
for name in saved_loggers:
logger_states[name] = getattr(saved_loggers[name],
@@ -135,8 +138,10 @@ class BaseTest(unittest.TestCase):
self.root_logger.setLevel(self.original_logging_level)
logging._acquireLock()
try:
- logging._levelNames.clear()
- logging._levelNames.update(self.saved_level_names)
+ logging._levelToName.clear()
+ logging._levelToName.update(self.saved_level_to_name)
+ logging._nameToLevel.clear()
+ logging._nameToLevel.update(self.saved_name_to_level)
logging._handlers.clear()
logging._handlers.update(self.saved_handlers)
logging._handlerList[:] = self.saved_handler_list
@@ -150,12 +155,12 @@ class BaseTest(unittest.TestCase):
finally:
logging._releaseLock()
- def assert_log_lines(self, expected_values, stream=None):
+ def assert_log_lines(self, expected_values, stream=None, pat=None):
"""Match the collected log lines against the regular expression
self.expected_log_pat, and compare the extracted group values to
the expected_values list of tuples."""
stream = stream or self.stream
- pat = re.compile(self.expected_log_pat)
+ pat = re.compile(pat or self.expected_log_pat)
actual_lines = stream.getvalue().splitlines()
self.assertEqual(len(actual_lines), len(expected_values))
for actual, expected in zip(actual_lines, expected_values):
@@ -430,7 +435,7 @@ class CustomLevelsAndFiltersTest(BaseTest):
"""Test various filtering possibilities with custom logging levels."""
# Skip the logger name group.
- expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
+ expected_log_pat = r"^[\w.]+ -> (\w+): (\d+)$"
def setUp(self):
BaseTest.setUp(self)
@@ -560,7 +565,7 @@ class HandlerTest(BaseTest):
self.assertEqual(h.facility, h.LOG_USER)
self.assertTrue(h.unixsocket)
h.close()
- except socket.error: # syslogd might not be available
+ except OSError: # syslogd might not be available
pass
for method in ('GET', 'POST', 'PUT'):
if method == 'PUT':
@@ -647,41 +652,6 @@ class StreamHandlerTest(BaseTest):
# -- if it proves to be of wider utility than just test_logging
if threading:
- class TestSMTPChannel(smtpd.SMTPChannel):
- """
- This derived class has had to be created because smtpd does not
- support use of custom channel maps, although they are allowed by
- asyncore's design. Issue #11959 has been raised to address this,
- and if resolved satisfactorily, some of this code can be removed.
- """
- def __init__(self, server, conn, addr, sockmap):
- asynchat.async_chat.__init__(self, conn, sockmap)
- self.smtp_server = server
- self.conn = conn
- self.addr = addr
- self.data_size_limit = None
- self.received_lines = []
- self.smtp_state = self.COMMAND
- self.seen_greeting = ''
- self.mailfrom = None
- self.rcpttos = []
- self.received_data = ''
- self.fqdn = socket.getfqdn()
- self.num_bytes = 0
- try:
- self.peer = conn.getpeername()
- except socket.error as err:
- # a race condition may occur if the other end is closing
- # before we can get the peername
- self.close()
- if err.args[0] != errno.ENOTCONN:
- raise
- return
- self.push('220 %s %s' % (self.fqdn, smtpd.__version__))
- self.set_terminator(b'\r\n')
- self.extended_smtp = False
-
-
class TestSMTPServer(smtpd.SMTPServer):
"""
This class implements a test SMTP server.
@@ -702,37 +672,14 @@ if threading:
:func:`asyncore.loop`. This avoids changing the
:mod:`asyncore` module's global state.
"""
- channel_class = TestSMTPChannel
def __init__(self, addr, handler, poll_interval, sockmap):
- self._localaddr = addr
- self._remoteaddr = None
- self.data_size_limit = None
- self.sockmap = sockmap
- asyncore.dispatcher.__init__(self, map=sockmap)
- try:
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.setblocking(0)
- self.set_socket(sock, map=sockmap)
- # try to re-use a server port if possible
- self.set_reuse_addr()
- self.bind(addr)
- self.port = sock.getsockname()[1]
- self.listen(5)
- except:
- self.close()
- raise
+ smtpd.SMTPServer.__init__(self, addr, None, map=sockmap)
+ self.port = self.socket.getsockname()[1]
self._handler = handler
self._thread = None
self.poll_interval = poll_interval
- def handle_accepted(self, conn, addr):
- """
- Redefined only because the base class does not pass in a
- map, forcing use of a global in :mod:`asyncore`.
- """
- channel = self.channel_class(self, conn, addr, self.sockmap)
-
def process_message(self, peer, mailfrom, rcpttos, data):
"""
Delegates to the handler passed in to the server's constructor.
@@ -763,8 +710,8 @@ if threading:
:func:`asyncore.loop`.
"""
try:
- asyncore.loop(poll_interval, map=self.sockmap)
- except select.error:
+ asyncore.loop(poll_interval, map=self._map)
+ except OSError:
# On FreeBSD 8, closing the server repeatably
# raises this error. We swallow it if the
# server has been closed.
@@ -871,7 +818,7 @@ if threading:
sock, addr = self.socket.accept()
if self.sslctx:
sock = self.sslctx.wrap_socket(sock, server_side=True)
- except socket.error as e:
+ except OSError as e:
# socket errors are silenced by the caller, print them here
sys.stderr.write("Got an error:\n%s\n" % e)
raise
@@ -908,6 +855,9 @@ if threading:
super(TestTCPServer, self).server_bind()
self.port = self.socket.getsockname()[1]
+ class TestUnixStreamServer(TestTCPServer):
+ address_family = socket.AF_UNIX
+
class TestUDPServer(ControlMixin, ThreadingUDPServer):
"""
A UDP server which is controllable using :class:`ControlMixin`.
@@ -937,7 +887,7 @@ if threading:
if data:
try:
super(DelegatingUDPRequestHandler, self).finish()
- except socket.error:
+ except OSError:
if not self.server._closed:
raise
@@ -955,6 +905,9 @@ if threading:
super(TestUDPServer, self).server_close()
self._closed = True
+ class TestUnixDatagramServer(TestUDPServer):
+ address_family = socket.AF_UNIX
+
# - end of server_helper section
@unittest.skipUnless(threading, 'Threading required for this test.')
@@ -991,7 +944,7 @@ class MemoryHandlerTest(BaseTest):
"""Tests for the MemoryHandler."""
# Do not bother with a logger name group.
- expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
+ expected_log_pat = r"^[\w.]+ -> (\w+): (\d+)$"
def setUp(self):
BaseTest.setUp(self)
@@ -1044,7 +997,7 @@ class ConfigFileTest(BaseTest):
"""Reading logging config from a .ini-style config file."""
- expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
+ expected_log_pat = r"^(\w+) \+\+ (\w+)$"
# config0 is a standard configuration.
config0 = """
@@ -1292,6 +1245,24 @@ class ConfigFileTest(BaseTest):
# Original logger output is empty.
self.assert_log_lines([])
+ def test_config0_using_cp_ok(self):
+ # A simple config file which overrides the default settings.
+ with captured_stdout() as output:
+ file = io.StringIO(textwrap.dedent(self.config0))
+ cp = configparser.ConfigParser()
+ cp.read_file(file)
+ logging.config.fileConfig(cp)
+ logger = logging.getLogger()
+ # Won't output anything
+ logger.info(self.next_message())
+ # Outputs a message
+ logger.error(self.next_message())
+ self.assert_log_lines([
+ ('ERROR', '2'),
+ ], stream=output)
+ # Original logger output is empty.
+ self.assert_log_lines([])
+
def test_config1_ok(self, config=config1):
# A config file defining a sub-parser as well.
with captured_stdout() as output:
@@ -1381,7 +1352,7 @@ class ConfigFileTest(BaseTest):
def test_logger_disabling(self):
self.apply_config(self.disable_test)
- logger = logging.getLogger('foo')
+ logger = logging.getLogger('some_pristine_logger')
self.assertFalse(logger.disabled)
self.apply_config(self.disable_test)
self.assertTrue(logger.disabled)
@@ -1394,17 +1365,23 @@ class SocketHandlerTest(BaseTest):
"""Test for SocketHandler objects."""
+ if threading:
+ server_class = TestTCPServer
+ address = ('localhost', 0)
+
def setUp(self):
"""Set up a TCP server to receive log messages, and a SocketHandler
pointing to that server's address and port."""
BaseTest.setUp(self)
- addr = ('localhost', 0)
- self.server = server = TestTCPServer(addr, self.handle_socket,
- 0.01)
+ self.server = server = self.server_class(self.address,
+ self.handle_socket, 0.01)
server.start()
server.ready.wait()
- self.sock_hdlr = logging.handlers.SocketHandler('localhost',
- server.port)
+ hcls = logging.handlers.SocketHandler
+ if isinstance(server.server_address, tuple):
+ self.sock_hdlr = hcls('localhost', server.port)
+ else:
+ self.sock_hdlr = hcls(server.server_address, None)
self.log_output = ''
self.root_logger.removeHandler(self.root_logger.handlers[0])
self.root_logger.addHandler(self.sock_hdlr)
@@ -1444,35 +1421,69 @@ class SocketHandlerTest(BaseTest):
self.assertEqual(self.log_output, "spam\neggs\n")
def test_noserver(self):
+ # Avoid timing-related failures due to SocketHandler's own hard-wired
+ # one-second timeout on socket.create_connection() (issue #16264).
+ self.sock_hdlr.retryStart = 2.5
# Kill the server
self.server.stop(2.0)
- #The logging call should try to connect, which should fail
+ # The logging call should try to connect, which should fail
try:
raise RuntimeError('Deliberate mistake')
except RuntimeError:
self.root_logger.exception('Never sent')
self.root_logger.error('Never sent, either')
now = time.time()
- self.assertTrue(self.sock_hdlr.retryTime > now)
+ self.assertGreater(self.sock_hdlr.retryTime, now)
time.sleep(self.sock_hdlr.retryTime - now + 0.001)
self.root_logger.error('Nor this')
+def _get_temp_domain_socket():
+ fd, fn = tempfile.mkstemp(prefix='test_logging_', suffix='.sock')
+ os.close(fd)
+ # just need a name - file can't be present, or we'll get an
+ # 'address already in use' error.
+ os.remove(fn)
+ return fn
+
+@unittest.skipUnless(threading, 'Threading required for this test.')
+class UnixSocketHandlerTest(SocketHandlerTest):
+
+ """Test for SocketHandler with unix sockets."""
+
+ if threading:
+ server_class = TestUnixStreamServer
+
+ def setUp(self):
+ # override the definition in the base class
+ self.address = _get_temp_domain_socket()
+ SocketHandlerTest.setUp(self)
+
+ def tearDown(self):
+ SocketHandlerTest.tearDown(self)
+ os.remove(self.address)
@unittest.skipUnless(threading, 'Threading required for this test.')
class DatagramHandlerTest(BaseTest):
"""Test for DatagramHandler."""
+ if threading:
+ server_class = TestUDPServer
+ address = ('localhost', 0)
+
def setUp(self):
"""Set up a UDP server to receive log messages, and a DatagramHandler
pointing to that server's address and port."""
BaseTest.setUp(self)
- addr = ('localhost', 0)
- self.server = server = TestUDPServer(addr, self.handle_datagram, 0.01)
+ self.server = server = self.server_class(self.address,
+ self.handle_datagram, 0.01)
server.start()
server.ready.wait()
- self.sock_hdlr = logging.handlers.DatagramHandler('localhost',
- server.port)
+ hcls = logging.handlers.DatagramHandler
+ if isinstance(server.server_address, tuple):
+ self.sock_hdlr = hcls('localhost', server.port)
+ else:
+ self.sock_hdlr = hcls(server.server_address, None)
self.log_output = ''
self.root_logger.removeHandler(self.root_logger.handlers[0])
self.root_logger.addHandler(self.sock_hdlr)
@@ -1507,21 +1518,44 @@ class DatagramHandlerTest(BaseTest):
@unittest.skipUnless(threading, 'Threading required for this test.')
+class UnixDatagramHandlerTest(DatagramHandlerTest):
+
+ """Test for DatagramHandler using Unix sockets."""
+
+ if threading:
+ server_class = TestUnixDatagramServer
+
+ def setUp(self):
+ # override the definition in the base class
+ self.address = _get_temp_domain_socket()
+ DatagramHandlerTest.setUp(self)
+
+ def tearDown(self):
+ DatagramHandlerTest.tearDown(self)
+ os.remove(self.address)
+
+@unittest.skipUnless(threading, 'Threading required for this test.')
class SysLogHandlerTest(BaseTest):
"""Test for SysLogHandler using UDP."""
+ if threading:
+ server_class = TestUDPServer
+ address = ('localhost', 0)
+
def setUp(self):
"""Set up a UDP server to receive log messages, and a SysLogHandler
pointing to that server's address and port."""
BaseTest.setUp(self)
- addr = ('localhost', 0)
- self.server = server = TestUDPServer(addr, self.handle_datagram,
- 0.01)
+ self.server = server = self.server_class(self.address,
+ self.handle_datagram, 0.01)
server.start()
server.ready.wait()
- self.sl_hdlr = logging.handlers.SysLogHandler(('localhost',
- server.port))
+ hcls = logging.handlers.SysLogHandler
+ if isinstance(server.server_address, tuple):
+ self.sl_hdlr = hcls(('localhost', server.port))
+ else:
+ self.sl_hdlr = hcls(server.server_address)
self.log_output = ''
self.root_logger.removeHandler(self.root_logger.handlers[0])
self.root_logger.addHandler(self.sl_hdlr)
@@ -1559,6 +1593,23 @@ class SysLogHandlerTest(BaseTest):
@unittest.skipUnless(threading, 'Threading required for this test.')
+class UnixSysLogHandlerTest(SysLogHandlerTest):
+
+ """Test for SysLogHandler with Unix sockets."""
+
+ if threading:
+ server_class = TestUnixDatagramServer
+
+ def setUp(self):
+ # override the definition in the base class
+ self.address = _get_temp_domain_socket()
+ SysLogHandlerTest.setUp(self)
+
+ def tearDown(self):
+ SysLogHandlerTest.tearDown(self)
+ os.remove(self.address)
+
+@unittest.skipUnless(threading, 'Threading required for this test.')
class HTTPHandlerTest(BaseTest):
"""Test for HTTPHandler."""
@@ -1779,7 +1830,7 @@ class WarningsTest(BaseTest):
logger.removeHandler(h)
s = stream.getvalue()
h.close()
- self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
+ self.assertGreater(s.find("UserWarning: I'm warning you...\n"), 0)
#See if an explicit file uses the original implementation
a_file = io.StringIO()
@@ -1817,7 +1868,7 @@ class ConfigDictTest(BaseTest):
"""Reading logging config from a dictionary."""
- expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
+ expected_log_pat = r"^(\w+) \+\+ (\w+)$"
# config0 is a standard configuration.
config0 = {
@@ -2389,6 +2440,32 @@ class ConfigDictTest(BaseTest):
},
}
+ # As config0, but with properties
+ config14 = {
+ 'version': 1,
+ 'formatters': {
+ 'form1' : {
+ 'format' : '%(levelname)s ++ %(message)s',
+ },
+ },
+ 'handlers' : {
+ 'hand1' : {
+ 'class' : 'logging.StreamHandler',
+ 'formatter' : 'form1',
+ 'level' : 'NOTSET',
+ 'stream' : 'ext://sys.stdout',
+ '.': {
+ 'foo': 'bar',
+ 'terminator': '!\n',
+ }
+ },
+ },
+ 'root' : {
+ 'level' : 'WARNING',
+ 'handlers' : ['hand1'],
+ },
+ }
+
out_of_order = {
"version": 1,
"formatters": {
@@ -2656,11 +2733,20 @@ class ConfigDictTest(BaseTest):
def test_config13_failure(self):
self.assertRaises(Exception, self.apply_config, self.config13)
+ def test_config14_ok(self):
+ with captured_stdout() as output:
+ self.apply_config(self.config14)
+ h = logging._handlers['hand1']
+ self.assertEqual(h.foo, 'bar')
+ self.assertEqual(h.terminator, '!\n')
+ logging.warning('Exclamation')
+ self.assertTrue(output.getvalue().endswith('Exclamation!\n'))
+
@unittest.skipUnless(threading, 'listen() needs threading to work')
- def setup_via_listener(self, text):
+ def setup_via_listener(self, text, verify=None):
text = text.encode("utf-8")
# Ask for a randomly assigned port (by using port 0)
- t = logging.config.listen(0)
+ t = logging.config.listen(0, verify)
t.start()
t.ready.wait()
# Now get the port allocated
@@ -2720,6 +2806,69 @@ class ConfigDictTest(BaseTest):
# Original logger output is empty.
self.assert_log_lines([])
+ @unittest.skipUnless(threading, 'Threading required for this test.')
+ def test_listen_verify(self):
+
+ def verify_fail(stuff):
+ return None
+
+ def verify_reverse(stuff):
+ return stuff[::-1]
+
+ logger = logging.getLogger("compiler.parser")
+ to_send = textwrap.dedent(ConfigFileTest.config1)
+ # First, specify a verification function that will fail.
+ # We expect to see no output, since our configuration
+ # never took effect.
+ with captured_stdout() as output:
+ self.setup_via_listener(to_send, verify_fail)
+ # Both will output a message
+ logger.info(self.next_message())
+ logger.error(self.next_message())
+ self.assert_log_lines([], stream=output)
+ # Original logger output has the stuff we logged.
+ self.assert_log_lines([
+ ('INFO', '1'),
+ ('ERROR', '2'),
+ ], pat=r"^[\w.]+ -> (\w+): (\d+)$")
+
+ # Now, perform no verification. Our configuration
+ # should take effect.
+
+ with captured_stdout() as output:
+ self.setup_via_listener(to_send) # no verify callable specified
+ logger = logging.getLogger("compiler.parser")
+ # Both will output a message
+ logger.info(self.next_message())
+ logger.error(self.next_message())
+ self.assert_log_lines([
+ ('INFO', '3'),
+ ('ERROR', '4'),
+ ], stream=output)
+ # Original logger output still has the stuff we logged before.
+ self.assert_log_lines([
+ ('INFO', '1'),
+ ('ERROR', '2'),
+ ], pat=r"^[\w.]+ -> (\w+): (\d+)$")
+
+ # Now, perform verification which transforms the bytes.
+
+ with captured_stdout() as output:
+ self.setup_via_listener(to_send[::-1], verify_reverse)
+ logger = logging.getLogger("compiler.parser")
+ # Both will output a message
+ logger.info(self.next_message())
+ logger.error(self.next_message())
+ self.assert_log_lines([
+ ('INFO', '5'),
+ ('ERROR', '6'),
+ ], stream=output)
+ # Original logger output still has the stuff we logged before.
+ self.assert_log_lines([
+ ('INFO', '1'),
+ ('ERROR', '2'),
+ ], pat=r"^[\w.]+ -> (\w+): (\d+)$")
+
def test_out_of_order(self):
self.apply_config(self.out_of_order)
handler = logging.getLogger('mymodule').handlers[0]
@@ -2779,14 +2928,14 @@ class ChildLoggerTest(BaseTest):
l2 = logging.getLogger('def.ghi')
c1 = r.getChild('xyz')
c2 = r.getChild('uvw.xyz')
- self.assertTrue(c1 is logging.getLogger('xyz'))
- self.assertTrue(c2 is logging.getLogger('uvw.xyz'))
+ self.assertIs(c1, logging.getLogger('xyz'))
+ self.assertIs(c2, logging.getLogger('uvw.xyz'))
c1 = l1.getChild('def')
c2 = c1.getChild('ghi')
c3 = l1.getChild('def.ghi')
- self.assertTrue(c1 is logging.getLogger('abc.def'))
- self.assertTrue(c2 is logging.getLogger('abc.def.ghi'))
- self.assertTrue(c2 is c3)
+ self.assertIs(c1, logging.getLogger('abc.def'))
+ self.assertIs(c2, logging.getLogger('abc.def.ghi'))
+ self.assertIs(c2, c3)
class DerivedLogRecord(logging.LogRecord):
@@ -2829,7 +2978,7 @@ class LogRecordFactoryTest(BaseTest):
class QueueHandlerTest(BaseTest):
# Do not bother with a logger name group.
- expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
+ expected_log_pat = r"^[\w.]+ -> (\w+): (\d+)$"
def setUp(self):
BaseTest.setUp(self)
@@ -3123,13 +3272,13 @@ class ShutdownTest(BaseTest):
self.assertEqual('0 - release', self.called[-1])
def test_with_ioerror_in_acquire(self):
- self._test_with_failure_in_method('acquire', IOError)
+ self._test_with_failure_in_method('acquire', OSError)
def test_with_ioerror_in_flush(self):
- self._test_with_failure_in_method('flush', IOError)
+ self._test_with_failure_in_method('flush', OSError)
def test_with_ioerror_in_close(self):
- self._test_with_failure_in_method('close', IOError)
+ self._test_with_failure_in_method('close', OSError)
def test_with_valueerror_in_acquire(self):
self._test_with_failure_in_method('acquire', ValueError)
@@ -3336,6 +3485,12 @@ class BasicConfigTest(unittest.TestCase):
self.assertEqual(logging.root.level, self.original_logging_level)
def test_filename(self):
+
+ def cleanup(h1, h2, fn):
+ h1.close()
+ h2.close()
+ os.remove(fn)
+
logging.basicConfig(filename='test.log')
self.assertEqual(len(logging.root.handlers), 1)
@@ -3343,17 +3498,23 @@ class BasicConfigTest(unittest.TestCase):
self.assertIsInstance(handler, logging.FileHandler)
expected = logging.FileHandler('test.log', 'a')
- self.addCleanup(expected.close)
self.assertEqual(handler.stream.mode, expected.stream.mode)
self.assertEqual(handler.stream.name, expected.stream.name)
+ self.addCleanup(cleanup, handler, expected, 'test.log')
def test_filemode(self):
+
+ def cleanup(h1, h2, fn):
+ h1.close()
+ h2.close()
+ os.remove(fn)
+
logging.basicConfig(filename='test.log', filemode='wb')
handler = logging.root.handlers[0]
expected = logging.FileHandler('test.log', 'wb')
- self.addCleanup(expected.close)
self.assertEqual(handler.stream.mode, expected.stream.mode)
+ self.addCleanup(cleanup, handler, expected, 'test.log')
def test_stream(self):
stream = io.StringIO()
@@ -3809,6 +3970,63 @@ class TimedRotatingFileHandlerTest(BaseFileTest):
assertRaises(ValueError, logging.handlers.TimedRotatingFileHandler,
self.fn, 'W7', delay=True)
+ def test_compute_rollover_daily_attime(self):
+ currentTime = 0
+ atTime = datetime.time(12, 0, 0)
+ rh = logging.handlers.TimedRotatingFileHandler(
+ self.fn, when='MIDNIGHT', interval=1, backupCount=0, utc=True,
+ atTime=atTime)
+ try:
+ actual = rh.computeRollover(currentTime)
+ self.assertEqual(actual, currentTime + 12 * 60 * 60)
+
+ actual = rh.computeRollover(currentTime + 13 * 60 * 60)
+ self.assertEqual(actual, currentTime + 36 * 60 * 60)
+ finally:
+ rh.close()
+
+ #@unittest.skipIf(True, 'Temporarily skipped while failures investigated.')
+ def test_compute_rollover_weekly_attime(self):
+ currentTime = int(time.time())
+ today = currentTime - currentTime % 86400
+
+ atTime = datetime.time(12, 0, 0)
+
+ wday = time.gmtime(today).tm_wday
+ for day in range(7):
+ rh = logging.handlers.TimedRotatingFileHandler(
+ self.fn, when='W%d' % day, interval=1, backupCount=0, utc=True,
+ atTime=atTime)
+ try:
+ if wday > day:
+ # The rollover day has already passed this week, so we
+ # go over into next week
+ expected = (7 - wday + day)
+ else:
+ expected = (day - wday)
+ # At this point expected is in days from now, convert to seconds
+ expected *= 24 * 60 * 60
+ # Add in the rollover time
+ expected += 12 * 60 * 60
+ # Add in adjustment for today
+ expected += today
+ actual = rh.computeRollover(today)
+ if actual != expected:
+ print('failed in timezone: %d' % time.timezone)
+ print('local vars: %s' % locals())
+ self.assertEqual(actual, expected)
+ if day == wday:
+ # goes into following week
+ expected += 7 * 24 * 60 * 60
+ actual = rh.computeRollover(today + 13 * 60 * 60)
+ if actual != expected:
+ print('failed in timezone: %d' % time.timezone)
+ print('local vars: %s' % locals())
+ self.assertEqual(actual, expected)
+ finally:
+ rh.close()
+
+
def secs(**kw):
return datetime.timedelta(**kw) // datetime.timedelta(seconds=1)
@@ -3867,7 +4085,7 @@ class NTEventLogHandlerTest(BaseTest):
h.handle(r)
h.close()
# Now see if the event is recorded
- self.assertTrue(num_recs < win32evtlog.GetNumberOfEventLogRecords(elh))
+ self.assertLess(num_recs, win32evtlog.GetNumberOfEventLogRecords(elh))
flags = win32evtlog.EVENTLOG_BACKWARDS_READ | \
win32evtlog.EVENTLOG_SEQUENTIAL_READ
found = False
@@ -3900,7 +4118,8 @@ def test_main():
SMTPHandlerTest, FileHandlerTest, RotatingFileHandlerTest,
LastResortTest, LogRecordTest, ExceptionTest,
SysLogHandlerTest, HTTPHandlerTest, NTEventLogHandlerTest,
- TimedRotatingFileHandlerTest
+ TimedRotatingFileHandlerTest, UnixSocketHandlerTest,
+ UnixDatagramHandlerTest, UnixSysLogHandlerTest
)
if __name__ == "__main__":
diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py
index baf1d6a3b2..6c30fed7c9 100644
--- a/Lib/test/test_long.py
+++ b/Lib/test/test_long.py
@@ -1079,7 +1079,7 @@ class LongTest(unittest.TestCase):
self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=True)
self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=False)
self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=True)
- self.assertRaises(OverflowError, (-1).to_bytes, 2, 'big', signed=False),
+ self.assertRaises(OverflowError, (-1).to_bytes, 2, 'big', signed=False)
self.assertRaises(OverflowError, (-1).to_bytes, 2, 'little', signed=False)
self.assertEqual((0).to_bytes(0, 'big'), b'')
self.assertEqual((1).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x01')
diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py
index ad9045604e..26d19da5e9 100644
--- a/Lib/test/test_lzma.py
+++ b/Lib/test/test_lzma.py
@@ -371,6 +371,8 @@ class FileTestCase(unittest.TestCase):
pass
with LZMAFile(BytesIO(), "w") as f:
pass
+ with LZMAFile(BytesIO(), "x") as f:
+ pass
with LZMAFile(BytesIO(), "a") as f:
pass
@@ -398,13 +400,29 @@ class FileTestCase(unittest.TestCase):
with LZMAFile(TESTFN, "ab"):
pass
+ def test_init_with_x_mode(self):
+ self.addCleanup(unlink, TESTFN)
+ for mode in ("x", "xb"):
+ unlink(TESTFN)
+ with LZMAFile(TESTFN, mode):
+ pass
+ with self.assertRaises(FileExistsError):
+ with LZMAFile(TESTFN, mode):
+ pass
+
def test_init_bad_mode(self):
with self.assertRaises(ValueError):
LZMAFile(BytesIO(COMPRESSED_XZ), (3, "x"))
with self.assertRaises(ValueError):
LZMAFile(BytesIO(COMPRESSED_XZ), "")
with self.assertRaises(ValueError):
- LZMAFile(BytesIO(COMPRESSED_XZ), "x")
+ LZMAFile(BytesIO(COMPRESSED_XZ), "xt")
+ with self.assertRaises(ValueError):
+ LZMAFile(BytesIO(COMPRESSED_XZ), "x+")
+ with self.assertRaises(ValueError):
+ LZMAFile(BytesIO(COMPRESSED_XZ), "rx")
+ with self.assertRaises(ValueError):
+ LZMAFile(BytesIO(COMPRESSED_XZ), "wx")
with self.assertRaises(ValueError):
LZMAFile(BytesIO(COMPRESSED_XZ), "rt")
with self.assertRaises(ValueError):
@@ -678,6 +696,20 @@ class FileTestCase(unittest.TestCase):
with LZMAFile(BytesIO(COMPRESSED_XZ[:128])) as f:
self.assertRaises(EOFError, f.read)
+ def test_read_truncated(self):
+ # Drop stream footer: CRC (4 bytes), index size (4 bytes),
+ # flags (2 bytes) and magic number (2 bytes).
+ truncated = COMPRESSED_XZ[:-12]
+ with LZMAFile(BytesIO(truncated)) as f:
+ self.assertRaises(EOFError, f.read)
+ with LZMAFile(BytesIO(truncated)) as f:
+ self.assertEqual(f.read(len(INPUT)), INPUT)
+ self.assertRaises(EOFError, f.read, 1)
+ # Incomplete 12-byte header.
+ for i in range(12):
+ with LZMAFile(BytesIO(truncated[:i])) as f:
+ self.assertRaises(EOFError, f.read, 1)
+
def test_read_bad_args(self):
f = LZMAFile(BytesIO(COMPRESSED_XZ))
f.close()
@@ -1017,8 +1049,6 @@ class OpenTestCase(unittest.TestCase):
with self.assertRaises(ValueError):
lzma.open(TESTFN, "")
with self.assertRaises(ValueError):
- lzma.open(TESTFN, "x")
- with self.assertRaises(ValueError):
lzma.open(TESTFN, "rbt")
with self.assertRaises(ValueError):
lzma.open(TESTFN, "rb", encoding="utf-8")
@@ -1067,6 +1097,16 @@ class OpenTestCase(unittest.TestCase):
with lzma.open(bio, "rt", newline="\r") as f:
self.assertEqual(f.readlines(), [text])
+ def test_x_mode(self):
+ self.addCleanup(unlink, TESTFN)
+ for mode in ("x", "xb", "xt"):
+ unlink(TESTFN)
+ with lzma.open(TESTFN, mode):
+ pass
+ with self.assertRaises(FileExistsError):
+ with lzma.open(TESTFN, mode):
+ pass
+
class MiscellaneousTestCase(unittest.TestCase):
diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py
index f2e4c63240..78e2a30dbc 100644
--- a/Lib/test/test_mailbox.py
+++ b/Lib/test/test_mailbox.py
@@ -596,7 +596,7 @@ class TestMaildir(TestMailbox, unittest.TestCase):
def setUp(self):
TestMailbox.setUp(self)
- if os.name in ('nt', 'os2') or sys.platform == 'cygwin':
+ if (os.name == 'nt') or (sys.platform == 'cygwin'):
self._box.colon = '!'
def assertMailboxEmpty(self):
diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py
index 7e37f39c6a..255eb87bb3 100644
--- a/Lib/test/test_marshal.py
+++ b/Lib/test/test_marshal.py
@@ -24,37 +24,13 @@ class HelperMixin:
class IntTestCase(unittest.TestCase, HelperMixin):
def test_ints(self):
- # Test the full range of Python ints.
- n = sys.maxsize
+ # Test a range of Python ints larger than the machine word size.
+ n = sys.maxsize ** 2
while n:
for expected in (-n, n):
self.helper(expected)
n = n >> 1
- def test_int64(self):
- # Simulate int marshaling on a 64-bit box. This is most interesting if
- # we're running the test on a 32-bit box, of course.
-
- def to_little_endian_string(value, nbytes):
- b = bytearray()
- for i in range(nbytes):
- b.append(value & 0xff)
- value >>= 8
- return b
-
- maxint64 = (1 << 63) - 1
- minint64 = -maxint64-1
-
- for base in maxint64, minint64, -maxint64, -(minint64 >> 1):
- while base:
- s = b'I' + to_little_endian_string(base, 8)
- got = marshal.loads(s)
- self.assertEqual(base, got)
- if base == -1: # a fixed-point for shifting right 1
- base = 0
- else:
- base >>= 1
-
def test_bool(self):
for b in (True, False):
self.helper(b)
@@ -201,10 +177,14 @@ class BugsTestCase(unittest.TestCase):
except Exception:
pass
- def test_loads_recursion(self):
+ def test_loads_2x_code(self):
s = b'c' + (b'X' * 4*4) + b'{' * 2**20
self.assertRaises(ValueError, marshal.loads, s)
+ def test_loads_recursion(self):
+ s = b'c' + (b'X' * 4*5) + b'{' * 2**20
+ self.assertRaises(ValueError, marshal.loads, s)
+
def test_recursion_limit(self):
# Create a deeply nested structure.
head = last = []
@@ -282,15 +262,20 @@ class BugsTestCase(unittest.TestCase):
def test_bad_reader(self):
class BadReader(io.BytesIO):
- def read(self, n=-1):
- b = super().read(n)
+ def readinto(self, buf):
+ n = super().readinto(buf)
if n is not None and n > 4:
- b += b' ' * 10**6
- return b
+ n += 10**6
+ return n
for value in (1.0, 1j, b'0123456789', '0123456789'):
self.assertRaises(ValueError, marshal.load,
BadReader(marshal.dumps(value)))
+ def _test_eof(self):
+ data = marshal.dumps(("hello", "dolly", None))
+ for i in range(len(data)):
+ self.assertRaises(EOFError, marshal.loads, data[0: i])
+
LARGE_SIZE = 2**31
pointer_size = 8 if sys.maxsize > 0xFFFFFFFF else 4
@@ -335,6 +320,122 @@ class LargeValuesTestCase(unittest.TestCase):
def test_bytearray(self, size):
self.check_unmarshallable(bytearray(size))
+def CollectObjectIDs(ids, obj):
+ """Collect object ids seen in a structure"""
+ if id(obj) in ids:
+ return
+ ids.add(id(obj))
+ if isinstance(obj, (list, tuple, set, frozenset)):
+ for e in obj:
+ CollectObjectIDs(ids, e)
+ elif isinstance(obj, dict):
+ for k, v in obj.items():
+ CollectObjectIDs(ids, k)
+ CollectObjectIDs(ids, v)
+ return len(ids)
+
+class InstancingTestCase(unittest.TestCase, HelperMixin):
+ intobj = 123321
+ floatobj = 1.2345
+ strobj = "abcde"*3
+ dictobj = {"hello":floatobj, "goodbye":floatobj, floatobj:"hello"}
+
+ def helper3(self, rsample, recursive=False, simple=False):
+ #we have two instances
+ sample = (rsample, rsample)
+
+ n0 = CollectObjectIDs(set(), sample)
+
+ s3 = marshal.dumps(sample, 3)
+ n3 = CollectObjectIDs(set(), marshal.loads(s3))
+
+ #same number of instances generated
+ self.assertEqual(n3, n0)
+
+ if not recursive:
+ #can compare with version 2
+ s2 = marshal.dumps(sample, 2)
+ n2 = CollectObjectIDs(set(), marshal.loads(s2))
+ #old format generated more instances
+ self.assertGreater(n2, n0)
+
+ #if complex objects are in there, old format is larger
+ if not simple:
+ self.assertGreater(len(s2), len(s3))
+ else:
+ self.assertGreaterEqual(len(s2), len(s3))
+
+ def testInt(self):
+ self.helper(self.intobj)
+ self.helper3(self.intobj, simple=True)
+
+ def testFloat(self):
+ self.helper(self.floatobj)
+ self.helper3(self.floatobj)
+
+ def testStr(self):
+ self.helper(self.strobj)
+ self.helper3(self.strobj)
+
+ def testDict(self):
+ self.helper(self.dictobj)
+ self.helper3(self.dictobj)
+
+ def testModule(self):
+ with open(__file__, "rb") as f:
+ code = f.read()
+ if __file__.endswith(".py"):
+ code = compile(code, __file__, "exec")
+ self.helper(code)
+ self.helper3(code)
+
+ def testRecursion(self):
+ d = dict(self.dictobj)
+ d["self"] = d
+ self.helper3(d, recursive=True)
+ l = [self.dictobj]
+ l.append(l)
+ self.helper3(l, recursive=True)
+
+class CompatibilityTestCase(unittest.TestCase):
+ def _test(self, version):
+ with open(__file__, "rb") as f:
+ code = f.read()
+ if __file__.endswith(".py"):
+ code = compile(code, __file__, "exec")
+ data = marshal.dumps(code, version)
+ marshal.loads(data)
+
+ def test0To3(self):
+ self._test(0)
+
+ def test1To3(self):
+ self._test(1)
+
+ def test2To3(self):
+ self._test(2)
+
+ def test3To3(self):
+ self._test(3)
+
+class InterningTestCase(unittest.TestCase, HelperMixin):
+ strobj = "this is an interned string"
+ strobj = sys.intern(strobj)
+
+ def testIntern(self):
+ s = marshal.loads(marshal.dumps(self.strobj))
+ self.assertEqual(s, self.strobj)
+ self.assertEqual(id(s), id(self.strobj))
+ s2 = sys.intern(s)
+ self.assertEqual(id(s2), id(s))
+
+ def testNoIntern(self):
+ s = marshal.loads(marshal.dumps(self.strobj, 2))
+ self.assertEqual(s, self.strobj)
+ self.assertNotEqual(id(s), id(self.strobj))
+ s2 = sys.intern(s)
+ self.assertNotEqual(id(s2), id(s))
+
def test_main():
support.run_unittest(IntTestCase,
diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py
index d611a3138b..4de4f65329 100644
--- a/Lib/test/test_memoryio.py
+++ b/Lib/test/test_memoryio.py
@@ -520,12 +520,12 @@ class TextIOTestMixin:
def test_relative_seek(self):
memio = self.ioclass()
- self.assertRaises(IOError, memio.seek, -1, 1)
- self.assertRaises(IOError, memio.seek, 3, 1)
- self.assertRaises(IOError, memio.seek, -3, 1)
- self.assertRaises(IOError, memio.seek, -1, 2)
- self.assertRaises(IOError, memio.seek, 1, 1)
- self.assertRaises(IOError, memio.seek, 1, 2)
+ self.assertRaises(OSError, memio.seek, -1, 1)
+ self.assertRaises(OSError, memio.seek, 3, 1)
+ self.assertRaises(OSError, memio.seek, -3, 1)
+ self.assertRaises(OSError, memio.seek, -1, 2)
+ self.assertRaises(OSError, memio.seek, 1, 1)
+ self.assertRaises(OSError, memio.seek, 1, 2)
def test_textio_properties(self):
memio = self.ioclass()
diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
index ee6b15ac14..ffd4f5851a 100644
--- a/Lib/test/test_memoryview.py
+++ b/Lib/test/test_memoryview.py
@@ -352,6 +352,15 @@ class AbstractMemoryTests:
self.assertIs(wr(), None)
self.assertIs(L[0], b)
+ def test_reversed(self):
+ for tp in self._types:
+ b = tp(self._source)
+ m = self._view(b)
+ aslist = list(reversed(m.tolist()))
+ self.assertEqual(list(reversed(m)), aslist)
+ self.assertEqual(list(reversed(m)), list(m[::-1]))
+
+
# Variations on source objects for the buffer: bytes-like objects, then arrays
# with itemsize > 1.
# NOTE: support for multi-dimensional objects is unimplemented.
diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py
index 899df8d818..6ca5e1b730 100644
--- a/Lib/test/test_mmap.py
+++ b/Lib/test/test_mmap.py
@@ -1,11 +1,12 @@
from test.support import (TESTFN, run_unittest, import_module, unlink,
- requires, _2G, _4G)
+ requires, _2G, _4G, gc_collect)
import unittest
import os
import re
import itertools
import socket
import sys
+import weakref
# Skip test if we can't import mmap.
mmap = import_module('mmap')
@@ -245,7 +246,7 @@ class MmapTests(unittest.TestCase):
def test_bad_file_desc(self):
# Try opening a bad file descriptor...
- self.assertRaises(mmap.error, mmap.mmap, -2, 4096)
+ self.assertRaises(OSError, mmap.mmap, -2, 4096)
def test_tougher_find(self):
# Do a tougher .find() test. SF bug 515943 pointed out that, in 2.2,
@@ -655,7 +656,7 @@ class MmapTests(unittest.TestCase):
m = mmap.mmap(f.fileno(), 0)
f.close()
try:
- m.resize(0) # will raise WindowsError
+ m.resize(0) # will raise OSError
except:
pass
try:
@@ -671,7 +672,7 @@ class MmapTests(unittest.TestCase):
# parameters to _get_osfhandle.
s = socket.socket()
try:
- with self.assertRaises(mmap.error):
+ with self.assertRaises(OSError):
m = mmap.mmap(s.fileno(), 10)
finally:
s.close()
@@ -682,14 +683,23 @@ class MmapTests(unittest.TestCase):
self.assertTrue(m.closed)
def test_context_manager_exception(self):
- # Test that the IOError gets passed through
+ # Test that the OSError gets passed through
with self.assertRaises(Exception) as exc:
with mmap.mmap(-1, 10) as m:
- raise IOError
- self.assertIsInstance(exc.exception, IOError,
+ raise OSError
+ self.assertIsInstance(exc.exception, OSError,
"wrong exception raised in context manager")
self.assertTrue(m.closed, "context manager failed")
+ def test_weakref(self):
+ # Check mmap objects are weakrefable
+ mm = mmap.mmap(-1, 16)
+ wr = weakref.ref(mm)
+ self.assertIs(wr(), mm)
+ del mm
+ gc_collect()
+ self.assertIs(wr(), None)
+
class LargeMmapTests(unittest.TestCase):
def setUp(self):
@@ -707,7 +717,7 @@ class LargeMmapTests(unittest.TestCase):
f.seek(num_zeroes)
f.write(tail)
f.flush()
- except (IOError, OverflowError):
+ except (OSError, OverflowError):
f.close()
raise unittest.SkipTest("filesystem does not have largefile support")
return f
diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py
index e5a2525d18..1230293670 100644
--- a/Lib/test/test_module.py
+++ b/Lib/test/test_module.py
@@ -1,6 +1,8 @@
# Test the module type
import unittest
+import weakref
from test.support import run_unittest, gc_collect
+from test.script_helper import assert_python_ok
import sys
ModuleType = type(sys)
@@ -33,7 +35,12 @@ class ModuleTests(unittest.TestCase):
foo = ModuleType("foo")
self.assertEqual(foo.__name__, "foo")
self.assertEqual(foo.__doc__, None)
- self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": None})
+ self.assertIs(foo.__loader__, None)
+ self.assertIs(foo.__package__, None)
+ self.assertIs(foo.__spec__, None)
+ self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": None,
+ "__loader__": None, "__package__": None,
+ "__spec__": None})
def test_ascii_docstring(self):
# ASCII docstring
@@ -41,7 +48,9 @@ class ModuleTests(unittest.TestCase):
self.assertEqual(foo.__name__, "foo")
self.assertEqual(foo.__doc__, "foodoc")
self.assertEqual(foo.__dict__,
- {"__name__": "foo", "__doc__": "foodoc"})
+ {"__name__": "foo", "__doc__": "foodoc",
+ "__loader__": None, "__package__": None,
+ "__spec__": None})
def test_unicode_docstring(self):
# Unicode docstring
@@ -49,7 +58,9 @@ class ModuleTests(unittest.TestCase):
self.assertEqual(foo.__name__, "foo")
self.assertEqual(foo.__doc__, "foodoc\u1234")
self.assertEqual(foo.__dict__,
- {"__name__": "foo", "__doc__": "foodoc\u1234"})
+ {"__name__": "foo", "__doc__": "foodoc\u1234",
+ "__loader__": None, "__package__": None,
+ "__spec__": None})
def test_reinit(self):
# Reinitialization should not replace the __dict__
@@ -61,10 +72,10 @@ class ModuleTests(unittest.TestCase):
self.assertEqual(foo.__doc__, "foodoc")
self.assertEqual(foo.bar, 42)
self.assertEqual(foo.__dict__,
- {"__name__": "foo", "__doc__": "foodoc", "bar": 42})
+ {"__name__": "foo", "__doc__": "foodoc", "bar": 42,
+ "__loader__": None, "__package__": None, "__spec__": None})
self.assertTrue(foo.__dict__ is d)
- @unittest.expectedFailure
def test_dont_clear_dict(self):
# See issue 7140.
def f():
@@ -89,6 +100,14 @@ a = A(destroyed)"""
gc_collect()
self.assertEqual(destroyed, [1])
+ def test_weakref(self):
+ m = ModuleType("foo")
+ wr = weakref.ref(m)
+ self.assertIs(wr(), m)
+ del m
+ gc_collect()
+ self.assertIs(wr(), None)
+
def test_module_repr_minimal(self):
# reprs when modules have no __file__, __name__, or __loader__
m = ModuleType('foo')
@@ -110,13 +129,19 @@ a = A(destroyed)"""
m.__file__ = '/tmp/foo.py'
self.assertEqual(repr(m), "<module '?' from '/tmp/foo.py'>")
+ def test_module_repr_with_loader_as_None(self):
+ m = ModuleType('foo')
+ assert m.__loader__ is None
+ self.assertEqual(repr(m), "<module 'foo'>")
+
def test_module_repr_with_bare_loader_but_no_name(self):
m = ModuleType('foo')
del m.__name__
# Yes, a class not an instance.
m.__loader__ = BareLoader
+ loader_repr = repr(BareLoader)
self.assertEqual(
- repr(m), "<module '?' (<class 'test.test_module.BareLoader'>)>")
+ repr(m), "<module '?' ({})>".format(loader_repr))
def test_module_repr_with_full_loader_but_no_name(self):
# m.__loader__.module_repr() will fail because the module has no
@@ -126,15 +151,17 @@ a = A(destroyed)"""
del m.__name__
# Yes, a class not an instance.
m.__loader__ = FullLoader
+ loader_repr = repr(FullLoader)
self.assertEqual(
- repr(m), "<module '?' (<class 'test.test_module.FullLoader'>)>")
+ repr(m), "<module '?' ({})>".format(loader_repr))
def test_module_repr_with_bare_loader(self):
m = ModuleType('foo')
# Yes, a class not an instance.
m.__loader__ = BareLoader
+ module_repr = repr(BareLoader)
self.assertEqual(
- repr(m), "<module 'foo' (<class 'test.test_module.BareLoader'>)>")
+ repr(m), "<module 'foo' ({})>".format(module_repr))
def test_module_repr_with_full_loader(self):
m = ModuleType('foo')
@@ -164,8 +191,25 @@ a = A(destroyed)"""
def test_module_repr_source(self):
r = repr(unittest)
- self.assertEqual(r[:25], "<module 'unittest' from '")
- self.assertEqual(r[-13:], "__init__.py'>")
+ starts_with = "<module 'unittest' from '"
+ ends_with = "__init__.py'>"
+ self.assertEqual(r[:len(starts_with)], starts_with,
+ '{!r} does not start with {!r}'.format(r, starts_with))
+ self.assertEqual(r[-len(ends_with):], ends_with,
+ '{!r} does not end with {!r}'.format(r, ends_with))
+
+ def test_module_finalization_at_shutdown(self):
+ # Module globals and builtins should still be available during shutdown
+ rc, out, err = assert_python_ok("-c", "from test import final_a")
+ self.assertFalse(err)
+ lines = out.splitlines()
+ self.assertEqual(set(lines), {
+ b"x = a",
+ b"x = b",
+ b"final_a.x = a",
+ b"final_b.x = b",
+ b"len = len",
+ b"shutil.rmtree = rmtree"})
# frozen and namespace module reprs are tested in importlib.
diff --git a/Lib/test/test_multibytecodec.py b/Lib/test/test_multibytecodec.py
index feb7bd595a..91148a6fc5 100644
--- a/Lib/test/test_multibytecodec.py
+++ b/Lib/test/test_multibytecodec.py
@@ -176,57 +176,28 @@ class Test_StreamReader(unittest.TestCase):
support.unlink(TESTFN)
class Test_StreamWriter(unittest.TestCase):
- if len('\U00012345') == 2: # UCS2
- def test_gb18030(self):
- s= io.BytesIO()
- c = codecs.getwriter('gb18030')(s)
- c.write('123')
- self.assertEqual(s.getvalue(), b'123')
- c.write('\U00012345')
- self.assertEqual(s.getvalue(), b'123\x907\x959')
- c.write('\U00012345'[0])
- self.assertEqual(s.getvalue(), b'123\x907\x959')
- c.write('\U00012345'[1] + '\U00012345' + '\uac00\u00ac')
- self.assertEqual(s.getvalue(),
- b'123\x907\x959\x907\x959\x907\x959\x827\xcf5\x810\x851')
- c.write('\U00012345'[0])
- self.assertEqual(s.getvalue(),
- b'123\x907\x959\x907\x959\x907\x959\x827\xcf5\x810\x851')
- self.assertRaises(UnicodeError, c.reset)
- self.assertEqual(s.getvalue(),
- b'123\x907\x959\x907\x959\x907\x959\x827\xcf5\x810\x851')
-
- def test_utf_8(self):
- s= io.BytesIO()
- c = codecs.getwriter('utf-8')(s)
- c.write('123')
- self.assertEqual(s.getvalue(), b'123')
- c.write('\U00012345')
- self.assertEqual(s.getvalue(), b'123\xf0\x92\x8d\x85')
-
- # Python utf-8 codec can't buffer surrogate pairs yet.
- if 0:
- c.write('\U00012345'[0])
- self.assertEqual(s.getvalue(), b'123\xf0\x92\x8d\x85')
- c.write('\U00012345'[1] + '\U00012345' + '\uac00\u00ac')
- self.assertEqual(s.getvalue(),
- b'123\xf0\x92\x8d\x85\xf0\x92\x8d\x85\xf0\x92\x8d\x85'
- b'\xea\xb0\x80\xc2\xac')
- c.write('\U00012345'[0])
- self.assertEqual(s.getvalue(),
- b'123\xf0\x92\x8d\x85\xf0\x92\x8d\x85\xf0\x92\x8d\x85'
- b'\xea\xb0\x80\xc2\xac')
- c.reset()
- self.assertEqual(s.getvalue(),
- b'123\xf0\x92\x8d\x85\xf0\x92\x8d\x85\xf0\x92\x8d\x85'
- b'\xea\xb0\x80\xc2\xac\xed\xa0\x88')
- c.write('\U00012345'[1])
- self.assertEqual(s.getvalue(),
- b'123\xf0\x92\x8d\x85\xf0\x92\x8d\x85\xf0\x92\x8d\x85'
- b'\xea\xb0\x80\xc2\xac\xed\xa0\x88\xed\xbd\x85')
-
- else: # UCS4
- pass
+ def test_gb18030(self):
+ s= io.BytesIO()
+ c = codecs.getwriter('gb18030')(s)
+ c.write('123')
+ self.assertEqual(s.getvalue(), b'123')
+ c.write('\U00012345')
+ self.assertEqual(s.getvalue(), b'123\x907\x959')
+ c.write('\uac00\u00ac')
+ self.assertEqual(s.getvalue(),
+ b'123\x907\x959\x827\xcf5\x810\x851')
+
+ def test_utf_8(self):
+ s= io.BytesIO()
+ c = codecs.getwriter('utf-8')(s)
+ c.write('123')
+ self.assertEqual(s.getvalue(), b'123')
+ c.write('\U00012345')
+ self.assertEqual(s.getvalue(), b'123\xf0\x92\x8d\x85')
+ c.write('\uac00\u00ac')
+ self.assertEqual(s.getvalue(),
+ b'123\xf0\x92\x8d\x85'
+ b'\xea\xb0\x80\xc2\xac')
def test_streamwriter_strwrite(self):
s = io.BytesIO()
diff --git a/Lib/test/test_multiprocessing_fork.py b/Lib/test/test_multiprocessing_fork.py
new file mode 100644
index 0000000000..2bf4e75644
--- /dev/null
+++ b/Lib/test/test_multiprocessing_fork.py
@@ -0,0 +1,7 @@
+import unittest
+import test._test_multiprocessing
+
+test._test_multiprocessing.install_tests_in_module_dict(globals(), 'fork')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_multiprocessing_forkserver.py b/Lib/test/test_multiprocessing_forkserver.py
new file mode 100644
index 0000000000..193a04a5fc
--- /dev/null
+++ b/Lib/test/test_multiprocessing_forkserver.py
@@ -0,0 +1,7 @@
+import unittest
+import test._test_multiprocessing
+
+test._test_multiprocessing.install_tests_in_module_dict(globals(), 'forkserver')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_multiprocessing_spawn.py b/Lib/test/test_multiprocessing_spawn.py
new file mode 100644
index 0000000000..334ae9e8f7
--- /dev/null
+++ b/Lib/test/test_multiprocessing_spawn.py
@@ -0,0 +1,7 @@
+import unittest
+import test._test_multiprocessing
+
+test._test_multiprocessing.install_tests_in_module_dict(globals(), 'spawn')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_namespace_pkgs.py b/Lib/test/test_namespace_pkgs.py
index 7067b12e8f..6639612631 100644
--- a/Lib/test/test_namespace_pkgs.py
+++ b/Lib/test/test_namespace_pkgs.py
@@ -1,7 +1,10 @@
-import sys
import contextlib
-import unittest
+import importlib.abc
+import importlib.machinery
import os
+import sys
+import types
+import unittest
from test.test_importlib import util
from test.support import run_unittest
@@ -286,9 +289,5 @@ class ModuleAndNamespacePackageInSameDir(NamespacePackageTest):
self.assertEqual(a_test.attr, 'in module')
-def test_main():
- run_unittest(*NamespacePackageTest.__subclasses__())
-
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_nntplib.py b/Lib/test/test_nntplib.py
index 1d52713f07..71a4ec022b 100644
--- a/Lib/test/test_nntplib.py
+++ b/Lib/test/test_nntplib.py
@@ -266,7 +266,7 @@ class NetworkedNNTPTestsMixin:
return False
try:
server.help()
- except (socket.error, EOFError):
+ except (OSError, EOFError):
return False
return True
diff --git a/Lib/test/test_normalization.py b/Lib/test/test_normalization.py
index 28ede34cd7..ab2eeb77da 100644
--- a/Lib/test/test_normalization.py
+++ b/Lib/test/test_normalization.py
@@ -43,7 +43,7 @@ class NormalizationTest(unittest.TestCase):
try:
testdata = open_urlresource(TESTDATAURL, encoding="utf-8",
check=check_version)
- except (IOError, HTTPException):
+ except (OSError, HTTPException):
self.skipTest("Could not retrieve " + TESTDATAURL)
self.addCleanup(testdata.close)
for line in testdata:
diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
index f8098767ce..285ef62a46 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -256,6 +256,40 @@ class TestNtpath(unittest.TestCase):
# dialogs (#4804)
ntpath.sameopenfile(-1, -1)
+ def test_ismount(self):
+ self.assertTrue(ntpath.ismount("c:\\"))
+ self.assertTrue(ntpath.ismount("C:\\"))
+ self.assertTrue(ntpath.ismount("c:/"))
+ self.assertTrue(ntpath.ismount("C:/"))
+ self.assertTrue(ntpath.ismount("\\\\.\\c:\\"))
+ self.assertTrue(ntpath.ismount("\\\\.\\C:\\"))
+
+ self.assertTrue(ntpath.ismount(b"c:\\"))
+ self.assertTrue(ntpath.ismount(b"C:\\"))
+ self.assertTrue(ntpath.ismount(b"c:/"))
+ self.assertTrue(ntpath.ismount(b"C:/"))
+ self.assertTrue(ntpath.ismount(b"\\\\.\\c:\\"))
+ self.assertTrue(ntpath.ismount(b"\\\\.\\C:\\"))
+
+ with support.temp_dir() as d:
+ self.assertFalse(ntpath.ismount(d))
+
+ if sys.platform == "win32":
+ #
+ # Make sure the current folder isn't the root folder
+ # (or any other volume root). The drive-relative
+ # locations below cannot then refer to mount points
+ #
+ drive, path = ntpath.splitdrive(sys.executable)
+ with support.change_cwd(os.path.dirname(sys.executable)):
+ self.assertFalse(ntpath.ismount(drive.lower()))
+ self.assertFalse(ntpath.ismount(drive.upper()))
+
+ self.assertTrue(ntpath.ismount("\\\\localhost\\c$"))
+ self.assertTrue(ntpath.ismount("\\\\localhost\\c$\\"))
+
+ self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$"))
+ self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$\\"))
class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
pathmodule = ntpath
diff --git a/Lib/test/test_openpty.py b/Lib/test/test_openpty.py
index 63843705b1..47851072c1 100644
--- a/Lib/test/test_openpty.py
+++ b/Lib/test/test_openpty.py
@@ -4,7 +4,7 @@ import os, unittest
from test.support import run_unittest
if not hasattr(os, "openpty"):
- raise unittest.SkipTest("No openpty() available.")
+ raise unittest.SkipTest("os.openpty() not available.")
class OpenptyTest(unittest.TestCase):
diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py
index fa608b9a52..ab58a98365 100644
--- a/Lib/test/test_operator.py
+++ b/Lib/test/test_operator.py
@@ -1,8 +1,10 @@
-import operator
import unittest
from test import support
+py_operator = support.import_fresh_module('operator', blocked=['_operator'])
+c_operator = support.import_fresh_module('operator', fresh=['_operator'])
+
class Seq1:
def __init__(self, lst):
self.lst = lst
@@ -32,8 +34,9 @@ class Seq2(object):
return other * self.lst
-class OperatorTestCase(unittest.TestCase):
+class OperatorTestCase:
def test_lt(self):
+ operator = self.module
self.assertRaises(TypeError, operator.lt)
self.assertRaises(TypeError, operator.lt, 1j, 2j)
self.assertFalse(operator.lt(1, 0))
@@ -44,6 +47,7 @@ class OperatorTestCase(unittest.TestCase):
self.assertTrue(operator.lt(1, 2.0))
def test_le(self):
+ operator = self.module
self.assertRaises(TypeError, operator.le)
self.assertRaises(TypeError, operator.le, 1j, 2j)
self.assertFalse(operator.le(1, 0))
@@ -54,6 +58,7 @@ class OperatorTestCase(unittest.TestCase):
self.assertTrue(operator.le(1, 2.0))
def test_eq(self):
+ operator = self.module
class C(object):
def __eq__(self, other):
raise SyntaxError
@@ -67,6 +72,7 @@ class OperatorTestCase(unittest.TestCase):
self.assertFalse(operator.eq(1, 2.0))
def test_ne(self):
+ operator = self.module
class C(object):
def __ne__(self, other):
raise SyntaxError
@@ -80,6 +86,7 @@ class OperatorTestCase(unittest.TestCase):
self.assertTrue(operator.ne(1, 2.0))
def test_ge(self):
+ operator = self.module
self.assertRaises(TypeError, operator.ge)
self.assertRaises(TypeError, operator.ge, 1j, 2j)
self.assertTrue(operator.ge(1, 0))
@@ -90,6 +97,7 @@ class OperatorTestCase(unittest.TestCase):
self.assertFalse(operator.ge(1, 2.0))
def test_gt(self):
+ operator = self.module
self.assertRaises(TypeError, operator.gt)
self.assertRaises(TypeError, operator.gt, 1j, 2j)
self.assertTrue(operator.gt(1, 0))
@@ -100,22 +108,26 @@ class OperatorTestCase(unittest.TestCase):
self.assertFalse(operator.gt(1, 2.0))
def test_abs(self):
+ operator = self.module
self.assertRaises(TypeError, operator.abs)
self.assertRaises(TypeError, operator.abs, None)
self.assertEqual(operator.abs(-1), 1)
self.assertEqual(operator.abs(1), 1)
def test_add(self):
+ operator = self.module
self.assertRaises(TypeError, operator.add)
self.assertRaises(TypeError, operator.add, None, None)
self.assertTrue(operator.add(3, 4) == 7)
def test_bitwise_and(self):
+ operator = self.module
self.assertRaises(TypeError, operator.and_)
self.assertRaises(TypeError, operator.and_, None, None)
self.assertTrue(operator.and_(0xf, 0xa) == 0xa)
def test_concat(self):
+ operator = self.module
self.assertRaises(TypeError, operator.concat)
self.assertRaises(TypeError, operator.concat, None, None)
self.assertTrue(operator.concat('py', 'thon') == 'python')
@@ -125,12 +137,14 @@ class OperatorTestCase(unittest.TestCase):
self.assertRaises(TypeError, operator.concat, 13, 29)
def test_countOf(self):
+ operator = self.module
self.assertRaises(TypeError, operator.countOf)
self.assertRaises(TypeError, operator.countOf, None, None)
self.assertTrue(operator.countOf([1, 2, 1, 3, 1, 4], 3) == 1)
self.assertTrue(operator.countOf([1, 2, 1, 3, 1, 4], 5) == 0)
def test_delitem(self):
+ operator = self.module
a = [4, 3, 2, 1]
self.assertRaises(TypeError, operator.delitem, a)
self.assertRaises(TypeError, operator.delitem, a, None)
@@ -138,33 +152,39 @@ class OperatorTestCase(unittest.TestCase):
self.assertTrue(a == [4, 2, 1])
def test_floordiv(self):
+ operator = self.module
self.assertRaises(TypeError, operator.floordiv, 5)
self.assertRaises(TypeError, operator.floordiv, None, None)
self.assertTrue(operator.floordiv(5, 2) == 2)
def test_truediv(self):
+ operator = self.module
self.assertRaises(TypeError, operator.truediv, 5)
self.assertRaises(TypeError, operator.truediv, None, None)
self.assertTrue(operator.truediv(5, 2) == 2.5)
def test_getitem(self):
+ operator = self.module
a = range(10)
self.assertRaises(TypeError, operator.getitem)
self.assertRaises(TypeError, operator.getitem, a, None)
self.assertTrue(operator.getitem(a, 2) == 2)
def test_indexOf(self):
+ operator = self.module
self.assertRaises(TypeError, operator.indexOf)
self.assertRaises(TypeError, operator.indexOf, None, None)
self.assertTrue(operator.indexOf([4, 3, 2, 1], 3) == 1)
self.assertRaises(ValueError, operator.indexOf, [4, 3, 2, 1], 0)
def test_invert(self):
+ operator = self.module
self.assertRaises(TypeError, operator.invert)
self.assertRaises(TypeError, operator.invert, None)
self.assertEqual(operator.inv(4), -5)
def test_lshift(self):
+ operator = self.module
self.assertRaises(TypeError, operator.lshift)
self.assertRaises(TypeError, operator.lshift, None, 42)
self.assertTrue(operator.lshift(5, 1) == 10)
@@ -172,16 +192,19 @@ class OperatorTestCase(unittest.TestCase):
self.assertRaises(ValueError, operator.lshift, 2, -1)
def test_mod(self):
+ operator = self.module
self.assertRaises(TypeError, operator.mod)
self.assertRaises(TypeError, operator.mod, None, 42)
self.assertTrue(operator.mod(5, 2) == 1)
def test_mul(self):
+ operator = self.module
self.assertRaises(TypeError, operator.mul)
self.assertRaises(TypeError, operator.mul, None, None)
self.assertTrue(operator.mul(5, 2) == 10)
def test_neg(self):
+ operator = self.module
self.assertRaises(TypeError, operator.neg)
self.assertRaises(TypeError, operator.neg, None)
self.assertEqual(operator.neg(5), -5)
@@ -190,11 +213,13 @@ class OperatorTestCase(unittest.TestCase):
self.assertEqual(operator.neg(-0), 0)
def test_bitwise_or(self):
+ operator = self.module
self.assertRaises(TypeError, operator.or_)
self.assertRaises(TypeError, operator.or_, None, None)
self.assertTrue(operator.or_(0xa, 0x5) == 0xf)
def test_pos(self):
+ operator = self.module
self.assertRaises(TypeError, operator.pos)
self.assertRaises(TypeError, operator.pos, None)
self.assertEqual(operator.pos(5), 5)
@@ -203,14 +228,15 @@ class OperatorTestCase(unittest.TestCase):
self.assertEqual(operator.pos(-0), 0)
def test_pow(self):
+ operator = self.module
self.assertRaises(TypeError, operator.pow)
self.assertRaises(TypeError, operator.pow, None, None)
self.assertEqual(operator.pow(3,5), 3**5)
- self.assertEqual(operator.__pow__(3,5), 3**5)
self.assertRaises(TypeError, operator.pow, 1)
self.assertRaises(TypeError, operator.pow, 1, 2, 3)
def test_rshift(self):
+ operator = self.module
self.assertRaises(TypeError, operator.rshift)
self.assertRaises(TypeError, operator.rshift, None, 42)
self.assertTrue(operator.rshift(5, 1) == 2)
@@ -218,12 +244,14 @@ class OperatorTestCase(unittest.TestCase):
self.assertRaises(ValueError, operator.rshift, 2, -1)
def test_contains(self):
+ operator = self.module
self.assertRaises(TypeError, operator.contains)
self.assertRaises(TypeError, operator.contains, None, None)
self.assertTrue(operator.contains(range(4), 2))
self.assertFalse(operator.contains(range(4), 5))
def test_setitem(self):
+ operator = self.module
a = list(range(3))
self.assertRaises(TypeError, operator.setitem, a)
self.assertRaises(TypeError, operator.setitem, a, None, None)
@@ -232,11 +260,13 @@ class OperatorTestCase(unittest.TestCase):
self.assertRaises(IndexError, operator.setitem, a, 4, 2)
def test_sub(self):
+ operator = self.module
self.assertRaises(TypeError, operator.sub)
self.assertRaises(TypeError, operator.sub, None, None)
self.assertTrue(operator.sub(5, 2) == 3)
def test_truth(self):
+ operator = self.module
class C(object):
def __bool__(self):
raise SyntaxError
@@ -248,11 +278,13 @@ class OperatorTestCase(unittest.TestCase):
self.assertFalse(operator.truth([]))
def test_bitwise_xor(self):
+ operator = self.module
self.assertRaises(TypeError, operator.xor)
self.assertRaises(TypeError, operator.xor, None, None)
self.assertTrue(operator.xor(0xb, 0xc) == 0x7)
def test_is(self):
+ operator = self.module
a = b = 'xyzpdq'
c = a[:3] + b[3:]
self.assertRaises(TypeError, operator.is_)
@@ -260,6 +292,7 @@ class OperatorTestCase(unittest.TestCase):
self.assertFalse(operator.is_(a,c))
def test_is_not(self):
+ operator = self.module
a = b = 'xyzpdq'
c = a[:3] + b[3:]
self.assertRaises(TypeError, operator.is_not)
@@ -267,6 +300,7 @@ class OperatorTestCase(unittest.TestCase):
self.assertTrue(operator.is_not(a,c))
def test_attrgetter(self):
+ operator = self.module
class A:
pass
a = A()
@@ -316,6 +350,7 @@ class OperatorTestCase(unittest.TestCase):
self.assertEqual(f(a), ('arthur', 'thomas', 'johnson'))
def test_itemgetter(self):
+ operator = self.module
a = 'ABCDE'
f = operator.itemgetter(2)
self.assertEqual(f(a), 'C')
@@ -350,12 +385,15 @@ class OperatorTestCase(unittest.TestCase):
self.assertRaises(TypeError, operator.itemgetter(2, 'x', 5), data)
def test_methodcaller(self):
+ operator = self.module
self.assertRaises(TypeError, operator.methodcaller)
class A:
def foo(self, *args, **kwds):
return args[0] + args[1]
def bar(self, f=42):
return f
+ def baz(*args, **kwds):
+ return kwds['name'], kwds['self']
a = A()
f = operator.methodcaller('foo')
self.assertRaises(IndexError, f, a)
@@ -366,8 +404,11 @@ class OperatorTestCase(unittest.TestCase):
self.assertRaises(TypeError, f, a, a)
f = operator.methodcaller('bar', f=5)
self.assertEqual(f(a), 5)
+ f = operator.methodcaller('baz', name='spam', self='eggs')
+ self.assertEqual(f(a), ('spam', 'eggs'))
def test_inplace(self):
+ operator = self.module
class C(object):
def __iadd__ (self, other): return "iadd"
def __iand__ (self, other): return "iand"
@@ -396,37 +437,48 @@ class OperatorTestCase(unittest.TestCase):
self.assertEqual(operator.itruediv (c, 5), "itruediv")
self.assertEqual(operator.ixor (c, 5), "ixor")
self.assertEqual(operator.iconcat (c, c), "iadd")
- self.assertEqual(operator.__iadd__ (c, 5), "iadd")
- self.assertEqual(operator.__iand__ (c, 5), "iand")
- self.assertEqual(operator.__ifloordiv__(c, 5), "ifloordiv")
- self.assertEqual(operator.__ilshift__ (c, 5), "ilshift")
- self.assertEqual(operator.__imod__ (c, 5), "imod")
- self.assertEqual(operator.__imul__ (c, 5), "imul")
- self.assertEqual(operator.__ior__ (c, 5), "ior")
- self.assertEqual(operator.__ipow__ (c, 5), "ipow")
- self.assertEqual(operator.__irshift__ (c, 5), "irshift")
- self.assertEqual(operator.__isub__ (c, 5), "isub")
- self.assertEqual(operator.__itruediv__ (c, 5), "itruediv")
- self.assertEqual(operator.__ixor__ (c, 5), "ixor")
- self.assertEqual(operator.__iconcat__ (c, c), "iadd")
-
-def test_main(verbose=None):
- import sys
- test_classes = (
- OperatorTestCase,
- )
-
- support.run_unittest(*test_classes)
-
- # verify reference counting
- if verbose and hasattr(sys, "gettotalrefcount"):
- import gc
- counts = [None] * 5
- for i in range(len(counts)):
- support.run_unittest(*test_classes)
- gc.collect()
- counts[i] = sys.gettotalrefcount()
- print(counts)
+
+ def test_length_hint(self):
+ operator = self.module
+ class X(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __length_hint__(self):
+ if type(self.value) is type:
+ raise self.value
+ else:
+ return self.value
+
+ self.assertEqual(operator.length_hint([], 2), 0)
+ self.assertEqual(operator.length_hint(iter([1, 2, 3])), 3)
+
+ self.assertEqual(operator.length_hint(X(2)), 2)
+ self.assertEqual(operator.length_hint(X(NotImplemented), 4), 4)
+ self.assertEqual(operator.length_hint(X(TypeError), 12), 12)
+ with self.assertRaises(TypeError):
+ operator.length_hint(X("abc"))
+ with self.assertRaises(ValueError):
+ operator.length_hint(X(-2))
+ with self.assertRaises(LookupError):
+ operator.length_hint(X(LookupError))
+
+ def test_dunder_is_original(self):
+ operator = self.module
+
+ names = [name for name in dir(operator) if not name.startswith('_')]
+ for name in names:
+ orig = getattr(operator, name)
+ dunder = getattr(operator, '__' + name.strip('_') + '__', None)
+ if dunder:
+ self.assertIs(dunder, orig)
+
+class PyOperatorTestCase(OperatorTestCase, unittest.TestCase):
+ module = py_operator
+
+@unittest.skipUnless(c_operator, 'requires _operator')
+class COperatorTestCase(OperatorTestCase, unittest.TestCase):
+ module = c_operator
if __name__ == "__main__":
- test_main(verbose=True)
+ unittest.main()
diff --git a/Lib/test/test_optparse.py b/Lib/test/test_optparse.py
index 78de278f76..94730119e9 100644
--- a/Lib/test/test_optparse.py
+++ b/Lib/test/test_optparse.py
@@ -730,7 +730,7 @@ class TestStandard(BaseTest):
def test_short_and_long_option_split(self):
self.assertParseOK(["-a", "xyz", "--foo", "bar"],
{'a': 'xyz', 'boo': None, 'foo': ["bar"]},
- []),
+ [])
def test_short_option_split_long_option_append(self):
self.assertParseOK(["--foo=bar", "-b", "123", "--foo", "baz"],
@@ -740,15 +740,15 @@ class TestStandard(BaseTest):
def test_short_option_split_one_positional_arg(self):
self.assertParseOK(["-a", "foo", "bar"],
{'a': "foo", 'boo': None, 'foo': None},
- ["bar"]),
+ ["bar"])
def test_short_option_consumes_separator(self):
self.assertParseOK(["-a", "--", "foo", "bar"],
{'a': "--", 'boo': None, 'foo': None},
- ["foo", "bar"]),
+ ["foo", "bar"])
self.assertParseOK(["-a", "--", "--foo", "bar"],
{'a': "--", 'boo': None, 'foo': ["bar"]},
- []),
+ [])
def test_short_option_joined_and_separator(self):
self.assertParseOK(["-ab", "--", "--foo", "bar"],
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 601c6b2e97..1ffa7dafeb 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -24,6 +24,9 @@ import itertools
import stat
import locale
import codecs
+import decimal
+import fractions
+import pickle
try:
import threading
except ImportError:
@@ -32,6 +35,10 @@ try:
import resource
except ImportError:
resource = None
+try:
+ import fcntl
+except ImportError:
+ fcntl = None
from test.script_helper import assert_python_ok
@@ -256,6 +263,13 @@ class StatAttributeTests(unittest.TestCase):
warnings.simplefilter("ignore", DeprecationWarning)
self.check_stat_attributes(fname)
+ def test_stat_result_pickle(self):
+ result = os.stat(self.fname)
+ p = pickle.dumps(result)
+ self.assertIn(b'\x03cos\nstat_result\n', p)
+ unpickled = pickle.loads(p)
+ self.assertEqual(result, unpickled)
+
@unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()')
def test_statvfs_attributes(self):
try:
@@ -263,7 +277,7 @@ class StatAttributeTests(unittest.TestCase):
except OSError as e:
# On AtheOS, glibc always returns ENOSYS
if e.errno == errno.ENOSYS:
- return
+ self.skipTest('os.statvfs() failed with ENOSYS')
# Make sure direct access works
self.assertEqual(result.f_bfree, result[3])
@@ -300,6 +314,21 @@ class StatAttributeTests(unittest.TestCase):
except TypeError:
pass
+ @unittest.skipUnless(hasattr(os, 'statvfs'),
+ "need os.statvfs()")
+ def test_statvfs_result_pickle(self):
+ try:
+ result = os.statvfs(self.fname)
+ except OSError as e:
+ # On AtheOS, glibc always returns ENOSYS
+ if e.errno == errno.ENOSYS:
+ self.skipTest('os.statvfs() failed with ENOSYS')
+
+ p = pickle.dumps(result)
+ self.assertIn(b'\x03cos\nstatvfs_result\n', p)
+ unpickled = pickle.loads(p)
+ self.assertEqual(result, unpickled)
+
def test_utime_dir(self):
delta = 1000000
st = os.stat(support.TESTFN)
@@ -478,9 +507,9 @@ class StatAttributeTests(unittest.TestCase):
# Verify that an open file can be stat'ed
try:
os.stat(r"c:\pagefile.sys")
- except WindowsError as e:
- if e.errno == 2: # file does not exist; cannot run test
- return
+ except FileNotFoundError:
+ pass # file does not exist; cannot run test
+ except OSError as e:
self.fail("Could not stat pagefile.sys")
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
@@ -876,6 +905,17 @@ class MakedirTests(unittest.TestCase):
os.makedirs(path, mode=mode, exist_ok=True)
os.umask(old_mask)
+ @unittest.skipUnless(hasattr(os, 'chown'), 'test needs os.chown')
+ def test_chown_uid_gid_arguments_must_be_index(self):
+ stat = os.stat(support.TESTFN)
+ uid = stat.st_uid
+ gid = stat.st_gid
+ for value in (-1.0, -1j, decimal.Decimal(-1), fractions.Fraction(-2, 2)):
+ self.assertRaises(TypeError, os.chown, support.TESTFN, value, gid)
+ self.assertRaises(TypeError, os.chown, support.TESTFN, uid, value)
+ self.assertIsNone(os.chown(support.TESTFN, uid, gid))
+ self.assertIsNone(os.chown(support.TESTFN, -1, -1))
+
def test_exist_ok_s_isgid_directory(self):
path = os.path.join(support.TESTFN, 'dir1')
S_ISGID = stat.S_ISGID
@@ -1133,27 +1173,27 @@ class ExecTests(unittest.TestCase):
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
class Win32ErrorTests(unittest.TestCase):
def test_rename(self):
- self.assertRaises(WindowsError, os.rename, support.TESTFN, support.TESTFN+".bak")
+ self.assertRaises(OSError, os.rename, support.TESTFN, support.TESTFN+".bak")
def test_remove(self):
- self.assertRaises(WindowsError, os.remove, support.TESTFN)
+ self.assertRaises(OSError, os.remove, support.TESTFN)
def test_chdir(self):
- self.assertRaises(WindowsError, os.chdir, support.TESTFN)
+ self.assertRaises(OSError, os.chdir, support.TESTFN)
def test_mkdir(self):
f = open(support.TESTFN, "w")
try:
- self.assertRaises(WindowsError, os.mkdir, support.TESTFN)
+ self.assertRaises(OSError, os.mkdir, support.TESTFN)
finally:
f.close()
os.unlink(support.TESTFN)
def test_utime(self):
- self.assertRaises(WindowsError, os.utime, support.TESTFN, None)
+ self.assertRaises(OSError, os.utime, support.TESTFN, None)
def test_chmod(self):
- self.assertRaises(WindowsError, os.chmod, support.TESTFN, 0)
+ self.assertRaises(OSError, os.chmod, support.TESTFN, 0)
class TestInvalidFD(unittest.TestCase):
singles = ["fchdir", "dup", "fdopen", "fdatasync", "fstat",
@@ -1278,41 +1318,57 @@ class PosixUidGidTests(unittest.TestCase):
@unittest.skipUnless(hasattr(os, 'setuid'), 'test needs os.setuid()')
def test_setuid(self):
if os.getuid() != 0:
- self.assertRaises(os.error, os.setuid, 0)
+ self.assertRaises(OSError, os.setuid, 0)
self.assertRaises(OverflowError, os.setuid, 1<<32)
@unittest.skipUnless(hasattr(os, 'setgid'), 'test needs os.setgid()')
def test_setgid(self):
if os.getuid() != 0 and not HAVE_WHEEL_GROUP:
- self.assertRaises(os.error, os.setgid, 0)
+ self.assertRaises(OSError, os.setgid, 0)
self.assertRaises(OverflowError, os.setgid, 1<<32)
@unittest.skipUnless(hasattr(os, 'seteuid'), 'test needs os.seteuid()')
def test_seteuid(self):
if os.getuid() != 0:
- self.assertRaises(os.error, os.seteuid, 0)
+ self.assertRaises(OSError, os.seteuid, 0)
self.assertRaises(OverflowError, os.seteuid, 1<<32)
@unittest.skipUnless(hasattr(os, 'setegid'), 'test needs os.setegid()')
def test_setegid(self):
if os.getuid() != 0 and not HAVE_WHEEL_GROUP:
- self.assertRaises(os.error, os.setegid, 0)
+ self.assertRaises(OSError, os.setegid, 0)
self.assertRaises(OverflowError, os.setegid, 1<<32)
@unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()')
def test_setreuid(self):
if os.getuid() != 0:
- self.assertRaises(os.error, os.setreuid, 0, 0)
+ self.assertRaises(OSError, os.setreuid, 0, 0)
self.assertRaises(OverflowError, os.setreuid, 1<<32, 0)
self.assertRaises(OverflowError, os.setreuid, 0, 1<<32)
+ @unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()')
+ def test_setreuid_neg1(self):
+ # Needs to accept -1. We run this in a subprocess to avoid
+ # altering the test runner's process state (issue8045).
+ subprocess.check_call([
+ sys.executable, '-c',
+ 'import os,sys;os.setreuid(-1,-1);sys.exit(0)'])
+
@unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()')
def test_setregid(self):
if os.getuid() != 0 and not HAVE_WHEEL_GROUP:
- self.assertRaises(os.error, os.setregid, 0, 0)
+ self.assertRaises(OSError, os.setregid, 0, 0)
self.assertRaises(OverflowError, os.setregid, 1<<32, 0)
self.assertRaises(OverflowError, os.setregid, 0, 1<<32)
+ @unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()')
+ def test_setregid_neg1(self):
+ # Needs to accept -1. We run this in a subprocess to avoid
+ # altering the test runner's process state (issue8045).
+ subprocess.check_call([
+ sys.executable, '-c',
+ 'import os,sys;os.setregid(-1,-1);sys.exit(0)'])
+
@unittest.skipIf(sys.platform == "win32", "Posix specific tests")
class Pep383Tests(unittest.TestCase):
def setUp(self):
@@ -1502,6 +1558,52 @@ class Win32KillTests(unittest.TestCase):
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
+class Win32ListdirTests(unittest.TestCase):
+ """Test listdir on Windows."""
+
+ def setUp(self):
+ self.created_paths = []
+ for i in range(2):
+ dir_name = 'SUB%d' % i
+ dir_path = os.path.join(support.TESTFN, dir_name)
+ file_name = 'FILE%d' % i
+ file_path = os.path.join(support.TESTFN, file_name)
+ os.makedirs(dir_path)
+ with open(file_path, 'w') as f:
+ f.write("I'm %s and proud of it. Blame test_os.\n" % file_path)
+ self.created_paths.extend([dir_name, file_name])
+ self.created_paths.sort()
+
+ def tearDown(self):
+ shutil.rmtree(support.TESTFN)
+
+ def test_listdir_no_extended_path(self):
+ """Test when the path is not an "extended" path."""
+ # unicode
+ self.assertEqual(
+ sorted(os.listdir(support.TESTFN)),
+ self.created_paths)
+ # bytes
+ self.assertEqual(
+ sorted(os.listdir(os.fsencode(support.TESTFN))),
+ [os.fsencode(path) for path in self.created_paths])
+
+ def test_listdir_extended_path(self):
+ """Test when the path starts with '\\\\?\\'."""
+ # See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
+ # unicode
+ path = '\\\\?\\' + os.path.abspath(support.TESTFN)
+ self.assertEqual(
+ sorted(os.listdir(path)),
+ self.created_paths)
+ # bytes
+ path = b'\\\\?\\' + os.fsencode(os.path.abspath(support.TESTFN))
+ self.assertEqual(
+ sorted(os.listdir(path)),
+ [os.fsencode(path) for path in self.created_paths])
+
+
+@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
@support.skip_unless_symlink
class Win32SymlinkTests(unittest.TestCase):
filelink = 'filelinktest'
@@ -2166,6 +2268,197 @@ class TermsizeTests(unittest.TestCase):
self.assertEqual(expected, actual)
+class OSErrorTests(unittest.TestCase):
+ def setUp(self):
+ class Str(str):
+ pass
+
+ self.bytes_filenames = []
+ self.unicode_filenames = []
+ if support.TESTFN_UNENCODABLE is not None:
+ decoded = support.TESTFN_UNENCODABLE
+ else:
+ decoded = support.TESTFN
+ self.unicode_filenames.append(decoded)
+ self.unicode_filenames.append(Str(decoded))
+ if support.TESTFN_UNDECODABLE is not None:
+ encoded = support.TESTFN_UNDECODABLE
+ else:
+ encoded = os.fsencode(support.TESTFN)
+ self.bytes_filenames.append(encoded)
+ self.bytes_filenames.append(memoryview(encoded))
+
+ self.filenames = self.bytes_filenames + self.unicode_filenames
+
+ def test_oserror_filename(self):
+ funcs = [
+ (self.filenames, os.chdir,),
+ (self.filenames, os.chmod, 0o777),
+ (self.filenames, os.lstat,),
+ (self.filenames, os.open, os.O_RDONLY),
+ (self.filenames, os.rmdir,),
+ (self.filenames, os.stat,),
+ (self.filenames, os.unlink,),
+ ]
+ if sys.platform == "win32":
+ funcs.extend((
+ (self.bytes_filenames, os.rename, b"dst"),
+ (self.bytes_filenames, os.replace, b"dst"),
+ (self.unicode_filenames, os.rename, "dst"),
+ (self.unicode_filenames, os.replace, "dst"),
+ # Issue #16414: Don't test undecodable names with listdir()
+ # because of a Windows bug.
+ #
+ # With the ANSI code page 932, os.listdir(b'\xe7') return an
+ # empty list (instead of failing), whereas os.listdir(b'\xff')
+ # raises a FileNotFoundError. It looks like a Windows bug:
+ # b'\xe7' directory does not exist, FindFirstFileA(b'\xe7')
+ # fails with ERROR_FILE_NOT_FOUND (2), instead of
+ # ERROR_PATH_NOT_FOUND (3).
+ (self.unicode_filenames, os.listdir,),
+ ))
+ else:
+ funcs.extend((
+ (self.filenames, os.listdir,),
+ (self.filenames, os.rename, "dst"),
+ (self.filenames, os.replace, "dst"),
+ ))
+ if hasattr(os, "chown"):
+ funcs.append((self.filenames, os.chown, 0, 0))
+ if hasattr(os, "lchown"):
+ funcs.append((self.filenames, os.lchown, 0, 0))
+ if hasattr(os, "truncate"):
+ funcs.append((self.filenames, os.truncate, 0))
+ if hasattr(os, "chflags"):
+ funcs.append((self.filenames, os.chflags, 0))
+ if hasattr(os, "lchflags"):
+ funcs.append((self.filenames, os.lchflags, 0))
+ if hasattr(os, "chroot"):
+ funcs.append((self.filenames, os.chroot,))
+ if hasattr(os, "link"):
+ if sys.platform == "win32":
+ funcs.append((self.bytes_filenames, os.link, b"dst"))
+ funcs.append((self.unicode_filenames, os.link, "dst"))
+ else:
+ funcs.append((self.filenames, os.link, "dst"))
+ if hasattr(os, "listxattr"):
+ funcs.extend((
+ (self.filenames, os.listxattr,),
+ (self.filenames, os.getxattr, "user.test"),
+ (self.filenames, os.setxattr, "user.test", b'user'),
+ (self.filenames, os.removexattr, "user.test"),
+ ))
+ if hasattr(os, "lchmod"):
+ funcs.append((self.filenames, os.lchmod, 0o777))
+ if hasattr(os, "readlink"):
+ if sys.platform == "win32":
+ funcs.append((self.unicode_filenames, os.readlink,))
+ else:
+ funcs.append((self.filenames, os.readlink,))
+
+ for filenames, func, *func_args in funcs:
+ for name in filenames:
+ try:
+ func(name, *func_args)
+ except OSError as err:
+ self.assertIs(err.filename, name)
+ else:
+ self.fail("No exception thrown by {}".format(func))
+
+class CPUCountTests(unittest.TestCase):
+ def test_cpu_count(self):
+ cpus = os.cpu_count()
+ if cpus is not None:
+ self.assertIsInstance(cpus, int)
+ self.assertGreater(cpus, 0)
+ else:
+ self.skipTest("Could not determine the number of CPUs")
+
+
+class FDInheritanceTests(unittest.TestCase):
+ def test_get_set_inheritable(self):
+ fd = os.open(__file__, os.O_RDONLY)
+ self.addCleanup(os.close, fd)
+ self.assertEqual(os.get_inheritable(fd), False)
+
+ os.set_inheritable(fd, True)
+ self.assertEqual(os.get_inheritable(fd), True)
+
+ @unittest.skipIf(fcntl is None, "need fcntl")
+ def test_get_inheritable_cloexec(self):
+ fd = os.open(__file__, os.O_RDONLY)
+ self.addCleanup(os.close, fd)
+ self.assertEqual(os.get_inheritable(fd), False)
+
+ # clear FD_CLOEXEC flag
+ flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+ flags &= ~fcntl.FD_CLOEXEC
+ fcntl.fcntl(fd, fcntl.F_SETFD, flags)
+
+ self.assertEqual(os.get_inheritable(fd), True)
+
+ @unittest.skipIf(fcntl is None, "need fcntl")
+ def test_set_inheritable_cloexec(self):
+ fd = os.open(__file__, os.O_RDONLY)
+ self.addCleanup(os.close, fd)
+ self.assertEqual(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC,
+ fcntl.FD_CLOEXEC)
+
+ os.set_inheritable(fd, True)
+ self.assertEqual(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC,
+ 0)
+
+ def test_open(self):
+ fd = os.open(__file__, os.O_RDONLY)
+ self.addCleanup(os.close, fd)
+ self.assertEqual(os.get_inheritable(fd), False)
+
+ @unittest.skipUnless(hasattr(os, 'pipe'), "need os.pipe()")
+ def test_pipe(self):
+ rfd, wfd = os.pipe()
+ self.addCleanup(os.close, rfd)
+ self.addCleanup(os.close, wfd)
+ self.assertEqual(os.get_inheritable(rfd), False)
+ self.assertEqual(os.get_inheritable(wfd), False)
+
+ def test_dup(self):
+ fd1 = os.open(__file__, os.O_RDONLY)
+ self.addCleanup(os.close, fd1)
+
+ fd2 = os.dup(fd1)
+ self.addCleanup(os.close, fd2)
+ self.assertEqual(os.get_inheritable(fd2), False)
+
+ @unittest.skipUnless(hasattr(os, 'dup2'), "need os.dup2()")
+ def test_dup2(self):
+ fd = os.open(__file__, os.O_RDONLY)
+ self.addCleanup(os.close, fd)
+
+ # inheritable by default
+ fd2 = os.open(__file__, os.O_RDONLY)
+ try:
+ os.dup2(fd, fd2)
+ self.assertEqual(os.get_inheritable(fd2), True)
+ finally:
+ os.close(fd2)
+
+ # force non-inheritable
+ fd3 = os.open(__file__, os.O_RDONLY)
+ try:
+ os.dup2(fd, fd3, inheritable=False)
+ self.assertEqual(os.get_inheritable(fd3), False)
+ finally:
+ os.close(fd3)
+
+ @unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()")
+ def test_openpty(self):
+ master_fd, slave_fd = os.openpty()
+ self.addCleanup(os.close, master_fd)
+ self.addCleanup(os.close, slave_fd)
+ self.assertEqual(os.get_inheritable(master_fd), False)
+ self.assertEqual(os.get_inheritable(slave_fd), False)
+
+
@support.reap_threads
def test_main():
support.run_unittest(
@@ -2183,6 +2476,7 @@ def test_main():
PosixUidGidTests,
Pep383Tests,
Win32KillTests,
+ Win32ListdirTests,
Win32SymlinkTests,
NonLocalSymlinkTests,
FSEncodingTests,
@@ -2195,7 +2489,10 @@ def test_main():
ExtendedAttributeTests,
Win32DeprecatedBytesAPI,
TermsizeTests,
+ OSErrorTests,
RemoveDirsTests,
+ CPUCountTests,
+ FDInheritanceTests,
)
if __name__ == "__main__":
diff --git a/Lib/test/test_ossaudiodev.py b/Lib/test/test_ossaudiodev.py
index 3908a0506e..c9e2a24767 100644
--- a/Lib/test/test_ossaudiodev.py
+++ b/Lib/test/test_ossaudiodev.py
@@ -44,7 +44,7 @@ class OSSAudioDevTests(unittest.TestCase):
def play_sound_file(self, data, rate, ssize, nchannels):
try:
dsp = ossaudiodev.open('w')
- except IOError as msg:
+ except OSError as msg:
if msg.args[0] in (errno.EACCES, errno.ENOENT,
errno.ENODEV, errno.EBUSY):
raise unittest.SkipTest(msg)
@@ -190,7 +190,7 @@ class OSSAudioDevTests(unittest.TestCase):
def test_main():
try:
dsp = ossaudiodev.open('w')
- except (ossaudiodev.error, IOError) as msg:
+ except (ossaudiodev.error, OSError) as msg:
if msg.args[0] in (errno.EACCES, errno.ENOENT,
errno.ENODEV, errno.EBUSY):
raise unittest.SkipTest(msg)
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
new file mode 100755
index 0000000000..8f0855e618
--- /dev/null
+++ b/Lib/test/test_pathlib.py
@@ -0,0 +1,1676 @@
+import collections
+import io
+import os
+import errno
+import pathlib
+import pickle
+import shutil
+import socket
+import stat
+import sys
+import tempfile
+import unittest
+from contextlib import contextmanager
+
+from test import support
+TESTFN = support.TESTFN
+
+try:
+ import grp, pwd
+except ImportError:
+ grp = pwd = None
+
+
+class _BaseFlavourTest(object):
+
+ def _check_parse_parts(self, arg, expected):
+ f = self.flavour.parse_parts
+ sep = self.flavour.sep
+ altsep = self.flavour.altsep
+ actual = f([x.replace('/', sep) for x in arg])
+ self.assertEqual(actual, expected)
+ if altsep:
+ actual = f([x.replace('/', altsep) for x in arg])
+ self.assertEqual(actual, expected)
+
+ def test_parse_parts_common(self):
+ check = self._check_parse_parts
+ sep = self.flavour.sep
+ # Unanchored parts
+ check([], ('', '', []))
+ check(['a'], ('', '', ['a']))
+ check(['a/'], ('', '', ['a']))
+ check(['a', 'b'], ('', '', ['a', 'b']))
+ # Expansion
+ check(['a/b'], ('', '', ['a', 'b']))
+ check(['a/b/'], ('', '', ['a', 'b']))
+ check(['a', 'b/c', 'd'], ('', '', ['a', 'b', 'c', 'd']))
+ # Collapsing and stripping excess slashes
+ check(['a', 'b//c', 'd'], ('', '', ['a', 'b', 'c', 'd']))
+ check(['a', 'b/c/', 'd'], ('', '', ['a', 'b', 'c', 'd']))
+ # Eliminating standalone dots
+ check(['.'], ('', '', []))
+ check(['.', '.', 'b'], ('', '', ['b']))
+ check(['a', '.', 'b'], ('', '', ['a', 'b']))
+ check(['a', '.', '.'], ('', '', ['a']))
+ # The first part is anchored
+ check(['/a/b'], ('', sep, [sep, 'a', 'b']))
+ check(['/a', 'b'], ('', sep, [sep, 'a', 'b']))
+ check(['/a/', 'b'], ('', sep, [sep, 'a', 'b']))
+ # Ignoring parts before an anchored part
+ check(['a', '/b', 'c'], ('', sep, [sep, 'b', 'c']))
+ check(['a', '/b', '/c'], ('', sep, [sep, 'c']))
+
+
+class PosixFlavourTest(_BaseFlavourTest, unittest.TestCase):
+ flavour = pathlib._posix_flavour
+
+ def test_parse_parts(self):
+ check = self._check_parse_parts
+ # Collapsing of excess leading slashes, except for the double-slash
+ # special case.
+ check(['//a', 'b'], ('', '//', ['//', 'a', 'b']))
+ check(['///a', 'b'], ('', '/', ['/', 'a', 'b']))
+ check(['////a', 'b'], ('', '/', ['/', 'a', 'b']))
+ # Paths which look like NT paths aren't treated specially
+ check(['c:a'], ('', '', ['c:a']))
+ check(['c:\\a'], ('', '', ['c:\\a']))
+ check(['\\a'], ('', '', ['\\a']))
+
+ def test_splitroot(self):
+ f = self.flavour.splitroot
+ self.assertEqual(f(''), ('', '', ''))
+ self.assertEqual(f('a'), ('', '', 'a'))
+ self.assertEqual(f('a/b'), ('', '', 'a/b'))
+ self.assertEqual(f('a/b/'), ('', '', 'a/b/'))
+ self.assertEqual(f('/a'), ('', '/', 'a'))
+ self.assertEqual(f('/a/b'), ('', '/', 'a/b'))
+ self.assertEqual(f('/a/b/'), ('', '/', 'a/b/'))
+ # The root is collapsed when there are redundant slashes
+ # except when there are exactly two leading slashes, which
+ # is a special case in POSIX.
+ self.assertEqual(f('//a'), ('', '//', 'a'))
+ self.assertEqual(f('///a'), ('', '/', 'a'))
+ self.assertEqual(f('///a/b'), ('', '/', 'a/b'))
+ # Paths which look like NT paths aren't treated specially
+ self.assertEqual(f('c:/a/b'), ('', '', 'c:/a/b'))
+ self.assertEqual(f('\\/a/b'), ('', '', '\\/a/b'))
+ self.assertEqual(f('\\a\\b'), ('', '', '\\a\\b'))
+
+
+class NTFlavourTest(_BaseFlavourTest, unittest.TestCase):
+ flavour = pathlib._windows_flavour
+
+ def test_parse_parts(self):
+ check = self._check_parse_parts
+ # First part is anchored
+ check(['c:'], ('c:', '', ['c:']))
+ check(['c:\\'], ('c:', '\\', ['c:\\']))
+ check(['\\'], ('', '\\', ['\\']))
+ check(['c:a'], ('c:', '', ['c:', 'a']))
+ check(['c:\\a'], ('c:', '\\', ['c:\\', 'a']))
+ check(['\\a'], ('', '\\', ['\\', 'a']))
+ # UNC paths
+ check(['\\\\a\\b'], ('\\\\a\\b', '\\', ['\\\\a\\b\\']))
+ check(['\\\\a\\b\\'], ('\\\\a\\b', '\\', ['\\\\a\\b\\']))
+ check(['\\\\a\\b\\c'], ('\\\\a\\b', '\\', ['\\\\a\\b\\', 'c']))
+ # Second part is anchored, so that the first part is ignored
+ check(['a', 'Z:b', 'c'], ('Z:', '', ['Z:', 'b', 'c']))
+ check(['a', 'Z:\\b', 'c'], ('Z:', '\\', ['Z:\\', 'b', 'c']))
+ check(['a', '\\b', 'c'], ('', '\\', ['\\', 'b', 'c']))
+ # UNC paths
+ check(['a', '\\\\b\\c', 'd'], ('\\\\b\\c', '\\', ['\\\\b\\c\\', 'd']))
+ # Collapsing and stripping excess slashes
+ check(['a', 'Z:\\\\b\\\\c\\', 'd\\'], ('Z:', '\\', ['Z:\\', 'b', 'c', 'd']))
+ # UNC paths
+ check(['a', '\\\\b\\c\\\\', 'd'], ('\\\\b\\c', '\\', ['\\\\b\\c\\', 'd']))
+ # Extended paths
+ check(['\\\\?\\c:\\'], ('\\\\?\\c:', '\\', ['\\\\?\\c:\\']))
+ check(['\\\\?\\c:\\a'], ('\\\\?\\c:', '\\', ['\\\\?\\c:\\', 'a']))
+ # Extended UNC paths (format is "\\?\UNC\server\share")
+ check(['\\\\?\\UNC\\b\\c'], ('\\\\?\\UNC\\b\\c', '\\', ['\\\\?\\UNC\\b\\c\\']))
+ check(['\\\\?\\UNC\\b\\c\\d'], ('\\\\?\\UNC\\b\\c', '\\', ['\\\\?\\UNC\\b\\c\\', 'd']))
+
+ def test_splitroot(self):
+ f = self.flavour.splitroot
+ self.assertEqual(f(''), ('', '', ''))
+ self.assertEqual(f('a'), ('', '', 'a'))
+ self.assertEqual(f('a\\b'), ('', '', 'a\\b'))
+ self.assertEqual(f('\\a'), ('', '\\', 'a'))
+ self.assertEqual(f('\\a\\b'), ('', '\\', 'a\\b'))
+ self.assertEqual(f('c:a\\b'), ('c:', '', 'a\\b'))
+ self.assertEqual(f('c:\\a\\b'), ('c:', '\\', 'a\\b'))
+ # Redundant slashes in the root are collapsed
+ self.assertEqual(f('\\\\a'), ('', '\\', 'a'))
+ self.assertEqual(f('\\\\\\a/b'), ('', '\\', 'a/b'))
+ self.assertEqual(f('c:\\\\a'), ('c:', '\\', 'a'))
+ self.assertEqual(f('c:\\\\\\a/b'), ('c:', '\\', 'a/b'))
+ # Valid UNC paths
+ self.assertEqual(f('\\\\a\\b'), ('\\\\a\\b', '\\', ''))
+ self.assertEqual(f('\\\\a\\b\\'), ('\\\\a\\b', '\\', ''))
+ self.assertEqual(f('\\\\a\\b\\c\\d'), ('\\\\a\\b', '\\', 'c\\d'))
+ # These are non-UNC paths (according to ntpath.py and test_ntpath)
+ # However, command.com says such paths are invalid, so it's
+ # difficult to know what the right semantics are
+ self.assertEqual(f('\\\\\\a\\b'), ('', '\\', 'a\\b'))
+ self.assertEqual(f('\\\\a'), ('', '\\', 'a'))
+
+
+#
+# Tests for the pure classes
+#
+
+class _BasePurePathTest(object):
+
+ # keys are canonical paths, values are list of tuples of arguments
+ # supposed to produce equal paths
+ equivalences = {
+ 'a/b': [
+ ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'),
+ ('a/b/',), ('a//b',), ('a//b//',),
+ # empty components get removed
+ ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''),
+ ],
+ '/b/c/d': [
+ ('a', '/b/c', 'd'), ('a', '///b//c', 'd/'),
+ ('/a', '/b/c', 'd'),
+ # empty components get removed
+ ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'),
+ ],
+ }
+
+ def setUp(self):
+ p = self.cls('a')
+ self.flavour = p._flavour
+ self.sep = self.flavour.sep
+ self.altsep = self.flavour.altsep
+
+ def test_constructor_common(self):
+ P = self.cls
+ p = P('a')
+ self.assertIsInstance(p, P)
+ P('a', 'b', 'c')
+ P('/a', 'b', 'c')
+ P('a/b/c')
+ P('/a/b/c')
+ self.assertEqual(P(P('a')), P('a'))
+ self.assertEqual(P(P('a'), 'b'), P('a/b'))
+ self.assertEqual(P(P('a'), P('b')), P('a/b'))
+
+ def test_join_common(self):
+ P = self.cls
+ p = P('a/b')
+ pp = p.joinpath('c')
+ self.assertEqual(pp, P('a/b/c'))
+ self.assertIs(type(pp), type(p))
+ pp = p.joinpath('c', 'd')
+ self.assertEqual(pp, P('a/b/c/d'))
+ pp = p.joinpath(P('c'))
+ self.assertEqual(pp, P('a/b/c'))
+ pp = p.joinpath('/c')
+ self.assertEqual(pp, P('/c'))
+
+ def test_div_common(self):
+ # Basically the same as joinpath()
+ P = self.cls
+ p = P('a/b')
+ pp = p / 'c'
+ self.assertEqual(pp, P('a/b/c'))
+ self.assertIs(type(pp), type(p))
+ pp = p / 'c/d'
+ self.assertEqual(pp, P('a/b/c/d'))
+ pp = p / 'c' / 'd'
+ self.assertEqual(pp, P('a/b/c/d'))
+ pp = 'c' / p / 'd'
+ self.assertEqual(pp, P('c/a/b/d'))
+ pp = p / P('c')
+ self.assertEqual(pp, P('a/b/c'))
+ pp = p/ '/c'
+ self.assertEqual(pp, P('/c'))
+
+ def _check_str(self, expected, args):
+ p = self.cls(*args)
+ self.assertEqual(str(p), expected.replace('/', self.sep))
+
+ def test_str_common(self):
+ # Canonicalized paths roundtrip
+ for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
+ self._check_str(pathstr, (pathstr,))
+ # Special case for the empty path
+ self._check_str('.', ('',))
+ # Other tests for str() are in test_equivalences()
+
+ def test_as_posix_common(self):
+ P = self.cls
+ for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
+ self.assertEqual(P(pathstr).as_posix(), pathstr)
+ # Other tests for as_posix() are in test_equivalences()
+
+ def test_as_bytes_common(self):
+ sep = os.fsencode(self.sep)
+ P = self.cls
+ self.assertEqual(bytes(P('a/b')), b'a' + sep + b'b')
+
+ def test_as_uri_common(self):
+ P = self.cls
+ with self.assertRaises(ValueError):
+ P('a').as_uri()
+ with self.assertRaises(ValueError):
+ P().as_uri()
+
+ def test_repr_common(self):
+ for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
+ p = self.cls(pathstr)
+ clsname = p.__class__.__name__
+ r = repr(p)
+ # The repr() is in the form ClassName("forward-slashes path")
+ self.assertTrue(r.startswith(clsname + '('), r)
+ self.assertTrue(r.endswith(')'), r)
+ inner = r[len(clsname) + 1 : -1]
+ self.assertEqual(eval(inner), p.as_posix())
+ # The repr() roundtrips
+ q = eval(r, pathlib.__dict__)
+ self.assertIs(q.__class__, p.__class__)
+ self.assertEqual(q, p)
+ self.assertEqual(repr(q), r)
+
+ def test_eq_common(self):
+ P = self.cls
+ self.assertEqual(P('a/b'), P('a/b'))
+ self.assertEqual(P('a/b'), P('a', 'b'))
+ self.assertNotEqual(P('a/b'), P('a'))
+ self.assertNotEqual(P('a/b'), P('/a/b'))
+ self.assertNotEqual(P('a/b'), P())
+ self.assertNotEqual(P('/a/b'), P('/'))
+ self.assertNotEqual(P(), P('/'))
+ self.assertNotEqual(P(), "")
+ self.assertNotEqual(P(), {})
+ self.assertNotEqual(P(), int)
+
+ def test_match_common(self):
+ P = self.cls
+ self.assertRaises(ValueError, P('a').match, '')
+ self.assertRaises(ValueError, P('a').match, '.')
+ # Simple relative pattern
+ self.assertTrue(P('b.py').match('b.py'))
+ self.assertTrue(P('a/b.py').match('b.py'))
+ self.assertTrue(P('/a/b.py').match('b.py'))
+ self.assertFalse(P('a.py').match('b.py'))
+ self.assertFalse(P('b/py').match('b.py'))
+ self.assertFalse(P('/a.py').match('b.py'))
+ self.assertFalse(P('b.py/c').match('b.py'))
+ # Wilcard relative pattern
+ self.assertTrue(P('b.py').match('*.py'))
+ self.assertTrue(P('a/b.py').match('*.py'))
+ self.assertTrue(P('/a/b.py').match('*.py'))
+ self.assertFalse(P('b.pyc').match('*.py'))
+ self.assertFalse(P('b./py').match('*.py'))
+ self.assertFalse(P('b.py/c').match('*.py'))
+ # Multi-part relative pattern
+ self.assertTrue(P('ab/c.py').match('a*/*.py'))
+ self.assertTrue(P('/d/ab/c.py').match('a*/*.py'))
+ self.assertFalse(P('a.py').match('a*/*.py'))
+ self.assertFalse(P('/dab/c.py').match('a*/*.py'))
+ self.assertFalse(P('ab/c.py/d').match('a*/*.py'))
+ # Absolute pattern
+ self.assertTrue(P('/b.py').match('/*.py'))
+ self.assertFalse(P('b.py').match('/*.py'))
+ self.assertFalse(P('a/b.py').match('/*.py'))
+ self.assertFalse(P('/a/b.py').match('/*.py'))
+ # Multi-part absolute pattern
+ self.assertTrue(P('/a/b.py').match('/a/*.py'))
+ self.assertFalse(P('/ab.py').match('/a/*.py'))
+ self.assertFalse(P('/a/b/c.py').match('/a/*.py'))
+
+ def test_ordering_common(self):
+ # Ordering is tuple-alike
+ def assertLess(a, b):
+ self.assertLess(a, b)
+ self.assertGreater(b, a)
+ P = self.cls
+ a = P('a')
+ b = P('a/b')
+ c = P('abc')
+ d = P('b')
+ assertLess(a, b)
+ assertLess(a, c)
+ assertLess(a, d)
+ assertLess(b, c)
+ assertLess(c, d)
+ P = self.cls
+ a = P('/a')
+ b = P('/a/b')
+ c = P('/abc')
+ d = P('/b')
+ assertLess(a, b)
+ assertLess(a, c)
+ assertLess(a, d)
+ assertLess(b, c)
+ assertLess(c, d)
+ with self.assertRaises(TypeError):
+ P() < {}
+
+ def test_parts_common(self):
+ # `parts` returns a tuple
+ sep = self.sep
+ P = self.cls
+ p = P('a/b')
+ parts = p.parts
+ self.assertEqual(parts, ('a', 'b'))
+ # The object gets reused
+ self.assertIs(parts, p.parts)
+ # When the path is absolute, the anchor is a separate part
+ p = P('/a/b')
+ parts = p.parts
+ self.assertEqual(parts, (sep, 'a', 'b'))
+
+ def test_equivalences(self):
+ for k, tuples in self.equivalences.items():
+ canon = k.replace('/', self.sep)
+ posix = k.replace(self.sep, '/')
+ if canon != posix:
+ tuples = tuples + [
+ tuple(part.replace('/', self.sep) for part in t)
+ for t in tuples
+ ]
+ tuples.append((posix, ))
+ pcanon = self.cls(canon)
+ for t in tuples:
+ p = self.cls(*t)
+ self.assertEqual(p, pcanon, "failed with args {}".format(t))
+ self.assertEqual(hash(p), hash(pcanon))
+ self.assertEqual(str(p), canon)
+ self.assertEqual(p.as_posix(), posix)
+
+ def test_parent_common(self):
+ # Relative
+ P = self.cls
+ p = P('a/b/c')
+ self.assertEqual(p.parent, P('a/b'))
+ self.assertEqual(p.parent.parent, P('a'))
+ self.assertEqual(p.parent.parent.parent, P())
+ self.assertEqual(p.parent.parent.parent.parent, P())
+ # Anchored
+ p = P('/a/b/c')
+ self.assertEqual(p.parent, P('/a/b'))
+ self.assertEqual(p.parent.parent, P('/a'))
+ self.assertEqual(p.parent.parent.parent, P('/'))
+ self.assertEqual(p.parent.parent.parent.parent, P('/'))
+
+ def test_parents_common(self):
+ # Relative
+ P = self.cls
+ p = P('a/b/c')
+ par = p.parents
+ self.assertEqual(len(par), 3)
+ self.assertEqual(par[0], P('a/b'))
+ self.assertEqual(par[1], P('a'))
+ self.assertEqual(par[2], P('.'))
+ self.assertEqual(list(par), [P('a/b'), P('a'), P('.')])
+ with self.assertRaises(IndexError):
+ par[-1]
+ with self.assertRaises(IndexError):
+ par[3]
+ with self.assertRaises(TypeError):
+ par[0] = p
+ # Anchored
+ p = P('/a/b/c')
+ par = p.parents
+ self.assertEqual(len(par), 3)
+ self.assertEqual(par[0], P('/a/b'))
+ self.assertEqual(par[1], P('/a'))
+ self.assertEqual(par[2], P('/'))
+ self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')])
+ with self.assertRaises(IndexError):
+ par[3]
+
+ def test_drive_common(self):
+ P = self.cls
+ self.assertEqual(P('a/b').drive, '')
+ self.assertEqual(P('/a/b').drive, '')
+ self.assertEqual(P('').drive, '')
+
+ def test_root_common(self):
+ P = self.cls
+ sep = self.sep
+ self.assertEqual(P('').root, '')
+ self.assertEqual(P('a/b').root, '')
+ self.assertEqual(P('/').root, sep)
+ self.assertEqual(P('/a/b').root, sep)
+
+ def test_anchor_common(self):
+ P = self.cls
+ sep = self.sep
+ self.assertEqual(P('').anchor, '')
+ self.assertEqual(P('a/b').anchor, '')
+ self.assertEqual(P('/').anchor, sep)
+ self.assertEqual(P('/a/b').anchor, sep)
+
+ def test_name_common(self):
+ P = self.cls
+ self.assertEqual(P('').name, '')
+ self.assertEqual(P('.').name, '')
+ self.assertEqual(P('/').name, '')
+ self.assertEqual(P('a/b').name, 'b')
+ self.assertEqual(P('/a/b').name, 'b')
+ self.assertEqual(P('/a/b/.').name, 'b')
+ self.assertEqual(P('a/b.py').name, 'b.py')
+ self.assertEqual(P('/a/b.py').name, 'b.py')
+
+ def test_suffix_common(self):
+ P = self.cls
+ self.assertEqual(P('').suffix, '')
+ self.assertEqual(P('.').suffix, '')
+ self.assertEqual(P('..').suffix, '')
+ self.assertEqual(P('/').suffix, '')
+ self.assertEqual(P('a/b').suffix, '')
+ self.assertEqual(P('/a/b').suffix, '')
+ self.assertEqual(P('/a/b/.').suffix, '')
+ self.assertEqual(P('a/b.py').suffix, '.py')
+ self.assertEqual(P('/a/b.py').suffix, '.py')
+ self.assertEqual(P('a/.hgrc').suffix, '')
+ self.assertEqual(P('/a/.hgrc').suffix, '')
+ self.assertEqual(P('a/.hg.rc').suffix, '.rc')
+ self.assertEqual(P('/a/.hg.rc').suffix, '.rc')
+ self.assertEqual(P('a/b.tar.gz').suffix, '.gz')
+ self.assertEqual(P('/a/b.tar.gz').suffix, '.gz')
+ self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '')
+ self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '')
+
+ def test_suffixes_common(self):
+ P = self.cls
+ self.assertEqual(P('').suffixes, [])
+ self.assertEqual(P('.').suffixes, [])
+ self.assertEqual(P('/').suffixes, [])
+ self.assertEqual(P('a/b').suffixes, [])
+ self.assertEqual(P('/a/b').suffixes, [])
+ self.assertEqual(P('/a/b/.').suffixes, [])
+ self.assertEqual(P('a/b.py').suffixes, ['.py'])
+ self.assertEqual(P('/a/b.py').suffixes, ['.py'])
+ self.assertEqual(P('a/.hgrc').suffixes, [])
+ self.assertEqual(P('/a/.hgrc').suffixes, [])
+ self.assertEqual(P('a/.hg.rc').suffixes, ['.rc'])
+ self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc'])
+ self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz'])
+ self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz'])
+ self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, [])
+ self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, [])
+
+ def test_stem_common(self):
+ P = self.cls
+ self.assertEqual(P('').stem, '')
+ self.assertEqual(P('.').stem, '')
+ self.assertEqual(P('..').stem, '..')
+ self.assertEqual(P('/').stem, '')
+ self.assertEqual(P('a/b').stem, 'b')
+ self.assertEqual(P('a/b.py').stem, 'b')
+ self.assertEqual(P('a/.hgrc').stem, '.hgrc')
+ self.assertEqual(P('a/.hg.rc').stem, '.hg')
+ self.assertEqual(P('a/b.tar.gz').stem, 'b.tar')
+ self.assertEqual(P('a/Some name. Ending with a dot.').stem,
+ 'Some name. Ending with a dot.')
+
+ def test_with_name_common(self):
+ P = self.cls
+ self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml'))
+ self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml'))
+ self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml'))
+ self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml'))
+ self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml'))
+ self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml'))
+ self.assertRaises(ValueError, P('').with_name, 'd.xml')
+ self.assertRaises(ValueError, P('.').with_name, 'd.xml')
+ self.assertRaises(ValueError, P('/').with_name, 'd.xml')
+
+ def test_with_suffix_common(self):
+ P = self.cls
+ self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz'))
+ self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz'))
+ self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz'))
+ self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz'))
+ self.assertRaises(ValueError, P('').with_suffix, '.gz')
+ self.assertRaises(ValueError, P('.').with_suffix, '.gz')
+ self.assertRaises(ValueError, P('/').with_suffix, '.gz')
+
+ def test_relative_to_common(self):
+ P = self.cls
+ p = P('a/b')
+ self.assertRaises(TypeError, p.relative_to)
+ self.assertEqual(p.relative_to(P()), P('a/b'))
+ self.assertEqual(p.relative_to(P('a')), P('b'))
+ self.assertEqual(p.relative_to(P('a/b')), P())
+ # With several args
+ self.assertEqual(p.relative_to('a', 'b'), P())
+ # Unrelated paths
+ self.assertRaises(ValueError, p.relative_to, P('c'))
+ self.assertRaises(ValueError, p.relative_to, P('a/b/c'))
+ self.assertRaises(ValueError, p.relative_to, P('a/c'))
+ self.assertRaises(ValueError, p.relative_to, P('/a'))
+ p = P('/a/b')
+ self.assertEqual(p.relative_to(P('/')), P('a/b'))
+ self.assertEqual(p.relative_to(P('/a')), P('b'))
+ self.assertEqual(p.relative_to(P('/a/b')), P())
+ # Unrelated paths
+ self.assertRaises(ValueError, p.relative_to, P('/c'))
+ self.assertRaises(ValueError, p.relative_to, P('/a/b/c'))
+ self.assertRaises(ValueError, p.relative_to, P('/a/c'))
+ self.assertRaises(ValueError, p.relative_to, P())
+ self.assertRaises(ValueError, p.relative_to, P('a'))
+
+ def test_pickling_common(self):
+ P = self.cls
+ p = P('/a/b')
+ for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
+ dumped = pickle.dumps(p, proto)
+ pp = pickle.loads(dumped)
+ self.assertIs(pp.__class__, p.__class__)
+ self.assertEqual(pp, p)
+ self.assertEqual(hash(pp), hash(p))
+ self.assertEqual(str(pp), str(p))
+
+
+class PurePosixPathTest(_BasePurePathTest, unittest.TestCase):
+ cls = pathlib.PurePosixPath
+
+ def test_root(self):
+ P = self.cls
+ self.assertEqual(P('/a/b').root, '/')
+ self.assertEqual(P('///a/b').root, '/')
+ # POSIX special case for two leading slashes
+ self.assertEqual(P('//a/b').root, '//')
+
+ def test_eq(self):
+ P = self.cls
+ self.assertNotEqual(P('a/b'), P('A/b'))
+ self.assertEqual(P('/a'), P('///a'))
+ self.assertNotEqual(P('/a'), P('//a'))
+
+ def test_as_uri(self):
+ P = self.cls
+ self.assertEqual(P('/').as_uri(), 'file:///')
+ self.assertEqual(P('/a/b.c').as_uri(), 'file:///a/b.c')
+ self.assertEqual(P('/a/b%#c').as_uri(), 'file:///a/b%25%23c')
+
+ def test_as_uri_non_ascii(self):
+ from urllib.parse import quote_from_bytes
+ P = self.cls
+ try:
+ os.fsencode('\xe9')
+ except UnicodeEncodeError:
+ self.skipTest("\\xe9 cannot be encoded to the filesystem encoding")
+ self.assertEqual(P('/a/b\xe9').as_uri(),
+ 'file:///a/b' + quote_from_bytes(os.fsencode('\xe9')))
+
+ def test_match(self):
+ P = self.cls
+ self.assertFalse(P('A.py').match('a.PY'))
+
+ def test_is_absolute(self):
+ P = self.cls
+ self.assertFalse(P().is_absolute())
+ self.assertFalse(P('a').is_absolute())
+ self.assertFalse(P('a/b/').is_absolute())
+ self.assertTrue(P('/').is_absolute())
+ self.assertTrue(P('/a').is_absolute())
+ self.assertTrue(P('/a/b/').is_absolute())
+ self.assertTrue(P('//a').is_absolute())
+ self.assertTrue(P('//a/b').is_absolute())
+
+ def test_is_reserved(self):
+ P = self.cls
+ self.assertIs(False, P('').is_reserved())
+ self.assertIs(False, P('/').is_reserved())
+ self.assertIs(False, P('/foo/bar').is_reserved())
+ self.assertIs(False, P('/dev/con/PRN/NUL').is_reserved())
+
+ def test_join(self):
+ P = self.cls
+ p = P('//a')
+ pp = p.joinpath('b')
+ self.assertEqual(pp, P('//a/b'))
+ pp = P('/a').joinpath('//c')
+ self.assertEqual(pp, P('//c'))
+ pp = P('//a').joinpath('/c')
+ self.assertEqual(pp, P('/c'))
+
+ def test_div(self):
+ # Basically the same as joinpath()
+ P = self.cls
+ p = P('//a')
+ pp = p / 'b'
+ self.assertEqual(pp, P('//a/b'))
+ pp = P('/a') / '//c'
+ self.assertEqual(pp, P('//c'))
+ pp = P('//a') / '/c'
+ self.assertEqual(pp, P('/c'))
+
+
+class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
+ cls = pathlib.PureWindowsPath
+
+ equivalences = _BasePurePathTest.equivalences.copy()
+ equivalences.update({
+ 'c:a': [ ('c:', 'a'), ('c:', 'a/'), ('/', 'c:', 'a') ],
+ 'c:/a': [
+ ('c:/', 'a'), ('c:', '/', 'a'), ('c:', '/a'),
+ ('/z', 'c:/', 'a'), ('//x/y', 'c:/', 'a'),
+ ],
+ '//a/b/': [ ('//a/b',) ],
+ '//a/b/c': [
+ ('//a/b', 'c'), ('//a/b/', 'c'),
+ ],
+ })
+
+ def test_str(self):
+ p = self.cls('a/b/c')
+ self.assertEqual(str(p), 'a\\b\\c')
+ p = self.cls('c:/a/b/c')
+ self.assertEqual(str(p), 'c:\\a\\b\\c')
+ p = self.cls('//a/b')
+ self.assertEqual(str(p), '\\\\a\\b\\')
+ p = self.cls('//a/b/c')
+ self.assertEqual(str(p), '\\\\a\\b\\c')
+ p = self.cls('//a/b/c/d')
+ self.assertEqual(str(p), '\\\\a\\b\\c\\d')
+
+ def test_eq(self):
+ P = self.cls
+ self.assertEqual(P('c:a/b'), P('c:a/b'))
+ self.assertEqual(P('c:a/b'), P('c:', 'a', 'b'))
+ self.assertNotEqual(P('c:a/b'), P('d:a/b'))
+ self.assertNotEqual(P('c:a/b'), P('c:/a/b'))
+ self.assertNotEqual(P('/a/b'), P('c:/a/b'))
+ # Case-insensitivity
+ self.assertEqual(P('a/B'), P('A/b'))
+ self.assertEqual(P('C:a/B'), P('c:A/b'))
+ self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b'))
+
+ def test_as_uri(self):
+ from urllib.parse import quote_from_bytes
+ P = self.cls
+ with self.assertRaises(ValueError):
+ P('/a/b').as_uri()
+ with self.assertRaises(ValueError):
+ P('c:a/b').as_uri()
+ self.assertEqual(P('c:/').as_uri(), 'file:///c:/')
+ self.assertEqual(P('c:/a/b.c').as_uri(), 'file:///c:/a/b.c')
+ self.assertEqual(P('c:/a/b%#c').as_uri(), 'file:///c:/a/b%25%23c')
+ self.assertEqual(P('c:/a/b\xe9').as_uri(), 'file:///c:/a/b%C3%A9')
+ self.assertEqual(P('//some/share/').as_uri(), 'file://some/share/')
+ self.assertEqual(P('//some/share/a/b.c').as_uri(),
+ 'file://some/share/a/b.c')
+ self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(),
+ 'file://some/share/a/b%25%23c%C3%A9')
+
+ def test_match_common(self):
+ P = self.cls
+ # Absolute patterns
+ self.assertTrue(P('c:/b.py').match('/*.py'))
+ self.assertTrue(P('c:/b.py').match('c:*.py'))
+ self.assertTrue(P('c:/b.py').match('c:/*.py'))
+ self.assertFalse(P('d:/b.py').match('c:/*.py')) # wrong drive
+ self.assertFalse(P('b.py').match('/*.py'))
+ self.assertFalse(P('b.py').match('c:*.py'))
+ self.assertFalse(P('b.py').match('c:/*.py'))
+ self.assertFalse(P('c:b.py').match('/*.py'))
+ self.assertFalse(P('c:b.py').match('c:/*.py'))
+ self.assertFalse(P('/b.py').match('c:*.py'))
+ self.assertFalse(P('/b.py').match('c:/*.py'))
+ # UNC patterns
+ self.assertTrue(P('//some/share/a.py').match('/*.py'))
+ self.assertTrue(P('//some/share/a.py').match('//some/share/*.py'))
+ self.assertFalse(P('//other/share/a.py').match('//some/share/*.py'))
+ self.assertFalse(P('//some/share/a/b.py').match('//some/share/*.py'))
+ # Case-insensitivity
+ self.assertTrue(P('B.py').match('b.PY'))
+ self.assertTrue(P('c:/a/B.Py').match('C:/A/*.pY'))
+ self.assertTrue(P('//Some/Share/B.Py').match('//somE/sharE/*.pY'))
+
+ def test_ordering_common(self):
+ # Case-insensitivity
+ def assertOrderedEqual(a, b):
+ self.assertLessEqual(a, b)
+ self.assertGreaterEqual(b, a)
+ P = self.cls
+ p = P('c:A/b')
+ q = P('C:a/B')
+ assertOrderedEqual(p, q)
+ self.assertFalse(p < q)
+ self.assertFalse(p > q)
+ p = P('//some/Share/A/b')
+ q = P('//Some/SHARE/a/B')
+ assertOrderedEqual(p, q)
+ self.assertFalse(p < q)
+ self.assertFalse(p > q)
+
+ def test_parts(self):
+ P = self.cls
+ p = P('c:a/b')
+ parts = p.parts
+ self.assertEqual(parts, ('c:', 'a', 'b'))
+ p = P('c:/a/b')
+ parts = p.parts
+ self.assertEqual(parts, ('c:\\', 'a', 'b'))
+ p = P('//a/b/c/d')
+ parts = p.parts
+ self.assertEqual(parts, ('\\\\a\\b\\', 'c', 'd'))
+
+ def test_parent(self):
+ # Anchored
+ P = self.cls
+ p = P('z:a/b/c')
+ self.assertEqual(p.parent, P('z:a/b'))
+ self.assertEqual(p.parent.parent, P('z:a'))
+ self.assertEqual(p.parent.parent.parent, P('z:'))
+ self.assertEqual(p.parent.parent.parent.parent, P('z:'))
+ p = P('z:/a/b/c')
+ self.assertEqual(p.parent, P('z:/a/b'))
+ self.assertEqual(p.parent.parent, P('z:/a'))
+ self.assertEqual(p.parent.parent.parent, P('z:/'))
+ self.assertEqual(p.parent.parent.parent.parent, P('z:/'))
+ p = P('//a/b/c/d')
+ self.assertEqual(p.parent, P('//a/b/c'))
+ self.assertEqual(p.parent.parent, P('//a/b'))
+ self.assertEqual(p.parent.parent.parent, P('//a/b'))
+
+ def test_parents(self):
+ # Anchored
+ P = self.cls
+ p = P('z:a/b/')
+ par = p.parents
+ self.assertEqual(len(par), 2)
+ self.assertEqual(par[0], P('z:a'))
+ self.assertEqual(par[1], P('z:'))
+ self.assertEqual(list(par), [P('z:a'), P('z:')])
+ with self.assertRaises(IndexError):
+ par[2]
+ p = P('z:/a/b/')
+ par = p.parents
+ self.assertEqual(len(par), 2)
+ self.assertEqual(par[0], P('z:/a'))
+ self.assertEqual(par[1], P('z:/'))
+ self.assertEqual(list(par), [P('z:/a'), P('z:/')])
+ with self.assertRaises(IndexError):
+ par[2]
+ p = P('//a/b/c/d')
+ par = p.parents
+ self.assertEqual(len(par), 2)
+ self.assertEqual(par[0], P('//a/b/c'))
+ self.assertEqual(par[1], P('//a/b'))
+ self.assertEqual(list(par), [P('//a/b/c'), P('//a/b')])
+ with self.assertRaises(IndexError):
+ par[2]
+
+ def test_drive(self):
+ P = self.cls
+ self.assertEqual(P('c:').drive, 'c:')
+ self.assertEqual(P('c:a/b').drive, 'c:')
+ self.assertEqual(P('c:/').drive, 'c:')
+ self.assertEqual(P('c:/a/b/').drive, 'c:')
+ self.assertEqual(P('//a/b').drive, '\\\\a\\b')
+ self.assertEqual(P('//a/b/').drive, '\\\\a\\b')
+ self.assertEqual(P('//a/b/c/d').drive, '\\\\a\\b')
+
+ def test_root(self):
+ P = self.cls
+ self.assertEqual(P('c:').root, '')
+ self.assertEqual(P('c:a/b').root, '')
+ self.assertEqual(P('c:/').root, '\\')
+ self.assertEqual(P('c:/a/b/').root, '\\')
+ self.assertEqual(P('//a/b').root, '\\')
+ self.assertEqual(P('//a/b/').root, '\\')
+ self.assertEqual(P('//a/b/c/d').root, '\\')
+
+ def test_anchor(self):
+ P = self.cls
+ self.assertEqual(P('c:').anchor, 'c:')
+ self.assertEqual(P('c:a/b').anchor, 'c:')
+ self.assertEqual(P('c:/').anchor, 'c:\\')
+ self.assertEqual(P('c:/a/b/').anchor, 'c:\\')
+ self.assertEqual(P('//a/b').anchor, '\\\\a\\b\\')
+ self.assertEqual(P('//a/b/').anchor, '\\\\a\\b\\')
+ self.assertEqual(P('//a/b/c/d').anchor, '\\\\a\\b\\')
+
+ def test_name(self):
+ P = self.cls
+ self.assertEqual(P('c:').name, '')
+ self.assertEqual(P('c:/').name, '')
+ self.assertEqual(P('c:a/b').name, 'b')
+ self.assertEqual(P('c:/a/b').name, 'b')
+ self.assertEqual(P('c:a/b.py').name, 'b.py')
+ self.assertEqual(P('c:/a/b.py').name, 'b.py')
+ self.assertEqual(P('//My.py/Share.php').name, '')
+ self.assertEqual(P('//My.py/Share.php/a/b').name, 'b')
+
+ def test_suffix(self):
+ P = self.cls
+ self.assertEqual(P('c:').suffix, '')
+ self.assertEqual(P('c:/').suffix, '')
+ self.assertEqual(P('c:a/b').suffix, '')
+ self.assertEqual(P('c:/a/b').suffix, '')
+ self.assertEqual(P('c:a/b.py').suffix, '.py')
+ self.assertEqual(P('c:/a/b.py').suffix, '.py')
+ self.assertEqual(P('c:a/.hgrc').suffix, '')
+ self.assertEqual(P('c:/a/.hgrc').suffix, '')
+ self.assertEqual(P('c:a/.hg.rc').suffix, '.rc')
+ self.assertEqual(P('c:/a/.hg.rc').suffix, '.rc')
+ self.assertEqual(P('c:a/b.tar.gz').suffix, '.gz')
+ self.assertEqual(P('c:/a/b.tar.gz').suffix, '.gz')
+ self.assertEqual(P('c:a/Some name. Ending with a dot.').suffix, '')
+ self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffix, '')
+ self.assertEqual(P('//My.py/Share.php').suffix, '')
+ self.assertEqual(P('//My.py/Share.php/a/b').suffix, '')
+
+ def test_suffixes(self):
+ P = self.cls
+ self.assertEqual(P('c:').suffixes, [])
+ self.assertEqual(P('c:/').suffixes, [])
+ self.assertEqual(P('c:a/b').suffixes, [])
+ self.assertEqual(P('c:/a/b').suffixes, [])
+ self.assertEqual(P('c:a/b.py').suffixes, ['.py'])
+ self.assertEqual(P('c:/a/b.py').suffixes, ['.py'])
+ self.assertEqual(P('c:a/.hgrc').suffixes, [])
+ self.assertEqual(P('c:/a/.hgrc').suffixes, [])
+ self.assertEqual(P('c:a/.hg.rc').suffixes, ['.rc'])
+ self.assertEqual(P('c:/a/.hg.rc').suffixes, ['.rc'])
+ self.assertEqual(P('c:a/b.tar.gz').suffixes, ['.tar', '.gz'])
+ self.assertEqual(P('c:/a/b.tar.gz').suffixes, ['.tar', '.gz'])
+ self.assertEqual(P('//My.py/Share.php').suffixes, [])
+ self.assertEqual(P('//My.py/Share.php/a/b').suffixes, [])
+ self.assertEqual(P('c:a/Some name. Ending with a dot.').suffixes, [])
+ self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffixes, [])
+
+ def test_stem(self):
+ P = self.cls
+ self.assertEqual(P('c:').stem, '')
+ self.assertEqual(P('c:.').stem, '')
+ self.assertEqual(P('c:..').stem, '..')
+ self.assertEqual(P('c:/').stem, '')
+ self.assertEqual(P('c:a/b').stem, 'b')
+ self.assertEqual(P('c:a/b.py').stem, 'b')
+ self.assertEqual(P('c:a/.hgrc').stem, '.hgrc')
+ self.assertEqual(P('c:a/.hg.rc').stem, '.hg')
+ self.assertEqual(P('c:a/b.tar.gz').stem, 'b.tar')
+ self.assertEqual(P('c:a/Some name. Ending with a dot.').stem,
+ 'Some name. Ending with a dot.')
+
+ def test_with_name(self):
+ P = self.cls
+ self.assertEqual(P('c:a/b').with_name('d.xml'), P('c:a/d.xml'))
+ self.assertEqual(P('c:/a/b').with_name('d.xml'), P('c:/a/d.xml'))
+ self.assertEqual(P('c:a/Dot ending.').with_name('d.xml'), P('c:a/d.xml'))
+ self.assertEqual(P('c:/a/Dot ending.').with_name('d.xml'), P('c:/a/d.xml'))
+ self.assertRaises(ValueError, P('c:').with_name, 'd.xml')
+ self.assertRaises(ValueError, P('c:/').with_name, 'd.xml')
+ self.assertRaises(ValueError, P('//My/Share').with_name, 'd.xml')
+
+ def test_with_suffix(self):
+ P = self.cls
+ self.assertEqual(P('c:a/b').with_suffix('.gz'), P('c:a/b.gz'))
+ self.assertEqual(P('c:/a/b').with_suffix('.gz'), P('c:/a/b.gz'))
+ self.assertEqual(P('c:a/b.py').with_suffix('.gz'), P('c:a/b.gz'))
+ self.assertEqual(P('c:/a/b.py').with_suffix('.gz'), P('c:/a/b.gz'))
+ self.assertRaises(ValueError, P('').with_suffix, '.gz')
+ self.assertRaises(ValueError, P('.').with_suffix, '.gz')
+ self.assertRaises(ValueError, P('/').with_suffix, '.gz')
+ self.assertRaises(ValueError, P('//My/Share').with_suffix, '.gz')
+
+ def test_relative_to(self):
+ P = self.cls
+ p = P('c:a/b')
+ self.assertEqual(p.relative_to(P('c:')), P('a/b'))
+ self.assertEqual(p.relative_to(P('c:a')), P('b'))
+ self.assertEqual(p.relative_to(P('c:a/b')), P())
+ # Unrelated paths
+ self.assertRaises(ValueError, p.relative_to, P())
+ self.assertRaises(ValueError, p.relative_to, P('d:'))
+ self.assertRaises(ValueError, p.relative_to, P('a'))
+ self.assertRaises(ValueError, p.relative_to, P('/a'))
+ self.assertRaises(ValueError, p.relative_to, P('c:a/b/c'))
+ self.assertRaises(ValueError, p.relative_to, P('c:a/c'))
+ self.assertRaises(ValueError, p.relative_to, P('c:/a'))
+ p = P('c:/a/b')
+ self.assertEqual(p.relative_to(P('c:')), P('/a/b'))
+ self.assertEqual(p.relative_to(P('c:/')), P('a/b'))
+ self.assertEqual(p.relative_to(P('c:/a')), P('b'))
+ self.assertEqual(p.relative_to(P('c:/a/b')), P())
+ # Unrelated paths
+ self.assertRaises(ValueError, p.relative_to, P('c:/c'))
+ self.assertRaises(ValueError, p.relative_to, P('c:/a/b/c'))
+ self.assertRaises(ValueError, p.relative_to, P('c:/a/c'))
+ self.assertRaises(ValueError, p.relative_to, P('c:a'))
+ self.assertRaises(ValueError, p.relative_to, P('d:'))
+ self.assertRaises(ValueError, p.relative_to, P('d:/'))
+ self.assertRaises(ValueError, p.relative_to, P('/a'))
+ self.assertRaises(ValueError, p.relative_to, P('//c/a'))
+ # UNC paths
+ p = P('//a/b/c/d')
+ self.assertEqual(p.relative_to(P('//a/b')), P('c/d'))
+ self.assertEqual(p.relative_to(P('//a/b/c')), P('d'))
+ self.assertEqual(p.relative_to(P('//a/b/c/d')), P())
+ # Unrelated paths
+ self.assertRaises(ValueError, p.relative_to, P('/a/b/c'))
+ self.assertRaises(ValueError, p.relative_to, P('c:/a/b/c'))
+ self.assertRaises(ValueError, p.relative_to, P('//z/b/c'))
+ self.assertRaises(ValueError, p.relative_to, P('//a/z/c'))
+
+ def test_is_absolute(self):
+ P = self.cls
+ # Under NT, only paths with both a drive and a root are absolute
+ self.assertFalse(P().is_absolute())
+ self.assertFalse(P('a').is_absolute())
+ self.assertFalse(P('a/b/').is_absolute())
+ self.assertFalse(P('/').is_absolute())
+ self.assertFalse(P('/a').is_absolute())
+ self.assertFalse(P('/a/b/').is_absolute())
+ self.assertFalse(P('c:').is_absolute())
+ self.assertFalse(P('c:a').is_absolute())
+ self.assertFalse(P('c:a/b/').is_absolute())
+ self.assertTrue(P('c:/').is_absolute())
+ self.assertTrue(P('c:/a').is_absolute())
+ self.assertTrue(P('c:/a/b/').is_absolute())
+ # UNC paths are absolute by definition
+ self.assertTrue(P('//a/b').is_absolute())
+ self.assertTrue(P('//a/b/').is_absolute())
+ self.assertTrue(P('//a/b/c').is_absolute())
+ self.assertTrue(P('//a/b/c/d').is_absolute())
+
+ def test_is_reserved(self):
+ P = self.cls
+ self.assertIs(False, P('').is_reserved())
+ self.assertIs(False, P('/').is_reserved())
+ self.assertIs(False, P('/foo/bar').is_reserved())
+ self.assertIs(True, P('con').is_reserved())
+ self.assertIs(True, P('NUL').is_reserved())
+ self.assertIs(True, P('NUL.txt').is_reserved())
+ self.assertIs(True, P('com1').is_reserved())
+ self.assertIs(True, P('com9.bar').is_reserved())
+ self.assertIs(False, P('bar.com9').is_reserved())
+ self.assertIs(True, P('lpt1').is_reserved())
+ self.assertIs(True, P('lpt9.bar').is_reserved())
+ self.assertIs(False, P('bar.lpt9').is_reserved())
+ # Only the last component matters
+ self.assertIs(False, P('c:/NUL/con/baz').is_reserved())
+ # UNC paths are never reserved
+ self.assertIs(False, P('//my/share/nul/con/aux').is_reserved())
+
+
+class PurePathTest(_BasePurePathTest, unittest.TestCase):
+ cls = pathlib.PurePath
+
+ def test_concrete_class(self):
+ p = self.cls('a')
+ self.assertIs(type(p),
+ pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath)
+
+ def test_different_flavours_unequal(self):
+ p = pathlib.PurePosixPath('a')
+ q = pathlib.PureWindowsPath('a')
+ self.assertNotEqual(p, q)
+
+ def test_different_flavours_unordered(self):
+ p = pathlib.PurePosixPath('a')
+ q = pathlib.PureWindowsPath('a')
+ with self.assertRaises(TypeError):
+ p < q
+ with self.assertRaises(TypeError):
+ p <= q
+ with self.assertRaises(TypeError):
+ p > q
+ with self.assertRaises(TypeError):
+ p >= q
+
+
+#
+# Tests for the concrete classes
+#
+
+# Make sure any symbolic links in the base test path are resolved
+BASE = os.path.realpath(TESTFN)
+join = lambda *x: os.path.join(BASE, *x)
+rel_join = lambda *x: os.path.join(TESTFN, *x)
+
+def symlink_skip_reason():
+ if not pathlib.supports_symlinks:
+ return "no system support for symlinks"
+ try:
+ os.symlink(__file__, BASE)
+ except OSError as e:
+ return str(e)
+ else:
+ support.unlink(BASE)
+ return None
+
+symlink_skip_reason = symlink_skip_reason()
+
+only_nt = unittest.skipIf(os.name != 'nt',
+ 'test requires a Windows-compatible system')
+only_posix = unittest.skipIf(os.name == 'nt',
+ 'test requires a POSIX-compatible system')
+with_symlinks = unittest.skipIf(symlink_skip_reason, symlink_skip_reason)
+
+
+@only_posix
+class PosixPathAsPureTest(PurePosixPathTest):
+ cls = pathlib.PosixPath
+
+@only_nt
+class WindowsPathAsPureTest(PureWindowsPathTest):
+ cls = pathlib.WindowsPath
+
+
+class _BasePathTest(object):
+ """Tests for the FS-accessing functionalities of the Path classes."""
+
+ # (BASE)
+ # |
+ # |-- dirA/
+ # |-- linkC -> "../dirB"
+ # |-- dirB/
+ # | |-- fileB
+ # |-- linkD -> "../dirB"
+ # |-- dirC/
+ # | |-- fileC
+ # | |-- fileD
+ # |-- fileA
+ # |-- linkA -> "fileA"
+ # |-- linkB -> "dirB"
+ #
+
+ def setUp(self):
+ os.mkdir(BASE)
+ self.addCleanup(shutil.rmtree, BASE)
+ os.mkdir(join('dirA'))
+ os.mkdir(join('dirB'))
+ os.mkdir(join('dirC'))
+ os.mkdir(join('dirC', 'dirD'))
+ with open(join('fileA'), 'wb') as f:
+ f.write(b"this is file A\n")
+ with open(join('dirB', 'fileB'), 'wb') as f:
+ f.write(b"this is file B\n")
+ with open(join('dirC', 'fileC'), 'wb') as f:
+ f.write(b"this is file C\n")
+ with open(join('dirC', 'dirD', 'fileD'), 'wb') as f:
+ f.write(b"this is file D\n")
+ if not symlink_skip_reason:
+ if os.name == 'nt':
+ # Workaround for http://bugs.python.org/issue13772
+ def dirlink(src, dest):
+ os.symlink(src, dest, target_is_directory=True)
+ else:
+ def dirlink(src, dest):
+ os.symlink(src, dest)
+ # Relative symlinks
+ os.symlink('fileA', join('linkA'))
+ os.symlink('non-existing', join('brokenLink'))
+ dirlink('dirB', join('linkB'))
+ dirlink(os.path.join('..', 'dirB'), join('dirA', 'linkC'))
+ # This one goes upwards but doesn't create a loop
+ dirlink(os.path.join('..', 'dirB'), join('dirB', 'linkD'))
+
+ def assertSame(self, path_a, path_b):
+ self.assertTrue(os.path.samefile(str(path_a), str(path_b)),
+ "%r and %r don't point to the same file" %
+ (path_a, path_b))
+
+ def assertFileNotFound(self, func, *args, **kwargs):
+ with self.assertRaises(FileNotFoundError) as cm:
+ func(*args, **kwargs)
+ self.assertEqual(cm.exception.errno, errno.ENOENT)
+
+ def _test_cwd(self, p):
+ q = self.cls(os.getcwd())
+ self.assertEqual(p, q)
+ self.assertEqual(str(p), str(q))
+ self.assertIs(type(p), type(q))
+ self.assertTrue(p.is_absolute())
+
+ def test_cwd(self):
+ p = self.cls.cwd()
+ self._test_cwd(p)
+
+ def test_empty_path(self):
+ # The empty path points to '.'
+ p = self.cls('')
+ self.assertEqual(p.stat(), os.stat('.'))
+
+ def test_exists(self):
+ P = self.cls
+ p = P(BASE)
+ self.assertIs(True, p.exists())
+ self.assertIs(True, (p / 'dirA').exists())
+ self.assertIs(True, (p / 'fileA').exists())
+ if not symlink_skip_reason:
+ self.assertIs(True, (p / 'linkA').exists())
+ self.assertIs(True, (p / 'linkB').exists())
+ self.assertIs(False, (p / 'foo').exists())
+ self.assertIs(False, P('/xyzzy').exists())
+
+ def test_open_common(self):
+ p = self.cls(BASE)
+ with (p / 'fileA').open('r') as f:
+ self.assertIsInstance(f, io.TextIOBase)
+ self.assertEqual(f.read(), "this is file A\n")
+ with (p / 'fileA').open('rb') as f:
+ self.assertIsInstance(f, io.BufferedIOBase)
+ self.assertEqual(f.read().strip(), b"this is file A")
+ with (p / 'fileA').open('rb', buffering=0) as f:
+ self.assertIsInstance(f, io.RawIOBase)
+ self.assertEqual(f.read().strip(), b"this is file A")
+
+ def test_iterdir(self):
+ P = self.cls
+ p = P(BASE)
+ it = p.iterdir()
+ paths = set(it)
+ expected = ['dirA', 'dirB', 'dirC', 'fileA']
+ if not symlink_skip_reason:
+ expected += ['linkA', 'linkB', 'brokenLink']
+ self.assertEqual(paths, { P(BASE, q) for q in expected })
+
+ @with_symlinks
+ def test_iterdir_symlink(self):
+ # __iter__ on a symlink to a directory
+ P = self.cls
+ p = P(BASE, 'linkB')
+ paths = set(p.iterdir())
+ expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] }
+ self.assertEqual(paths, expected)
+
+ def test_iterdir_nodir(self):
+ # __iter__ on something that is not a directory
+ p = self.cls(BASE, 'fileA')
+ with self.assertRaises(OSError) as cm:
+ next(p.iterdir())
+ # ENOENT or EINVAL under Windows, ENOTDIR otherwise
+ # (see issue #12802)
+ self.assertIn(cm.exception.errno, (errno.ENOTDIR,
+ errno.ENOENT, errno.EINVAL))
+
+ def test_glob_common(self):
+ def _check(glob, expected):
+ self.assertEqual(set(glob), { P(BASE, q) for q in expected })
+ P = self.cls
+ p = P(BASE)
+ it = p.glob("fileA")
+ self.assertIsInstance(it, collections.Iterator)
+ _check(it, ["fileA"])
+ _check(p.glob("fileB"), [])
+ _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"])
+ if symlink_skip_reason:
+ _check(p.glob("*A"), ['dirA', 'fileA'])
+ else:
+ _check(p.glob("*A"), ['dirA', 'fileA', 'linkA'])
+ if symlink_skip_reason:
+ _check(p.glob("*B/*"), ['dirB/fileB'])
+ else:
+ _check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD',
+ 'linkB/fileB', 'linkB/linkD'])
+ if symlink_skip_reason:
+ _check(p.glob("*/fileB"), ['dirB/fileB'])
+ else:
+ _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB'])
+
+ def test_rglob_common(self):
+ def _check(glob, expected):
+ self.assertEqual(set(glob), { P(BASE, q) for q in expected })
+ P = self.cls
+ p = P(BASE)
+ it = p.rglob("fileA")
+ self.assertIsInstance(it, collections.Iterator)
+ # XXX cannot test because of symlink loops in the test setup
+ #_check(it, ["fileA"])
+ #_check(p.rglob("fileB"), ["dirB/fileB"])
+ #_check(p.rglob("*/fileA"), [""])
+ #_check(p.rglob("*/fileB"), ["dirB/fileB"])
+ #_check(p.rglob("file*"), ["fileA", "dirB/fileB"])
+ # No symlink loops here
+ p = P(BASE, "dirC")
+ _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
+ _check(p.rglob("*/*"), ["dirC/dirD/fileD"])
+
+ def test_glob_dotdot(self):
+ # ".." is not special in globs
+ P = self.cls
+ p = P(BASE)
+ self.assertEqual(set(p.glob("..")), { P(BASE, "..") })
+ self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") })
+ self.assertEqual(set(p.glob("../xyzzy")), set())
+
+ def _check_resolve_relative(self, p, expected):
+ q = p.resolve()
+ self.assertEqual(q, expected)
+
+ def _check_resolve_absolute(self, p, expected):
+ q = p.resolve()
+ self.assertEqual(q, expected)
+
+ @with_symlinks
+ def test_resolve_common(self):
+ P = self.cls
+ p = P(BASE, 'foo')
+ with self.assertRaises(OSError) as cm:
+ p.resolve()
+ self.assertEqual(cm.exception.errno, errno.ENOENT)
+ # These are all relative symlinks
+ p = P(BASE, 'dirB', 'fileB')
+ self._check_resolve_relative(p, p)
+ p = P(BASE, 'linkA')
+ self._check_resolve_relative(p, P(BASE, 'fileA'))
+ p = P(BASE, 'dirA', 'linkC', 'fileB')
+ self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB'))
+ p = P(BASE, 'dirB', 'linkD', 'fileB')
+ self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB'))
+ # Now create absolute symlinks
+ d = tempfile.mkdtemp(suffix='-dirD')
+ self.addCleanup(shutil.rmtree, d)
+ os.symlink(os.path.join(d), join('dirA', 'linkX'))
+ os.symlink(join('dirB'), os.path.join(d, 'linkY'))
+ p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB')
+ self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB'))
+
+ def test_with(self):
+ p = self.cls(BASE)
+ it = p.iterdir()
+ it2 = p.iterdir()
+ next(it2)
+ with p:
+ pass
+ # I/O operation on closed path
+ self.assertRaises(ValueError, next, it)
+ self.assertRaises(ValueError, next, it2)
+ self.assertRaises(ValueError, p.open)
+ self.assertRaises(ValueError, p.resolve)
+ self.assertRaises(ValueError, p.absolute)
+ self.assertRaises(ValueError, p.__enter__)
+
+ def test_chmod(self):
+ p = self.cls(BASE) / 'fileA'
+ mode = p.stat().st_mode
+ # Clear writable bit
+ new_mode = mode & ~0o222
+ p.chmod(new_mode)
+ self.assertEqual(p.stat().st_mode, new_mode)
+ # Set writable bit
+ new_mode = mode | 0o222
+ p.chmod(new_mode)
+ self.assertEqual(p.stat().st_mode, new_mode)
+
+ # XXX also need a test for lchmod
+
+ def test_stat(self):
+ p = self.cls(BASE) / 'fileA'
+ st = p.stat()
+ self.assertEqual(p.stat(), st)
+ # Change file mode by flipping write bit
+ p.chmod(st.st_mode ^ 0o222)
+ self.addCleanup(p.chmod, st.st_mode)
+ self.assertNotEqual(p.stat(), st)
+
+ @with_symlinks
+ def test_lstat(self):
+ p = self.cls(BASE)/ 'linkA'
+ st = p.stat()
+ self.assertNotEqual(st, p.lstat())
+
+ def test_lstat_nosymlink(self):
+ p = self.cls(BASE) / 'fileA'
+ st = p.stat()
+ self.assertEqual(st, p.lstat())
+
+ @unittest.skipUnless(pwd, "the pwd module is needed for this test")
+ def test_owner(self):
+ p = self.cls(BASE) / 'fileA'
+ uid = p.stat().st_uid
+ try:
+ name = pwd.getpwuid(uid).pw_name
+ except KeyError:
+ self.skipTest(
+ "user %d doesn't have an entry in the system database" % uid)
+ self.assertEqual(name, p.owner())
+
+ @unittest.skipUnless(grp, "the grp module is needed for this test")
+ def test_group(self):
+ p = self.cls(BASE) / 'fileA'
+ gid = p.stat().st_gid
+ try:
+ name = grp.getgrgid(gid).gr_name
+ except KeyError:
+ self.skipTest(
+ "group %d doesn't have an entry in the system database" % gid)
+ self.assertEqual(name, p.group())
+
+ def test_unlink(self):
+ p = self.cls(BASE) / 'fileA'
+ p.unlink()
+ self.assertFileNotFound(p.stat)
+ self.assertFileNotFound(p.unlink)
+
+ def test_rmdir(self):
+ p = self.cls(BASE) / 'dirA'
+ for q in p.iterdir():
+ q.unlink()
+ p.rmdir()
+ self.assertFileNotFound(p.stat)
+ self.assertFileNotFound(p.unlink)
+
+ def test_rename(self):
+ P = self.cls(BASE)
+ p = P / 'fileA'
+ size = p.stat().st_size
+ # Renaming to another path
+ q = P / 'dirA' / 'fileAA'
+ p.rename(q)
+ self.assertEqual(q.stat().st_size, size)
+ self.assertFileNotFound(p.stat)
+ # Renaming to a str of a relative path
+ r = rel_join('fileAAA')
+ q.rename(r)
+ self.assertEqual(os.stat(r).st_size, size)
+ self.assertFileNotFound(q.stat)
+
+ def test_replace(self):
+ P = self.cls(BASE)
+ p = P / 'fileA'
+ size = p.stat().st_size
+ # Replacing a non-existing path
+ q = P / 'dirA' / 'fileAA'
+ p.replace(q)
+ self.assertEqual(q.stat().st_size, size)
+ self.assertFileNotFound(p.stat)
+ # Replacing another (existing) path
+ r = rel_join('dirB', 'fileB')
+ q.replace(r)
+ self.assertEqual(os.stat(r).st_size, size)
+ self.assertFileNotFound(q.stat)
+
+ def test_touch_common(self):
+ P = self.cls(BASE)
+ p = P / 'newfileA'
+ self.assertFalse(p.exists())
+ p.touch()
+ self.assertTrue(p.exists())
+ st = p.stat()
+ old_mtime = st.st_mtime
+ old_mtime_ns = st.st_mtime_ns
+ # Rewind the mtime sufficiently far in the past to work around
+ # filesystem-specific timestamp granularity.
+ os.utime(str(p), (old_mtime - 10, old_mtime - 10))
+ # The file mtime should be refreshed by calling touch() again
+ p.touch()
+ st = p.stat()
+ self.assertGreaterEqual(st.st_mtime_ns, old_mtime_ns)
+ self.assertGreaterEqual(st.st_mtime, old_mtime)
+ # Now with exist_ok=False
+ p = P / 'newfileB'
+ self.assertFalse(p.exists())
+ p.touch(mode=0o700, exist_ok=False)
+ self.assertTrue(p.exists())
+ self.assertRaises(OSError, p.touch, exist_ok=False)
+
+ def test_touch_nochange(self):
+ P = self.cls(BASE)
+ p = P / 'fileA'
+ p.touch()
+ with p.open('rb') as f:
+ self.assertEqual(f.read().strip(), b"this is file A")
+
+ def test_mkdir(self):
+ P = self.cls(BASE)
+ p = P / 'newdirA'
+ self.assertFalse(p.exists())
+ p.mkdir()
+ self.assertTrue(p.exists())
+ self.assertTrue(p.is_dir())
+ with self.assertRaises(OSError) as cm:
+ p.mkdir()
+ self.assertEqual(cm.exception.errno, errno.EEXIST)
+ # XXX test `mode` arg
+
+ def test_mkdir_parents(self):
+ # Creating a chain of directories
+ p = self.cls(BASE, 'newdirB', 'newdirC')
+ self.assertFalse(p.exists())
+ with self.assertRaises(OSError) as cm:
+ p.mkdir()
+ self.assertEqual(cm.exception.errno, errno.ENOENT)
+ p.mkdir(parents=True)
+ self.assertTrue(p.exists())
+ self.assertTrue(p.is_dir())
+ with self.assertRaises(OSError) as cm:
+ p.mkdir(parents=True)
+ self.assertEqual(cm.exception.errno, errno.EEXIST)
+ # XXX test `mode` arg
+
+ @with_symlinks
+ def test_symlink_to(self):
+ P = self.cls(BASE)
+ target = P / 'fileA'
+ # Symlinking a path target
+ link = P / 'dirA' / 'linkAA'
+ link.symlink_to(target)
+ self.assertEqual(link.stat(), target.stat())
+ self.assertNotEqual(link.lstat(), target.stat())
+ # Symlinking a str target
+ link = P / 'dirA' / 'linkAAA'
+ link.symlink_to(str(target))
+ self.assertEqual(link.stat(), target.stat())
+ self.assertNotEqual(link.lstat(), target.stat())
+ self.assertFalse(link.is_dir())
+ # Symlinking to a directory
+ target = P / 'dirB'
+ link = P / 'dirA' / 'linkAAAA'
+ link.symlink_to(target, target_is_directory=True)
+ self.assertEqual(link.stat(), target.stat())
+ self.assertNotEqual(link.lstat(), target.stat())
+ self.assertTrue(link.is_dir())
+ self.assertTrue(list(link.iterdir()))
+
+ def test_is_dir(self):
+ P = self.cls(BASE)
+ self.assertTrue((P / 'dirA').is_dir())
+ self.assertFalse((P / 'fileA').is_dir())
+ self.assertFalse((P / 'non-existing').is_dir())
+ if not symlink_skip_reason:
+ self.assertFalse((P / 'linkA').is_dir())
+ self.assertTrue((P / 'linkB').is_dir())
+ self.assertFalse((P/ 'brokenLink').is_dir())
+
+ def test_is_file(self):
+ P = self.cls(BASE)
+ self.assertTrue((P / 'fileA').is_file())
+ self.assertFalse((P / 'dirA').is_file())
+ self.assertFalse((P / 'non-existing').is_file())
+ if not symlink_skip_reason:
+ self.assertTrue((P / 'linkA').is_file())
+ self.assertFalse((P / 'linkB').is_file())
+ self.assertFalse((P/ 'brokenLink').is_file())
+
+ def test_is_symlink(self):
+ P = self.cls(BASE)
+ self.assertFalse((P / 'fileA').is_symlink())
+ self.assertFalse((P / 'dirA').is_symlink())
+ self.assertFalse((P / 'non-existing').is_symlink())
+ if not symlink_skip_reason:
+ self.assertTrue((P / 'linkA').is_symlink())
+ self.assertTrue((P / 'linkB').is_symlink())
+ self.assertTrue((P/ 'brokenLink').is_symlink())
+
+ def test_is_fifo_false(self):
+ P = self.cls(BASE)
+ self.assertFalse((P / 'fileA').is_fifo())
+ self.assertFalse((P / 'dirA').is_fifo())
+ self.assertFalse((P / 'non-existing').is_fifo())
+
+ @unittest.skipUnless(hasattr(os, "mkfifo"), "os.mkfifo() required")
+ def test_is_fifo_true(self):
+ P = self.cls(BASE, 'myfifo')
+ os.mkfifo(str(P))
+ self.assertTrue(P.is_fifo())
+ self.assertFalse(P.is_socket())
+ self.assertFalse(P.is_file())
+
+ def test_is_socket_false(self):
+ P = self.cls(BASE)
+ self.assertFalse((P / 'fileA').is_socket())
+ self.assertFalse((P / 'dirA').is_socket())
+ self.assertFalse((P / 'non-existing').is_socket())
+
+ @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required")
+ def test_is_socket_true(self):
+ P = self.cls(BASE, 'mysock')
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.addCleanup(sock.close)
+ try:
+ sock.bind(str(P))
+ except OSError as e:
+ if "AF_UNIX path too long" in str(e):
+ self.skipTest("cannot bind Unix socket: " + str(e))
+ self.assertTrue(P.is_socket())
+ self.assertFalse(P.is_fifo())
+ self.assertFalse(P.is_file())
+
+ def test_is_block_device_false(self):
+ P = self.cls(BASE)
+ self.assertFalse((P / 'fileA').is_block_device())
+ self.assertFalse((P / 'dirA').is_block_device())
+ self.assertFalse((P / 'non-existing').is_block_device())
+
+ def test_is_char_device_false(self):
+ P = self.cls(BASE)
+ self.assertFalse((P / 'fileA').is_char_device())
+ self.assertFalse((P / 'dirA').is_char_device())
+ self.assertFalse((P / 'non-existing').is_char_device())
+
+ def test_is_char_device_true(self):
+ # Under Unix, /dev/null should generally be a char device
+ P = self.cls('/dev/null')
+ if not P.exists():
+ self.skipTest("/dev/null required")
+ self.assertTrue(P.is_char_device())
+ self.assertFalse(P.is_block_device())
+ self.assertFalse(P.is_file())
+
+ def test_pickling_common(self):
+ p = self.cls(BASE, 'fileA')
+ for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
+ dumped = pickle.dumps(p, proto)
+ pp = pickle.loads(dumped)
+ self.assertEqual(pp.stat(), p.stat())
+
+ def test_parts_interning(self):
+ P = self.cls
+ p = P('/usr/bin/foo')
+ q = P('/usr/local/bin')
+ # 'usr'
+ self.assertIs(p.parts[1], q.parts[1])
+ # 'bin'
+ self.assertIs(p.parts[2], q.parts[3])
+
+
+class PathTest(_BasePathTest, unittest.TestCase):
+ cls = pathlib.Path
+
+ def test_concrete_class(self):
+ p = self.cls('a')
+ self.assertIs(type(p),
+ pathlib.WindowsPath if os.name == 'nt' else pathlib.PosixPath)
+
+ def test_unsupported_flavour(self):
+ if os.name == 'nt':
+ self.assertRaises(NotImplementedError, pathlib.PosixPath)
+ else:
+ self.assertRaises(NotImplementedError, pathlib.WindowsPath)
+
+
+@only_posix
+class PosixPathTest(_BasePathTest, unittest.TestCase):
+ cls = pathlib.PosixPath
+
+ def _check_symlink_loop(self, *args):
+ path = self.cls(*args)
+ with self.assertRaises(RuntimeError):
+ print(path.resolve())
+
+ def test_open_mode(self):
+ old_mask = os.umask(0)
+ self.addCleanup(os.umask, old_mask)
+ p = self.cls(BASE)
+ with (p / 'new_file').open('wb'):
+ pass
+ st = os.stat(join('new_file'))
+ self.assertEqual(stat.S_IMODE(st.st_mode), 0o666)
+ os.umask(0o022)
+ with (p / 'other_new_file').open('wb'):
+ pass
+ st = os.stat(join('other_new_file'))
+ self.assertEqual(stat.S_IMODE(st.st_mode), 0o644)
+
+ def test_touch_mode(self):
+ old_mask = os.umask(0)
+ self.addCleanup(os.umask, old_mask)
+ p = self.cls(BASE)
+ (p / 'new_file').touch()
+ st = os.stat(join('new_file'))
+ self.assertEqual(stat.S_IMODE(st.st_mode), 0o666)
+ os.umask(0o022)
+ (p / 'other_new_file').touch()
+ st = os.stat(join('other_new_file'))
+ self.assertEqual(stat.S_IMODE(st.st_mode), 0o644)
+ (p / 'masked_new_file').touch(mode=0o750)
+ st = os.stat(join('masked_new_file'))
+ self.assertEqual(stat.S_IMODE(st.st_mode), 0o750)
+
+ @with_symlinks
+ def test_resolve_loop(self):
+ # Loop detection for broken symlinks under POSIX
+ P = self.cls
+ # Loops with relative symlinks
+ os.symlink('linkX/inside', join('linkX'))
+ self._check_symlink_loop(BASE, 'linkX')
+ os.symlink('linkY', join('linkY'))
+ self._check_symlink_loop(BASE, 'linkY')
+ os.symlink('linkZ/../linkZ', join('linkZ'))
+ self._check_symlink_loop(BASE, 'linkZ')
+ # Loops with absolute symlinks
+ os.symlink(join('linkU/inside'), join('linkU'))
+ self._check_symlink_loop(BASE, 'linkU')
+ os.symlink(join('linkV'), join('linkV'))
+ self._check_symlink_loop(BASE, 'linkV')
+ os.symlink(join('linkW/../linkW'), join('linkW'))
+ self._check_symlink_loop(BASE, 'linkW')
+
+ def test_glob(self):
+ P = self.cls
+ p = P(BASE)
+ given = set(p.glob("FILEa"))
+ expect = set() if not support.fs_is_case_insensitive(BASE) else given
+ self.assertEqual(given, expect)
+ self.assertEqual(set(p.glob("FILEa*")), set())
+
+ def test_rglob(self):
+ P = self.cls
+ p = P(BASE, "dirC")
+ given = set(p.rglob("FILEd"))
+ expect = set() if not support.fs_is_case_insensitive(BASE) else given
+ self.assertEqual(given, expect)
+ self.assertEqual(set(p.rglob("FILEd*")), set())
+
+
+@only_nt
+class WindowsPathTest(_BasePathTest, unittest.TestCase):
+ cls = pathlib.WindowsPath
+
+ def test_glob(self):
+ P = self.cls
+ p = P(BASE)
+ self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") })
+
+ def test_rglob(self):
+ P = self.cls
+ p = P(BASE, "dirC")
+ self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 03084e4d12..74253b338f 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -1,9 +1,9 @@
# A test suite for pdb; not very comprehensive at the moment.
import doctest
-import imp
import pdb
import sys
+import types
import unittest
import subprocess
import textwrap
@@ -205,7 +205,8 @@ def test_pdb_breakpoint_commands():
... 'enable 1',
... 'clear 1',
... 'commands 2',
- ... 'print 42',
+ ... 'p "42"',
+ ... 'print("42", 7*6)', # Issue 18764 (not about breakpoints)
... 'end',
... 'continue', # will stop at breakpoint 2 (line 4)
... 'clear', # clear all!
@@ -252,11 +253,13 @@ def test_pdb_breakpoint_commands():
(Pdb) clear 1
Deleted breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>:3
(Pdb) commands 2
- (com) print 42
+ (com) p "42"
+ (com) print("42", 7*6)
(com) end
(Pdb) continue
1
- 42
+ '42'
+ 42 42
> <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>(4)test_function()
-> print(2)
(Pdb) clear
@@ -464,7 +467,7 @@ def test_pdb_skip_modules():
# Module for testing skipping of module that makes a callback
-mod = imp.new_module('module_to_skip')
+mod = types.ModuleType('module_to_skip')
exec('def foo_pony(callback): x = 1; callback(); return None', mod.__dict__)
@@ -597,6 +600,311 @@ def test_pdb_run_with_code_object():
(Pdb) continue
"""
+def test_next_until_return_at_return_event():
+ """Test that pdb stops after a next/until/return issued at a return debug event.
+
+ >>> def test_function_2():
+ ... x = 1
+ ... x = 2
+
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True).set_trace()
+ ... test_function_2()
+ ... test_function_2()
+ ... test_function_2()
+ ... end = 1
+
+ >>> with PdbTestInput(['break test_function_2',
+ ... 'continue',
+ ... 'return',
+ ... 'next',
+ ... 'continue',
+ ... 'return',
+ ... 'until',
+ ... 'continue',
+ ... 'return',
+ ... 'return',
+ ... 'continue']):
+ ... test_function()
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[1]>(3)test_function()
+ -> test_function_2()
+ (Pdb) break test_function_2
+ Breakpoint 1 at <doctest test.test_pdb.test_next_until_return_at_return_event[0]>:1
+ (Pdb) continue
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(2)test_function_2()
+ -> x = 1
+ (Pdb) return
+ --Return--
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(3)test_function_2()->None
+ -> x = 2
+ (Pdb) next
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[1]>(4)test_function()
+ -> test_function_2()
+ (Pdb) continue
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(2)test_function_2()
+ -> x = 1
+ (Pdb) return
+ --Return--
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(3)test_function_2()->None
+ -> x = 2
+ (Pdb) until
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[1]>(5)test_function()
+ -> test_function_2()
+ (Pdb) continue
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(2)test_function_2()
+ -> x = 1
+ (Pdb) return
+ --Return--
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[0]>(3)test_function_2()->None
+ -> x = 2
+ (Pdb) return
+ > <doctest test.test_pdb.test_next_until_return_at_return_event[1]>(6)test_function()
+ -> end = 1
+ (Pdb) continue
+ """
+
+def test_pdb_next_command_for_generator():
+ """Testing skip unwindng stack on yield for generators for "next" command
+
+ >>> def test_gen():
+ ... yield 0
+ ... return 1
+ ... yield 2
+
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True).set_trace()
+ ... it = test_gen()
+ ... try:
+ ... assert next(it) == 0
+ ... next(it)
+ ... except StopIteration as ex:
+ ... assert ex.value == 1
+ ... print("finished")
+
+ >>> with PdbTestInput(['step',
+ ... 'step',
+ ... 'step',
+ ... 'next',
+ ... 'next',
+ ... 'step',
+ ... 'step',
+ ... 'continue']):
+ ... test_function()
+ > <doctest test.test_pdb.test_pdb_next_command_for_generator[1]>(3)test_function()
+ -> it = test_gen()
+ (Pdb) step
+ > <doctest test.test_pdb.test_pdb_next_command_for_generator[1]>(4)test_function()
+ -> try:
+ (Pdb) step
+ > <doctest test.test_pdb.test_pdb_next_command_for_generator[1]>(5)test_function()
+ -> assert next(it) == 0
+ (Pdb) step
+ --Call--
+ > <doctest test.test_pdb.test_pdb_next_command_for_generator[0]>(1)test_gen()
+ -> def test_gen():
+ (Pdb) next
+ > <doctest test.test_pdb.test_pdb_next_command_for_generator[0]>(2)test_gen()
+ -> yield 0
+ (Pdb) next
+ > <doctest test.test_pdb.test_pdb_next_command_for_generator[0]>(3)test_gen()
+ -> return 1
+ (Pdb) step
+ --Return--
+ > <doctest test.test_pdb.test_pdb_next_command_for_generator[0]>(3)test_gen()->1
+ -> return 1
+ (Pdb) step
+ StopIteration: 1
+ > <doctest test.test_pdb.test_pdb_next_command_for_generator[1]>(6)test_function()
+ -> next(it)
+ (Pdb) continue
+ finished
+ """
+
+def test_pdb_return_command_for_generator():
+ """Testing no unwindng stack on yield for generators
+ for "return" command
+
+ >>> def test_gen():
+ ... yield 0
+ ... return 1
+ ... yield 2
+
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True).set_trace()
+ ... it = test_gen()
+ ... try:
+ ... assert next(it) == 0
+ ... next(it)
+ ... except StopIteration as ex:
+ ... assert ex.value == 1
+ ... print("finished")
+
+ >>> with PdbTestInput(['step',
+ ... 'step',
+ ... 'step',
+ ... 'return',
+ ... 'step',
+ ... 'step',
+ ... 'continue']):
+ ... test_function()
+ > <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(3)test_function()
+ -> it = test_gen()
+ (Pdb) step
+ > <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(4)test_function()
+ -> try:
+ (Pdb) step
+ > <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(5)test_function()
+ -> assert next(it) == 0
+ (Pdb) step
+ --Call--
+ > <doctest test.test_pdb.test_pdb_return_command_for_generator[0]>(1)test_gen()
+ -> def test_gen():
+ (Pdb) return
+ StopIteration: 1
+ > <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(6)test_function()
+ -> next(it)
+ (Pdb) step
+ > <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(7)test_function()
+ -> except StopIteration as ex:
+ (Pdb) step
+ > <doctest test.test_pdb.test_pdb_return_command_for_generator[1]>(8)test_function()
+ -> assert ex.value == 1
+ (Pdb) continue
+ finished
+ """
+
+def test_pdb_until_command_for_generator():
+ """Testing no unwindng stack on yield for generators
+ for "until" command if target breakpoing is not reached
+
+ >>> def test_gen():
+ ... yield 0
+ ... yield 1
+ ... yield 2
+
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True).set_trace()
+ ... for i in test_gen():
+ ... print(i)
+ ... print("finished")
+
+ >>> with PdbTestInput(['step',
+ ... 'until 4',
+ ... 'step',
+ ... 'step',
+ ... 'continue']):
+ ... test_function()
+ > <doctest test.test_pdb.test_pdb_until_command_for_generator[1]>(3)test_function()
+ -> for i in test_gen():
+ (Pdb) step
+ --Call--
+ > <doctest test.test_pdb.test_pdb_until_command_for_generator[0]>(1)test_gen()
+ -> def test_gen():
+ (Pdb) until 4
+ 0
+ 1
+ > <doctest test.test_pdb.test_pdb_until_command_for_generator[0]>(4)test_gen()
+ -> yield 2
+ (Pdb) step
+ --Return--
+ > <doctest test.test_pdb.test_pdb_until_command_for_generator[0]>(4)test_gen()->2
+ -> yield 2
+ (Pdb) step
+ > <doctest test.test_pdb.test_pdb_until_command_for_generator[1]>(4)test_function()
+ -> print(i)
+ (Pdb) continue
+ 2
+ finished
+ """
+
+def test_pdb_next_command_in_generator_for_loop():
+ """The next command on returning from a generator controled by a for loop.
+
+ >>> def test_gen():
+ ... yield 0
+ ... return 1
+
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True).set_trace()
+ ... for i in test_gen():
+ ... print('value', i)
+ ... x = 123
+
+ >>> with PdbTestInput(['break test_gen',
+ ... 'continue',
+ ... 'next',
+ ... 'next',
+ ... 'next',
+ ... 'continue']):
+ ... test_function()
+ > <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[1]>(3)test_function()
+ -> for i in test_gen():
+ (Pdb) break test_gen
+ Breakpoint 6 at <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>:1
+ (Pdb) continue
+ > <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>(2)test_gen()
+ -> yield 0
+ (Pdb) next
+ value 0
+ > <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>(3)test_gen()
+ -> return 1
+ (Pdb) next
+ Internal StopIteration: 1
+ > <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[1]>(3)test_function()
+ -> for i in test_gen():
+ (Pdb) next
+ > <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[1]>(5)test_function()
+ -> x = 123
+ (Pdb) continue
+ """
+
+def test_pdb_next_command_subiterator():
+ """The next command in a generator with a subiterator.
+
+ >>> def test_subgenerator():
+ ... yield 0
+ ... return 1
+
+ >>> def test_gen():
+ ... x = yield from test_subgenerator()
+ ... return x
+
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True).set_trace()
+ ... for i in test_gen():
+ ... print('value', i)
+ ... x = 123
+
+ >>> with PdbTestInput(['step',
+ ... 'step',
+ ... 'next',
+ ... 'next',
+ ... 'next',
+ ... 'continue']):
+ ... test_function()
+ > <doctest test.test_pdb.test_pdb_next_command_subiterator[2]>(3)test_function()
+ -> for i in test_gen():
+ (Pdb) step
+ --Call--
+ > <doctest test.test_pdb.test_pdb_next_command_subiterator[1]>(1)test_gen()
+ -> def test_gen():
+ (Pdb) step
+ > <doctest test.test_pdb.test_pdb_next_command_subiterator[1]>(2)test_gen()
+ -> x = yield from test_subgenerator()
+ (Pdb) next
+ value 0
+ > <doctest test.test_pdb.test_pdb_next_command_subiterator[1]>(3)test_gen()
+ -> return x
+ (Pdb) next
+ Internal StopIteration: 1
+ > <doctest test.test_pdb.test_pdb_next_command_subiterator[2]>(3)test_function()
+ -> for i in test_gen():
+ (Pdb) next
+ > <doctest test.test_pdb.test_pdb_next_command_subiterator[2]>(5)test_function()
+ -> x = 123
+ (Pdb) continue
+ """
+
class PdbTestCase(unittest.TestCase):
@@ -617,6 +925,36 @@ class PdbTestCase(unittest.TestCase):
stderr = stderr and bytes.decode(stderr)
return stdout, stderr
+ def _assert_find_function(self, file_content, func_name, expected):
+ file_content = textwrap.dedent(file_content)
+
+ with open(support.TESTFN, 'w') as f:
+ f.write(file_content)
+
+ expected = None if not expected else (
+ expected[0], support.TESTFN, expected[1])
+ self.assertEqual(
+ expected, pdb.find_function(func_name, support.TESTFN))
+
+ def test_find_function_empty_file(self):
+ self._assert_find_function('', 'foo', None)
+
+ def test_find_function_found(self):
+ self._assert_find_function(
+ """\
+ def foo():
+ pass
+
+ def bar():
+ pass
+
+ def quux():
+ pass
+ """,
+ 'bar',
+ ('bar', 4),
+ )
+
def test_issue7964(self):
# open the file as binary so we can force \r\n newline
with open(support.TESTFN, 'wb') as f:
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 1cacdea569..50257923fe 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -5,44 +5,28 @@ from io import StringIO
import unittest
from math import copysign
-def disassemble(func):
- f = StringIO()
- tmp = sys.stdout
- sys.stdout = f
- try:
- dis.dis(func)
- finally:
- sys.stdout = tmp
- result = f.getvalue()
- f.close()
- return result
+from test.bytecode_helper import BytecodeTestCase
-def dis_single(line):
- return disassemble(compile(line, '', 'single'))
-
-
-class TestTranforms(unittest.TestCase):
+class TestTranforms(BytecodeTestCase):
def test_unot(self):
# UNARY_NOT POP_JUMP_IF_FALSE --> POP_JUMP_IF_TRUE'
def unot(x):
if not x == 2:
del x
- asm = disassemble(unot)
- for elem in ('UNARY_NOT', 'POP_JUMP_IF_FALSE'):
- self.assertNotIn(elem, asm)
- for elem in ('POP_JUMP_IF_TRUE',):
- self.assertIn(elem, asm)
+ self.assertNotInBytecode(unot, 'UNARY_NOT')
+ self.assertNotInBytecode(unot, 'POP_JUMP_IF_FALSE')
+ self.assertInBytecode(unot, 'POP_JUMP_IF_TRUE')
def test_elim_inversion_of_is_or_in(self):
- for line, elem in (
- ('not a is b', '(is not)',),
- ('not a in b', '(not in)',),
- ('not a is not b', '(is)',),
- ('not a not in b', '(in)',),
+ for line, cmp_op in (
+ ('not a is b', 'is not',),
+ ('not a in b', 'not in',),
+ ('not a is not b', 'is',),
+ ('not a not in b', 'in',),
):
- asm = dis_single(line)
- self.assertIn(elem, asm)
+ code = compile(line, '', 'single')
+ self.assertInBytecode(code, 'COMPARE_OP', cmp_op)
def test_global_as_constant(self):
# LOAD_GLOBAL None/True/False --> LOAD_CONST None/True/False
@@ -56,17 +40,14 @@ class TestTranforms(unittest.TestCase):
def h(x):
False
return x
- for func, name in ((f, 'None'), (g, 'True'), (h, 'False')):
- asm = disassemble(func)
- for elem in ('LOAD_GLOBAL',):
- self.assertNotIn(elem, asm)
- for elem in ('LOAD_CONST', '('+name+')'):
- self.assertIn(elem, asm)
+ for func, elem in ((f, None), (g, True), (h, False)):
+ self.assertNotInBytecode(func, 'LOAD_GLOBAL')
+ self.assertInBytecode(func, 'LOAD_CONST', elem)
def f():
'Adding a docstring made this test fail in Py2.5.0'
return None
- self.assertIn('LOAD_CONST', disassemble(f))
- self.assertNotIn('LOAD_GLOBAL', disassemble(f))
+ self.assertNotInBytecode(f, 'LOAD_GLOBAL')
+ self.assertInBytecode(f, 'LOAD_CONST', None)
def test_while_one(self):
# Skip over: LOAD_CONST trueconst POP_JUMP_IF_FALSE xx
@@ -74,11 +55,10 @@ class TestTranforms(unittest.TestCase):
while 1:
pass
return list
- asm = disassemble(f)
for elem in ('LOAD_CONST', 'POP_JUMP_IF_FALSE'):
- self.assertNotIn(elem, asm)
+ self.assertNotInBytecode(f, elem)
for elem in ('JUMP_ABSOLUTE',):
- self.assertIn(elem, asm)
+ self.assertInBytecode(f, elem)
def test_pack_unpack(self):
for line, elem in (
@@ -86,28 +66,30 @@ class TestTranforms(unittest.TestCase):
('a, b = a, b', 'ROT_TWO',),
('a, b, c = a, b, c', 'ROT_THREE',),
):
- asm = dis_single(line)
- self.assertIn(elem, asm)
- self.assertNotIn('BUILD_TUPLE', asm)
- self.assertNotIn('UNPACK_TUPLE', asm)
+ code = compile(line,'','single')
+ self.assertInBytecode(code, elem)
+ self.assertNotInBytecode(code, 'BUILD_TUPLE')
+ self.assertNotInBytecode(code, 'UNPACK_TUPLE')
def test_folding_of_tuples_of_constants(self):
for line, elem in (
- ('a = 1,2,3', '((1, 2, 3))'),
- ('("a","b","c")', "(('a', 'b', 'c'))"),
- ('a,b,c = 1,2,3', '((1, 2, 3))'),
- ('(None, 1, None)', '((None, 1, None))'),
- ('((1, 2), 3, 4)', '(((1, 2), 3, 4))'),
+ ('a = 1,2,3', (1, 2, 3)),
+ ('("a","b","c")', ('a', 'b', 'c')),
+ ('a,b,c = 1,2,3', (1, 2, 3)),
+ ('(None, 1, None)', (None, 1, None)),
+ ('((1, 2), 3, 4)', ((1, 2), 3, 4)),
):
- asm = dis_single(line)
- self.assertIn(elem, asm)
- self.assertNotIn('BUILD_TUPLE', asm)
+ code = compile(line,'','single')
+ self.assertInBytecode(code, 'LOAD_CONST', elem)
+ self.assertNotInBytecode(code, 'BUILD_TUPLE')
# Long tuples should be folded too.
- asm = dis_single(repr(tuple(range(10000))))
+ code = compile(repr(tuple(range(10000))),'','single')
+ self.assertNotInBytecode(code, 'BUILD_TUPLE')
# One LOAD_CONST for the tuple, one for the None return value
- self.assertEqual(asm.count('LOAD_CONST'), 2)
- self.assertNotIn('BUILD_TUPLE', asm)
+ load_consts = [instr for instr in dis.get_instructions(code)
+ if instr.opname == 'LOAD_CONST']
+ self.assertEqual(len(load_consts), 2)
# Bug 1053819: Tuple of constants misidentified when presented with:
# . . . opcode_with_arg 100 unary_opcode BUILD_TUPLE 1 . . .
@@ -129,14 +111,14 @@ class TestTranforms(unittest.TestCase):
def test_folding_of_lists_of_constants(self):
for line, elem in (
# in/not in constants with BUILD_LIST should be folded to a tuple:
- ('a in [1,2,3]', '(1, 2, 3)'),
- ('a not in ["a","b","c"]', "(('a', 'b', 'c'))"),
- ('a in [None, 1, None]', '((None, 1, None))'),
- ('a not in [(1, 2), 3, 4]', '(((1, 2), 3, 4))'),
+ ('a in [1,2,3]', (1, 2, 3)),
+ ('a not in ["a","b","c"]', ('a', 'b', 'c')),
+ ('a in [None, 1, None]', (None, 1, None)),
+ ('a not in [(1, 2), 3, 4]', ((1, 2), 3, 4)),
):
- asm = dis_single(line)
- self.assertIn(elem, asm)
- self.assertNotIn('BUILD_LIST', asm)
+ code = compile(line, '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', elem)
+ self.assertNotInBytecode(code, 'BUILD_LIST')
def test_folding_of_sets_of_constants(self):
for line, elem in (
@@ -147,18 +129,9 @@ class TestTranforms(unittest.TestCase):
('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})),
('a in {1, 2, 3, 3, 2, 1}', frozenset({1, 2, 3})),
):
- asm = dis_single(line)
- self.assertNotIn('BUILD_SET', asm)
-
- # Verify that the frozenset 'elem' is in the disassembly
- # The ordering of the elements in repr( frozenset ) isn't
- # guaranteed, so we jump through some hoops to ensure that we have
- # the frozenset we expect:
- self.assertIn('frozenset', asm)
- # Extract the frozenset literal from the disassembly:
- m = re.match(r'.*(frozenset\({.*}\)).*', asm, re.DOTALL)
- self.assertTrue(m)
- self.assertEqual(eval(m.group(1)), elem)
+ code = compile(line, '', 'single')
+ self.assertNotInBytecode(code, 'BUILD_SET')
+ self.assertInBytecode(code, 'LOAD_CONST', elem)
# Ensure that the resulting code actually works:
def f(a):
@@ -176,98 +149,103 @@ class TestTranforms(unittest.TestCase):
def test_folding_of_binops_on_constants(self):
for line, elem in (
- ('a = 2+3+4', '(9)'), # chained fold
- ('"@"*4', "('@@@@')"), # check string ops
- ('a="abc" + "def"', "('abcdef')"), # check string ops
- ('a = 3**4', '(81)'), # binary power
- ('a = 3*4', '(12)'), # binary multiply
- ('a = 13//4', '(3)'), # binary floor divide
- ('a = 14%4', '(2)'), # binary modulo
- ('a = 2+3', '(5)'), # binary add
- ('a = 13-4', '(9)'), # binary subtract
- ('a = (12,13)[1]', '(13)'), # binary subscr
- ('a = 13 << 2', '(52)'), # binary lshift
- ('a = 13 >> 2', '(3)'), # binary rshift
- ('a = 13 & 7', '(5)'), # binary and
- ('a = 13 ^ 7', '(10)'), # binary xor
- ('a = 13 | 7', '(15)'), # binary or
+ ('a = 2+3+4', 9), # chained fold
+ ('"@"*4', '@@@@'), # check string ops
+ ('a="abc" + "def"', 'abcdef'), # check string ops
+ ('a = 3**4', 81), # binary power
+ ('a = 3*4', 12), # binary multiply
+ ('a = 13//4', 3), # binary floor divide
+ ('a = 14%4', 2), # binary modulo
+ ('a = 2+3', 5), # binary add
+ ('a = 13-4', 9), # binary subtract
+ ('a = (12,13)[1]', 13), # binary subscr
+ ('a = 13 << 2', 52), # binary lshift
+ ('a = 13 >> 2', 3), # binary rshift
+ ('a = 13 & 7', 5), # binary and
+ ('a = 13 ^ 7', 10), # binary xor
+ ('a = 13 | 7', 15), # binary or
):
- asm = dis_single(line)
- self.assertIn(elem, asm, asm)
- self.assertNotIn('BINARY_', asm)
+ code = compile(line, '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', elem)
+ for instr in dis.get_instructions(code):
+ self.assertFalse(instr.opname.startswith('BINARY_'))
# Verify that unfoldables are skipped
- asm = dis_single('a=2+"b"')
- self.assertIn('(2)', asm)
- self.assertIn("('b')", asm)
+ code = compile('a=2+"b"', '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', 2)
+ self.assertInBytecode(code, 'LOAD_CONST', 'b')
# Verify that large sequences do not result from folding
- asm = dis_single('a="x"*1000')
- self.assertIn('(1000)', asm)
+ code = compile('a="x"*1000', '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', 1000)
def test_binary_subscr_on_unicode(self):
# valid code get optimized
- asm = dis_single('"foo"[0]')
- self.assertIn("('f')", asm)
- self.assertNotIn('BINARY_SUBSCR', asm)
- asm = dis_single('"\u0061\uffff"[1]')
- self.assertIn("('\\uffff')", asm)
- self.assertNotIn('BINARY_SUBSCR', asm)
- asm = dis_single('"\U00012345abcdef"[3]')
- self.assertIn("('c')", asm)
- self.assertNotIn('BINARY_SUBSCR', asm)
+ code = compile('"foo"[0]', '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', 'f')
+ self.assertNotInBytecode(code, 'BINARY_SUBSCR')
+ code = compile('"\u0061\uffff"[1]', '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', '\uffff')
+ self.assertNotInBytecode(code,'BINARY_SUBSCR')
+
+ # With PEP 393, non-BMP char get optimized
+ code = compile('"\U00012345"[0]', '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', '\U00012345')
+ self.assertNotInBytecode(code, 'BINARY_SUBSCR')
# invalid code doesn't get optimized
# out of range
- asm = dis_single('"fuu"[10]')
- self.assertIn('BINARY_SUBSCR', asm)
+ code = compile('"fuu"[10]', '', 'single')
+ self.assertInBytecode(code, 'BINARY_SUBSCR')
def test_folding_of_unaryops_on_constants(self):
for line, elem in (
- ('-0.5', '(-0.5)'), # unary negative
- ('-0.0', '(-0.0)'), # -0.0
- ('-(1.0-1.0)','(-0.0)'), # -0.0 after folding
- ('-0', '(0)'), # -0
- ('~-2', '(1)'), # unary invert
- ('+1', '(1)'), # unary positive
+ ('-0.5', -0.5), # unary negative
+ ('-0.0', -0.0), # -0.0
+ ('-(1.0-1.0)', -0.0), # -0.0 after folding
+ ('-0', 0), # -0
+ ('~-2', 1), # unary invert
+ ('+1', 1), # unary positive
):
- asm = dis_single(line)
- self.assertIn(elem, asm, asm)
- self.assertNotIn('UNARY_', asm)
+ code = compile(line, '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', elem)
+ for instr in dis.get_instructions(code):
+ self.assertFalse(instr.opname.startswith('UNARY_'))
# Check that -0.0 works after marshaling
def negzero():
return -(1.0-1.0)
- self.assertNotIn('UNARY_', disassemble(negzero))
- self.assertTrue(copysign(1.0, negzero()) < 0)
+ for instr in dis.get_instructions(code):
+ self.assertFalse(instr.opname.startswith('UNARY_'))
# Verify that unfoldables are skipped
- for line, elem in (
- ('-"abc"', "('abc')"), # unary negative
- ('~"abc"', "('abc')"), # unary invert
+ for line, elem, opname in (
+ ('-"abc"', 'abc', 'UNARY_NEGATIVE'),
+ ('~"abc"', 'abc', 'UNARY_INVERT'),
):
- asm = dis_single(line)
- self.assertIn(elem, asm, asm)
- self.assertIn('UNARY_', asm)
+ code = compile(line, '', 'single')
+ self.assertInBytecode(code, 'LOAD_CONST', elem)
+ self.assertInBytecode(code, opname)
def test_elim_extra_return(self):
# RETURN LOAD_CONST None RETURN --> RETURN
def f(x):
return x
- asm = disassemble(f)
- self.assertNotIn('LOAD_CONST', asm)
- self.assertNotIn('(None)', asm)
- self.assertEqual(asm.split().count('RETURN_VALUE'), 1)
+ self.assertNotInBytecode(f, 'LOAD_CONST', None)
+ returns = [instr for instr in dis.get_instructions(f)
+ if instr.opname == 'RETURN_VALUE']
+ self.assertEqual(len(returns), 1)
def test_elim_jump_to_return(self):
# JUMP_FORWARD to RETURN --> RETURN
def f(cond, true_value, false_value):
return true_value if cond else false_value
- asm = disassemble(f)
- self.assertNotIn('JUMP_FORWARD', asm)
- self.assertNotIn('JUMP_ABSOLUTE', asm)
- self.assertEqual(asm.split().count('RETURN_VALUE'), 2)
+ self.assertNotInBytecode(f, 'JUMP_FORWARD')
+ self.assertNotInBytecode(f, 'JUMP_ABSOLUTE')
+ returns = [instr for instr in dis.get_instructions(f)
+ if instr.opname == 'RETURN_VALUE']
+ self.assertEqual(len(returns), 2)
def test_elim_jump_after_return1(self):
# Eliminate dead code: jumps immediately after returns can't be reached
@@ -280,48 +258,53 @@ class TestTranforms(unittest.TestCase):
if cond1: return 4
return 5
return 6
- asm = disassemble(f)
- self.assertNotIn('JUMP_FORWARD', asm)
- self.assertNotIn('JUMP_ABSOLUTE', asm)
- self.assertEqual(asm.split().count('RETURN_VALUE'), 6)
+ self.assertNotInBytecode(f, 'JUMP_FORWARD')
+ self.assertNotInBytecode(f, 'JUMP_ABSOLUTE')
+ returns = [instr for instr in dis.get_instructions(f)
+ if instr.opname == 'RETURN_VALUE']
+ self.assertEqual(len(returns), 6)
def test_elim_jump_after_return2(self):
# Eliminate dead code: jumps immediately after returns can't be reached
def f(cond1, cond2):
while 1:
if cond1: return 4
- asm = disassemble(f)
- self.assertNotIn('JUMP_FORWARD', asm)
+ self.assertNotInBytecode(f, 'JUMP_FORWARD')
# There should be one jump for the while loop.
- self.assertEqual(asm.split().count('JUMP_ABSOLUTE'), 1)
- self.assertEqual(asm.split().count('RETURN_VALUE'), 2)
+ returns = [instr for instr in dis.get_instructions(f)
+ if instr.opname == 'JUMP_ABSOLUTE']
+ self.assertEqual(len(returns), 1)
+ returns = [instr for instr in dis.get_instructions(f)
+ if instr.opname == 'RETURN_VALUE']
+ self.assertEqual(len(returns), 2)
def test_make_function_doesnt_bail(self):
def f():
def g()->1+1:
pass
return g
- asm = disassemble(f)
- self.assertNotIn('BINARY_ADD', asm)
+ self.assertNotInBytecode(f, 'BINARY_ADD')
def test_constant_folding(self):
# Issue #11244: aggressive constant folding.
exprs = [
- "3 * -5",
- "-3 * 5",
- "2 * (3 * 4)",
- "(2 * 3) * 4",
- "(-1, 2, 3)",
- "(1, -2, 3)",
- "(1, 2, -3)",
- "(1, 2, -3) * 6",
- "lambda x: x in {(3 * -5) + (-1 - 6), (1, -2, 3) * 2, None}",
+ '3 * -5',
+ '-3 * 5',
+ '2 * (3 * 4)',
+ '(2 * 3) * 4',
+ '(-1, 2, 3)',
+ '(1, -2, 3)',
+ '(1, 2, -3)',
+ '(1, 2, -3) * 6',
+ 'lambda x: x in {(3 * -5) + (-1 - 6), (1, -2, 3) * 2, None}',
]
for e in exprs:
- asm = dis_single(e)
- self.assertNotIn('UNARY_', asm, e)
- self.assertNotIn('BINARY_', asm, e)
- self.assertNotIn('BUILD_', asm, e)
+ code = compile(e, '', 'single')
+ for instr in dis.get_instructions(code):
+ self.assertFalse(instr.opname.startswith('UNARY_'))
+ self.assertFalse(instr.opname.startswith('BINARY_'))
+ self.assertFalse(instr.opname.startswith('BUILD_'))
+
class TestBuglets(unittest.TestCase):
@@ -343,7 +326,7 @@ def test_main(verbose=None):
support.run_unittest(*test_classes)
# verify reference counting
- if verbose and hasattr(sys, "gettotalrefcount"):
+ if verbose and hasattr(sys, 'gettotalrefcount'):
import gc
counts = [None] * 5
for i in range(len(counts)):
diff --git a/Lib/test/test_pep247.py b/Lib/test/test_pep247.py
index 7f10472856..b85a26ad65 100644
--- a/Lib/test/test_pep247.py
+++ b/Lib/test/test_pep247.py
@@ -15,12 +15,14 @@ class Pep247Test(unittest.TestCase):
self.assertTrue(module.digest_size is None or module.digest_size > 0)
self.check_object(module.new, module.digest_size, key)
- def check_object(self, cls, digest_size, key):
+ def check_object(self, cls, digest_size, key, digestmod=None):
if key is not None:
- obj1 = cls(key)
- obj2 = cls(key, b'string')
- h1 = cls(key, b'string').digest()
- obj3 = cls(key)
+ if digestmod is None:
+ digestmod = md5
+ obj1 = cls(key, digestmod=digestmod)
+ obj2 = cls(key, b'string', digestmod=digestmod)
+ h1 = cls(key, b'string', digestmod=digestmod).digest()
+ obj3 = cls(key, digestmod=digestmod)
obj3.update(b'string')
h2 = obj3.digest()
else:
diff --git a/Lib/test/test_pep277.py b/Lib/test/test_pep277.py
index 4b16cbb383..9bae6dcad7 100644
--- a/Lib/test/test_pep277.py
+++ b/Lib/test/test_pep277.py
@@ -99,10 +99,6 @@ class UnicodeFileTests(unittest.TestCase):
with self.assertRaises(expected_exception) as c:
fn(filename)
exc_filename = c.exception.filename
- # listdir may append a wildcard to the filename
- if fn is os.listdir and sys.platform == 'win32':
- exc_filename, _, wildcard = exc_filename.rpartition(os.sep)
- self.assertEqual(wildcard, '*.*')
if check_filename:
self.assertEqual(exc_filename, filename, "Function '%s(%a) failed "
"with bad filename in the exception: %a" %
diff --git a/Lib/test/test_pep352.py b/Lib/test/test_pep352.py
index 558cdb56d2..7c98c460b9 100644
--- a/Lib/test/test_pep352.py
+++ b/Lib/test/test_pep352.py
@@ -1,7 +1,6 @@
import unittest
import builtins
import warnings
-from test.support import run_unittest
import os
from platform import system as platform_system
@@ -180,8 +179,6 @@ class UsageTests(unittest.TestCase):
# Catching a string is bad.
self.catch_fails("spam")
-def test_main():
- run_unittest(ExceptionClassTests, UsageTests)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_pkg.py b/Lib/test/test_pkg.py
index 355efe7566..9883000945 100644
--- a/Lib/test/test_pkg.py
+++ b/Lib/test/test_pkg.py
@@ -199,14 +199,14 @@ class TestPkg(unittest.TestCase):
import t5
self.assertEqual(fixdir(dir(t5)),
['__cached__', '__doc__', '__file__', '__loader__',
- '__name__', '__package__', '__path__', 'foo',
- 'string', 't5'])
+ '__name__', '__package__', '__path__', '__spec__',
+ 'foo', 'string', 't5'])
self.assertEqual(fixdir(dir(t5.foo)),
['__cached__', '__doc__', '__file__', '__loader__',
- '__name__', '__package__', 'string'])
+ '__name__', '__package__', '__spec__', 'string'])
self.assertEqual(fixdir(dir(t5.string)),
['__cached__', '__doc__', '__file__', '__loader__',
- '__name__', '__package__', 'spam'])
+ '__name__', '__package__', '__spec__', 'spam'])
def test_6(self):
hier = [
@@ -222,14 +222,15 @@ class TestPkg(unittest.TestCase):
import t6
self.assertEqual(fixdir(dir(t6)),
['__all__', '__cached__', '__doc__', '__file__',
- '__loader__', '__name__', '__package__', '__path__'])
+ '__loader__', '__name__', '__package__', '__path__',
+ '__spec__'])
s = """
import t6
from t6 import *
self.assertEqual(fixdir(dir(t6)),
['__all__', '__cached__', '__doc__', '__file__',
'__loader__', '__name__', '__package__',
- '__path__', 'eggs', 'ham', 'spam'])
+ '__path__', '__spec__', 'eggs', 'ham', 'spam'])
self.assertEqual(dir(), ['eggs', 'ham', 'self', 'spam', 't6'])
"""
self.run_code(s)
@@ -256,18 +257,19 @@ class TestPkg(unittest.TestCase):
import t7 as tas
self.assertEqual(fixdir(dir(tas)),
['__cached__', '__doc__', '__file__', '__loader__',
- '__name__', '__package__', '__path__'])
+ '__name__', '__package__', '__path__', '__spec__'])
self.assertFalse(t7)
from t7 import sub as subpar
self.assertEqual(fixdir(dir(subpar)),
['__cached__', '__doc__', '__file__', '__loader__',
- '__name__', '__package__', '__path__'])
+ '__name__', '__package__', '__path__', '__spec__'])
self.assertFalse(t7)
self.assertFalse(sub)
from t7.sub import subsub as subsubsub
self.assertEqual(fixdir(dir(subsubsub)),
['__cached__', '__doc__', '__file__', '__loader__',
- '__name__', '__package__', '__path__', 'spam'])
+ '__name__', '__package__', '__path__', '__spec__',
+ 'spam'])
self.assertFalse(t7)
self.assertFalse(sub)
self.assertFalse(subsub)
diff --git a/Lib/test/test_pkgimport.py b/Lib/test/test_pkgimport.py
index a8426b5c9a..370b2aae28 100644
--- a/Lib/test/test_pkgimport.py
+++ b/Lib/test/test_pkgimport.py
@@ -6,7 +6,7 @@ import random
import tempfile
import unittest
-from imp import cache_from_source
+from importlib.util import cache_from_source
from test.support import run_unittest, create_empty_file
class TestImport(unittest.TestCase):
diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py
index fd0661450a..d73b2117d3 100644
--- a/Lib/test/test_pkgutil.py
+++ b/Lib/test/test_pkgutil.py
@@ -1,12 +1,12 @@
from test.support import run_unittest, unload, check_warnings
import unittest
import sys
-import imp
import importlib
import pkgutil
import os
import os.path
import tempfile
+import types
import shutil
import zipfile
@@ -105,7 +105,7 @@ class PkgutilPEP302Tests(unittest.TestCase):
class MyTestLoader(object):
def load_module(self, fullname):
# Create an empty module
- mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
+ mod = sys.modules.setdefault(fullname, types.ModuleType(fullname))
mod.__file__ = "<%s>" % self.__class__.__name__
mod.__loader__ = self
# Make it a package
@@ -200,6 +200,8 @@ class ExtendPathTests(unittest.TestCase):
dirname = self.create_init(pkgname)
pathitem = os.path.join(dirname, pkgname)
fullname = '{}.{}'.format(pkgname, modname)
+ sys.modules.pop(fullname, None)
+ sys.modules.pop(pkgname, None)
try:
self.create_submodule(dirname, pkgname, modname, 0)
@@ -208,9 +210,16 @@ class ExtendPathTests(unittest.TestCase):
importers = list(iter_importers(fullname))
expected_importer = get_importer(pathitem)
for finder in importers:
+ loader = finder.find_module(fullname)
+ try:
+ loader = loader.loader
+ except AttributeError:
+ # For now we still allow raw loaders from
+ # find_module().
+ pass
self.assertIsInstance(finder, importlib.machinery.FileFinder)
self.assertEqual(finder, expected_importer)
- self.assertIsInstance(finder.find_module(fullname),
+ self.assertIsInstance(loader,
importlib.machinery.SourceFileLoader)
self.assertIsNone(finder.find_module(pkgname))
@@ -222,8 +231,11 @@ class ExtendPathTests(unittest.TestCase):
finally:
shutil.rmtree(dirname)
del sys.path[0]
- del sys.modules['spam']
- del sys.modules['spam.eggs']
+ try:
+ del sys.modules['spam']
+ del sys.modules['spam.eggs']
+ except KeyError:
+ pass
def test_mixed_namespace(self):
diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py
index 9e86b3de64..abb1d19e63 100644
--- a/Lib/test/test_plistlib.py
+++ b/Lib/test/test_plistlib.py
@@ -1,94 +1,87 @@
-# Copyright (C) 2003 Python Software Foundation
+# Copyright (C) 2003-2013 Python Software Foundation
import unittest
import plistlib
import os
import datetime
+import codecs
+import binascii
+import collections
from test import support
+from io import BytesIO
+ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY)
-# This test data was generated through Cocoa's NSDictionary class
-TESTDATA = b"""<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
-"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>aDate</key>
- <date>2004-10-26T10:33:33Z</date>
- <key>aDict</key>
- <dict>
- <key>aFalseValue</key>
- <false/>
- <key>aTrueValue</key>
- <true/>
- <key>aUnicodeValue</key>
- <string>M\xc3\xa4ssig, Ma\xc3\x9f</string>
- <key>anotherString</key>
- <string>&lt;hello &amp; 'hi' there!&gt;</string>
- <key>deeperDict</key>
- <dict>
- <key>a</key>
- <integer>17</integer>
- <key>b</key>
- <real>32.5</real>
- <key>c</key>
- <array>
- <integer>1</integer>
- <integer>2</integer>
- <string>text</string>
- </array>
- </dict>
- </dict>
- <key>aFloat</key>
- <real>0.5</real>
- <key>aList</key>
- <array>
- <string>A</string>
- <string>B</string>
- <integer>12</integer>
- <real>32.5</real>
- <array>
- <integer>1</integer>
- <integer>2</integer>
- <integer>3</integer>
- </array>
- </array>
- <key>aString</key>
- <string>Doodah</string>
- <key>anEmptyDict</key>
- <dict/>
- <key>anEmptyList</key>
- <array/>
- <key>anInt</key>
- <integer>728</integer>
- <key>nestedData</key>
- <array>
- <data>
- PGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5r
- PgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5
- IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBi
- aW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3Rz
- IG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQID
- PGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAw==
- </data>
- </array>
- <key>someData</key>
- <data>
- PGJpbmFyeSBndW5rPg==
- </data>
- <key>someMoreData</key>
- <data>
- PGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8
- bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxs
- b3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxv
- dHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90
- cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAw==
- </data>
- <key>\xc3\x85benraa</key>
- <string>That was a unicode key.</string>
-</dict>
-</plist>
-""".replace(b" " * 8, b"\t") # Apple as well as plistlib.py output hard tabs
+# The testdata is generated using Mac/Tools/plistlib_generate_testdata.py
+# (which using PyObjC to control the Cocoa classes for generating plists)
+TESTDATA={
+ plistlib.FMT_XML: binascii.a2b_base64(b'''
+ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
+ WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
+ IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
+ LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YURh
+ dGU8L2tleT4KCTxkYXRlPjIwMDQtMTAtMjZUMTA6MzM6MzNaPC9kYXRlPgoJ
+ PGtleT5hRGljdDwva2V5PgoJPGRpY3Q+CgkJPGtleT5hRmFsc2VWYWx1ZTwv
+ a2V5PgoJCTxmYWxzZS8+CgkJPGtleT5hVHJ1ZVZhbHVlPC9rZXk+CgkJPHRy
+ dWUvPgoJCTxrZXk+YVVuaWNvZGVWYWx1ZTwva2V5PgoJCTxzdHJpbmc+TcOk
+ c3NpZywgTWHDnzwvc3RyaW5nPgoJCTxrZXk+YW5vdGhlclN0cmluZzwva2V5
+ PgoJCTxzdHJpbmc+Jmx0O2hlbGxvICZhbXA7ICdoaScgdGhlcmUhJmd0Ozwv
+ c3RyaW5nPgoJCTxrZXk+ZGVlcGVyRGljdDwva2V5PgoJCTxkaWN0PgoJCQk8
+ a2V5PmE8L2tleT4KCQkJPGludGVnZXI+MTc8L2ludGVnZXI+CgkJCTxrZXk+
+ Yjwva2V5PgoJCQk8cmVhbD4zMi41PC9yZWFsPgoJCQk8a2V5PmM8L2tleT4K
+ CQkJPGFycmF5PgoJCQkJPGludGVnZXI+MTwvaW50ZWdlcj4KCQkJCTxpbnRl
+ Z2VyPjI8L2ludGVnZXI+CgkJCQk8c3RyaW5nPnRleHQ8L3N0cmluZz4KCQkJ
+ PC9hcnJheT4KCQk8L2RpY3Q+Cgk8L2RpY3Q+Cgk8a2V5PmFGbG9hdDwva2V5
+ PgoJPHJlYWw+MC41PC9yZWFsPgoJPGtleT5hTGlzdDwva2V5PgoJPGFycmF5
+ PgoJCTxzdHJpbmc+QTwvc3RyaW5nPgoJCTxzdHJpbmc+Qjwvc3RyaW5nPgoJ
+ CTxpbnRlZ2VyPjEyPC9pbnRlZ2VyPgoJCTxyZWFsPjMyLjU8L3JlYWw+CgkJ
+ PGFycmF5PgoJCQk8aW50ZWdlcj4xPC9pbnRlZ2VyPgoJCQk8aW50ZWdlcj4y
+ PC9pbnRlZ2VyPgoJCQk8aW50ZWdlcj4zPC9pbnRlZ2VyPgoJCTwvYXJyYXk+
+ Cgk8L2FycmF5PgoJPGtleT5hU3RyaW5nPC9rZXk+Cgk8c3RyaW5nPkRvb2Rh
+ aDwvc3RyaW5nPgoJPGtleT5hbkVtcHR5RGljdDwva2V5PgoJPGRpY3QvPgoJ
+ PGtleT5hbkVtcHR5TGlzdDwva2V5PgoJPGFycmF5Lz4KCTxrZXk+YW5JbnQ8
+ L2tleT4KCTxpbnRlZ2VyPjcyODwvaW50ZWdlcj4KCTxrZXk+bmVzdGVkRGF0
+ YTwva2V5PgoJPGFycmF5PgoJCTxkYXRhPgoJCVBHeHZkSE1nYjJZZ1ltbHVZ
+ WEo1SUdkMWJtcytBQUVDQXp4c2IzUnpJRzltSUdKcGJtRnllU0JuZFc1cgoJ
+ CVBnQUJBZ004Ykc5MGN5QnZaaUJpYVc1aGNua2daM1Z1YXo0QUFRSURQR3h2
+ ZEhNZ2IyWWdZbWx1WVhKNQoJCUlHZDFibXMrQUFFQ0F6eHNiM1J6SUc5bUlH
+ SnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOGJHOTBjeUJ2WmlCaQoJCWFXNWhjbmtn
+ WjNWdWF6NEFBUUlEUEd4dmRITWdiMllnWW1sdVlYSjVJR2QxYm1zK0FBRUNB
+ enhzYjNSegoJCUlHOW1JR0pwYm1GeWVTQm5kVzVyUGdBQkFnTThiRzkwY3lC
+ dlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRAoJCVBHeHZkSE1nYjJZZ1ltbHVZ
+ WEo1SUdkMWJtcytBQUVDQXc9PQoJCTwvZGF0YT4KCTwvYXJyYXk+Cgk8a2V5
+ PnNvbWVEYXRhPC9rZXk+Cgk8ZGF0YT4KCVBHSnBibUZ5ZVNCbmRXNXJQZz09
+ Cgk8L2RhdGE+Cgk8a2V5PnNvbWVNb3JlRGF0YTwva2V5PgoJPGRhdGE+CglQ
+ R3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMrQUFFQ0F6eHNiM1J6SUc5bUlH
+ SnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOAoJYkc5MGN5QnZaaUJpYVc1aGNua2da
+ M1Z1YXo0QUFRSURQR3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMrQUFFQ0F6
+ eHMKCWIzUnpJRzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004Ykc5MGN5QnZa
+ aUJpYVc1aGNua2daM1Z1YXo0QUFRSURQR3h2CglkSE1nYjJZZ1ltbHVZWEo1
+ SUdkMWJtcytBQUVDQXp4c2IzUnpJRzltSUdKcGJtRnllU0JuZFc1clBnQUJB
+ Z004Ykc5MAoJY3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRFBHeHZkSE1n
+ YjJZZ1ltbHVZWEo1SUdkMWJtcytBQUVDQXc9PQoJPC9kYXRhPgoJPGtleT7D
+ hWJlbnJhYTwva2V5PgoJPHN0cmluZz5UaGF0IHdhcyBhIHVuaWNvZGUga2V5
+ Ljwvc3RyaW5nPgo8L2RpY3Q+CjwvcGxpc3Q+Cg=='''),
+ plistlib.FMT_BINARY: binascii.a2b_base64(b'''
+ YnBsaXN0MDDcAQIDBAUGBwgJCgsMDQ4iIykqKywtLy4wVWFEYXRlVWFEaWN0
+ VmFGbG9hdFVhTGlzdFdhU3RyaW5nW2FuRW1wdHlEaWN0W2FuRW1wdHlMaXN0
+ VWFuSW50Wm5lc3RlZERhdGFYc29tZURhdGFcc29tZU1vcmVEYXRhZwDFAGIA
+ ZQBuAHIAYQBhM0GcuX30AAAA1Q8QERITFBUWFxhbYUZhbHNlVmFsdWVaYVRy
+ dWVWYWx1ZV1hVW5pY29kZVZhbHVlXWFub3RoZXJTdHJpbmdaZGVlcGVyRGlj
+ dAgJawBNAOQAcwBzAGkAZwAsACAATQBhAN9fEBU8aGVsbG8gJiAnaGknIHRo
+ ZXJlIT7TGRobHB0eUWFRYlFjEBEjQEBAAAAAAACjHyAhEAEQAlR0ZXh0Iz/g
+ AAAAAAAApSQlJh0nUUFRQhAMox8gKBADVkRvb2RhaNCgEQLYoS5PEPo8bG90
+ cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAEC
+ Azxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vu
+ az4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFy
+ eSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2Yg
+ YmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90
+ cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDTTxiaW5hcnkgZ3Vuaz5fEBdUaGF0IHdh
+ cyBhIHVuaWNvZGUga2V5LgAIACEAJwAtADQAOgBCAE4AWgBgAGsAdACBAJAA
+ mQCkALAAuwDJANcA4gDjAOQA+wETARoBHAEeASABIgErAS8BMQEzATgBQQFH
+ AUkBSwFNAVEBUwFaAVsBXAFfAWECXgJsAAAAAAAAAgEAAAAAAAAAMQAAAAAA
+ AAAAAAAAAAAAAoY='''),
+}
class TestPlistlib(unittest.TestCase):
@@ -99,7 +92,7 @@ class TestPlistlib(unittest.TestCase):
except:
pass
- def _create(self):
+ def _create(self, fmt=None):
pl = dict(
aString="Doodah",
aList=["A", "B", 12, 32.5, [1, 2, 3]],
@@ -112,9 +105,9 @@ class TestPlistlib(unittest.TestCase):
aFalseValue=False,
deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]),
),
- someData = plistlib.Data(b"<binary gunk>"),
- someMoreData = plistlib.Data(b"<lots of binary gunk>\0\1\2\3" * 10),
- nestedData = [plistlib.Data(b"<lots of binary gunk>\0\1\2\3" * 10)],
+ someData = b"<binary gunk>",
+ someMoreData = b"<lots of binary gunk>\0\1\2\3" * 10,
+ nestedData = [b"<lots of binary gunk>\0\1\2\3" * 10],
aDate = datetime.datetime(2004, 10, 26, 10, 33, 33),
anEmptyDict = dict(),
anEmptyList = list()
@@ -129,49 +122,191 @@ class TestPlistlib(unittest.TestCase):
def test_io(self):
pl = self._create()
- plistlib.writePlist(pl, support.TESTFN)
- pl2 = plistlib.readPlist(support.TESTFN)
+ with open(support.TESTFN, 'wb') as fp:
+ plistlib.dump(pl, fp)
+
+ with open(support.TESTFN, 'rb') as fp:
+ pl2 = plistlib.load(fp)
+
self.assertEqual(dict(pl), dict(pl2))
+ self.assertRaises(AttributeError, plistlib.dump, pl, 'filename')
+ self.assertRaises(AttributeError, plistlib.load, 'filename')
+
+
def test_bytes(self):
pl = self._create()
- data = plistlib.writePlistToBytes(pl)
- pl2 = plistlib.readPlistFromBytes(data)
+ data = plistlib.dumps(pl)
+ pl2 = plistlib.loads(data)
+ self.assertNotIsInstance(pl, plistlib._InternalDict)
self.assertEqual(dict(pl), dict(pl2))
- data2 = plistlib.writePlistToBytes(pl2)
+ data2 = plistlib.dumps(pl2)
self.assertEqual(data, data2)
def test_indentation_array(self):
- data = [[[[[[[[{'test': plistlib.Data(b'aaaaaa')}]]]]]]]]
- self.assertEqual(plistlib.readPlistFromBytes(plistlib.writePlistToBytes(data)), data)
+ data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]]
+ self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
def test_indentation_dict(self):
- data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': plistlib.Data(b'aaaaaa')}}}}}}}}}
- self.assertEqual(plistlib.readPlistFromBytes(plistlib.writePlistToBytes(data)), data)
+ data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': b'aaaaaa'}}}}}}}}}
+ self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
def test_indentation_dict_mix(self):
- data = {'1': {'2': [{'3': [[[[[{'test': plistlib.Data(b'aaaaaa')}]]]]]}]}}
- self.assertEqual(plistlib.readPlistFromBytes(plistlib.writePlistToBytes(data)), data)
+ data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}}
+ self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
def test_appleformatting(self):
- pl = plistlib.readPlistFromBytes(TESTDATA)
- data = plistlib.writePlistToBytes(pl)
- self.assertEqual(data, TESTDATA,
- "generated data was not identical to Apple's output")
+ for use_builtin_types in (True, False):
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt, use_builtin_types=use_builtin_types):
+ pl = plistlib.loads(TESTDATA[fmt],
+ use_builtin_types=use_builtin_types)
+ data = plistlib.dumps(pl, fmt=fmt)
+ self.assertEqual(data, TESTDATA[fmt],
+ "generated data was not identical to Apple's output")
+
def test_appleformattingfromliteral(self):
- pl = self._create()
- pl2 = plistlib.readPlistFromBytes(TESTDATA)
- self.assertEqual(dict(pl), dict(pl2),
- "generated data was not identical to Apple's output")
+ self.maxDiff = None
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt):
+ pl = self._create(fmt=fmt)
+ pl2 = plistlib.loads(TESTDATA[fmt])
+ self.assertEqual(dict(pl), dict(pl2),
+ "generated data was not identical to Apple's output")
def test_bytesio(self):
- from io import BytesIO
- b = BytesIO()
- pl = self._create()
- plistlib.writePlist(pl, b)
- pl2 = plistlib.readPlist(BytesIO(b.getvalue()))
- self.assertEqual(dict(pl), dict(pl2))
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt):
+ b = BytesIO()
+ pl = self._create(fmt=fmt)
+ plistlib.dump(pl, b, fmt=fmt)
+ pl2 = plistlib.load(BytesIO(b.getvalue()))
+ self.assertEqual(dict(pl), dict(pl2))
+
+ def test_keysort_bytesio(self):
+ pl = collections.OrderedDict()
+ pl['b'] = 1
+ pl['a'] = 2
+ pl['c'] = 3
+
+ for fmt in ALL_FORMATS:
+ for sort_keys in (False, True):
+ with self.subTest(fmt=fmt, sort_keys=sort_keys):
+ b = BytesIO()
+
+ plistlib.dump(pl, b, fmt=fmt, sort_keys=sort_keys)
+ pl2 = plistlib.load(BytesIO(b.getvalue()),
+ dict_type=collections.OrderedDict)
+
+ self.assertEqual(dict(pl), dict(pl2))
+ if sort_keys:
+ self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
+ else:
+ self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
+
+ def test_keysort(self):
+ pl = collections.OrderedDict()
+ pl['b'] = 1
+ pl['a'] = 2
+ pl['c'] = 3
+
+ for fmt in ALL_FORMATS:
+ for sort_keys in (False, True):
+ with self.subTest(fmt=fmt, sort_keys=sort_keys):
+ data = plistlib.dumps(pl, fmt=fmt, sort_keys=sort_keys)
+ pl2 = plistlib.loads(data, dict_type=collections.OrderedDict)
+
+ self.assertEqual(dict(pl), dict(pl2))
+ if sort_keys:
+ self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
+ else:
+ self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
+
+ def test_keys_no_string(self):
+ pl = { 42: 'aNumber' }
+
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt):
+ self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
+
+ b = BytesIO()
+ self.assertRaises(TypeError, plistlib.dump, pl, b, fmt=fmt)
+
+ def test_skipkeys(self):
+ pl = {
+ 42: 'aNumber',
+ 'snake': 'aWord',
+ }
+
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt):
+ data = plistlib.dumps(
+ pl, fmt=fmt, skipkeys=True, sort_keys=False)
+
+ pl2 = plistlib.loads(data)
+ self.assertEqual(pl2, {'snake': 'aWord'})
+
+ fp = BytesIO()
+ plistlib.dump(
+ pl, fp, fmt=fmt, skipkeys=True, sort_keys=False)
+ data = fp.getvalue()
+ pl2 = plistlib.loads(fp.getvalue())
+ self.assertEqual(pl2, {'snake': 'aWord'})
+
+ def test_tuple_members(self):
+ pl = {
+ 'first': (1, 2),
+ 'second': (1, 2),
+ 'third': (3, 4),
+ }
+
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt):
+ data = plistlib.dumps(pl, fmt=fmt)
+ pl2 = plistlib.loads(data)
+ self.assertEqual(pl2, {
+ 'first': [1, 2],
+ 'second': [1, 2],
+ 'third': [3, 4],
+ })
+ self.assertIsNot(pl2['first'], pl2['second'])
+
+ def test_list_members(self):
+ pl = {
+ 'first': [1, 2],
+ 'second': [1, 2],
+ 'third': [3, 4],
+ }
+
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt):
+ data = plistlib.dumps(pl, fmt=fmt)
+ pl2 = plistlib.loads(data)
+ self.assertEqual(pl2, {
+ 'first': [1, 2],
+ 'second': [1, 2],
+ 'third': [3, 4],
+ })
+ self.assertIsNot(pl2['first'], pl2['second'])
+
+ def test_dict_members(self):
+ pl = {
+ 'first': {'a': 1},
+ 'second': {'a': 1},
+ 'third': {'b': 2 },
+ }
+
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt):
+ data = plistlib.dumps(pl, fmt=fmt)
+ pl2 = plistlib.loads(data)
+ self.assertEqual(pl2, {
+ 'first': {'a': 1},
+ 'second': {'a': 1},
+ 'third': {'b': 2 },
+ })
+ self.assertIsNot(pl2['first'], pl2['second'])
def test_controlcharacters(self):
for i in range(128):
@@ -179,25 +314,27 @@ class TestPlistlib(unittest.TestCase):
testString = "string containing %s" % c
if i >= 32 or c in "\r\n\t":
# \r, \n and \t are the only legal control chars in XML
- plistlib.writePlistToBytes(testString)
+ plistlib.dumps(testString, fmt=plistlib.FMT_XML)
else:
self.assertRaises(ValueError,
- plistlib.writePlistToBytes,
+ plistlib.dumps,
testString)
def test_nondictroot(self):
- test1 = "abc"
- test2 = [1, 2, 3, "abc"]
- result1 = plistlib.readPlistFromBytes(plistlib.writePlistToBytes(test1))
- result2 = plistlib.readPlistFromBytes(plistlib.writePlistToBytes(test2))
- self.assertEqual(test1, result1)
- self.assertEqual(test2, result2)
+ for fmt in ALL_FORMATS:
+ with self.subTest(fmt=fmt):
+ test1 = "abc"
+ test2 = [1, 2, 3, "abc"]
+ result1 = plistlib.loads(plistlib.dumps(test1, fmt=fmt))
+ result2 = plistlib.loads(plistlib.dumps(test2, fmt=fmt))
+ self.assertEqual(test1, result1)
+ self.assertEqual(test2, result2)
def test_invalidarray(self):
for i in ["<key>key inside an array</key>",
"<key>key inside an array2</key><real>3</real>",
"<true/><key>key inside an array3</key>"]:
- self.assertRaises(ValueError, plistlib.readPlistFromBytes,
+ self.assertRaises(ValueError, plistlib.loads,
("<plist><array>%s</array></plist>"%i).encode())
def test_invaliddict(self):
@@ -206,22 +343,130 @@ class TestPlistlib(unittest.TestCase):
"<string>missing key</string>",
"<key>k1</key><string>v1</string><real>5.3</real>"
"<key>k1</key><key>k2</key><string>double key</string>"]:
- self.assertRaises(ValueError, plistlib.readPlistFromBytes,
+ self.assertRaises(ValueError, plistlib.loads,
("<plist><dict>%s</dict></plist>"%i).encode())
- self.assertRaises(ValueError, plistlib.readPlistFromBytes,
+ self.assertRaises(ValueError, plistlib.loads,
("<plist><array><dict>%s</dict></array></plist>"%i).encode())
def test_invalidinteger(self):
- self.assertRaises(ValueError, plistlib.readPlistFromBytes,
+ self.assertRaises(ValueError, plistlib.loads,
b"<plist><integer>not integer</integer></plist>")
def test_invalidreal(self):
- self.assertRaises(ValueError, plistlib.readPlistFromBytes,
+ self.assertRaises(ValueError, plistlib.loads,
b"<plist><integer>not real</integer></plist>")
+ def test_xml_encodings(self):
+ base = TESTDATA[plistlib.FMT_XML]
+
+ for xml_encoding, encoding, bom in [
+ (b'utf-8', 'utf-8', codecs.BOM_UTF8),
+ (b'utf-16', 'utf-16-le', codecs.BOM_UTF16_LE),
+ (b'utf-16', 'utf-16-be', codecs.BOM_UTF16_BE),
+ # Expat does not support UTF-32
+ #(b'utf-32', 'utf-32-le', codecs.BOM_UTF32_LE),
+ #(b'utf-32', 'utf-32-be', codecs.BOM_UTF32_BE),
+ ]:
+
+ pl = self._create(fmt=plistlib.FMT_XML)
+ with self.subTest(encoding=encoding):
+ data = base.replace(b'UTF-8', xml_encoding)
+ data = bom + data.decode('utf-8').encode(encoding)
+ pl2 = plistlib.loads(data)
+ self.assertEqual(dict(pl), dict(pl2))
+
+
+class TestPlistlibDeprecated(unittest.TestCase):
+ def test_io_deprecated(self):
+ pl_in = {
+ 'key': 42,
+ 'sub': {
+ 'key': 9,
+ 'alt': 'value',
+ 'data': b'buffer',
+ }
+ }
+ pl_out = plistlib._InternalDict({
+ 'key': 42,
+ 'sub': plistlib._InternalDict({
+ 'key': 9,
+ 'alt': 'value',
+ 'data': plistlib.Data(b'buffer'),
+ })
+ })
+
+ self.addCleanup(support.unlink, support.TESTFN)
+ with self.assertWarns(DeprecationWarning):
+ plistlib.writePlist(pl_in, support.TESTFN)
+
+ with self.assertWarns(DeprecationWarning):
+ pl2 = plistlib.readPlist(support.TESTFN)
+
+ self.assertEqual(pl_out, pl2)
+
+ os.unlink(support.TESTFN)
+
+ with open(support.TESTFN, 'wb') as fp:
+ with self.assertWarns(DeprecationWarning):
+ plistlib.writePlist(pl_in, fp)
+
+ with open(support.TESTFN, 'rb') as fp:
+ with self.assertWarns(DeprecationWarning):
+ pl2 = plistlib.readPlist(fp)
+
+ self.assertEqual(pl_out, pl2)
+
+ def test_bytes_deprecated(self):
+ pl = {
+ 'key': 42,
+ 'sub': {
+ 'key': 9,
+ 'alt': 'value',
+ 'data': b'buffer',
+ }
+ }
+ with self.assertWarns(DeprecationWarning):
+ data = plistlib.writePlistToBytes(pl)
+
+ with self.assertWarns(DeprecationWarning):
+ pl2 = plistlib.readPlistFromBytes(data)
+
+ self.assertIsInstance(pl2, plistlib._InternalDict)
+ self.assertEqual(pl2, plistlib._InternalDict(
+ key=42,
+ sub=plistlib._InternalDict(
+ key=9,
+ alt='value',
+ data=plistlib.Data(b'buffer'),
+ )
+ ))
+
+ with self.assertWarns(DeprecationWarning):
+ data2 = plistlib.writePlistToBytes(pl2)
+ self.assertEqual(data, data2)
+
+ def test_dataobject_deprecated(self):
+ in_data = { 'key': plistlib.Data(b'hello') }
+ out_data = { 'key': b'hello' }
+
+ buf = plistlib.dumps(in_data)
+
+ cur = plistlib.loads(buf)
+ self.assertEqual(cur, out_data)
+ self.assertNotEqual(cur, in_data)
+
+ cur = plistlib.loads(buf, use_builtin_types=False)
+ self.assertNotEqual(cur, out_data)
+ self.assertEqual(cur, in_data)
+
+ with self.assertWarns(DeprecationWarning):
+ cur = plistlib.readPlistFromBytes(buf)
+ self.assertNotEqual(cur, out_data)
+ self.assertEqual(cur, in_data)
+
def test_main():
- support.run_unittest(TestPlistlib)
+ support.run_unittest(TestPlistlib, TestPlistlibDeprecated)
if __name__ == '__main__':
diff --git a/Lib/test/test_poll.py b/Lib/test/test_poll.py
index f98a280e9a..a1e5c3dbf8 100644
--- a/Lib/test/test_poll.py
+++ b/Lib/test/test_poll.py
@@ -1,6 +1,7 @@
# Test case for the os.poll() function
import os
+import subprocess
import random
import select
import _testcapi
@@ -15,7 +16,7 @@ from test.support import TESTFN, run_unittest, reap_threads
try:
select.poll
except AttributeError:
- raise unittest.SkipTest("select.poll not defined -- skipping test_poll")
+ raise unittest.SkipTest("select.poll not defined")
def find_ready_matching(ready, flag):
@@ -76,13 +77,11 @@ class PollTests(unittest.TestCase):
self.assertEqual(bufs, [MSG] * NUM_PIPES)
- def poll_unit_tests(self):
+ def test_poll_unit_tests(self):
# returns NVAL for invalid file descriptor
- FD = 42
- try:
- os.close(FD)
- except OSError:
- pass
+ FD, w = os.pipe()
+ os.close(FD)
+ os.close(w)
p = select.poll()
p.register(FD)
r = p.poll()
@@ -125,7 +124,9 @@ class PollTests(unittest.TestCase):
def test_poll2(self):
cmd = 'for i in 0 1 2 3 4 5 6 7 8 9; do echo testing...; sleep 1; done'
- p = os.popen(cmd, 'r')
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
+ bufsize=0)
+ p = proc.stdout
pollster = select.poll()
pollster.register( p, select.POLLIN )
for tout in (0, 1000, 2000, 4000, 8000, 16000) + (-1,)*10:
@@ -135,7 +136,7 @@ class PollTests(unittest.TestCase):
fd, flags = fdlist[0]
if flags & select.POLLHUP:
line = p.readline()
- if line != "":
+ if line != b"":
self.fail('error: pipe seems to be closed, but still returns data')
continue
@@ -143,6 +144,7 @@ class PollTests(unittest.TestCase):
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)
diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py
index cbce8524eb..70fe4265c5 100644
--- a/Lib/test/test_poplib.py
+++ b/Lib/test/test_poplib.py
@@ -18,6 +18,14 @@ threading = test_support.import_module('threading')
HOST = test_support.HOST
PORT = 0
+SUPPORTS_SSL = False
+if hasattr(poplib, 'POP3_SSL'):
+ import ssl
+
+ SUPPORTS_SSL = True
+ CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "keycert.pem")
+requires_ssl = skipUnless(SUPPORTS_SSL, 'SSL not supported')
+
# the dummy data returned by server when LIST and RETR commands are issued
LIST_RESP = b'1 1\r\n2 2\r\n3 3\r\n4 4\r\n5 5\r\n.\r\n'
RETR_RESP = b"""From: postmaster@python.org\
@@ -33,11 +41,15 @@ line3\r\n\
class DummyPOP3Handler(asynchat.async_chat):
+ CAPAS = {'UIDL': [], 'IMPLEMENTATION': ['python-testlib-pop-server']}
+
def __init__(self, conn):
asynchat.async_chat.__init__(self, conn)
self.set_terminator(b"\r\n")
self.in_buffer = []
self.push('+OK dummy pop3 server ready. <timestamp>')
+ self.tls_active = False
+ self.tls_starting = False
def collect_incoming_data(self, data):
self.in_buffer.append(data)
@@ -112,6 +124,65 @@ class DummyPOP3Handler(asynchat.async_chat):
self.push('+OK closing.')
self.close_when_done()
+ def _get_capas(self):
+ _capas = dict(self.CAPAS)
+ if not self.tls_active and SUPPORTS_SSL:
+ _capas['STLS'] = []
+ return _capas
+
+ def cmd_capa(self, arg):
+ self.push('+OK Capability list follows')
+ if self._get_capas():
+ for cap, params in self._get_capas().items():
+ _ln = [cap]
+ if params:
+ _ln.extend(params)
+ self.push(' '.join(_ln))
+ self.push('.')
+
+ if SUPPORTS_SSL:
+
+ def cmd_stls(self, arg):
+ if self.tls_active is False:
+ self.push('+OK Begin TLS negotiation')
+ tls_sock = ssl.wrap_socket(self.socket, certfile=CERTFILE,
+ server_side=True,
+ do_handshake_on_connect=False,
+ suppress_ragged_eofs=False)
+ self.del_channel()
+ self.set_socket(tls_sock)
+ self.tls_active = True
+ self.tls_starting = True
+ self.in_buffer = []
+ self._do_tls_handshake()
+ else:
+ self.push('-ERR Command not permitted when TLS active')
+
+ def _do_tls_handshake(self):
+ try:
+ self.socket.do_handshake()
+ except ssl.SSLError as err:
+ if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
+ ssl.SSL_ERROR_WANT_WRITE):
+ return
+ elif err.args[0] == ssl.SSL_ERROR_EOF:
+ return self.handle_close()
+ raise
+ except OSError as err:
+ if err.args[0] == errno.ECONNABORTED:
+ return self.handle_close()
+ else:
+ self.tls_active = True
+ self.tls_starting = False
+
+ def handle_read(self):
+ if self.tls_starting:
+ self._do_tls_handshake()
+ else:
+ try:
+ asynchat.async_chat.handle_read(self)
+ except ssl.SSLEOFError:
+ self.handle_close()
class DummyPOP3Server(asyncore.dispatcher, threading.Thread):
@@ -236,19 +307,36 @@ class TestPOP3Class(TestCase):
self.client.uidl()
self.client.uidl('foo')
+ def test_capa(self):
+ capa = self.client.capa()
+ self.assertTrue('IMPLEMENTATION' in capa.keys())
+
def test_quit(self):
resp = self.client.quit()
self.assertTrue(resp)
self.assertIsNone(self.client.sock)
self.assertIsNone(self.client.file)
+ @requires_ssl
+ def test_stls_capa(self):
+ capa = self.client.capa()
+ self.assertTrue('STLS' in capa.keys())
-SUPPORTS_SSL = False
-if hasattr(poplib, 'POP3_SSL'):
- import ssl
+ @requires_ssl
+ def test_stls(self):
+ expected = b'+OK Begin TLS negotiation'
+ resp = self.client.stls()
+ self.assertEqual(resp, expected)
- SUPPORTS_SSL = True
- CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "keycert.pem")
+ @requires_ssl
+ def test_stls_context(self):
+ expected = b'+OK Begin TLS negotiation'
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ resp = self.client.stls(context=ctx)
+ self.assertEqual(resp, expected)
+
+
+if SUPPORTS_SSL:
class DummyPOP3_SSLHandler(DummyPOP3Handler):
@@ -260,35 +348,13 @@ if hasattr(poplib, 'POP3_SSL'):
self.del_channel()
self.set_socket(ssl_socket)
# Must try handshake before calling push()
- self._ssl_accepting = True
- self._do_ssl_handshake()
+ self.tls_active = True
+ self.tls_starting = True
+ self._do_tls_handshake()
self.set_terminator(b"\r\n")
self.in_buffer = []
self.push('+OK dummy pop3 server ready. <timestamp>')
- def _do_ssl_handshake(self):
- try:
- self.socket.do_handshake()
- except ssl.SSLError as err:
- if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
- ssl.SSL_ERROR_WANT_WRITE):
- return
- elif err.args[0] == ssl.SSL_ERROR_EOF:
- return self.handle_close()
- raise
- except socket.error as err:
- if err.args[0] == errno.ECONNABORTED:
- return self.handle_close()
- else:
- self._ssl_accepting = False
-
- def handle_read(self):
- if self._ssl_accepting:
- self._do_ssl_handshake()
- else:
- DummyPOP3Handler.handle_read(self)
-
-requires_ssl = skipUnless(SUPPORTS_SSL, 'SSL not supported')
@requires_ssl
class TestPOP3_SSLClass(TestPOP3Class):
@@ -306,20 +372,60 @@ class TestPOP3_SSLClass(TestPOP3Class):
def test_context(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host,
- self.server.port, keyfile=CERTFILE, context=ctx)
+ self.server.port, keyfile=CERTFILE, context=ctx)
self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host,
- self.server.port, certfile=CERTFILE, context=ctx)
+ self.server.port, certfile=CERTFILE, context=ctx)
self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host,
- self.server.port, keyfile=CERTFILE,
- certfile=CERTFILE, context=ctx)
+ self.server.port, keyfile=CERTFILE,
+ certfile=CERTFILE, context=ctx)
self.client.quit()
self.client = poplib.POP3_SSL(self.server.host, self.server.port,
- context=ctx)
+ context=ctx)
self.assertIsInstance(self.client.sock, ssl.SSLSocket)
self.assertIs(self.client.sock.context, ctx)
self.assertTrue(self.client.noop().startswith(b'+OK'))
+ def test_stls(self):
+ self.assertRaises(poplib.error_proto, self.client.stls)
+
+ test_stls_context = test_stls
+
+ def test_stls_capa(self):
+ capa = self.client.capa()
+ self.assertFalse('STLS' in capa.keys())
+
+
+@requires_ssl
+class TestPOP3_TLSClass(TestPOP3Class):
+ # repeat previous tests by using poplib.POP3.stls()
+
+ def setUp(self):
+ self.server = DummyPOP3Server((HOST, PORT))
+ self.server.start()
+ self.client = poplib.POP3(self.server.host, self.server.port, timeout=3)
+ self.client.stls()
+
+ def tearDown(self):
+ if self.client.file is not None and self.client.sock is not None:
+ try:
+ self.client.quit()
+ except poplib.error_proto:
+ # happens in the test_too_long_lines case; the overlong
+ # response will be treated as response to QUIT and raise
+ # this exception
+ pass
+ self.server.stop()
+
+ def test_stls(self):
+ self.assertRaises(poplib.error_proto, self.client.stls)
+
+ test_stls_context = test_stls
+
+ def test_stls_capa(self):
+ capa = self.client.capa()
+ self.assertFalse(b'STLS' in capa.keys())
+
class TestTimeouts(TestCase):
@@ -377,7 +483,7 @@ class TestTimeouts(TestCase):
def test_main():
tests = [TestPOP3Class, TestTimeouts,
- TestPOP3_SSLClass]
+ TestPOP3_SSLClass, TestPOP3_TLSClass]
thread_info = test_support.threading_setup()
try:
test_support.run_unittest(*tests)
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 02bb6acb82..1eceebe7ac 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -582,8 +582,10 @@ class PosixTester(unittest.TestCase):
r, w = os.pipe2(os.O_CLOEXEC|os.O_NONBLOCK)
self.addCleanup(os.close, r)
self.addCleanup(os.close, w)
- self.assertTrue(fcntl.fcntl(r, fcntl.F_GETFD) & fcntl.FD_CLOEXEC)
- self.assertTrue(fcntl.fcntl(w, fcntl.F_GETFD) & fcntl.FD_CLOEXEC)
+ self.assertFalse(os.get_inheritable(r))
+ self.assertFalse(os.get_inheritable(w))
+ self.assertTrue(fcntl.fcntl(r, fcntl.F_GETFL) & os.O_NONBLOCK)
+ self.assertTrue(fcntl.fcntl(w, fcntl.F_GETFL) & os.O_NONBLOCK)
# try reading from an empty pipe: this should fail, not block
self.assertRaises(OSError, os.read, r, 1)
# try a write big enough to fill-up the pipe: this should either
@@ -626,7 +628,7 @@ class PosixTester(unittest.TestCase):
self.assertEqual(st.st_flags | stat.UF_IMMUTABLE, new_st.st_flags)
try:
fd = open(target_file, 'w+')
- except IOError as e:
+ except OSError as e:
self.assertEqual(e.errno, errno.EPERM)
finally:
posix.chflags(target_file, st.st_flags)
@@ -684,41 +686,39 @@ class PosixTester(unittest.TestCase):
@unittest.skipUnless(hasattr(posix, 'getcwd'), 'test needs posix.getcwd()')
def test_getcwd_long_pathnames(self):
- if hasattr(posix, 'getcwd'):
- dirname = 'getcwd-test-directory-0123456789abcdef-01234567890abcdef'
- curdir = os.getcwd()
- base_path = os.path.abspath(support.TESTFN) + '.getcwd'
+ dirname = 'getcwd-test-directory-0123456789abcdef-01234567890abcdef'
+ curdir = os.getcwd()
+ base_path = os.path.abspath(support.TESTFN) + '.getcwd'
- try:
- os.mkdir(base_path)
- os.chdir(base_path)
- except:
-# Just returning nothing instead of the SkipTest exception,
-# because the test results in Error in that case.
-# Is that ok?
-# raise unittest.SkipTest("cannot create directory for testing")
- return
-
- def _create_and_do_getcwd(dirname, current_path_length = 0):
- try:
- os.mkdir(dirname)
- except:
- raise unittest.SkipTest("mkdir cannot create directory sufficiently deep for getcwd test")
-
- os.chdir(dirname)
- try:
- os.getcwd()
- if current_path_length < 1027:
- _create_and_do_getcwd(dirname, current_path_length + len(dirname) + 1)
- finally:
- os.chdir('..')
- os.rmdir(dirname)
-
- _create_and_do_getcwd(dirname)
+ try:
+ os.mkdir(base_path)
+ os.chdir(base_path)
+ except:
+ # Just returning nothing instead of the SkipTest exception, because
+ # the test results in Error in that case. Is that ok?
+ # raise unittest.SkipTest("cannot create directory for testing")
+ return
- finally:
- os.chdir(curdir)
- support.rmtree(base_path)
+ def _create_and_do_getcwd(dirname, current_path_length = 0):
+ try:
+ os.mkdir(dirname)
+ except:
+ raise unittest.SkipTest("mkdir cannot create directory sufficiently deep for getcwd test")
+
+ os.chdir(dirname)
+ try:
+ os.getcwd()
+ if current_path_length < 1027:
+ _create_and_do_getcwd(dirname, current_path_length + len(dirname) + 1)
+ finally:
+ os.chdir('..')
+ os.rmdir(dirname)
+
+ _create_and_do_getcwd(dirname)
+
+ finally:
+ os.chdir(curdir)
+ support.rmtree(base_path)
@unittest.skipUnless(hasattr(posix, 'getgrouplist'), "test needs posix.getgrouplist()")
@unittest.skipUnless(hasattr(pwd, 'getpwuid'), "test needs pwd.getpwuid()")
diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py
index 0e7d866485..412849cff3 100644
--- a/Lib/test/test_posixpath.py
+++ b/Lib/test/test_posixpath.py
@@ -186,63 +186,6 @@ class PosixPathTest(unittest.TestCase):
if not f.close():
f.close()
- @staticmethod
- def _create_file(filename):
- with open(filename, 'wb') as f:
- f.write(b'foo')
-
- def test_samefile(self):
- test_fn = support.TESTFN + "1"
- self._create_file(test_fn)
- self.assertTrue(posixpath.samefile(test_fn, test_fn))
- self.assertRaises(TypeError, posixpath.samefile)
-
- @unittest.skipIf(
- sys.platform.startswith('win'),
- "posixpath.samefile does not work on links in Windows")
- @unittest.skipUnless(hasattr(os, "symlink"),
- "Missing symlink implementation")
- def test_samefile_on_links(self):
- test_fn1 = support.TESTFN + "1"
- test_fn2 = support.TESTFN + "2"
- self._create_file(test_fn1)
-
- os.symlink(test_fn1, test_fn2)
- self.assertTrue(posixpath.samefile(test_fn1, test_fn2))
- os.remove(test_fn2)
-
- self._create_file(test_fn2)
- self.assertFalse(posixpath.samefile(test_fn1, test_fn2))
-
-
- def test_samestat(self):
- test_fn = support.TESTFN + "1"
- self._create_file(test_fn)
- test_fns = [test_fn]*2
- stats = map(os.stat, test_fns)
- self.assertTrue(posixpath.samestat(*stats))
-
- @unittest.skipIf(
- sys.platform.startswith('win'),
- "posixpath.samestat does not work on links in Windows")
- @unittest.skipUnless(hasattr(os, "symlink"),
- "Missing symlink implementation")
- def test_samestat_on_links(self):
- test_fn1 = support.TESTFN + "1"
- test_fn2 = support.TESTFN + "2"
- self._create_file(test_fn1)
- test_fns = (test_fn1, test_fn2)
- os.symlink(*test_fns)
- stats = map(os.stat, test_fns)
- self.assertTrue(posixpath.samestat(*stats))
- os.remove(test_fn2)
-
- self._create_file(test_fn2)
- stats = map(os.stat, test_fns)
- self.assertFalse(posixpath.samestat(*stats))
-
- self.assertRaises(TypeError, posixpath.samestat)
-
def test_ismount(self):
self.assertIs(posixpath.ismount("/"), True)
with warnings.catch_warnings():
@@ -595,11 +538,6 @@ class PosixPathTest(unittest.TestCase):
finally:
os.getcwdb = real_getcwdb
- def test_sameopenfile(self):
- fname = support.TESTFN + "1"
- with open(fname, "wb") as a, open(fname, "wb") as b:
- self.assertTrue(posixpath.sameopenfile(a.fileno(), b.fileno()))
-
class PosixCommonTest(test_genericpath.CommonTest, unittest.TestCase):
pathmodule = posixpath
diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py
index a85298e26f..3d364c4595 100644
--- a/Lib/test/test_pprint.py
+++ b/Lib/test/test_pprint.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
import pprint
import test.support
import unittest
@@ -530,6 +532,54 @@ frozenset2({0,
self.assertEqual(pprint.pformat(dict.fromkeys(keys, 0)),
'{%r: 0, %r: 0}' % tuple(sorted(keys, key=id)))
+ def test_str_wrap(self):
+ # pprint tries to wrap strings intelligently
+ fox = 'the quick brown fox jumped over a lazy dog'
+ self.assertEqual(pprint.pformat(fox, width=20), """\
+'the quick brown '
+'fox jumped over '
+'a lazy dog'""")
+ self.assertEqual(pprint.pformat({'a': 1, 'b': fox, 'c': 2},
+ width=26), """\
+{'a': 1,
+ 'b': 'the quick brown '
+ 'fox jumped over '
+ 'a lazy dog',
+ 'c': 2}""")
+ # With some special characters
+ # - \n always triggers a new line in the pprint
+ # - \t and \n are escaped
+ # - non-ASCII is allowed
+ # - an apostrophe doesn't disrupt the pprint
+ special = "Portons dix bons \"whiskys\"\nà l'avocat goujat\t qui fumait au zoo"
+ self.assertEqual(pprint.pformat(special, width=20), """\
+'Portons dix bons '
+'"whiskys"\\n'
+"à l'avocat "
+'goujat\\t qui '
+'fumait au zoo'""")
+ # An unwrappable string is formatted as its repr
+ unwrappable = "x" * 100
+ self.assertEqual(pprint.pformat(unwrappable, width=80), repr(unwrappable))
+ self.assertEqual(pprint.pformat(''), "''")
+ # Check that the pprint is a usable repr
+ special *= 10
+ for width in range(3, 40):
+ formatted = pprint.pformat(special, width=width)
+ self.assertEqual(eval("(" + formatted + ")"), special)
+
+ def test_compact(self):
+ o = ([list(range(i * i)) for i in range(5)] +
+ [list(range(i)) for i in range(6)])
+ expected = """\
+[[], [0], [0, 1, 2, 3],
+ [0, 1, 2, 3, 4, 5, 6, 7, 8],
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15],
+ [], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3],
+ [0, 1, 2, 3, 4]]"""
+ self.assertEqual(pprint.pformat(o, width=48, compact=True), expected)
+
class DottedPrettyPrinter(pprint.PrettyPrinter):
diff --git a/Lib/test/test_print.py b/Lib/test/test_print.py
index 9d6dbea46b..7eea349115 100644
--- a/Lib/test/test_print.py
+++ b/Lib/test/test_print.py
@@ -1,62 +1,55 @@
-"""Test correct operation of the print function.
-"""
-
-# In 2.6, this gives us the behavior we want. In 3.0, it has
-# no function, but it still must parse correctly.
-from __future__ import print_function
-
import unittest
-from test import support
+from io import StringIO
-try:
- # 3.x
- from io import StringIO
-except ImportError:
- # 2.x
- from StringIO import StringIO
+from test import support
NotDefined = object()
# A dispatch table all 8 combinations of providing
-# sep, end, and file
+# sep, end, and file.
# I use this machinery so that I'm not just passing default
-# values to print, I'm either passing or not passing in the
-# arguments
+# values to print, I'm either passing or not passing in the
+# arguments.
dispatch = {
(False, False, False):
- lambda args, sep, end, file: print(*args),
+ lambda args, sep, end, file: print(*args),
(False, False, True):
- lambda args, sep, end, file: print(file=file, *args),
+ lambda args, sep, end, file: print(file=file, *args),
(False, True, False):
- lambda args, sep, end, file: print(end=end, *args),
+ lambda args, sep, end, file: print(end=end, *args),
(False, True, True):
- lambda args, sep, end, file: print(end=end, file=file, *args),
+ lambda args, sep, end, file: print(end=end, file=file, *args),
(True, False, False):
- lambda args, sep, end, file: print(sep=sep, *args),
+ lambda args, sep, end, file: print(sep=sep, *args),
(True, False, True):
- lambda args, sep, end, file: print(sep=sep, file=file, *args),
+ lambda args, sep, end, file: print(sep=sep, file=file, *args),
(True, True, False):
- lambda args, sep, end, file: print(sep=sep, end=end, *args),
+ lambda args, sep, end, file: print(sep=sep, end=end, *args),
(True, True, True):
- lambda args, sep, end, file: print(sep=sep, end=end, file=file, *args),
- }
+ lambda args, sep, end, file: print(sep=sep, end=end, file=file, *args),
+}
+
# Class used to test __str__ and print
class ClassWith__str__:
def __init__(self, x):
self.x = x
+
def __str__(self):
return self.x
+
class TestPrint(unittest.TestCase):
+ """Test correct operation of the print function."""
+
def check(self, expected, args,
- sep=NotDefined, end=NotDefined, file=NotDefined):
+ sep=NotDefined, end=NotDefined, file=NotDefined):
# Capture sys.stdout in a StringIO. Call print with args,
- # and with sep, end, and file, if they're defined. Result
- # must match expected.
+ # and with sep, end, and file, if they're defined. Result
+ # must match expected.
- # Look up the actual function to call, based on if sep, end, and file
- # are defined
+ # Look up the actual function to call, based on if sep, end,
+ # and file are defined.
fn = dispatch[(sep is not NotDefined,
end is not NotDefined,
file is not NotDefined)]
@@ -69,7 +62,7 @@ class TestPrint(unittest.TestCase):
def test_print(self):
def x(expected, args, sep=NotDefined, end=NotDefined):
# Run the test 2 ways: not using file, and using
- # file directed to a StringIO
+ # file directed to a StringIO.
self.check(expected, args, sep=sep, end=end)
@@ -101,11 +94,6 @@ class TestPrint(unittest.TestCase):
x('*\n', (ClassWith__str__('*'),))
x('abc 1\n', (ClassWith__str__('abc'), 1))
-# # 2.x unicode tests
-# x(u'1 2\n', ('1', u'2'))
-# x(u'u\1234\n', (u'u\1234',))
-# x(u' abc 1\n', (' ', ClassWith__str__(u'abc'), 1))
-
# errors
self.assertRaises(TypeError, print, '', sep=3)
self.assertRaises(TypeError, print, '', end=3)
@@ -113,12 +101,14 @@ class TestPrint(unittest.TestCase):
def test_print_flush(self):
# operation of the flush flag
- class filelike():
+ class filelike:
def __init__(self):
self.written = ''
self.flushed = 0
+
def write(self, str):
self.written += str
+
def flush(self):
self.flushed += 1
@@ -130,15 +120,13 @@ class TestPrint(unittest.TestCase):
self.assertEqual(f.flushed, 2)
# ensure exceptions from flush are passed through
- class noflush():
+ class noflush:
def write(self, str):
pass
+
def flush(self):
raise RuntimeError
self.assertRaises(RuntimeError, print, 1, file=noflush(), flush=True)
-def test_main():
- support.run_unittest(TestPrint)
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_profile.py b/Lib/test/test_profile.py
index cd7ec58e23..1fc3c42669 100644
--- a/Lib/test/test_profile.py
+++ b/Lib/test/test_profile.py
@@ -3,9 +3,11 @@
import sys
import pstats
import unittest
+import os
from difflib import unified_diff
from io import StringIO
-from test.support import run_unittest
+from test.support import TESTFN, run_unittest, unlink
+from contextlib import contextmanager
import profile
from test.profilee import testfunc, timer
@@ -14,9 +16,13 @@ from test.profilee import testfunc, timer
class ProfileTest(unittest.TestCase):
profilerclass = profile.Profile
+ profilermodule = profile
methodnames = ['print_stats', 'print_callers', 'print_callees']
expected_max_output = ':0(max)'
+ def tearDown(self):
+ unlink(TESTFN)
+
def get_expected_output(self):
return _ProfileOutput
@@ -74,6 +80,19 @@ class ProfileTest(unittest.TestCase):
self.assertIn(self.expected_max_output, res,
"Profiling {0!r} didn't report max:\n{1}".format(stmt, res))
+ def test_run(self):
+ with silent():
+ self.profilermodule.run("int('1')")
+ self.profilermodule.run("int('1')", filename=TESTFN)
+ self.assertTrue(os.path.exists(TESTFN))
+
+ def test_runctx(self):
+ with silent():
+ self.profilermodule.runctx("testfunc()", globals(), locals())
+ self.profilermodule.runctx("testfunc()", globals(), locals(),
+ filename=TESTFN)
+ self.assertTrue(os.path.exists(TESTFN))
+
def regenerate_expected_output(filename, cls):
filename = filename.rstrip('co')
@@ -95,6 +114,14 @@ def regenerate_expected_output(filename, cls):
method, results[i+1]))
f.write('\nif __name__ == "__main__":\n main()\n')
+@contextmanager
+def silent():
+ stdout = sys.stdout
+ try:
+ sys.stdout = StringIO()
+ yield
+ finally:
+ sys.stdout = stdout
def test_main():
run_unittest(ProfileTest)
diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py
index 29297f8841..8916861f5b 100644
--- a/Lib/test/test_pty.py
+++ b/Lib/test/test_pty.py
@@ -187,7 +187,7 @@ class PtyTest(unittest.TestCase):
##debug("Reading from master_fd now that the child has exited")
##try:
## s1 = os.read(master_fd, 1024)
- ##except os.error:
+ ##except OSError:
## pass
##else:
## raise TestFailed("Read from master_fd did not raise exception")
diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
index f3c1a6a44b..154c08a6a2 100644
--- a/Lib/test/test_py_compile.py
+++ b/Lib/test/test_py_compile.py
@@ -1,7 +1,9 @@
-import imp
+import importlib.util
import os
import py_compile
import shutil
+import stat
+import sys
import tempfile
import unittest
@@ -13,7 +15,7 @@ class PyCompileTests(unittest.TestCase):
self.directory = tempfile.mkdtemp()
self.source_path = os.path.join(self.directory, '_test.py')
self.pyc_path = self.source_path + 'c'
- self.cache_path = imp.cache_from_source(self.source_path)
+ self.cache_path = importlib.util.cache_from_source(self.source_path)
self.cwd_drive = os.path.splitdrive(os.getcwd())[0]
# In these tests we compute relative paths. When using Windows, the
# current working directory path and the 'self.source_path' might be
@@ -35,6 +37,26 @@ class PyCompileTests(unittest.TestCase):
self.assertTrue(os.path.exists(self.pyc_path))
self.assertFalse(os.path.exists(self.cache_path))
+ def test_do_not_overwrite_symlinks(self):
+ # In the face of a cfile argument being a symlink, bail out.
+ # Issue #17222
+ try:
+ os.symlink(self.pyc_path + '.actual', self.pyc_path)
+ except (NotImplementedError, OSError):
+ self.skipTest('need to be able to create a symlink for a file')
+ else:
+ assert os.path.islink(self.pyc_path)
+ with self.assertRaises(FileExistsError):
+ py_compile.compile(self.source_path, self.pyc_path)
+
+ @unittest.skipIf(not os.path.exists(os.devnull) or os.path.isfile(os.devnull),
+ 'requires os.devnull and for it to be a non-regular file')
+ def test_do_not_overwrite_nonregular_files(self):
+ # In the face of a cfile argument being a non-regular file, bail out.
+ # Issue #17222
+ with self.assertRaises(FileExistsError):
+ py_compile.compile(self.source_path, os.devnull)
+
def test_cache_path(self):
py_compile.compile(self.source_path)
self.assertTrue(os.path.exists(self.cache_path))
@@ -54,8 +76,22 @@ class PyCompileTests(unittest.TestCase):
self.assertTrue(os.path.exists(self.pyc_path))
self.assertFalse(os.path.exists(self.cache_path))
-def test_main():
- support.run_unittest(PyCompileTests)
+ @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
+ 'non-root user required')
+ @unittest.skipIf(os.name == 'nt',
+ 'cannot control directory permissions on Windows')
+ def test_exceptions_propagate(self):
+ # Make sure that exceptions raised thanks to issues with writing
+ # bytecode.
+ # http://bugs.python.org/issue17244
+ mode = os.stat(self.directory)
+ os.chmod(self.directory, stat.S_IREAD)
+ try:
+ with self.assertRaises(IOError):
+ py_compile.compile(self.source_path, self.pyc_path)
+ finally:
+ os.chmod(self.directory, mode.st_mode)
+
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py
index e83989e2d8..88aff898d4 100644
--- a/Lib/test/test_pyclbr.py
+++ b/Lib/test/test_pyclbr.py
@@ -142,7 +142,7 @@ class PyclbrTest(TestCase):
self.checkModule('pyclbr')
self.checkModule('ast')
self.checkModule('doctest', ignore=("TestResults", "_SpoofOut",
- "DocTestCase"))
+ "DocTestCase", '_DocTestSuite'))
self.checkModule('difflib', ignore=("Match",))
def test_decorators(self):
@@ -158,7 +158,7 @@ class PyclbrTest(TestCase):
cm('random', ignore=('Random',)) # from _random import Random as CoreGenerator
cm('cgi', ignore=('log',)) # set with = in module
cm('pickle')
- cm('aifc', ignore=('openfp',)) # set with = in module
+ cm('aifc', ignore=('openfp', '_aifc_params')) # set with = in module
cm('sre_parse', ignore=('dump',)) # from sre_constants import *
cm('pdb')
cm('pydoc')
diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py
index 43f4163dae..cdf28a4d3d 100644
--- a/Lib/test/test_pydoc.py
+++ b/Lib/test/test_pydoc.py
@@ -11,6 +11,7 @@ import re
import string
import test.support
import time
+import types
import unittest
import xml.etree
import textwrap
@@ -29,10 +30,6 @@ try:
except ImportError:
threading = None
-# Just in case sys.modules["test"] has the optional attribute __loader__.
-if hasattr(pydoc_mod, "__loader__"):
- del pydoc_mod.__loader__
-
if test.support.HAVE_DOCSTRINGS:
expected_data_docstrings = (
'dictionary for instance variables (if defined)',
@@ -212,6 +209,75 @@ missing_pattern = "no Python documentation found for '%s'"
# output pattern for module with bad imports
badimport_pattern = "problem in %s - ImportError: No module named %r"
+expected_dynamicattribute_pattern = """
+Help on class DA in module %s:
+
+class DA(builtins.object)
+ | Data descriptors defined here:
+ |\x20\x20
+ | __dict__%s
+ |\x20\x20
+ | __weakref__%s
+ |\x20\x20
+ | ham
+ |\x20\x20
+ | ----------------------------------------------------------------------
+ | Data and other attributes inherited from Meta:
+ |\x20\x20
+ | ham = 'spam'
+""".strip()
+
+expected_virtualattribute_pattern1 = """
+Help on class Class in module %s:
+
+class Class(builtins.object)
+ | Data and other attributes inherited from Meta:
+ |\x20\x20
+ | LIFE = 42
+""".strip()
+
+expected_virtualattribute_pattern2 = """
+Help on class Class1 in module %s:
+
+class Class1(builtins.object)
+ | Data and other attributes inherited from Meta1:
+ |\x20\x20
+ | one = 1
+""".strip()
+
+expected_virtualattribute_pattern3 = """
+Help on class Class2 in module %s:
+
+class Class2(Class1)
+ | Method resolution order:
+ | Class2
+ | Class1
+ | builtins.object
+ |\x20\x20
+ | Data and other attributes inherited from Meta1:
+ |\x20\x20
+ | one = 1
+ |\x20\x20
+ | ----------------------------------------------------------------------
+ | Data and other attributes inherited from Meta3:
+ |\x20\x20
+ | three = 3
+ |\x20\x20
+ | ----------------------------------------------------------------------
+ | Data and other attributes inherited from Meta2:
+ |\x20\x20
+ | two = 2
+""".strip()
+
+expected_missingattribute_pattern = """
+Help on class C in module %s:
+
+class C(builtins.object)
+ | Data and other attributes defined here:
+ |\x20\x20
+ | here = 'present!'
+""".strip()
+
def run_pydoc(module_name, *args, **env):
"""
Runs pydoc on the specified module. Returns the stripped
@@ -421,6 +487,31 @@ class PydocDocTest(unittest.TestCase):
synopsis = pydoc.synopsis(TESTFN, {})
self.assertEqual(synopsis, 'line 1: h\xe9')
+ def test_splitdoc_with_description(self):
+ example_string = "I Am A Doc\n\n\nHere is my description"
+ self.assertEqual(pydoc.splitdoc(example_string),
+ ('I Am A Doc', '\nHere is my description'))
+
+ def test_is_object_or_method(self):
+ doc = pydoc.Doc()
+ # Bound Method
+ self.assertTrue(pydoc._is_some_method(doc.fail))
+ # Method Descriptor
+ self.assertTrue(pydoc._is_some_method(int.__add__))
+ # String
+ self.assertFalse(pydoc._is_some_method("I am not a method"))
+
+ def test_is_package_when_not_package(self):
+ with test.support.temp_cwd() as test_dir:
+ self.assertFalse(pydoc.ispackage(test_dir))
+
+ def test_is_package_when_is_package(self):
+ with test.support.temp_cwd() as test_dir:
+ init_path = os.path.join(test_dir, '__init__.py')
+ open(init_path, 'w').close()
+ self.assertTrue(pydoc.ispackage(test_dir))
+ os.remove(init_path)
+
def test_allmethods(self):
# issue 17476: allmethods was no longer returning unbound methods.
# This test is a bit fragile in the face of changes to object and type,
@@ -615,6 +706,127 @@ class TestHelper(unittest.TestCase):
self.assertEqual(sorted(pydoc.Helper.keywords),
sorted(keyword.kwlist))
+class PydocWithMetaClasses(unittest.TestCase):
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
+ 'trace function introduces __locals__ unexpectedly')
+ def test_DynamicClassAttribute(self):
+ class Meta(type):
+ def __getattr__(self, name):
+ if name == 'ham':
+ return 'spam'
+ return super().__getattr__(name)
+ class DA(metaclass=Meta):
+ @types.DynamicClassAttribute
+ def ham(self):
+ return 'eggs'
+ expected_text_data_docstrings = tuple('\n | ' + s if s else ''
+ for s in expected_data_docstrings)
+ output = StringIO()
+ helper = pydoc.Helper(output=output)
+ helper(DA)
+ expected_text = expected_dynamicattribute_pattern % (
+ (__name__,) + expected_text_data_docstrings[:2])
+ result = output.getvalue().strip()
+ if result != expected_text:
+ print_diffs(expected_text, result)
+ self.fail("outputs are not equal, see diff above")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
+ 'trace function introduces __locals__ unexpectedly')
+ def test_virtualClassAttributeWithOneMeta(self):
+ class Meta(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'LIFE']
+ def __getattr__(self, name):
+ if name =='LIFE':
+ return 42
+ return super().__getattr(name)
+ class Class(metaclass=Meta):
+ pass
+ output = StringIO()
+ helper = pydoc.Helper(output=output)
+ helper(Class)
+ expected_text = expected_virtualattribute_pattern1 % __name__
+ result = output.getvalue().strip()
+ if result != expected_text:
+ print_diffs(expected_text, result)
+ self.fail("outputs are not equal, see diff above")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
+ 'trace function introduces __locals__ unexpectedly')
+ def test_virtualClassAttributeWithTwoMeta(self):
+ class Meta1(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'one']
+ def __getattr__(self, name):
+ if name =='one':
+ return 1
+ return super().__getattr__(name)
+ class Meta2(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'two']
+ def __getattr__(self, name):
+ if name =='two':
+ return 2
+ return super().__getattr__(name)
+ class Meta3(Meta1, Meta2):
+ def __dir__(cls):
+ return list(sorted(set(
+ ['__class__', '__module__', '__name__', 'three'] +
+ Meta1.__dir__(cls) + Meta2.__dir__(cls))))
+ def __getattr__(self, name):
+ if name =='three':
+ return 3
+ return super().__getattr__(name)
+ class Class1(metaclass=Meta1):
+ pass
+ class Class2(Class1, metaclass=Meta3):
+ pass
+ fail1 = fail2 = False
+ output = StringIO()
+ helper = pydoc.Helper(output=output)
+ helper(Class1)
+ expected_text1 = expected_virtualattribute_pattern2 % __name__
+ result1 = output.getvalue().strip()
+ if result1 != expected_text1:
+ print_diffs(expected_text1, result1)
+ fail1 = True
+ output = StringIO()
+ helper = pydoc.Helper(output=output)
+ helper(Class2)
+ expected_text2 = expected_virtualattribute_pattern3 % __name__
+ result2 = output.getvalue().strip()
+ if result2 != expected_text2:
+ print_diffs(expected_text2, result2)
+ fail2 = True
+ if fail1 or fail2:
+ self.fail("outputs are not equal, see diff above")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
+ 'trace function introduces __locals__ unexpectedly')
+ def test_buggy_dir(self):
+ class M(type):
+ def __dir__(cls):
+ return ['__class__', '__name__', 'missing', 'here']
+ class C(metaclass=M):
+ here = 'present!'
+ output = StringIO()
+ helper = pydoc.Helper(output=output)
+ helper(C)
+ expected_text = expected_missingattribute_pattern % __name__
+ result = output.getvalue().strip()
+ if result != expected_text:
+ print_diffs(expected_text, result)
+ self.fail("outputs are not equal, see diff above")
+
@reap_threads
def test_main():
try:
@@ -624,6 +836,7 @@ def test_main():
PydocServerTest,
PydocUrlHandlerTest,
TestHelper,
+ PydocWithMetaClasses,
)
finally:
reap_children()
diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py
index d363a6a1d1..7d1a53a550 100644
--- a/Lib/test/test_random.py
+++ b/Lib/test/test_random.py
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
import unittest
+import unittest.mock
import random
import time
import pickle
import warnings
+from functools import partial
from math import log, exp, pi, fsum, sin
from test import support
@@ -46,6 +48,48 @@ class TestBasicOps:
self.assertRaises(TypeError, self.gen.seed, 1, 2, 3, 4)
self.assertRaises(TypeError, type(self.gen), [])
+ @unittest.mock.patch('random._urandom') # os.urandom
+ def test_seed_when_randomness_source_not_found(self, urandom_mock):
+ # Random.seed() uses time.time() when an operating system specific
+ # randomness source is not found. To test this on machines were it
+ # exists, run the above test, test_seedargs(), again after mocking
+ # os.urandom() so that it raises the exception expected when the
+ # randomness source is not available.
+ urandom_mock.side_effect = NotImplementedError
+ self.test_seedargs()
+
+ def test_shuffle(self):
+ shuffle = self.gen.shuffle
+ lst = []
+ shuffle(lst)
+ self.assertEqual(lst, [])
+ lst = [37]
+ shuffle(lst)
+ self.assertEqual(lst, [37])
+ seqs = [list(range(n)) for n in range(10)]
+ shuffled_seqs = [list(range(n)) for n in range(10)]
+ for shuffled_seq in shuffled_seqs:
+ shuffle(shuffled_seq)
+ for (seq, shuffled_seq) in zip(seqs, shuffled_seqs):
+ self.assertEqual(len(seq), len(shuffled_seq))
+ self.assertEqual(set(seq), set(shuffled_seq))
+ # The above tests all would pass if the shuffle was a
+ # no-op. The following non-deterministic test covers that. It
+ # asserts that the shuffled sequence of 1000 distinct elements
+ # must be different from the original one. Although there is
+ # mathematically a non-zero probability that this could
+ # actually happen in a genuinely random shuffle, it is
+ # completely negligible, given that the number of possible
+ # permutations of 1000 objects is 1000! (factorial of 1000),
+ # which is considerably larger than the number of atoms in the
+ # universe...
+ lst = list(range(1000))
+ shuffled_lst = list(range(1000))
+ shuffle(shuffled_lst)
+ self.assertTrue(lst != shuffled_lst)
+ shuffle(lst)
+ self.assertTrue(lst != shuffled_lst)
+
def test_choice(self):
choice = self.gen.choice
with self.assertRaises(IndexError):
@@ -65,6 +109,8 @@ class TestBasicOps:
self.assertEqual(len(uniq), k)
self.assertTrue(uniq <= set(population))
self.assertEqual(self.gen.sample([], 0), []) # test edge case N==k==0
+ # Exception raised if size of sample exceeds that of population
+ self.assertRaises(ValueError, self.gen.sample, population, N+1)
def test_sample_distribution(self):
# For the entire allowable range of 0 <= k <= N, validate that
@@ -205,6 +251,25 @@ class SystemRandom_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertEqual(set(range(start,stop)),
set([self.gen.randrange(start,stop) for i in range(100)]))
+ def test_randrange_nonunit_step(self):
+ rint = self.gen.randrange(0, 10, 2)
+ self.assertIn(rint, (0, 2, 4, 6, 8))
+ rint = self.gen.randrange(0, 2, 2)
+ self.assertEqual(rint, 0)
+
+ def test_randrange_errors(self):
+ raises = partial(self.assertRaises, ValueError, self.gen.randrange)
+ # Empty range
+ raises(3, 3)
+ raises(-721)
+ raises(0, 100, -12)
+ # Non-integer start/stop
+ raises(3.14159)
+ raises(0, 2.71828)
+ # Zero and non-integer step
+ raises(0, 42, 0)
+ raises(0, 42, 3.14159)
+
def test_genrandbits(self):
# Verify ranges
for k in range(1, 1000):
@@ -274,6 +339,16 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
# Last element s/b an int also
self.assertRaises(TypeError, self.gen.setstate, (2, (0,)*624+('a',), None))
+ # Little trick to make "tuple(x % (2**32) for x in internalstate)"
+ # raise ValueError. I cannot think of a simple way to achieve this, so
+ # I am opting for using a generator as the middle argument of setstate
+ # which attempts to cast a NaN to integer.
+ state_values = self.gen.getstate()[1]
+ state_values = list(state_values)
+ state_values[-1] = float('nan')
+ state = (int(x) for x in state_values)
+ self.assertRaises(TypeError, self.gen.setstate, (2, state, None))
+
def test_referenceImplementation(self):
# Compare the python implementation with results from the original
# code. Create 2000 53-bit precision random floats. Compare only
@@ -413,6 +488,38 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertEqual(k, numbits) # note the stronger assertion
self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion
+ @unittest.mock.patch('random.Random.random')
+ def test_randbelow_overriden_random(self, random_mock):
+ # Random._randbelow() can only use random() when the built-in one
+ # has been overridden but no new getrandbits() method was supplied.
+ random_mock.side_effect = random.SystemRandom().random
+ maxsize = 1<<random.BPF
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ # Population range too large (n >= maxsize)
+ self.gen._randbelow(maxsize+1, maxsize = maxsize)
+ self.gen._randbelow(5640, maxsize = maxsize)
+
+ # This might be going too far to test a single line, but because of our
+ # noble aim of achieving 100% test coverage we need to write a case in
+ # which the following line in Random._randbelow() gets executed:
+ #
+ # rem = maxsize % n
+ # limit = (maxsize - rem) / maxsize
+ # r = random()
+ # while r >= limit:
+ # r = random() # <== *This line* <==<
+ #
+ # Therefore, to guarantee that the while loop is executed at least
+ # once, we need to mock random() so that it returns a number greater
+ # than 'limit' the first time it gets called.
+
+ n = 42
+ epsilon = 0.01
+ limit = (maxsize - (maxsize % n)) / maxsize
+ random_mock.side_effect = [limit + epsilon, limit - epsilon]
+ self.gen._randbelow(n, maxsize = maxsize)
+
def test_randrange_bug_1590891(self):
start = 1000000000000
stop = -100000000000000000000
@@ -530,6 +637,106 @@ class TestDistributions(unittest.TestCase):
random.vonmisesvariate(0, 1e15)
random.vonmisesvariate(0, 1e100)
+ def test_gammavariate_errors(self):
+ # Both alpha and beta must be > 0.0
+ self.assertRaises(ValueError, random.gammavariate, -1, 3)
+ self.assertRaises(ValueError, random.gammavariate, 0, 2)
+ self.assertRaises(ValueError, random.gammavariate, 2, 0)
+ self.assertRaises(ValueError, random.gammavariate, 1, -3)
+
+ @unittest.mock.patch('random.Random.random')
+ def test_gammavariate_full_code_coverage(self, random_mock):
+ # There are three different possibilities in the current implementation
+ # of random.gammavariate(), depending on the value of 'alpha'. What we
+ # are going to do here is to fix the values returned by random() to
+ # generate test cases that provide 100% line coverage of the method.
+
+ # #1: alpha > 1.0: we want the first random number to be outside the
+ # [1e-7, .9999999] range, so that the continue statement executes
+ # once. The values of u1 and u2 will be 0.5 and 0.3, respectively.
+ random_mock.side_effect = [1e-8, 0.5, 0.3]
+ returned_value = random.gammavariate(1.1, 2.3)
+ self.assertAlmostEqual(returned_value, 2.53)
+
+ # #2: alpha == 1: first random number less than 1e-7 to that the body
+ # of the while loop executes once. Then random.random() returns 0.45,
+ # which causes while to stop looping and the algorithm to terminate.
+ random_mock.side_effect = [1e-8, 0.45]
+ returned_value = random.gammavariate(1.0, 3.14)
+ self.assertAlmostEqual(returned_value, 2.507314166123803)
+
+ # #3: 0 < alpha < 1. This is the most complex region of code to cover,
+ # as there are multiple if-else statements. Let's take a look at the
+ # source code, and determine the values that we need accordingly:
+ #
+ # while 1:
+ # u = random()
+ # b = (_e + alpha)/_e
+ # p = b*u
+ # if p <= 1.0: # <=== (A)
+ # x = p ** (1.0/alpha)
+ # else: # <=== (B)
+ # x = -_log((b-p)/alpha)
+ # u1 = random()
+ # if p > 1.0: # <=== (C)
+ # if u1 <= x ** (alpha - 1.0): # <=== (D)
+ # break
+ # elif u1 <= _exp(-x): # <=== (E)
+ # break
+ # return x * beta
+ #
+ # First, we want (A) to be True. For that we need that:
+ # b*random() <= 1.0
+ # r1 = random() <= 1.0 / b
+ #
+ # We now get to the second if-else branch, and here, since p <= 1.0,
+ # (C) is False and we take the elif branch, (E). For it to be True,
+ # so that the break is executed, we need that:
+ # r2 = random() <= _exp(-x)
+ # r2 <= _exp(-(p ** (1.0/alpha)))
+ # r2 <= _exp(-((b*r1) ** (1.0/alpha)))
+
+ _e = random._e
+ _exp = random._exp
+ _log = random._log
+ alpha = 0.35
+ beta = 1.45
+ b = (_e + alpha)/_e
+ epsilon = 0.01
+
+ r1 = 0.8859296441566 # 1.0 / b
+ r2 = 0.3678794411714 # _exp(-((b*r1) ** (1.0/alpha)))
+
+ # These four "random" values result in the following trace:
+ # (A) True, (E) False --> [next iteration of while]
+ # (A) True, (E) True --> [while loop breaks]
+ random_mock.side_effect = [r1, r2 + epsilon, r1, r2]
+ returned_value = random.gammavariate(alpha, beta)
+ self.assertAlmostEqual(returned_value, 1.4499999999997544)
+
+ # Let's now make (A) be False. If this is the case, when we get to the
+ # second if-else 'p' is greater than 1, so (C) evaluates to True. We
+ # now encounter a second if statement, (D), which in order to execute
+ # must satisfy the following condition:
+ # r2 <= x ** (alpha - 1.0)
+ # r2 <= (-_log((b-p)/alpha)) ** (alpha - 1.0)
+ # r2 <= (-_log((b-(b*r1))/alpha)) ** (alpha - 1.0)
+ r1 = 0.8959296441566 # (1.0 / b) + epsilon -- so that (A) is False
+ r2 = 0.9445400408898141
+
+ # And these four values result in the following trace:
+ # (B) and (C) True, (D) False --> [next iteration of while]
+ # (B) and (C) True, (D) True [while loop breaks]
+ random_mock.side_effect = [r1, r2 + epsilon, r1, r2]
+ returned_value = random.gammavariate(alpha, beta)
+ self.assertAlmostEqual(returned_value, 1.5830349561760781)
+
+ @unittest.mock.patch('random.Random.gammavariate')
+ def test_betavariate_return_zero(self, gammavariate_mock):
+ # betavariate() returns zero when the Gamma distribution
+ # that it uses internally returns this same value.
+ gammavariate_mock.return_value = 0.0
+ self.assertEqual(0.0, random.betavariate(2.71828, 3.14159))
class TestModule(unittest.TestCase):
def testMagicConstants(self):
diff --git a/Lib/test/test_range.py b/Lib/test/test_range.py
index 2a13bfeabd..d9d4cf88a2 100644
--- a/Lib/test/test_range.py
+++ b/Lib/test/test_range.py
@@ -313,7 +313,7 @@ class RangeTest(unittest.TestCase):
self.assertRaises(TypeError, range, IN())
# Test use of user-defined classes in slice indices.
- self.assertEqual(list(range(10)[:I(5)]), list(range(5)))
+ self.assertEqual(range(10)[:I(5)], range(5))
with self.assertRaises(RuntimeError):
range(0, 10)[:IX()]
@@ -353,9 +353,10 @@ class RangeTest(unittest.TestCase):
(13, 21, 3), (-2, 2, 2), (2**65, 2**65+2)]
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
for t in testcases:
- r = range(*t)
- self.assertEqual(list(pickle.loads(pickle.dumps(r, proto))),
- list(r))
+ with self.subTest(proto=proto, test=t):
+ r = range(*t)
+ self.assertEqual(list(pickle.loads(pickle.dumps(r, proto))),
+ list(r))
def test_iterator_pickling(self):
testcases = [(13,), (0, 11), (-22, 10), (20, 3, -1),
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index f093812442..a4e11c6746 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -3,10 +3,12 @@ from test.support import verbose, run_unittest, gc_collect, bigmemtest, _2G, \
import io
import re
from re import Scanner
+import sre_compile
import sre_constants
import sys
import string
import traceback
+import unittest
from weakref import proxy
# Misc tests from Tim Peters' re.doc
@@ -15,10 +17,26 @@ from weakref import proxy
# what you're doing. Some of these tests were carefully modeled to
# cover most of the code.
-import unittest
+class S(str):
+ def __getitem__(self, index):
+ return S(super().__getitem__(index))
+
+class B(bytes):
+ def __getitem__(self, index):
+ return B(super().__getitem__(index))
class ReTests(unittest.TestCase):
+ def assertTypedEqual(self, actual, expect, msg=None):
+ self.assertEqual(actual, expect, msg)
+ def recurse(actual, expect):
+ if isinstance(expect, (tuple, list)):
+ for x, y in zip(actual, expect):
+ recurse(x, y)
+ else:
+ self.assertIs(type(actual), type(expect), msg)
+ recurse(actual, expect)
+
def test_keep_buffer(self):
# See bug 14212
b = bytearray(b'x')
@@ -53,6 +71,15 @@ class ReTests(unittest.TestCase):
return str(int_value + 1)
def test_basic_re_sub(self):
+ self.assertTypedEqual(re.sub('y', 'a', 'xyz'), 'xaz')
+ self.assertTypedEqual(re.sub('y', S('a'), S('xyz')), 'xaz')
+ self.assertTypedEqual(re.sub(b'y', b'a', b'xyz'), b'xaz')
+ self.assertTypedEqual(re.sub(b'y', B(b'a'), B(b'xyz')), b'xaz')
+ self.assertTypedEqual(re.sub(b'y', bytearray(b'a'), bytearray(b'xyz')), b'xaz')
+ self.assertTypedEqual(re.sub(b'y', memoryview(b'a'), memoryview(b'xyz')), b'xaz')
+ for y in ("\xe0", "\u0430", "\U0001d49c"):
+ self.assertEqual(re.sub(y, 'a', 'x%sz' % y), 'xaz')
+
self.assertEqual(re.sub("(?i)b+", "x", "bbbb BBBB"), 'x x')
self.assertEqual(re.sub(r'\d+', self.bump_num, '08.2 -2 23x99y'),
'9.3 -3 24x100y')
@@ -210,10 +237,29 @@ class ReTests(unittest.TestCase):
self.assertEqual(re.subn("b*", "x", "xyz", 2), ('xxxyz', 2))
def test_re_split(self):
- self.assertEqual(re.split(":", ":a:b::c"), ['', 'a', 'b', '', 'c'])
- self.assertEqual(re.split(":*", ":a:b::c"), ['', 'a', 'b', 'c'])
- self.assertEqual(re.split("(:*)", ":a:b::c"),
- ['', ':', 'a', ':', 'b', '::', 'c'])
+ for string in ":a:b::c", S(":a:b::c"):
+ self.assertTypedEqual(re.split(":", string),
+ ['', 'a', 'b', '', 'c'])
+ self.assertTypedEqual(re.split(":*", string),
+ ['', 'a', 'b', 'c'])
+ self.assertTypedEqual(re.split("(:*)", string),
+ ['', ':', 'a', ':', 'b', '::', 'c'])
+ for string in (b":a:b::c", B(b":a:b::c"), bytearray(b":a:b::c"),
+ memoryview(b":a:b::c")):
+ self.assertTypedEqual(re.split(b":", string),
+ [b'', b'a', b'b', b'', b'c'])
+ self.assertTypedEqual(re.split(b":*", string),
+ [b'', b'a', b'b', b'c'])
+ self.assertTypedEqual(re.split(b"(:*)", string),
+ [b'', b':', b'a', b':', b'b', b'::', b'c'])
+ for a, b, c in ("\xe0\xdf\xe7", "\u0430\u0431\u0432",
+ "\U0001d49c\U0001d49e\U0001d4b5"):
+ string = ":%s:%s::%s" % (a, b, c)
+ self.assertEqual(re.split(":", string), ['', a, b, '', c])
+ self.assertEqual(re.split(":*", string), ['', a, b, c])
+ self.assertEqual(re.split("(:*)", string),
+ ['', ':', a, ':', b, '::', c])
+
self.assertEqual(re.split("(?::*)", ":a:b::c"), ['', 'a', 'b', 'c'])
self.assertEqual(re.split("(:)*", ":a:b::c"),
['', ':', 'a', ':', 'b', ':', 'c'])
@@ -235,22 +281,53 @@ class ReTests(unittest.TestCase):
def test_re_findall(self):
self.assertEqual(re.findall(":+", "abc"), [])
- self.assertEqual(re.findall(":+", "a:b::c:::d"), [":", "::", ":::"])
- self.assertEqual(re.findall("(:+)", "a:b::c:::d"), [":", "::", ":::"])
- self.assertEqual(re.findall("(:)(:*)", "a:b::c:::d"), [(":", ""),
- (":", ":"),
- (":", "::")])
+ for string in "a:b::c:::d", S("a:b::c:::d"):
+ self.assertTypedEqual(re.findall(":+", string),
+ [":", "::", ":::"])
+ self.assertTypedEqual(re.findall("(:+)", string),
+ [":", "::", ":::"])
+ self.assertTypedEqual(re.findall("(:)(:*)", string),
+ [(":", ""), (":", ":"), (":", "::")])
+ for string in (b"a:b::c:::d", B(b"a:b::c:::d"), bytearray(b"a:b::c:::d"),
+ memoryview(b"a:b::c:::d")):
+ self.assertTypedEqual(re.findall(b":+", string),
+ [b":", b"::", b":::"])
+ self.assertTypedEqual(re.findall(b"(:+)", string),
+ [b":", b"::", b":::"])
+ self.assertTypedEqual(re.findall(b"(:)(:*)", string),
+ [(b":", b""), (b":", b":"), (b":", b"::")])
+ for x in ("\xe0", "\u0430", "\U0001d49c"):
+ xx = x * 2
+ xxx = x * 3
+ string = "a%sb%sc%sd" % (x, xx, xxx)
+ self.assertEqual(re.findall("%s+" % x, string), [x, xx, xxx])
+ self.assertEqual(re.findall("(%s+)" % x, string), [x, xx, xxx])
+ self.assertEqual(re.findall("(%s)(%s*)" % (x, x), string),
+ [(x, ""), (x, x), (x, xx)])
def test_bug_117612(self):
self.assertEqual(re.findall(r"(a|(b))", "aba"),
[("a", ""),("b", "b"),("a", "")])
def test_re_match(self):
- self.assertEqual(re.match('a', 'a').groups(), ())
- self.assertEqual(re.match('(a)', 'a').groups(), ('a',))
- self.assertEqual(re.match(r'(a)', 'a').group(0), 'a')
- self.assertEqual(re.match(r'(a)', 'a').group(1), 'a')
- self.assertEqual(re.match(r'(a)', 'a').group(1, 1), ('a', 'a'))
+ for string in 'a', S('a'):
+ self.assertEqual(re.match('a', string).groups(), ())
+ self.assertEqual(re.match('(a)', string).groups(), ('a',))
+ self.assertEqual(re.match('(a)', string).group(0), 'a')
+ self.assertEqual(re.match('(a)', string).group(1), 'a')
+ self.assertEqual(re.match('(a)', string).group(1, 1), ('a', 'a'))
+ for string in b'a', B(b'a'), bytearray(b'a'), memoryview(b'a'):
+ self.assertEqual(re.match(b'a', string).groups(), ())
+ self.assertEqual(re.match(b'(a)', string).groups(), (b'a',))
+ self.assertEqual(re.match(b'(a)', string).group(0), b'a')
+ self.assertEqual(re.match(b'(a)', string).group(1), b'a')
+ self.assertEqual(re.match(b'(a)', string).group(1, 1), (b'a', b'a'))
+ for a in ("\xe0", "\u0430", "\U0001d49c"):
+ self.assertEqual(re.match(a, a).groups(), ())
+ self.assertEqual(re.match('(%s)' % a, a).groups(), (a,))
+ self.assertEqual(re.match('(%s)' % a, a).group(0), a)
+ self.assertEqual(re.match('(%s)' % a, a).group(1), a)
+ self.assertEqual(re.match('(%s)' % a, a).group(1, 1), (a, a))
pat = re.compile('((a)|(b))(c)?')
self.assertEqual(pat.match('a').groups(), ('a', 'a', None, None))
@@ -272,6 +349,36 @@ class ReTests(unittest.TestCase):
(None, 'b', None))
self.assertEqual(pat.match('ac').group(1, 'b2', 3), ('a', None, 'c'))
+ def test_re_fullmatch(self):
+ # Issue 16203: Proposal: add re.fullmatch() method.
+ self.assertEqual(re.fullmatch(r"a", "a").span(), (0, 1))
+ for string in "ab", S("ab"):
+ self.assertEqual(re.fullmatch(r"a|ab", string).span(), (0, 2))
+ for string in b"ab", B(b"ab"), bytearray(b"ab"), memoryview(b"ab"):
+ self.assertEqual(re.fullmatch(br"a|ab", string).span(), (0, 2))
+ for a, b in "\xe0\xdf", "\u0430\u0431", "\U0001d49c\U0001d49e":
+ r = r"%s|%s" % (a, a + b)
+ self.assertEqual(re.fullmatch(r, a + b).span(), (0, 2))
+ self.assertEqual(re.fullmatch(r".*?$", "abc").span(), (0, 3))
+ self.assertEqual(re.fullmatch(r".*?", "abc").span(), (0, 3))
+ self.assertEqual(re.fullmatch(r"a.*?b", "ab").span(), (0, 2))
+ self.assertEqual(re.fullmatch(r"a.*?b", "abb").span(), (0, 3))
+ self.assertEqual(re.fullmatch(r"a.*?b", "axxb").span(), (0, 4))
+ self.assertIsNone(re.fullmatch(r"a+", "ab"))
+ self.assertIsNone(re.fullmatch(r"abc$", "abc\n"))
+ self.assertIsNone(re.fullmatch(r"abc\Z", "abc\n"))
+ self.assertIsNone(re.fullmatch(r"(?m)abc$", "abc\n"))
+ self.assertEqual(re.fullmatch(r"ab(?=c)cd", "abcd").span(), (0, 4))
+ self.assertEqual(re.fullmatch(r"ab(?<=b)cd", "abcd").span(), (0, 4))
+ self.assertEqual(re.fullmatch(r"(?=a|ab)ab", "ab").span(), (0, 2))
+
+ self.assertEqual(
+ re.compile(r"bc").fullmatch("abcd", pos=1, endpos=3).span(), (1, 3))
+ self.assertEqual(
+ re.compile(r".*?$").fullmatch("abcd", pos=1, endpos=3).span(), (1, 3))
+ self.assertEqual(
+ re.compile(r".*?").fullmatch("abcd", pos=1, endpos=3).span(), (1, 3))
+
def test_re_groupref_exists(self):
self.assertEqual(re.match('^(\()?([^()]+)(?(1)\))$', '(a)').groups(),
('(', 'a'))
@@ -1053,6 +1160,28 @@ class ReTests(unittest.TestCase):
self.assertEqual(re.compile(pattern, re.S).findall(b'xyz'),
[b'xyz'], msg=pattern)
+ def test_match_repr(self):
+ for string in '[abracadabra]', S('[abracadabra]'):
+ m = re.search(r'(.+)(.*?)\1', string)
+ self.assertEqual(repr(m), "<%s.%s object; "
+ "span=(1, 12), match='abracadabra'>" %
+ (type(m).__module__, type(m).__qualname__))
+ for string in (b'[abracadabra]', B(b'[abracadabra]'),
+ bytearray(b'[abracadabra]'),
+ memoryview(b'[abracadabra]')):
+ m = re.search(rb'(.+)(.*?)\1', string)
+ self.assertEqual(repr(m), "<%s.%s object; "
+ "span=(1, 12), match=b'abracadabra'>" %
+ (type(m).__module__, type(m).__qualname__))
+
+ first, second = list(re.finditer("(aa)|(bb)", "aa bb"))
+ self.assertEqual(repr(first), "<%s.%s object; "
+ "span=(0, 2), match='aa'>" %
+ (type(second).__module__, type(first).__qualname__))
+ self.assertEqual(repr(second), "<%s.%s object; "
+ "span=(3, 5), match='bb'>" %
+ (type(second).__module__, type(second).__qualname__))
+
def test_bug_2537(self):
# issue 2537: empty submatches
@@ -1064,6 +1193,84 @@ class ReTests(unittest.TestCase):
self.assertEqual(m.group(1), "")
self.assertEqual(m.group(2), "y")
+
+class PatternReprTests(unittest.TestCase):
+ def check(self, pattern, expected):
+ self.assertEqual(repr(re.compile(pattern)), expected)
+
+ def check_flags(self, pattern, flags, expected):
+ self.assertEqual(repr(re.compile(pattern, flags)), expected)
+
+ def test_without_flags(self):
+ self.check('random pattern',
+ "re.compile('random pattern')")
+
+ def test_single_flag(self):
+ self.check_flags('random pattern', re.IGNORECASE,
+ "re.compile('random pattern', re.IGNORECASE)")
+
+ def test_multiple_flags(self):
+ self.check_flags('random pattern', re.I|re.S|re.X,
+ "re.compile('random pattern', "
+ "re.IGNORECASE|re.DOTALL|re.VERBOSE)")
+
+ def test_unicode_flag(self):
+ self.check_flags('random pattern', re.U,
+ "re.compile('random pattern')")
+ self.check_flags('random pattern', re.I|re.S|re.U,
+ "re.compile('random pattern', "
+ "re.IGNORECASE|re.DOTALL)")
+
+ def test_inline_flags(self):
+ self.check('(?i)pattern',
+ "re.compile('(?i)pattern', re.IGNORECASE)")
+
+ def test_unknown_flags(self):
+ self.check_flags('random pattern', 0x123000,
+ "re.compile('random pattern', 0x123000)")
+ self.check_flags('random pattern', 0x123000|re.I,
+ "re.compile('random pattern', re.IGNORECASE|0x123000)")
+
+ def test_bytes(self):
+ self.check(b'bytes pattern',
+ "re.compile(b'bytes pattern')")
+ self.check_flags(b'bytes pattern', re.A,
+ "re.compile(b'bytes pattern', re.ASCII)")
+
+ def test_quotes(self):
+ self.check('random "double quoted" pattern',
+ '''re.compile('random "double quoted" pattern')''')
+ self.check("random 'single quoted' pattern",
+ '''re.compile("random 'single quoted' pattern")''')
+ self.check('''both 'single' and "double" quotes''',
+ '''re.compile('both \\'single\\' and "double" quotes')''')
+
+ def test_long_pattern(self):
+ pattern = 'Very %spattern' % ('long ' * 1000)
+ r = repr(re.compile(pattern))
+ self.assertLess(len(r), 300)
+ self.assertEqual(r[:30], "re.compile('Very long long lon")
+ r = repr(re.compile(pattern, re.I))
+ self.assertLess(len(r), 300)
+ self.assertEqual(r[:30], "re.compile('Very long long lon")
+ self.assertEqual(r[-16:], ", re.IGNORECASE)")
+
+
+class ImplementationTest(unittest.TestCase):
+ """
+ Test implementation details of the re module.
+ """
+
+ def test_overlap_table(self):
+ f = sre_compile._generate_overlap_table
+ self.assertEqual(f(""), [])
+ self.assertEqual(f("a"), [0])
+ self.assertEqual(f("abcd"), [0, 0, 0, 0])
+ self.assertEqual(f("aaaa"), [0, 1, 2, 3])
+ self.assertEqual(f("ababba"), [0, 0, 1, 2, 0, 1])
+ self.assertEqual(f("abcabdac"), [0, 0, 0, 1, 2, 0, 1, 0])
+
+
def run_re_tests():
from test.re_tests import tests, SUCCEED, FAIL, SYNTAX_ERROR
if verbose:
@@ -1193,7 +1400,7 @@ def run_re_tests():
def test_main():
- run_unittest(ReTests)
+ run_unittest(__name__)
run_re_tests()
if __name__ == "__main__":
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
new file mode 100644
index 0000000000..a398a4f836
--- /dev/null
+++ b/Lib/test/test_regrtest.py
@@ -0,0 +1,275 @@
+"""
+Tests of regrtest.py.
+"""
+
+import argparse
+import faulthandler
+import getopt
+import os.path
+import unittest
+from test import regrtest, support
+
+class ParseArgsTestCase(unittest.TestCase):
+
+ """Test regrtest's argument parsing."""
+
+ def checkError(self, args, msg):
+ with support.captured_stderr() as err, self.assertRaises(SystemExit):
+ regrtest._parse_args(args)
+ self.assertIn(msg, err.getvalue())
+
+ def test_help(self):
+ for opt in '-h', '--help':
+ with self.subTest(opt=opt):
+ with support.captured_stdout() as out, \
+ self.assertRaises(SystemExit):
+ regrtest._parse_args([opt])
+ self.assertIn('Run Python regression tests.', out.getvalue())
+
+ @unittest.skipUnless(hasattr(faulthandler, 'dump_traceback_later'),
+ "faulthandler.dump_traceback_later() required")
+ def test_timeout(self):
+ ns = regrtest._parse_args(['--timeout', '4.2'])
+ self.assertEqual(ns.timeout, 4.2)
+ self.checkError(['--timeout'], 'expected one argument')
+ self.checkError(['--timeout', 'foo'], 'invalid float value')
+
+ def test_wait(self):
+ ns = regrtest._parse_args(['--wait'])
+ self.assertTrue(ns.wait)
+
+ def test_slaveargs(self):
+ ns = regrtest._parse_args(['--slaveargs', '[[], {}]'])
+ self.assertEqual(ns.slaveargs, '[[], {}]')
+ self.checkError(['--slaveargs'], 'expected one argument')
+
+ def test_start(self):
+ for opt in '-S', '--start':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, 'foo'])
+ self.assertEqual(ns.start, 'foo')
+ self.checkError([opt], 'expected one argument')
+
+ def test_verbose(self):
+ ns = regrtest._parse_args(['-v'])
+ self.assertEqual(ns.verbose, 1)
+ ns = regrtest._parse_args(['-vvv'])
+ self.assertEqual(ns.verbose, 3)
+ ns = regrtest._parse_args(['--verbose'])
+ self.assertEqual(ns.verbose, 1)
+ ns = regrtest._parse_args(['--verbose'] * 3)
+ self.assertEqual(ns.verbose, 3)
+ ns = regrtest._parse_args([])
+ self.assertEqual(ns.verbose, 0)
+
+ def test_verbose2(self):
+ for opt in '-w', '--verbose2':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.verbose2)
+
+ def test_verbose3(self):
+ for opt in '-W', '--verbose3':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.verbose3)
+
+ def test_quiet(self):
+ for opt in '-q', '--quiet':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.quiet)
+ self.assertEqual(ns.verbose, 0)
+
+ def test_slow(self):
+ for opt in '-o', '--slow':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.print_slow)
+
+ def test_header(self):
+ ns = regrtest._parse_args(['--header'])
+ self.assertTrue(ns.header)
+
+ def test_randomize(self):
+ for opt in '-r', '--randomize':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.randomize)
+
+ def test_randseed(self):
+ ns = regrtest._parse_args(['--randseed', '12345'])
+ self.assertEqual(ns.random_seed, 12345)
+ self.assertTrue(ns.randomize)
+ self.checkError(['--randseed'], 'expected one argument')
+ self.checkError(['--randseed', 'foo'], 'invalid int value')
+
+ def test_fromfile(self):
+ for opt in '-f', '--fromfile':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, 'foo'])
+ self.assertEqual(ns.fromfile, 'foo')
+ self.checkError([opt], 'expected one argument')
+ self.checkError([opt, 'foo', '-s'], "don't go together")
+
+ def test_exclude(self):
+ for opt in '-x', '--exclude':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.exclude)
+
+ def test_single(self):
+ for opt in '-s', '--single':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.single)
+ self.checkError([opt, '-f', 'foo'], "don't go together")
+
+ def test_match(self):
+ for opt in '-m', '--match':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, 'pattern'])
+ self.assertEqual(ns.match_tests, 'pattern')
+ self.checkError([opt], 'expected one argument')
+
+ def test_failfast(self):
+ for opt in '-G', '--failfast':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, '-v'])
+ self.assertTrue(ns.failfast)
+ ns = regrtest._parse_args([opt, '-W'])
+ self.assertTrue(ns.failfast)
+ self.checkError([opt], '-G/--failfast needs either -v or -W')
+
+ def test_use(self):
+ for opt in '-u', '--use':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, 'gui,network'])
+ self.assertEqual(ns.use_resources, ['gui', 'network'])
+ ns = regrtest._parse_args([opt, 'gui,none,network'])
+ self.assertEqual(ns.use_resources, ['network'])
+ expected = list(regrtest.RESOURCE_NAMES)
+ expected.remove('gui')
+ ns = regrtest._parse_args([opt, 'all,-gui'])
+ self.assertEqual(ns.use_resources, expected)
+ self.checkError([opt], 'expected one argument')
+ self.checkError([opt, 'foo'], 'invalid resource')
+
+ def test_memlimit(self):
+ for opt in '-M', '--memlimit':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, '4G'])
+ self.assertEqual(ns.memlimit, '4G')
+ self.checkError([opt], 'expected one argument')
+
+ def test_testdir(self):
+ ns = regrtest._parse_args(['--testdir', 'foo'])
+ self.assertEqual(ns.testdir, os.path.join(support.SAVEDCWD, 'foo'))
+ self.checkError(['--testdir'], 'expected one argument')
+
+ def test_runleaks(self):
+ for opt in '-L', '--runleaks':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.runleaks)
+
+ def test_huntrleaks(self):
+ for opt in '-R', '--huntrleaks':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, ':'])
+ self.assertEqual(ns.huntrleaks, (5, 4, 'reflog.txt'))
+ ns = regrtest._parse_args([opt, '6:'])
+ self.assertEqual(ns.huntrleaks, (6, 4, 'reflog.txt'))
+ ns = regrtest._parse_args([opt, ':3'])
+ self.assertEqual(ns.huntrleaks, (5, 3, 'reflog.txt'))
+ ns = regrtest._parse_args([opt, '6:3:leaks.log'])
+ self.assertEqual(ns.huntrleaks, (6, 3, 'leaks.log'))
+ self.checkError([opt], 'expected one argument')
+ self.checkError([opt, '6'],
+ 'needs 2 or 3 colon-separated arguments')
+ self.checkError([opt, 'foo:'], 'invalid huntrleaks value')
+ self.checkError([opt, '6:foo'], 'invalid huntrleaks value')
+
+ def test_multiprocess(self):
+ for opt in '-j', '--multiprocess':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, '2'])
+ self.assertEqual(ns.use_mp, 2)
+ self.checkError([opt], 'expected one argument')
+ self.checkError([opt, 'foo'], 'invalid int value')
+ self.checkError([opt, '2', '-T'], "don't go together")
+ self.checkError([opt, '2', '-l'], "don't go together")
+ self.checkError([opt, '2', '-M', '4G'], "don't go together")
+
+ def test_coverage(self):
+ for opt in '-T', '--coverage':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.trace)
+
+ def test_coverdir(self):
+ for opt in '-D', '--coverdir':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, 'foo'])
+ self.assertEqual(ns.coverdir,
+ os.path.join(support.SAVEDCWD, 'foo'))
+ self.checkError([opt], 'expected one argument')
+
+ def test_nocoverdir(self):
+ for opt in '-N', '--nocoverdir':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertIsNone(ns.coverdir)
+
+ def test_threshold(self):
+ for opt in '-t', '--threshold':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt, '1000'])
+ self.assertEqual(ns.threshold, 1000)
+ self.checkError([opt], 'expected one argument')
+ self.checkError([opt, 'foo'], 'invalid int value')
+
+ def test_nowindows(self):
+ for opt in '-n', '--nowindows':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.nowindows)
+
+ def test_forever(self):
+ for opt in '-F', '--forever':
+ with self.subTest(opt=opt):
+ ns = regrtest._parse_args([opt])
+ self.assertTrue(ns.forever)
+
+
+ def test_unrecognized_argument(self):
+ self.checkError(['--xxx'], 'usage:')
+
+ def test_long_option__partial(self):
+ ns = regrtest._parse_args(['--qui'])
+ self.assertTrue(ns.quiet)
+ self.assertEqual(ns.verbose, 0)
+
+ def test_two_options(self):
+ ns = regrtest._parse_args(['--quiet', '--exclude'])
+ self.assertTrue(ns.quiet)
+ self.assertEqual(ns.verbose, 0)
+ self.assertTrue(ns.exclude)
+
+ def test_option_with_empty_string_value(self):
+ ns = regrtest._parse_args(['--start', ''])
+ self.assertEqual(ns.start, '')
+
+ def test_arg(self):
+ ns = regrtest._parse_args(['foo'])
+ self.assertEqual(ns.args, ['foo'])
+
+ def test_option_and_arg(self):
+ ns = regrtest._parse_args(['--quiet', 'foo'])
+ self.assertTrue(ns.quiet)
+ self.assertEqual(ns.verbose, 0)
+ self.assertEqual(ns.args, ['foo'])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py
index 589ecdd4da..a504bde4e4 100644
--- a/Lib/test/test_reprlib.py
+++ b/Lib/test/test_reprlib.py
@@ -3,11 +3,11 @@
Nick Mathewson
"""
-import imp
import sys
import os
import shutil
import importlib
+import importlib.util
import unittest
from test.support import run_unittest, create_empty_file, verbose
@@ -241,7 +241,8 @@ class LongReprTest(unittest.TestCase):
source_path_len += 2 * (len(self.longname) + 1)
# a path separator + `module_name` + ".py"
source_path_len += len(module_name) + 1 + len(".py")
- cached_path_len = source_path_len + len(imp.cache_from_source("x.py")) - len("x.py")
+ cached_path_len = (source_path_len +
+ len(importlib.util.cache_from_source("x.py")) - len("x.py"))
if os.name == 'nt' and cached_path_len >= 258:
# Under Windows, the max path len is 260 including C's terminating
# NUL character.
@@ -252,6 +253,7 @@ class LongReprTest(unittest.TestCase):
print("cached_path_len =", cached_path_len)
def test_module(self):
+ self.maxDiff = None
self._check_path_limitations(self.pkgname)
create_empty_file(os.path.join(self.subpkgname, self.pkgname + '.py'))
importlib.invalidate_caches()
diff --git a/Lib/test/test_resource.py b/Lib/test/test_resource.py
index 0cf61cb17a..006198fc41 100644
--- a/Lib/test/test_resource.py
+++ b/Lib/test/test_resource.py
@@ -1,3 +1,6 @@
+import contextlib
+import sys
+import os
import unittest
from test import support
import time
@@ -61,7 +64,7 @@ class ResourceTest(unittest.TestCase):
for i in range(5):
time.sleep(.1)
f.flush()
- except IOError:
+ except OSError:
if not limit_set:
raise
if limit_set:
@@ -129,6 +132,27 @@ class ResourceTest(unittest.TestCase):
self.assertIsInstance(pagesize, int)
self.assertGreaterEqual(pagesize, 0)
+ @unittest.skipUnless(sys.platform == 'linux', 'test requires Linux')
+ def test_linux_constants(self):
+ for attr in ['MSGQUEUE', 'NICE', 'RTPRIO', 'RTTIME', 'SIGPENDING']:
+ with contextlib.suppress(AttributeError):
+ self.assertIsInstance(getattr(resource, 'RLIMIT_' + attr), int)
+
+ @unittest.skipUnless(hasattr(resource, 'prlimit'), 'no prlimit')
+ @support.requires_linux_version(2, 6, 36)
+ def test_prlimit(self):
+ self.assertRaises(TypeError, resource.prlimit)
+ if os.geteuid() != 0:
+ self.assertRaises(PermissionError, resource.prlimit,
+ 1, resource.RLIMIT_AS)
+ self.assertRaises(ProcessLookupError, resource.prlimit,
+ -1, resource.RLIMIT_AS)
+ limit = resource.getrlimit(resource.RLIMIT_AS)
+ self.assertEqual(resource.prlimit(0, resource.RLIMIT_AS), limit)
+ self.assertEqual(resource.prlimit(0, resource.RLIMIT_AS, limit),
+ limit)
+
+
def test_main(verbose=None):
support.run_unittest(ResourceTest)
diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py
index 2ddba3413a..f19c4ab7eb 100644
--- a/Lib/test/test_runpy.py
+++ b/Lib/test/test_runpy.py
@@ -47,6 +47,7 @@ implicit_namespace = {
"__cached__": None,
"__package__": None,
"__doc__": None,
+# "__spec__": None, # XXX Uncomment.
}
example_namespace = {
"sys": sys,
@@ -56,7 +57,7 @@ example_namespace = {
"run_name_in_sys_modules": False,
"module_in_sys_modules": False,
"nested": dict(implicit_namespace,
- x=1, __name__="<run>", __loader__=None),
+ x=1, __name__="<run>", __loader__=None, __spec__=None),
}
example_namespace.update(implicit_namespace)
@@ -243,6 +244,7 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin):
"__name__": mod_name,
"__file__": mod_fname,
"__package__": mod_name.rpartition(".")[0],
+# "__spec__": None, # XXX Needs to be set.
})
if alter_sys:
expected_ns.update({
@@ -279,6 +281,7 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin):
"__name__": mod_name,
"__file__": mod_fname,
"__package__": pkg_name,
+# "__spec__": None, # XXX Needs to be set.
})
if alter_sys:
expected_ns.update({
@@ -575,12 +578,5 @@ s = "non-ASCII: h\xe9"
self.assertEqual(result['s'], "non-ASCII: h\xe9")
-def test_main():
- run_unittest(
- ExecutionLayerTestCase,
- RunModuleTestCase,
- RunPathTestCase
- )
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_sched.py b/Lib/test/test_sched.py
index 070886d1ea..c6abf3d89a 100644
--- a/Lib/test/test_sched.py
+++ b/Lib/test/test_sched.py
@@ -197,8 +197,5 @@ class TestCase(unittest.TestCase):
self.assertEqual(l, [])
-def test_main():
- support.run_unittest(TestCase)
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_scope.py b/Lib/test/test_scope.py
index 26ce0424f8..b325545f32 100644
--- a/Lib/test/test_scope.py
+++ b/Lib/test/test_scope.py
@@ -715,6 +715,19 @@ class ScopeTests(unittest.TestCase):
def b():
global a
+ def testClassNamespaceOverridesClosure(self):
+ # See #17853.
+ x = 42
+ class X:
+ locals()["x"] = 43
+ y = x
+ self.assertEqual(X.y, 43)
+ class X:
+ locals()["x"] = 43
+ del x
+ self.assertFalse(hasattr(X, "x"))
+ self.assertEqual(x, 42)
+
@cpython_only
def testCellLeak(self):
# Issue 17927.
@@ -743,10 +756,6 @@ class ScopeTests(unittest.TestCase):
del tester
self.assertIsNone(ref())
- def test__Class__Global(self):
- s = "class X:\n global __class__\n def f(self): super()"
- self.assertRaises(SyntaxError, exec, s)
-
def test_main():
run_unittest(ScopeTests)
diff --git a/Lib/test/test_select.py b/Lib/test/test_select.py
index ddb9a0f67e..8f9a1c9d88 100644
--- a/Lib/test/test_select.py
+++ b/Lib/test/test_select.py
@@ -5,7 +5,7 @@ import sys
import unittest
from test import support
-@unittest.skipIf(sys.platform[:3] in ('win', 'os2', 'riscos'),
+@unittest.skipIf((sys.platform[:3]=='win'),
"can't easily test on this system")
class SelectTestCase(unittest.TestCase):
@@ -32,7 +32,7 @@ class SelectTestCase(unittest.TestCase):
fp.close()
try:
select.select([fd], [], [], 0)
- except select.error as err:
+ except OSError as err:
self.assertEqual(err.errno, errno.EBADF)
else:
self.fail("exception not raised")
diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py
new file mode 100644
index 0000000000..f5e67b1005
--- /dev/null
+++ b/Lib/test/test_selectors.py
@@ -0,0 +1,430 @@
+import errno
+import random
+import selectors
+import signal
+import socket
+from test import support
+from time import sleep
+import unittest
+import unittest.mock
+try:
+ from time import monotonic as time
+except ImportError:
+ from time import time as time
+try:
+ import resource
+except ImportError:
+ resource = None
+
+
+if hasattr(socket, 'socketpair'):
+ socketpair = socket.socketpair
+else:
+ def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
+ with socket.socket(family, type, proto) as l:
+ l.bind((support.HOST, 0))
+ l.listen(3)
+ c = socket.socket(family, type, proto)
+ try:
+ c.connect(l.getsockname())
+ caddr = c.getsockname()
+ while True:
+ a, addr = l.accept()
+ # check that we've got the correct client
+ if addr == caddr:
+ return c, a
+ a.close()
+ except OSError:
+ c.close()
+ raise
+
+
+def find_ready_matching(ready, flag):
+ match = []
+ for key, events in ready:
+ if events & flag:
+ match.append(key.fileobj)
+ return match
+
+
+class BaseSelectorTestCase(unittest.TestCase):
+
+ def test_register(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ key = s.register(rd, selectors.EVENT_READ, "data")
+ self.assertIsInstance(key, selectors.SelectorKey)
+ self.assertEqual(key.fileobj, rd)
+ self.assertEqual(key.fd, rd.fileno())
+ self.assertEqual(key.events, selectors.EVENT_READ)
+ self.assertEqual(key.data, "data")
+
+ # register an unknown event
+ self.assertRaises(ValueError, s.register, 0, 999999)
+
+ # register an invalid FD
+ self.assertRaises(ValueError, s.register, -10, selectors.EVENT_READ)
+
+ # register twice
+ self.assertRaises(KeyError, s.register, rd, selectors.EVENT_READ)
+
+ # register the same FD, but with a different object
+ self.assertRaises(KeyError, s.register, rd.fileno(),
+ selectors.EVENT_READ)
+
+ def test_unregister(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ s.register(rd, selectors.EVENT_READ)
+ s.unregister(rd)
+
+ # unregister an unknown file obj
+ self.assertRaises(KeyError, s.unregister, 999999)
+
+ # unregister twice
+ self.assertRaises(KeyError, s.unregister, rd)
+
+ def test_modify(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ key = s.register(rd, selectors.EVENT_READ)
+
+ # modify events
+ key2 = s.modify(rd, selectors.EVENT_WRITE)
+ self.assertNotEqual(key.events, key2.events)
+ self.assertEqual(key2, s.get_key(rd))
+
+ s.unregister(rd)
+
+ # modify data
+ d1 = object()
+ d2 = object()
+
+ key = s.register(rd, selectors.EVENT_READ, d1)
+ key2 = s.modify(rd, selectors.EVENT_READ, d2)
+ self.assertEqual(key.events, key2.events)
+ self.assertNotEqual(key.data, key2.data)
+ self.assertEqual(key2, s.get_key(rd))
+ self.assertEqual(key2.data, d2)
+
+ # modify unknown file obj
+ self.assertRaises(KeyError, s.modify, 999999, selectors.EVENT_READ)
+
+ # modify use a shortcut
+ d3 = object()
+ s.register = unittest.mock.Mock()
+ s.unregister = unittest.mock.Mock()
+
+ s.modify(rd, selectors.EVENT_READ, d3)
+ self.assertFalse(s.register.called)
+ self.assertFalse(s.unregister.called)
+
+ def test_close(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ s.register(rd, selectors.EVENT_READ)
+ s.register(wr, selectors.EVENT_WRITE)
+
+ s.close()
+ self.assertRaises(KeyError, s.get_key, rd)
+ self.assertRaises(KeyError, s.get_key, wr)
+
+ def test_get_key(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ key = s.register(rd, selectors.EVENT_READ, "data")
+ self.assertEqual(key, s.get_key(rd))
+
+ # unknown file obj
+ self.assertRaises(KeyError, s.get_key, 999999)
+
+ def test_get_map(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ keys = s.get_map()
+ self.assertFalse(keys)
+ self.assertEqual(len(keys), 0)
+ self.assertEqual(list(keys), [])
+ key = s.register(rd, selectors.EVENT_READ, "data")
+ self.assertIn(rd, keys)
+ self.assertEqual(key, keys[rd])
+ self.assertEqual(len(keys), 1)
+ self.assertEqual(list(keys), [rd.fileno()])
+ self.assertEqual(list(keys.values()), [key])
+
+ # unknown file obj
+ with self.assertRaises(KeyError):
+ keys[999999]
+
+ # Read-only mapping
+ with self.assertRaises(TypeError):
+ del keys[rd]
+
+ def test_select(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ s.register(rd, selectors.EVENT_READ)
+ wr_key = s.register(wr, selectors.EVENT_WRITE)
+
+ result = s.select()
+ for key, events in result:
+ self.assertTrue(isinstance(key, selectors.SelectorKey))
+ self.assertTrue(events)
+ self.assertFalse(events & ~(selectors.EVENT_READ |
+ selectors.EVENT_WRITE))
+
+ self.assertEqual([(wr_key, selectors.EVENT_WRITE)], result)
+
+ def test_context_manager(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ with s as sel:
+ sel.register(rd, selectors.EVENT_READ)
+ sel.register(wr, selectors.EVENT_WRITE)
+
+ self.assertRaises(KeyError, s.get_key, rd)
+ self.assertRaises(KeyError, s.get_key, wr)
+
+ def test_fileno(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ if hasattr(s, 'fileno'):
+ fd = s.fileno()
+ self.assertTrue(isinstance(fd, int))
+ self.assertGreaterEqual(fd, 0)
+
+ def test_selector(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ NUM_SOCKETS = 12
+ MSG = b" This is a test."
+ MSG_LEN = len(MSG)
+ readers = []
+ writers = []
+ r2w = {}
+ w2r = {}
+
+ for i in range(NUM_SOCKETS):
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+ s.register(rd, selectors.EVENT_READ)
+ s.register(wr, selectors.EVENT_WRITE)
+ readers.append(rd)
+ writers.append(wr)
+ r2w[rd] = wr
+ w2r[wr] = rd
+
+ bufs = []
+
+ while writers:
+ ready = s.select()
+ ready_writers = find_ready_matching(ready, selectors.EVENT_WRITE)
+ if not ready_writers:
+ self.fail("no sockets ready for writing")
+ wr = random.choice(ready_writers)
+ wr.send(MSG)
+
+ for i in range(10):
+ ready = s.select()
+ ready_readers = find_ready_matching(ready,
+ selectors.EVENT_READ)
+ if ready_readers:
+ break
+ # there might be a delay between the write to the write end and
+ # the read end is reported ready
+ sleep(0.1)
+ else:
+ self.fail("no sockets ready for reading")
+ self.assertEqual([w2r[wr]], ready_readers)
+ rd = ready_readers[0]
+ buf = rd.recv(MSG_LEN)
+ self.assertEqual(len(buf), MSG_LEN)
+ bufs.append(buf)
+ s.unregister(r2w[rd])
+ s.unregister(rd)
+ writers.remove(r2w[rd])
+
+ self.assertEqual(bufs, [MSG] * NUM_SOCKETS)
+
+ def test_timeout(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ s.register(wr, selectors.EVENT_WRITE)
+ t = time()
+ self.assertEqual(1, len(s.select(0)))
+ self.assertEqual(1, len(s.select(-1)))
+ self.assertLess(time() - t, 0.5)
+
+ s.unregister(wr)
+ s.register(rd, selectors.EVENT_READ)
+ t = time()
+ self.assertFalse(s.select(0))
+ self.assertFalse(s.select(-1))
+ self.assertLess(time() - t, 0.5)
+
+ t0 = time()
+ self.assertFalse(s.select(1))
+ t1 = time()
+ dt = t1 - t0
+ self.assertTrue(0.8 <= dt <= 1.6, dt)
+
+ @unittest.skipUnless(hasattr(signal, "alarm"),
+ "signal.alarm() required for this test")
+ def test_select_interrupt(self):
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ rd, wr = socketpair()
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ orig_alrm_handler = signal.signal(signal.SIGALRM, lambda *args: None)
+ self.addCleanup(signal.signal, signal.SIGALRM, orig_alrm_handler)
+ self.addCleanup(signal.alarm, 0)
+
+ signal.alarm(1)
+
+ s.register(rd, selectors.EVENT_READ)
+ t = time()
+ self.assertFalse(s.select(2))
+ self.assertLess(time() - t, 2.5)
+
+
+class ScalableSelectorMixIn:
+
+ # see issue #18963 for why it's skipped on older OS X versions
+ @support.requires_mac_ver(10, 5)
+ @unittest.skipUnless(resource, "Test needs resource module")
+ def test_above_fd_setsize(self):
+ # A scalable implementation should have no problem with more than
+ # FD_SETSIZE file descriptors. Since we don't know the value, we just
+ # try to set the soft RLIMIT_NOFILE to the hard RLIMIT_NOFILE ceiling.
+ soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
+ try:
+ resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard))
+ self.addCleanup(resource.setrlimit, resource.RLIMIT_NOFILE,
+ (soft, hard))
+ NUM_FDS = hard
+ except (OSError, ValueError):
+ NUM_FDS = soft
+
+ # guard for already allocated FDs (stdin, stdout...)
+ NUM_FDS -= 32
+
+ s = self.SELECTOR()
+ self.addCleanup(s.close)
+
+ for i in range(NUM_FDS // 2):
+ try:
+ rd, wr = socketpair()
+ except OSError:
+ # too many FDs, skip - note that we should only catch EMFILE
+ # here, but apparently *BSD and Solaris can fail upon connect()
+ # or bind() with EADDRNOTAVAIL, so let's be safe
+ self.skipTest("FD limit reached")
+
+ self.addCleanup(rd.close)
+ self.addCleanup(wr.close)
+
+ try:
+ s.register(rd, selectors.EVENT_READ)
+ s.register(wr, selectors.EVENT_WRITE)
+ except OSError as e:
+ if e.errno == errno.ENOSPC:
+ # this can be raised by epoll if we go over
+ # fs.epoll.max_user_watches sysctl
+ self.skipTest("FD limit reached")
+ raise
+
+ self.assertEqual(NUM_FDS // 2, len(s.select()))
+
+
+class DefaultSelectorTestCase(BaseSelectorTestCase):
+
+ SELECTOR = selectors.DefaultSelector
+
+
+class SelectSelectorTestCase(BaseSelectorTestCase):
+
+ SELECTOR = selectors.SelectSelector
+
+
+@unittest.skipUnless(hasattr(selectors, 'PollSelector'),
+ "Test needs selectors.PollSelector")
+class PollSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn):
+
+ SELECTOR = getattr(selectors, 'PollSelector', None)
+
+
+@unittest.skipUnless(hasattr(selectors, 'EpollSelector'),
+ "Test needs selectors.EpollSelector")
+class EpollSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn):
+
+ SELECTOR = getattr(selectors, 'EpollSelector', None)
+
+
+@unittest.skipUnless(hasattr(selectors, 'KqueueSelector'),
+ "Test needs selectors.KqueueSelector)")
+class KqueueSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn):
+
+ SELECTOR = getattr(selectors, 'KqueueSelector', None)
+
+
+def test_main():
+ tests = [DefaultSelectorTestCase, SelectSelectorTestCase,
+ PollSelectorTestCase, EpollSelectorTestCase,
+ KqueueSelectorTestCase]
+ support.run_unittest(*tests)
+ support.reap_children()
+
+
+if __name__ == "__main__":
+ test_main()
diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py
index c0bca2fb88..bfef621093 100644
--- a/Lib/test/test_set.py
+++ b/Lib/test/test_set.py
@@ -848,8 +848,6 @@ class TestBasicOps:
for v in self.set:
self.assertIn(v, self.values)
setiter = iter(self.set)
- # note: __length_hint__ is an internal undocumented API,
- # don't rely on it in your own programs
self.assertEqual(setiter.__length_hint__(), len(self.set))
def test_pickling(self):
diff --git a/Lib/test/test_shelve.py b/Lib/test/test_shelve.py
index 13c126566d..bd51d868fe 100644
--- a/Lib/test/test_shelve.py
+++ b/Lib/test/test_shelve.py
@@ -148,6 +148,19 @@ class TestCase(unittest.TestCase):
p2 = d[encodedkey]
self.assertNotEqual(p1, p2) # Write creates new object in store
+ def test_with(self):
+ d1 = {}
+ with shelve.Shelf(d1, protocol=2, writeback=False) as s:
+ s['key1'] = [1,2,3,4]
+ self.assertEqual(s['key1'], [1,2,3,4])
+ self.assertEqual(len(s), 1)
+ self.assertRaises(ValueError, len, s)
+ try:
+ s['key1']
+ except ValueError:
+ pass
+ else:
+ self.fail('Closed shelf should not find a key')
from test import mapping_tests
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index df95bd9a5c..deb1577611 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -18,7 +18,8 @@ from shutil import (_make_tarball, _make_zipfile, make_archive,
register_archive_format, unregister_archive_format,
get_archive_formats, Error, unpack_archive,
register_unpack_format, RegistryError,
- unregister_unpack_format, get_unpack_formats)
+ unregister_unpack_format, get_unpack_formats,
+ SameFileError)
import tarfile
import warnings
@@ -742,14 +743,14 @@ class TestShutil(unittest.TestCase):
os.chmod(restrictive_subdir, 0o600)
shutil.copytree(src_dir, dst_dir)
- self.assertEquals(os.stat(src_dir).st_mode, os.stat(dst_dir).st_mode)
- self.assertEquals(os.stat(os.path.join(src_dir, 'permissive.txt')).st_mode,
+ self.assertEqual(os.stat(src_dir).st_mode, os.stat(dst_dir).st_mode)
+ self.assertEqual(os.stat(os.path.join(src_dir, 'permissive.txt')).st_mode,
os.stat(os.path.join(dst_dir, 'permissive.txt')).st_mode)
- self.assertEquals(os.stat(os.path.join(src_dir, 'restrictive.txt')).st_mode,
+ self.assertEqual(os.stat(os.path.join(src_dir, 'restrictive.txt')).st_mode,
os.stat(os.path.join(dst_dir, 'restrictive.txt')).st_mode)
restrictive_subdir_dst = os.path.join(dst_dir,
os.path.split(restrictive_subdir)[1])
- self.assertEquals(os.stat(restrictive_subdir).st_mode,
+ self.assertEqual(os.stat(restrictive_subdir).st_mode,
os.stat(restrictive_subdir_dst).st_mode)
@unittest.skipUnless(hasattr(os, 'link'), 'requires os.link')
@@ -765,7 +766,7 @@ class TestShutil(unittest.TestCase):
with open(src, 'w') as f:
f.write('cheddar')
os.link(src, dst)
- self.assertRaises(shutil.Error, shutil.copyfile, src, dst)
+ self.assertRaises(shutil.SameFileError, shutil.copyfile, src, dst)
with open(src, 'r') as f:
self.assertEqual(f.read(), 'cheddar')
os.remove(dst)
@@ -785,7 +786,7 @@ class TestShutil(unittest.TestCase):
# to TESTFN/TESTFN/cheese, while it should point at
# TESTFN/cheese.
os.symlink('cheese', dst)
- self.assertRaises(shutil.Error, shutil.copyfile, src, dst)
+ self.assertRaises(shutil.SameFileError, shutil.copyfile, src, dst)
with open(src, 'r') as f:
self.assertEqual(f.read(), 'cheddar')
os.remove(dst)
@@ -1293,6 +1294,16 @@ class TestShutil(unittest.TestCase):
self.assertTrue(os.path.exists(rv))
self.assertEqual(read_file(src_file), read_file(dst_file))
+ def test_copyfile_same_file(self):
+ # copyfile() should raise SameFileError if the source and destination
+ # are the same.
+ src_dir = self.mkdtemp()
+ src_file = os.path.join(src_dir, 'foo')
+ write_file(src_file, 'foo')
+ self.assertRaises(SameFileError, shutil.copyfile, src_file, src_file)
+ # But Error should work too, to stay backward compatible.
+ self.assertRaises(Error, shutil.copyfile, src_file, src_file)
+
def test_copytree_return_value(self):
# copytree returns its destination path.
src_dir = self.mkdtemp()
@@ -1588,7 +1599,7 @@ class TestCopyFile(unittest.TestCase):
self._exited_with = exc_type, exc_val, exc_tb
if self._raise_in_exit:
self._raised = True
- raise IOError("Cannot close")
+ raise OSError("Cannot close")
return self._suppress_at_exit
def tearDown(self):
@@ -1602,12 +1613,12 @@ class TestCopyFile(unittest.TestCase):
def test_w_source_open_fails(self):
def _open(filename, mode='r'):
if filename == 'srcfile':
- raise IOError('Cannot open "srcfile"')
+ raise OSError('Cannot open "srcfile"')
assert 0 # shouldn't reach here.
self._set_shutil_open(_open)
- self.assertRaises(IOError, shutil.copyfile, 'srcfile', 'destfile')
+ self.assertRaises(OSError, shutil.copyfile, 'srcfile', 'destfile')
def test_w_dest_open_fails(self):
@@ -1617,14 +1628,14 @@ class TestCopyFile(unittest.TestCase):
if filename == 'srcfile':
return srcfile
if filename == 'destfile':
- raise IOError('Cannot open "destfile"')
+ raise OSError('Cannot open "destfile"')
assert 0 # shouldn't reach here.
self._set_shutil_open(_open)
shutil.copyfile('srcfile', 'destfile')
self.assertTrue(srcfile._entered)
- self.assertTrue(srcfile._exited_with[0] is IOError)
+ self.assertTrue(srcfile._exited_with[0] is OSError)
self.assertEqual(srcfile._exited_with[1].args,
('Cannot open "destfile"',))
@@ -1646,7 +1657,7 @@ class TestCopyFile(unittest.TestCase):
self.assertTrue(srcfile._entered)
self.assertTrue(destfile._entered)
self.assertTrue(destfile._raised)
- self.assertTrue(srcfile._exited_with[0] is IOError)
+ self.assertTrue(srcfile._exited_with[0] is OSError)
self.assertEqual(srcfile._exited_with[1].args,
('Cannot close',))
@@ -1664,7 +1675,7 @@ class TestCopyFile(unittest.TestCase):
self._set_shutil_open(_open)
- self.assertRaises(IOError,
+ self.assertRaises(OSError,
shutil.copyfile, 'srcfile', 'destfile')
self.assertTrue(srcfile._entered)
self.assertTrue(destfile._entered)
@@ -1735,9 +1746,5 @@ class TermsizeTests(unittest.TestCase):
self.assertEqual(expected, actual)
-def test_main():
- support.run_unittest(TestShutil, TestMove, TestCopyFile,
- TermsizeTests, TestWhich)
-
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
index 9b4ba5016e..3530d8a14e 100644
--- a/Lib/test/test_signal.py
+++ b/Lib/test/test_signal.py
@@ -15,9 +15,6 @@ try:
except ImportError:
threading = None
-if sys.platform in ('os2', 'riscos'):
- raise unittest.SkipTest("Can't test signal on %s" % sys.platform)
-
class HandlerBCalled(Exception):
pass
@@ -36,7 +33,7 @@ def exit_subprocess():
def ignoring_eintr(__func, *args, **kwargs):
try:
return __func(*args, **kwargs)
- except EnvironmentError as e:
+ except OSError as e:
if e.errno != errno.EINTR:
raise
return None
@@ -278,6 +275,57 @@ class WakeupSignalTests(unittest.TestCase):
assert_python_ok('-c', code)
+ def test_wakeup_write_error(self):
+ # Issue #16105: write() errors in the C signal handler should not
+ # pass silently.
+ # Use a subprocess to have only one thread.
+ code = """if 1:
+ import errno
+ import fcntl
+ import os
+ import signal
+ import sys
+ import time
+ from test.support import captured_stderr
+
+ def handler(signum, frame):
+ 1/0
+
+ signal.signal(signal.SIGALRM, handler)
+ r, w = os.pipe()
+ flags = fcntl.fcntl(r, fcntl.F_GETFL, 0)
+ fcntl.fcntl(r, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+ # Set wakeup_fd a read-only file descriptor to trigger the error
+ signal.set_wakeup_fd(r)
+ try:
+ with captured_stderr() as err:
+ signal.alarm(1)
+ time.sleep(5.0)
+ except ZeroDivisionError:
+ # An ignored exception should have been printed out on stderr
+ err = err.getvalue()
+ if ('Exception ignored when trying to write to the signal wakeup fd'
+ not in err):
+ raise AssertionError(err)
+ if ('OSError: [Errno %d]' % errno.EBADF) not in err:
+ raise AssertionError(err)
+ else:
+ raise AssertionError("ZeroDivisionError not raised")
+ """
+ r, w = os.pipe()
+ try:
+ os.write(r, b'x')
+ except OSError:
+ pass
+ else:
+ self.skipTest("OS doesn't report write() error on the read end of a pipe")
+ finally:
+ os.close(r)
+ os.close(w)
+
+ assert_python_ok('-c', code)
+
def test_wakeup_fd_early(self):
self.check_wakeup("""def test():
import select
@@ -315,10 +363,10 @@ class WakeupSignalTests(unittest.TestCase):
# We attempt to get a signal during the select call
try:
select.select([read], [], [], TIMEOUT_FULL)
- except select.error:
+ except OSError:
pass
else:
- raise Exception("select.error not raised")
+ raise Exception("OSError not raised")
after_time = time.time()
dt = after_time - before_time
if dt >= TIMEOUT_HALF:
diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py
index c294c65b58..cb7c393074 100644
--- a/Lib/test/test_site.py
+++ b/Lib/test/test_site.py
@@ -231,11 +231,7 @@ class HelperFunctionsTests(unittest.TestCase):
site.PREFIXES = ['xoxo']
dirs = site.getsitepackages()
- if sys.platform in ('os2emx', 'riscos'):
- self.assertEqual(len(dirs), 1)
- wanted = os.path.join('xoxo', 'Lib', 'site-packages')
- self.assertEqual(dirs[0], wanted)
- elif (sys.platform == "darwin" and
+ if (sys.platform == "darwin" and
sysconfig.get_config_var("PYTHONFRAMEWORK")):
# OS X framework builds
site.PREFIXES = ['Python.framework']
@@ -432,5 +428,38 @@ class ImportSideEffectTests(unittest.TestCase):
self.assertEqual(code, 200, msg="Can't find " + url)
+class StartupImportTests(unittest.TestCase):
+
+ def test_startup_imports(self):
+ # This tests checks which modules are loaded by Python when it
+ # initially starts upon startup.
+ popen = subprocess.Popen([sys.executable, '-I', '-v', '-c',
+ 'import sys; print(set(sys.modules))'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = popen.communicate()
+ stdout = stdout.decode('utf-8')
+ stderr = stderr.decode('utf-8')
+ modules = eval(stdout)
+
+ self.assertIn('site', modules)
+
+ # http://bugs.python.org/issue19205
+ re_mods = {'re', '_sre', 'sre_compile', 'sre_constants', 'sre_parse'}
+ # _osx_support uses the re module in many placs
+ if sys.platform != 'darwin':
+ self.assertFalse(modules.intersection(re_mods), stderr)
+ # http://bugs.python.org/issue9548
+ self.assertNotIn('locale', modules, stderr)
+ if sys.platform != 'darwin':
+ # http://bugs.python.org/issue19209
+ self.assertNotIn('copyreg', modules, stderr)
+ # http://bugs.python.org/issue19218>
+ collection_mods = {'_collections', 'collections', 'functools',
+ 'heapq', 'itertools', 'keyword', 'operator',
+ 'reprlib', 'types', 'weakref'}
+ self.assertFalse(modules.intersection(collection_mods), stderr)
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_slice.py b/Lib/test/test_slice.py
index 2df9271da7..9203d5eb19 100644
--- a/Lib/test/test_slice.py
+++ b/Lib/test/test_slice.py
@@ -4,8 +4,70 @@ import unittest
from test import support
from pickle import loads, dumps
+import itertools
+import operator
import sys
+
+def evaluate_slice_index(arg):
+ """
+ Helper function to convert a slice argument to an integer, and raise
+ TypeError with a suitable message on failure.
+
+ """
+ if hasattr(arg, '__index__'):
+ return operator.index(arg)
+ else:
+ raise TypeError(
+ "slice indices must be integers or "
+ "None or have an __index__ method")
+
+def slice_indices(slice, length):
+ """
+ Reference implementation for the slice.indices method.
+
+ """
+ # Compute step and length as integers.
+ length = operator.index(length)
+ step = 1 if slice.step is None else evaluate_slice_index(slice.step)
+
+ # Raise ValueError for negative length or zero step.
+ if length < 0:
+ raise ValueError("length should not be negative")
+ if step == 0:
+ raise ValueError("slice step cannot be zero")
+
+ # Find lower and upper bounds for start and stop.
+ lower = -1 if step < 0 else 0
+ upper = length - 1 if step < 0 else length
+
+ # Compute start.
+ if slice.start is None:
+ start = upper if step < 0 else lower
+ else:
+ start = evaluate_slice_index(slice.start)
+ start = max(start + length, lower) if start < 0 else min(start, upper)
+
+ # Compute stop.
+ if slice.stop is None:
+ stop = lower if step < 0 else upper
+ else:
+ stop = evaluate_slice_index(slice.stop)
+ stop = max(stop + length, lower) if stop < 0 else min(stop, upper)
+
+ return start, stop, step
+
+
+# Class providing an __index__ method. Used for testing slice.indices.
+
+class MyIndexable(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __index__(self):
+ return self.value
+
+
class SliceTest(unittest.TestCase):
def test_constructor(self):
@@ -75,6 +137,22 @@ class SliceTest(unittest.TestCase):
s = slice(obj)
self.assertTrue(s.stop is obj)
+ def check_indices(self, slice, length):
+ try:
+ actual = slice.indices(length)
+ except ValueError:
+ actual = "valueerror"
+ try:
+ expected = slice_indices(slice, length)
+ except ValueError:
+ expected = "valueerror"
+ self.assertEqual(actual, expected)
+
+ if length >= 0 and slice.step != 0:
+ actual = range(*slice.indices(length))
+ expected = range(length)[slice]
+ self.assertEqual(actual, expected)
+
def test_indices(self):
self.assertEqual(slice(None ).indices(10), (0, 10, 1))
self.assertEqual(slice(None, None, 2).indices(10), (0, 10, 2))
@@ -108,7 +186,41 @@ class SliceTest(unittest.TestCase):
self.assertEqual(list(range(10))[::sys.maxsize - 1], [0])
- self.assertRaises(OverflowError, slice(None).indices, 1<<100)
+ # Check a variety of start, stop, step and length values, including
+ # values exceeding sys.maxsize (see issue #14794).
+ vals = [None, -2**100, -2**30, -53, -7, -1, 0, 1, 7, 53, 2**30, 2**100]
+ lengths = [0, 1, 7, 53, 2**30, 2**100]
+ for slice_args in itertools.product(vals, repeat=3):
+ s = slice(*slice_args)
+ for length in lengths:
+ self.check_indices(s, length)
+ self.check_indices(slice(0, 10, 1), -3)
+
+ # Negative length should raise ValueError
+ with self.assertRaises(ValueError):
+ slice(None).indices(-1)
+
+ # Zero step should raise ValueError
+ with self.assertRaises(ValueError):
+ slice(0, 10, 0).indices(5)
+
+ # Using a start, stop or step or length that can't be interpreted as an
+ # integer should give a TypeError ...
+ with self.assertRaises(TypeError):
+ slice(0.0, 10, 1).indices(5)
+ with self.assertRaises(TypeError):
+ slice(0, 10.0, 1).indices(5)
+ with self.assertRaises(TypeError):
+ slice(0, 10, 1.0).indices(5)
+ with self.assertRaises(TypeError):
+ slice(0, 10, 1).indices(5.0)
+
+ # ... but it should be fine to use a custom class that provides index.
+ self.assertEqual(slice(0, 10, 1).indices(5), (0, 5, 1))
+ self.assertEqual(slice(MyIndexable(0), 10, 1).indices(5), (0, 5, 1))
+ self.assertEqual(slice(0, MyIndexable(10), 1).indices(5), (0, 5, 1))
+ self.assertEqual(slice(0, 10, MyIndexable(1)).indices(5), (0, 5, 1))
+ self.assertEqual(slice(0, 10, 1).indices(MyIndexable(5)), (0, 5, 1))
def test_setslice_without_getslice(self):
tmp = []
diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py
index 8d1dbbfc43..e6f39dec77 100644
--- a/Lib/test/test_smtplib.py
+++ b/Lib/test/test_smtplib.py
@@ -222,7 +222,7 @@ class DebuggingServerTests(unittest.TestCase):
self.assertEqual(smtp.source_address, ('127.0.0.1', port))
self.assertEqual(smtp.local_hostname, 'localhost')
smtp.quit()
- except IOError as e:
+ except OSError as e:
if e.errno == errno.EADDRINUSE:
self.skipTest("couldn't bind to port %d" % port)
raise
@@ -524,12 +524,6 @@ class DebuggingServerTests(unittest.TestCase):
class NonConnectingTests(unittest.TestCase):
- def setUp(self):
- smtplib.socket = mock_socket
-
- def tearDown(self):
- smtplib.socket = socket
-
def testNotConnected(self):
# Test various operations on an unconnected SMTP object that
# should raise exceptions (at present the attempt in SMTP.send
@@ -541,10 +535,10 @@ class NonConnectingTests(unittest.TestCase):
smtp.send, 'test msg')
def testNonnumericPort(self):
- # check that non-numeric port raises socket.error
- self.assertRaises(mock_socket.error, smtplib.SMTP,
+ # check that non-numeric port raises OSError
+ self.assertRaises(OSError, smtplib.SMTP,
"localhost", "bogus")
- self.assertRaises(mock_socket.error, smtplib.SMTP,
+ self.assertRaises(OSError, smtplib.SMTP,
"localhost:bogus")
@@ -825,6 +819,15 @@ class SMTPSimTests(unittest.TestCase):
self.assertIn(sim_auth_credentials['cram-md5'], str(err))
smtp.close()
+ def testAUTH_multiple(self):
+ # Test that multiple authentication methods are tried.
+ self.serv.add_feature("AUTH BOGUS PLAIN LOGIN CRAM-MD5")
+ smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
+ try: smtp.login(sim_auth[0], sim_auth[1])
+ except smtplib.SMTPAuthenticationError as err:
+ self.assertIn(sim_auth_login_password, str(err))
+ smtp.close()
+
def test_with_statement(self):
with smtplib.SMTP(HOST, self.port) as smtp:
code, message = smtp.noop()
diff --git a/Lib/test/test_sndhdr.py b/Lib/test/test_sndhdr.py
index 10046887d7..5e0abe0b36 100644
--- a/Lib/test/test_sndhdr.py
+++ b/Lib/test/test_sndhdr.py
@@ -12,7 +12,7 @@ class TestFormats(unittest.TestCase):
('sndhdr.hcom', ('hcom', 22050.0, 1, -1, 8)),
('sndhdr.sndt', ('sndt', 44100, 1, 5, 8)),
('sndhdr.voc', ('voc', 0, 1, -1, 8)),
- ('sndhdr.wav', ('wav', 44100, 2, -1, 16)),
+ ('sndhdr.wav', ('wav', 44100, 2, 5, 16)),
):
filename = findfile(filename, subdir="sndhdrdata")
what = sndhdr.what(filename)
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 12517ae95d..3c9fbf0603 100644
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -2,7 +2,6 @@
import unittest
from test import support
-from unittest.case import _ExpectedFailure
import errno
import io
@@ -24,13 +23,13 @@ import math
import pickle
import struct
try:
- import fcntl
-except ImportError:
- fcntl = False
-try:
import multiprocessing
except ImportError:
multiprocessing = False
+try:
+ import fcntl
+except ImportError:
+ fcntl = None
HOST = support.HOST
MSG = 'Michael Gilfix was here\u1234\r\n'.encode('utf-8') ## test unicode string and carriage return
@@ -46,7 +45,7 @@ def _have_socket_can():
"""Check whether CAN sockets are supported on this host."""
try:
s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
- except (AttributeError, socket.error, OSError):
+ except (AttributeError, OSError):
return False
else:
s.close()
@@ -121,12 +120,42 @@ class SocketCANTest(unittest.TestCase):
interface = 'vcan0'
bufsize = 128
+ """The CAN frame structure is defined in <linux/can.h>:
+
+ struct can_frame {
+ canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
+ __u8 can_dlc; /* data length code: 0 .. 8 */
+ __u8 data[8] __attribute__((aligned(8)));
+ };
+ """
+ can_frame_fmt = "=IB3x8s"
+ can_frame_size = struct.calcsize(can_frame_fmt)
+
+ """The Broadcast Management Command frame structure is defined
+ in <linux/can/bcm.h>:
+
+ struct bcm_msg_head {
+ __u32 opcode;
+ __u32 flags;
+ __u32 count;
+ struct timeval ival1, ival2;
+ canid_t can_id;
+ __u32 nframes;
+ struct can_frame frames[0];
+ }
+
+ `bcm_msg_head` must be 8 bytes aligned because of the `frames` member (see
+ `struct can_frame` definition). Must use native not standard types for packing.
+ """
+ bcm_cmd_msg_fmt = "@3I4l2I"
+ bcm_cmd_msg_fmt += "x" * (struct.calcsize(bcm_cmd_msg_fmt) % 8)
+
def setUp(self):
self.s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
self.addCleanup(self.s.close)
try:
self.s.bind((self.interface,))
- except socket.error:
+ except OSError:
self.skipTest('network interface `%s` does not exist' %
self.interface)
@@ -242,9 +271,6 @@ class ThreadableTest:
raise TypeError("test_func must be a callable function")
try:
test_func()
- except _ExpectedFailure:
- # We deliberately ignore expected failures
- pass
except BaseException as e:
self.queue.put(e)
finally:
@@ -295,7 +321,7 @@ class ThreadedCANSocketTest(SocketCANTest, ThreadableTest):
self.cli = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
try:
self.cli.bind((self.interface,))
- except socket.error:
+ except OSError:
# skipTest should not be called here, and will be called in the
# server instead
pass
@@ -543,11 +569,7 @@ class SCTPStreamBase(InetTestBase):
class Inet6TestBase(InetTestBase):
"""Base class for IPv6 socket tests."""
- # Don't use "localhost" here - it may not have an IPv6 address
- # assigned to it by default (e.g. in /etc/hosts), and if someone
- # has assigned it an IPv4-mapped address, then it's unlikely to
- # work with the full IPv6 API.
- host = "::1"
+ host = support.HOSTv6
class UDP6TestBase(Inet6TestBase):
"""Base class for UDP-over-IPv6 tests."""
@@ -608,7 +630,7 @@ def requireSocket(*args):
for obj in args]
try:
s = socket.socket(*callargs)
- except socket.error as e:
+ except OSError as e:
# XXX: check errno?
err = str(e)
else:
@@ -626,8 +648,17 @@ class GeneralModuleTests(unittest.TestCase):
def test_repr(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.addCleanup(s.close)
- self.assertTrue(repr(s).startswith("<socket.socket object"))
+ with s:
+ self.assertIn('fd=%i' % s.fileno(), repr(s))
+ self.assertIn('family=%s' % socket.AF_INET, repr(s))
+ self.assertIn('type=%s' % socket.SOCK_STREAM, repr(s))
+ self.assertIn('proto=0', repr(s))
+ self.assertNotIn('raddr', repr(s))
+ s.bind(('127.0.0.1', 0))
+ self.assertIn('laddr', repr(s))
+ self.assertIn(str(s.getsockname()), repr(s))
+ self.assertIn('[closed]', repr(s))
+ self.assertNotIn('laddr', repr(s))
def test_weakref(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -645,11 +676,11 @@ class GeneralModuleTests(unittest.TestCase):
def testSocketError(self):
# Testing socket module exceptions
msg = "Error raising socket exception (%s)."
- with self.assertRaises(socket.error, msg=msg % 'socket.error'):
- raise socket.error
- with self.assertRaises(socket.error, msg=msg % 'socket.herror'):
+ with self.assertRaises(OSError, msg=msg % 'OSError'):
+ raise OSError
+ with self.assertRaises(OSError, msg=msg % 'socket.herror'):
raise socket.herror
- with self.assertRaises(socket.error, msg=msg % 'socket.gaierror'):
+ with self.assertRaises(OSError, msg=msg % 'socket.gaierror'):
raise socket.gaierror
def testSendtoErrors(self):
@@ -712,13 +743,13 @@ class GeneralModuleTests(unittest.TestCase):
hostname = socket.gethostname()
try:
ip = socket.gethostbyname(hostname)
- except socket.error:
+ except OSError:
# Probably name lookup wasn't set up right; skip this test
return
self.assertTrue(ip.find('.') >= 0, "Error resolving host to ip.")
try:
hname, aliases, ipaddrs = socket.gethostbyaddr(ip)
- except socket.error:
+ except OSError:
# Probably a similar problem as above; skip this test
return
all_host_names = [hostname, hname] + aliases
@@ -726,13 +757,27 @@ class GeneralModuleTests(unittest.TestCase):
if not fqhn in all_host_names:
self.fail("Error testing host resolution mechanisms. (fqdn: %s, all: %s)" % (fqhn, repr(all_host_names)))
+ def test_host_resolution(self):
+ for addr in ['0.1.1.~1', '1+.1.1.1', '::1q', '::1::2',
+ '1:1:1:1:1:1:1:1:1']:
+ self.assertRaises(OSError, socket.gethostbyname, addr)
+ self.assertRaises(OSError, socket.gethostbyaddr, addr)
+
+ for addr in [support.HOST, '10.0.0.1', '255.255.255.255']:
+ self.assertEqual(socket.gethostbyname(addr), addr)
+
+ # we don't test support.HOSTv6 because there's a chance it doesn't have
+ # a matching name entry (e.g. 'ip6-localhost')
+ for host in [support.HOST]:
+ self.assertIn(host, socket.gethostbyaddr(host)[2])
+
@unittest.skipUnless(hasattr(socket, 'sethostname'), "test needs socket.sethostname()")
@unittest.skipUnless(hasattr(socket, 'gethostname'), "test needs socket.gethostname()")
def test_sethostname(self):
oldhn = socket.gethostname()
try:
socket.sethostname('new')
- except socket.error as e:
+ except OSError as e:
if e.errno == errno.EPERM:
self.skipTest("test should be run as root")
else:
@@ -766,8 +811,8 @@ class GeneralModuleTests(unittest.TestCase):
'socket.if_nameindex() not available.')
def testInvalidInterfaceNameIndex(self):
# test nonexistent interface index/name
- self.assertRaises(socket.error, socket.if_indextoname, 0)
- self.assertRaises(socket.error, socket.if_nametoindex, '_DEADBEEF')
+ self.assertRaises(OSError, socket.if_indextoname, 0)
+ self.assertRaises(OSError, socket.if_nametoindex, '_DEADBEEF')
# test with invalid values
self.assertRaises(TypeError, socket.if_nametoindex, 0)
self.assertRaises(TypeError, socket.if_indextoname, '_DEADBEEF')
@@ -789,7 +834,7 @@ class GeneralModuleTests(unittest.TestCase):
try:
# On some versions, this crashes the interpreter.
socket.getnameinfo(('x', 0, 0, 0), 0)
- except socket.error:
+ except OSError:
pass
def testNtoH(self):
@@ -836,17 +881,17 @@ class GeneralModuleTests(unittest.TestCase):
try:
port = socket.getservbyname(service, 'tcp')
break
- except socket.error:
+ except OSError:
pass
else:
- raise socket.error
+ raise OSError
# Try same call with optional protocol omitted
port2 = socket.getservbyname(service)
eq(port, port2)
# Try udp, but don't barf if it doesn't exist
try:
udpport = socket.getservbyname(service, 'udp')
- except socket.error:
+ except OSError:
udpport = None
else:
eq(udpport, port)
@@ -902,7 +947,7 @@ class GeneralModuleTests(unittest.TestCase):
g = lambda a: inet_pton(AF_INET, a)
assertInvalid = lambda func,a: self.assertRaises(
- (socket.error, ValueError), func, a
+ (OSError, ValueError), func, a
)
self.assertEqual(b'\x00\x00\x00\x00', f('0.0.0.0'))
@@ -935,9 +980,17 @@ class GeneralModuleTests(unittest.TestCase):
return
except ImportError:
return
+
+ if sys.platform == "win32":
+ try:
+ inet_pton(AF_INET6, '::')
+ except OSError as e:
+ if e.winerror == 10022:
+ return # IPv6 might not be installed on this PC
+
f = lambda a: inet_pton(AF_INET6, a)
assertInvalid = lambda a: self.assertRaises(
- (socket.error, ValueError), f, a
+ (OSError, ValueError), f, a
)
self.assertEqual(b'\x00' * 16, f('::'))
@@ -986,7 +1039,7 @@ class GeneralModuleTests(unittest.TestCase):
from socket import inet_ntoa as f, inet_ntop, AF_INET
g = lambda a: inet_ntop(AF_INET, a)
assertInvalid = lambda func,a: self.assertRaises(
- (socket.error, ValueError), func, a
+ (OSError, ValueError), func, a
)
self.assertEqual('1.0.1.0', f(b'\x01\x00\x01\x00'))
@@ -1013,9 +1066,17 @@ class GeneralModuleTests(unittest.TestCase):
return
except ImportError:
return
+
+ if sys.platform == "win32":
+ try:
+ inet_ntop(AF_INET6, b'\x00' * 16)
+ except OSError as e:
+ if e.winerror == 10022:
+ return # IPv6 might not be installed on this PC
+
f = lambda a: inet_ntop(AF_INET6, a)
assertInvalid = lambda a: self.assertRaises(
- (socket.error, ValueError), f, a
+ (OSError, ValueError), f, a
)
self.assertEqual('::', f(b'\x00' * 16))
@@ -1043,7 +1104,7 @@ class GeneralModuleTests(unittest.TestCase):
# At least for eCos. This is required for the S/390 to pass.
try:
my_ip_addr = socket.gethostbyname(socket.gethostname())
- except socket.error:
+ except OSError:
# Probably name lookup wasn't set up right; skip this test
return
self.assertIn(name[0], ("0.0.0.0", my_ip_addr), '%s invalid' % name[0])
@@ -1070,13 +1131,19 @@ class GeneralModuleTests(unittest.TestCase):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
sock.close()
- self.assertRaises(socket.error, sock.send, b"spam")
+ self.assertRaises(OSError, sock.send, b"spam")
def testNewAttributes(self):
# testing .family, .type and .protocol
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.assertEqual(sock.family, socket.AF_INET)
- self.assertEqual(sock.type, socket.SOCK_STREAM)
+ if hasattr(socket, 'SOCK_CLOEXEC'):
+ self.assertIn(sock.type,
+ (socket.SOCK_STREAM | socket.SOCK_CLOEXEC,
+ socket.SOCK_STREAM))
+ else:
+ self.assertEqual(sock.type, socket.SOCK_STREAM)
self.assertEqual(sock.proto, 0)
sock.close()
@@ -1129,9 +1196,12 @@ class GeneralModuleTests(unittest.TestCase):
socket.getaddrinfo(HOST, 80)
socket.getaddrinfo(HOST, None)
# test family and socktype filters
- infos = socket.getaddrinfo(HOST, None, socket.AF_INET)
- for family, _, _, _, _ in infos:
+ infos = socket.getaddrinfo(HOST, 80, socket.AF_INET, socket.SOCK_STREAM)
+ for family, type, _, _, _ in infos:
self.assertEqual(family, socket.AF_INET)
+ self.assertEqual(str(family), 'AddressFamily.AF_INET')
+ self.assertEqual(type, socket.SOCK_STREAM)
+ self.assertEqual(str(type), 'SocketType.SOCK_STREAM')
infos = socket.getaddrinfo(HOST, None, 0, socket.SOCK_STREAM)
for _, socktype, _, _, _ in infos:
self.assertEqual(socktype, socket.SOCK_STREAM)
@@ -1173,7 +1243,7 @@ class GeneralModuleTests(unittest.TestCase):
def test_getnameinfo(self):
# only IP addresses are allowed
- self.assertRaises(socket.error, socket.getnameinfo, ('mail.python.org',0), 0)
+ self.assertRaises(OSError, socket.getnameinfo, ('mail.python.org',0), 0)
@unittest.skipUnless(support.is_resource_enabled('network'),
'network is not enabled')
@@ -1285,10 +1355,31 @@ class GeneralModuleTests(unittest.TestCase):
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
def test_flowinfo(self):
self.assertRaises(OverflowError, socket.getnameinfo,
- ('::1',0, 0xffffffff), 0)
+ (support.HOSTv6, 0, 0xffffffff), 0)
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
- self.assertRaises(OverflowError, s.bind, ('::1', 0, -10))
-
+ self.assertRaises(OverflowError, s.bind, (support.HOSTv6, 0, -10))
+
+ def test_str_for_enums(self):
+ # Make sure that the AF_* and SOCK_* constants have enum-like string
+ # reprs.
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ self.assertEqual(str(s.family), 'AddressFamily.AF_INET')
+ self.assertEqual(str(s.type), 'SocketType.SOCK_STREAM')
+
+ @unittest.skipIf(os.name == 'nt', 'Will not work on Windows')
+ def test_uknown_socket_family_repr(self):
+ # Test that when created with a family that's not one of the known
+ # AF_*/SOCK_* constants, socket.family just returns the number.
+ #
+ # To do this we fool socket.socket into believing it already has an
+ # open fd because on this path it doesn't actually verify the family and
+ # type and populates the socket object.
+ #
+ # On Windows this trick won't work, so the test is skipped.
+ fd, _ = tempfile.mkstemp()
+ with socket.socket(family=42424, type=13331, fileno=fd) as s:
+ self.assertEqual(s.family, 42424)
+ self.assertEqual(s.type, 13331)
@unittest.skipUnless(HAVE_SOCKET_CAN, 'SocketCan required for this test.')
class BasicCANTest(unittest.TestCase):
@@ -1298,10 +1389,35 @@ class BasicCANTest(unittest.TestCase):
socket.PF_CAN
socket.CAN_RAW
+ @unittest.skipUnless(hasattr(socket, "CAN_BCM"),
+ 'socket.CAN_BCM required for this test.')
+ def testBCMConstants(self):
+ socket.CAN_BCM
+
+ # opcodes
+ socket.CAN_BCM_TX_SETUP # create (cyclic) transmission task
+ socket.CAN_BCM_TX_DELETE # remove (cyclic) transmission task
+ socket.CAN_BCM_TX_READ # read properties of (cyclic) transmission task
+ socket.CAN_BCM_TX_SEND # send one CAN frame
+ socket.CAN_BCM_RX_SETUP # create RX content filter subscription
+ socket.CAN_BCM_RX_DELETE # remove RX content filter subscription
+ socket.CAN_BCM_RX_READ # read properties of RX content filter subscription
+ socket.CAN_BCM_TX_STATUS # reply to TX_READ request
+ socket.CAN_BCM_TX_EXPIRED # notification on performed transmissions (count=0)
+ socket.CAN_BCM_RX_STATUS # reply to RX_READ request
+ socket.CAN_BCM_RX_TIMEOUT # cyclic message is absent
+ socket.CAN_BCM_RX_CHANGED # updated CAN frame (detected content change)
+
def testCreateSocket(self):
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
pass
+ @unittest.skipUnless(hasattr(socket, "CAN_BCM"),
+ 'socket.CAN_BCM required for this test.')
+ def testCreateBCMSocket(self):
+ with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_BCM) as s:
+ pass
+
def testBindAny(self):
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
s.bind(('', ))
@@ -1309,7 +1425,7 @@ class BasicCANTest(unittest.TestCase):
def testTooLongInterfaceName(self):
# most systems limit IFNAMSIZ to 16, take 1024 to be sure
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
- self.assertRaisesRegex(socket.error, 'interface name too long',
+ self.assertRaisesRegex(OSError, 'interface name too long',
s.bind, ('x' * 1024,))
@unittest.skipUnless(hasattr(socket, "CAN_RAW_LOOPBACK"),
@@ -1334,19 +1450,8 @@ class BasicCANTest(unittest.TestCase):
@unittest.skipUnless(HAVE_SOCKET_CAN, 'SocketCan required for this test.')
-@unittest.skipUnless(thread, 'Threading required for this test.')
class CANTest(ThreadedCANSocketTest):
- """The CAN frame structure is defined in <linux/can.h>:
-
- struct can_frame {
- canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
- __u8 can_dlc; /* data length code: 0 .. 8 */
- __u8 data[8] __attribute__((aligned(8)));
- };
- """
- can_frame_fmt = "=IB3x8s"
-
def __init__(self, methodName='runTest'):
ThreadedCANSocketTest.__init__(self, methodName=methodName)
@@ -1395,6 +1500,46 @@ class CANTest(ThreadedCANSocketTest):
self.cf2 = self.build_can_frame(0x12, b'\x99\x22\x33')
self.cli.send(self.cf2)
+ @unittest.skipUnless(hasattr(socket, "CAN_BCM"),
+ 'socket.CAN_BCM required for this test.')
+ def _testBCM(self):
+ cf, addr = self.cli.recvfrom(self.bufsize)
+ self.assertEqual(self.cf, cf)
+ can_id, can_dlc, data = self.dissect_can_frame(cf)
+ self.assertEqual(self.can_id, can_id)
+ self.assertEqual(self.data, data)
+
+ @unittest.skipUnless(hasattr(socket, "CAN_BCM"),
+ 'socket.CAN_BCM required for this test.')
+ def testBCM(self):
+ bcm = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_BCM)
+ self.addCleanup(bcm.close)
+ bcm.connect((self.interface,))
+ self.can_id = 0x123
+ self.data = bytes([0xc0, 0xff, 0xee])
+ self.cf = self.build_can_frame(self.can_id, self.data)
+ opcode = socket.CAN_BCM_TX_SEND
+ flags = 0
+ count = 0
+ ival1_seconds = ival1_usec = ival2_seconds = ival2_usec = 0
+ bcm_can_id = 0x0222
+ nframes = 1
+ assert len(self.cf) == 16
+ header = struct.pack(self.bcm_cmd_msg_fmt,
+ opcode,
+ flags,
+ count,
+ ival1_seconds,
+ ival1_usec,
+ ival2_seconds,
+ ival2_usec,
+ bcm_can_id,
+ nframes,
+ )
+ header_plus_frame = header + self.cf
+ bytes_sent = bcm.send(header_plus_frame)
+ self.assertEqual(bytes_sent, len(header_plus_frame))
+
@unittest.skipUnless(HAVE_SOCKET_RDS, 'RDS sockets required for this test.')
class BasicRDSTest(unittest.TestCase):
@@ -1609,7 +1754,7 @@ class BasicTCPTest(SocketConnectedTest):
self.assertEqual(f, fileno)
# cli_conn cannot be used anymore...
self.assertTrue(self.cli_conn._closed)
- self.assertRaises(socket.error, self.cli_conn.recv, 1024)
+ self.assertRaises(OSError, self.cli_conn.recv, 1024)
self.cli_conn.close()
# ...but we can create another socket using the (still open)
# file descriptor
@@ -1978,7 +2123,7 @@ class SendmsgTests(SendrecvmsgServerTimeoutBase):
def _testSendmsgExcessCmsgReject(self):
if not hasattr(socket, "CMSG_SPACE"):
# Can only send one item
- with self.assertRaises(socket.error) as cm:
+ with self.assertRaises(OSError) as cm:
self.sendmsgToServer([MSG], [(0, 0, b""), (0, 0, b"")])
self.assertIsNone(cm.exception.errno)
self.sendToServer(b"done")
@@ -1989,7 +2134,7 @@ class SendmsgTests(SendrecvmsgServerTimeoutBase):
def _testSendmsgAfterClose(self):
self.cli_sock.close()
- self.assertRaises(socket.error, self.sendmsgToServer, [MSG])
+ self.assertRaises(OSError, self.sendmsgToServer, [MSG])
class SendmsgStreamTests(SendmsgTests):
@@ -2033,7 +2178,7 @@ class SendmsgStreamTests(SendmsgTests):
@testSendmsgDontWait.client_skip
def _testSendmsgDontWait(self):
try:
- with self.assertRaises(socket.error) as cm:
+ with self.assertRaises(OSError) as cm:
while True:
self.sendmsgToServer([b"a"*512], [], socket.MSG_DONTWAIT)
self.assertIn(cm.exception.errno,
@@ -2053,9 +2198,9 @@ class SendmsgConnectionlessTests(SendmsgTests):
pass
def _testSendmsgNoDestAddr(self):
- self.assertRaises(socket.error, self.cli_sock.sendmsg,
+ self.assertRaises(OSError, self.cli_sock.sendmsg,
[MSG])
- self.assertRaises(socket.error, self.cli_sock.sendmsg,
+ self.assertRaises(OSError, self.cli_sock.sendmsg,
[MSG], [], 0, None)
@@ -2141,7 +2286,7 @@ class RecvmsgGenericTests(SendrecvmsgBase):
def testRecvmsgAfterClose(self):
# Check that recvmsg[_into]() fails on a closed socket.
self.serv_sock.close()
- self.assertRaises(socket.error, self.doRecvmsg, self.serv_sock, 1024)
+ self.assertRaises(OSError, self.doRecvmsg, self.serv_sock, 1024)
def _testRecvmsgAfterClose(self):
pass
@@ -2587,7 +2732,7 @@ class SCMRightsTest(SendrecvmsgServerTimeoutBase):
# call fails, just send msg with no ancillary data.
try:
nbytes = self.sendmsgToServer([msg], ancdata)
- except socket.error as e:
+ except OSError as e:
# Check that it was the system call that failed
self.assertIsInstance(e.errno, int)
nbytes = self.sendmsgToServer([msg])
@@ -2965,7 +3110,7 @@ class RFC3542AncillaryTest(SendrecvmsgServerTimeoutBase):
array.array("i", [self.traffic_class]).tobytes() + b"\x00"),
(socket.IPPROTO_IPV6, socket.IPV6_HOPLIMIT,
array.array("i", [self.hop_limit]))])
- except socket.error as e:
+ except OSError as e:
self.assertIsInstance(e.errno, int)
nbytes = self.sendmsgToServer(
[MSG],
@@ -3423,10 +3568,10 @@ class InterruptedRecvTimeoutTest(InterruptedTimeoutBase, UDPTestBase):
self.serv.settimeout(self.timeout)
def checkInterruptedRecv(self, func, *args, **kwargs):
- # Check that func(*args, **kwargs) raises socket.error with an
+ # Check that func(*args, **kwargs) raises OSError with an
# errno of EINTR when interrupted by a signal.
self.setAlarm(self.alarm_time)
- with self.assertRaises(socket.error) as cm:
+ with self.assertRaises(OSError) as cm:
func(*args, **kwargs)
self.assertNotIsInstance(cm.exception, socket.timeout)
self.assertEqual(cm.exception.errno, errno.EINTR)
@@ -3483,9 +3628,9 @@ class InterruptedSendTimeoutTest(InterruptedTimeoutBase,
def checkInterruptedSend(self, func, *args, **kwargs):
# Check that func(*args, **kwargs), run in a loop, raises
- # socket.error with an errno of EINTR when interrupted by a
+ # OSError with an errno of EINTR when interrupted by a
# signal.
- with self.assertRaises(socket.error) as cm:
+ with self.assertRaises(OSError) as cm:
while True:
self.setAlarm(self.alarm_time)
func(*args, **kwargs)
@@ -3584,7 +3729,7 @@ class NonBlockingTCPTests(ThreadedTCPSocketTest):
start = time.time()
try:
self.serv.accept()
- except socket.error:
+ except OSError:
pass
end = time.time()
self.assertTrue((end - start) < 1.0, "Error setting non-blocking mode.")
@@ -3610,7 +3755,7 @@ class NonBlockingTCPTests(ThreadedTCPSocketTest):
start = time.time()
try:
self.serv.accept()
- except socket.error:
+ except OSError:
pass
end = time.time()
self.assertTrue((end - start) < 1.0, "Error creating with non-blocking mode.")
@@ -3640,7 +3785,7 @@ class NonBlockingTCPTests(ThreadedTCPSocketTest):
self.serv.setblocking(0)
try:
conn, addr = self.serv.accept()
- except socket.error:
+ except OSError:
pass
else:
self.fail("Error trying to do non-blocking accept.")
@@ -3670,7 +3815,7 @@ class NonBlockingTCPTests(ThreadedTCPSocketTest):
conn.setblocking(0)
try:
msg = conn.recv(len(MSG))
- except socket.error:
+ except OSError:
pass
else:
self.fail("Error trying to do non-blocking recv.")
@@ -3753,7 +3898,7 @@ class FileObjectClassTestCase(SocketConnectedTest):
# First read raises a timeout
self.assertRaises(socket.timeout, self.read_file.read, 1)
# Second read is disallowed
- with self.assertRaises(IOError) as ctx:
+ with self.assertRaises(OSError) as ctx:
self.read_file.read(1)
self.assertIn("cannot read from timed out object", str(ctx.exception))
@@ -3845,7 +3990,7 @@ class FileObjectClassTestCase(SocketConnectedTest):
self.read_file.close()
self.assertRaises(ValueError, self.read_file.fileno)
self.cli_conn.close()
- self.assertRaises(socket.error, self.cli_conn.getsockname)
+ self.assertRaises(OSError, self.cli_conn.getsockname)
def _testRealClose(self):
pass
@@ -3882,7 +4027,7 @@ class FileObjectInterruptedTestCase(unittest.TestCase):
@staticmethod
def _raise_eintr():
- raise socket.error(errno.EINTR, "interrupted")
+ raise OSError(errno.EINTR, "interrupted")
def _textiowrap_mock_socket(self, mock, buffering=-1):
raw = socket.SocketIO(mock, "r")
@@ -3994,7 +4139,7 @@ class UnbufferedFileObjectClassTestCase(FileObjectClassTestCase):
self.assertEqual(msg, self.read_msg)
# ...until the file is itself closed
self.read_file.close()
- self.assertRaises(socket.error, self.cli_conn.recv, 1024)
+ self.assertRaises(OSError, self.cli_conn.recv, 1024)
def _testMakefileClose(self):
self.write_file.write(self.write_msg)
@@ -4143,7 +4288,7 @@ class NetworkConnectionNoServer(unittest.TestCase):
port = support.find_unused_port()
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.addCleanup(cli.close)
- with self.assertRaises(socket.error) as cm:
+ with self.assertRaises(OSError) as cm:
cli.connect((HOST, port))
self.assertEqual(cm.exception.errno, errno.ECONNREFUSED)
@@ -4151,7 +4296,7 @@ class NetworkConnectionNoServer(unittest.TestCase):
# Issue #9792: errors raised by create_connection() should have
# a proper errno attribute.
port = support.find_unused_port()
- with self.assertRaises(socket.error) as cm:
+ with self.assertRaises(OSError) as cm:
socket.create_connection((HOST, port))
# Issue #16257: create_connection() calls getaddrinfo() against
@@ -4299,7 +4444,7 @@ class TCPTimeoutTest(SocketTCPTest):
foo = self.serv.accept()
except socket.timeout:
self.fail("caught timeout instead of error (TCP)")
- except socket.error:
+ except OSError:
ok = True
except:
self.fail("caught unexpected exception (TCP)")
@@ -4356,7 +4501,7 @@ class UDPTimeoutTest(SocketUDPTest):
foo = self.serv.recv(1024)
except socket.timeout:
self.fail("caught timeout instead of error (UDP)")
- except socket.error:
+ except OSError:
ok = True
except:
self.fail("caught unexpected exception (UDP)")
@@ -4366,10 +4511,10 @@ class UDPTimeoutTest(SocketUDPTest):
class TestExceptions(unittest.TestCase):
def testExceptionTree(self):
- self.assertTrue(issubclass(socket.error, Exception))
- self.assertTrue(issubclass(socket.herror, socket.error))
- self.assertTrue(issubclass(socket.gaierror, socket.error))
- self.assertTrue(issubclass(socket.timeout, socket.error))
+ self.assertTrue(issubclass(OSError, Exception))
+ self.assertTrue(issubclass(socket.herror, OSError))
+ self.assertTrue(issubclass(socket.gaierror, OSError))
+ self.assertTrue(issubclass(socket.timeout, OSError))
@unittest.skipUnless(sys.platform == 'linux', 'Linux specific test')
class TestLinuxAbstractNamespace(unittest.TestCase):
@@ -4396,7 +4541,7 @@ class TestLinuxAbstractNamespace(unittest.TestCase):
def testNameOverflow(self):
address = "\x00" + "h" * self.UNIX_PATH_MAX
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
- self.assertRaises(socket.error, s.bind, address)
+ self.assertRaises(OSError, s.bind, address)
def testStrName(self):
# Check that an abstract name can be passed as a string.
@@ -4638,7 +4783,7 @@ class ContextManagersTest(ThreadedTCPSocketTest):
self.assertTrue(sock._closed)
# exception inside with block
with socket.socket() as sock:
- self.assertRaises(socket.error, sock.sendall, b'foo')
+ self.assertRaises(OSError, sock.sendall, b'foo')
self.assertTrue(sock._closed)
def testCreateConnectionBase(self):
@@ -4666,19 +4811,76 @@ class ContextManagersTest(ThreadedTCPSocketTest):
with socket.create_connection(address) as sock:
sock.close()
self.assertTrue(sock._closed)
- self.assertRaises(socket.error, sock.sendall, b'foo')
+ self.assertRaises(OSError, sock.sendall, b'foo')
-@unittest.skipUnless(hasattr(socket, "SOCK_CLOEXEC"),
- "SOCK_CLOEXEC not defined")
-@unittest.skipUnless(fcntl, "module fcntl not available")
-class CloexecConstantTest(unittest.TestCase):
+class InheritanceTest(unittest.TestCase):
+ @unittest.skipUnless(hasattr(socket, "SOCK_CLOEXEC"),
+ "SOCK_CLOEXEC not defined")
@support.requires_linux_version(2, 6, 28)
def test_SOCK_CLOEXEC(self):
with socket.socket(socket.AF_INET,
socket.SOCK_STREAM | socket.SOCK_CLOEXEC) as s:
self.assertTrue(s.type & socket.SOCK_CLOEXEC)
- self.assertTrue(fcntl.fcntl(s, fcntl.F_GETFD) & fcntl.FD_CLOEXEC)
+ self.assertFalse(s.get_inheritable())
+
+ def test_default_inheritable(self):
+ sock = socket.socket()
+ with sock:
+ self.assertEqual(sock.get_inheritable(), False)
+
+ def test_dup(self):
+ sock = socket.socket()
+ with sock:
+ newsock = sock.dup()
+ sock.close()
+ with newsock:
+ self.assertEqual(newsock.get_inheritable(), False)
+
+ def test_set_inheritable(self):
+ sock = socket.socket()
+ with sock:
+ sock.set_inheritable(True)
+ self.assertEqual(sock.get_inheritable(), True)
+
+ sock.set_inheritable(False)
+ self.assertEqual(sock.get_inheritable(), False)
+
+ @unittest.skipIf(fcntl is None, "need fcntl")
+ def test_get_inheritable_cloexec(self):
+ sock = socket.socket()
+ with sock:
+ fd = sock.fileno()
+ self.assertEqual(sock.get_inheritable(), False)
+
+ # clear FD_CLOEXEC flag
+ flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+ flags &= ~fcntl.FD_CLOEXEC
+ fcntl.fcntl(fd, fcntl.F_SETFD, flags)
+
+ self.assertEqual(sock.get_inheritable(), True)
+
+ @unittest.skipIf(fcntl is None, "need fcntl")
+ def test_set_inheritable_cloexec(self):
+ sock = socket.socket()
+ with sock:
+ fd = sock.fileno()
+ self.assertEqual(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC,
+ fcntl.FD_CLOEXEC)
+
+ sock.set_inheritable(True)
+ self.assertEqual(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC,
+ 0)
+
+
+ @unittest.skipUnless(hasattr(socket, "socketpair"),
+ "need socket.socketpair()")
+ def test_socketpair(self):
+ s1, s2 = socket.socketpair()
+ self.addCleanup(s1.close)
+ self.addCleanup(s2.close)
+ self.assertEqual(s1.get_inheritable(), False)
+ self.assertEqual(s2.get_inheritable(), False)
@unittest.skipUnless(hasattr(socket, "SOCK_NONBLOCK"),
@@ -4847,7 +5049,7 @@ def test_main():
NetworkConnectionAttributesTest,
NetworkConnectionBehaviourTest,
ContextManagersTest,
- CloexecConstantTest,
+ InheritanceTest,
NonblockConstantTest
])
tests.append(BasicSocketPairTest)
diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py
index 59d8e5dcb7..0617b30a69 100644
--- a/Lib/test/test_socketserver.py
+++ b/Lib/test/test_socketserver.py
@@ -2,8 +2,8 @@
Test suite for socketserver.
"""
+import _imp as imp
import contextlib
-import imp
import os
import select
import signal
@@ -29,7 +29,7 @@ HOST = test.support.HOST
HAVE_UNIX_SOCKETS = hasattr(socket, "AF_UNIX")
requires_unix_sockets = unittest.skipUnless(HAVE_UNIX_SOCKETS,
'requires Unix sockets')
-HAVE_FORKING = hasattr(os, "fork") and os.name != "os2"
+HAVE_FORKING = hasattr(os, "fork")
requires_forking = unittest.skipUnless(HAVE_FORKING, 'requires forking')
def signal_alarm(n):
@@ -85,7 +85,7 @@ class SocketServerTest(unittest.TestCase):
for fn in self.test_files:
try:
os.remove(fn)
- except os.error:
+ except OSError:
pass
self.test_files[:] = []
@@ -96,21 +96,7 @@ class SocketServerTest(unittest.TestCase):
# XXX: We need a way to tell AF_UNIX to pick its own name
# like AF_INET provides port==0.
dir = None
- if os.name == 'os2':
- dir = '\socket'
fn = tempfile.mktemp(prefix='unix_socket.', dir=dir)
- if os.name == 'os2':
- # AF_UNIX socket names on OS/2 require a specific prefix
- # which can't include a drive letter and must also use
- # backslashes as directory separators
- if fn[1] == ':':
- fn = fn[2:]
- if fn[0] in (os.sep, os.altsep):
- fn = fn[1:]
- if os.sep == '/':
- fn = fn.replace(os.sep, os.altsep)
- else:
- fn = fn.replace(os.altsep, os.sep)
self.test_files.append(fn)
return fn
diff --git a/Lib/test/test_pep263.py b/Lib/test/test_source_encoding.py
index 324ae38619..cd9d2b374c 100644
--- a/Lib/test/test_pep263.py
+++ b/Lib/test/test_source_encoding.py
@@ -1,9 +1,12 @@
# -*- coding: koi8-r -*-
import unittest
-from test import support
+from test.support import TESTFN, unlink, unload
+import importlib
+import os
+import sys
-class PEP263Test(unittest.TestCase):
+class SourceEncodingTest(unittest.TestCase):
def test_pep263(self):
self.assertEqual(
@@ -72,9 +75,61 @@ class PEP263Test(unittest.TestCase):
with self.assertRaisesRegex(SyntaxError, 'BOM'):
compile(b'\xef\xbb\xbf# -*- coding: fake -*-\n', 'dummy', 'exec')
+ def test_bad_coding(self):
+ module_name = 'bad_coding'
+ self.verify_bad_module(module_name)
-def test_main():
- support.run_unittest(PEP263Test)
+ def test_bad_coding2(self):
+ module_name = 'bad_coding2'
+ self.verify_bad_module(module_name)
-if __name__=="__main__":
- test_main()
+ def verify_bad_module(self, module_name):
+ self.assertRaises(SyntaxError, __import__, 'test.' + module_name)
+
+ path = os.path.dirname(__file__)
+ filename = os.path.join(path, module_name + '.py')
+ with open(filename, "rb") as fp:
+ bytes = fp.read()
+ self.assertRaises(SyntaxError, compile, bytes, filename, 'exec')
+
+ def test_exec_valid_coding(self):
+ d = {}
+ exec(b'# coding: cp949\na = "\xaa\xa7"\n', d)
+ self.assertEqual(d['a'], '\u3047')
+
+ def test_file_parse(self):
+ # issue1134: all encodings outside latin-1 and utf-8 fail on
+ # multiline strings and long lines (>512 columns)
+ unload(TESTFN)
+ filename = TESTFN + ".py"
+ f = open(filename, "w", encoding="cp1252")
+ sys.path.insert(0, os.curdir)
+ try:
+ with f:
+ f.write("# -*- coding: cp1252 -*-\n")
+ f.write("'''A short string\n")
+ f.write("'''\n")
+ f.write("'A very long string %s'\n" % ("X" * 1000))
+
+ importlib.invalidate_caches()
+ __import__(TESTFN)
+ finally:
+ del sys.path[0]
+ unlink(filename)
+ unlink(filename + "c")
+ unlink(filename + "o")
+ unload(TESTFN)
+
+ def test_error_from_string(self):
+ # See http://bugs.python.org/issue6289
+ input = "# coding: ascii\n\N{SNOWMAN}".encode('utf-8')
+ with self.assertRaises(SyntaxError) as c:
+ compile(input, "<string>", "exec")
+ expected = "'ascii' codec can't decode byte 0xe2 in position 16: " \
+ "ordinal not in range(128)"
+ self.assertTrue(c.exception.args[0].startswith(expected),
+ msg=c.exception.args[0])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 06d4598a87..ed263c3974 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -6,6 +6,7 @@ from test import support
import socket
import select
import time
+import datetime
import gc
import os
import errno
@@ -17,19 +18,15 @@ import asyncore
import weakref
import platform
import functools
+from unittest import mock
ssl = support.import_module("ssl")
-PROTOCOLS = [
- ssl.PROTOCOL_SSLv3,
- ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1
-]
-if hasattr(ssl, 'PROTOCOL_SSLv2'):
- PROTOCOLS.append(ssl.PROTOCOL_SSLv2)
-
+PROTOCOLS = sorted(ssl._PROTOCOL_NAMES)
HOST = support.HOST
-data_file = lambda name: os.path.join(os.path.dirname(__file__), name)
+def data_file(*name):
+ return os.path.join(os.path.dirname(__file__), *name)
# The custom key and certificate files used in test_ssl are generated
# using Lib/test/make_ssl_certs.py.
@@ -47,6 +44,17 @@ ONLYKEY_PROTECTED = data_file("ssl_key.passwd.pem")
KEY_PASSWORD = "somepass"
CAPATH = data_file("capath")
BYTES_CAPATH = os.fsencode(CAPATH)
+CAFILE_NEURONIO = data_file("capath", "4e1295a3.0")
+CAFILE_CACERT = data_file("capath", "5ed36f99.0")
+
+
+# empty CRL
+CRLFILE = data_file("revocation.crl")
+
+# Two keys and certs signed by the same CA (for SNI tests)
+SIGNED_CERTFILE = data_file("keycert3.pem")
+SIGNED_CERTFILE2 = data_file("keycert4.pem")
+SIGNING_CA = data_file("pycacert.pem")
SVN_PYTHON_ORG_ROOT_CERT = data_file("https_svn_python_org_root.pem")
@@ -60,6 +68,7 @@ NULLBYTECERT = data_file("nullbytecert.pem")
DHFILE = data_file("dh512.pem")
BYTES_DHFILE = os.fsencode(DHFILE)
+
def handle_error(prefix):
exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
if support.verbose:
@@ -73,6 +82,23 @@ def no_sslv2_implies_sslv3_hello():
# 0.9.7h or higher
return ssl.OPENSSL_VERSION_INFO >= (0, 9, 7, 8, 15)
+def have_verify_flags():
+ # 0.9.8 or higher
+ return ssl.OPENSSL_VERSION_INFO >= (0, 9, 8, 0, 15)
+
+def asn1time(cert_time):
+ # Some versions of OpenSSL ignore seconds, see #18207
+ # 0.9.8.i
+ if ssl._OPENSSL_API_VERSION == (0, 9, 8, 9, 15):
+ fmt = "%b %d %H:%M:%S %Y GMT"
+ dt = datetime.datetime.strptime(cert_time, fmt)
+ dt = dt.replace(second=0)
+ cert_time = dt.strftime(fmt)
+ # %d adds leading zero but ASN1_TIME_print() uses leading space
+ if cert_time[4] == "0":
+ cert_time = cert_time[:4] + " " + cert_time[5:]
+
+ return cert_time
# Issue #9415: Ubuntu hijacks their OpenSSL and forcefully disables SSLv2
def skip_if_broken_ubuntu_ssl(func):
@@ -90,14 +116,12 @@ def skip_if_broken_ubuntu_ssl(func):
else:
return func
+needs_sni = unittest.skipUnless(ssl.HAS_SNI, "SNI support needed for this test")
+
class BasicSocketTests(unittest.TestCase):
def test_constants(self):
- #ssl.PROTOCOL_SSLv2
- ssl.PROTOCOL_SSLv23
- ssl.PROTOCOL_SSLv3
- ssl.PROTOCOL_TLSv1
ssl.CERT_NONE
ssl.CERT_OPTIONAL
ssl.CERT_REQUIRED
@@ -175,8 +199,9 @@ class BasicSocketTests(unittest.TestCase):
(('organizationName', 'Python Software Foundation'),),
(('commonName', 'localhost'),))
)
- self.assertEqual(p['notAfter'], 'Oct 5 23:01:56 2020 GMT')
- self.assertEqual(p['notBefore'], 'Oct 8 23:01:56 2010 GMT')
+ # Note the next three asserts will fail if the keys are regenerated
+ self.assertEqual(p['notAfter'], asn1time('Oct 5 23:01:56 2020 GMT'))
+ self.assertEqual(p['notBefore'], asn1time('Oct 8 23:01:56 2010 GMT'))
self.assertEqual(p['serialNumber'], 'D7C7381919AFC24E')
self.assertEqual(p['subject'],
((('countryName', 'XY'),),
@@ -194,6 +219,12 @@ class BasicSocketTests(unittest.TestCase):
(('DNS', 'projects.developer.nokia.com'),
('DNS', 'projects.forum.nokia.com'))
)
+ # extra OCSP and AIA fields
+ self.assertEqual(p['OCSP'], ('http://ocsp.verisign.com',))
+ self.assertEqual(p['caIssuers'],
+ ('http://SVRIntl-G3-aia.verisign.com/SVRIntlG3.cer',))
+ self.assertEqual(p['crlDistributionPoints'],
+ ('http://SVRIntl-G3-crl.verisign.com/SVRIntlG3.crl',))
def test_parse_cert_CVE_2013_4238(self):
p = ssl._ssl._test_decode_cert(NULLBYTECERT)
@@ -276,15 +307,15 @@ class BasicSocketTests(unittest.TestCase):
def test_wrapped_unconnected(self):
# Methods on an unconnected SSLSocket propagate the original
- # socket.error raise by the underlying socket object.
+ # OSError raise by the underlying socket object.
s = socket.socket(socket.AF_INET)
with ssl.wrap_socket(s) as ss:
- self.assertRaises(socket.error, ss.recv, 1)
- self.assertRaises(socket.error, ss.recv_into, bytearray(b'x'))
- self.assertRaises(socket.error, ss.recvfrom, 1)
- self.assertRaises(socket.error, ss.recvfrom_into, bytearray(b'x'), 1)
- self.assertRaises(socket.error, ss.send, b'x')
- self.assertRaises(socket.error, ss.sendto, b'x', ('0.0.0.0', 0))
+ self.assertRaises(OSError, ss.recv, 1)
+ self.assertRaises(OSError, ss.recv_into, bytearray(b'x'))
+ self.assertRaises(OSError, ss.recvfrom, 1)
+ self.assertRaises(OSError, ss.recvfrom_into, bytearray(b'x'), 1)
+ self.assertRaises(OSError, ss.send, b'x')
+ self.assertRaises(OSError, ss.sendto, b'x', ('0.0.0.0', 0))
def test_timeout(self):
# Issue #8524: when creating an SSL socket, the timeout of the
@@ -309,15 +340,15 @@ class BasicSocketTests(unittest.TestCase):
with ssl.wrap_socket(sock, server_side=True, certfile=CERTFILE) as s:
self.assertRaisesRegex(ValueError, "can't connect in server-side mode",
s.connect, (HOST, 8080))
- with self.assertRaises(IOError) as cm:
+ with self.assertRaises(OSError) as cm:
with socket.socket() as sock:
ssl.wrap_socket(sock, certfile=WRONGCERT)
self.assertEqual(cm.exception.errno, errno.ENOENT)
- with self.assertRaises(IOError) as cm:
+ with self.assertRaises(OSError) as cm:
with socket.socket() as sock:
ssl.wrap_socket(sock, certfile=CERTFILE, keyfile=WRONGCERT)
self.assertEqual(cm.exception.errno, errno.ENOENT)
- with self.assertRaises(IOError) as cm:
+ with self.assertRaises(OSError) as cm:
with socket.socket() as sock:
ssl.wrap_socket(sock, certfile=WRONGCERT, keyfile=WRONGCERT)
self.assertEqual(cm.exception.errno, errno.ENOENT)
@@ -489,15 +520,121 @@ class BasicSocketTests(unittest.TestCase):
support.gc_collect()
self.assertIn(r, str(cm.warning.args[0]))
+ def test_get_default_verify_paths(self):
+ paths = ssl.get_default_verify_paths()
+ self.assertEqual(len(paths), 6)
+ self.assertIsInstance(paths, ssl.DefaultVerifyPaths)
+
+ with support.EnvironmentVarGuard() as env:
+ env["SSL_CERT_DIR"] = CAPATH
+ env["SSL_CERT_FILE"] = CERTFILE
+ paths = ssl.get_default_verify_paths()
+ self.assertEqual(paths.cafile, CERTFILE)
+ self.assertEqual(paths.capath, CAPATH)
+
+ @unittest.skipUnless(sys.platform == "win32", "Windows specific")
+ def test_enum_certificates(self):
+ self.assertTrue(ssl.enum_certificates("CA"))
+ self.assertTrue(ssl.enum_certificates("ROOT"))
+
+ self.assertRaises(TypeError, ssl.enum_certificates)
+ self.assertRaises(WindowsError, ssl.enum_certificates, "")
+
+ trust_oids = set()
+ for storename in ("CA", "ROOT"):
+ store = ssl.enum_certificates(storename)
+ self.assertIsInstance(store, list)
+ for element in store:
+ self.assertIsInstance(element, tuple)
+ self.assertEqual(len(element), 3)
+ cert, enc, trust = element
+ self.assertIsInstance(cert, bytes)
+ self.assertIn(enc, {"x509_asn", "pkcs_7_asn"})
+ self.assertIsInstance(trust, (set, bool))
+ if isinstance(trust, set):
+ trust_oids.update(trust)
+
+ serverAuth = "1.3.6.1.5.5.7.3.1"
+ self.assertIn(serverAuth, trust_oids)
+
+ @unittest.skipUnless(sys.platform == "win32", "Windows specific")
+ def test_enum_crls(self):
+ self.assertTrue(ssl.enum_crls("CA"))
+ self.assertRaises(TypeError, ssl.enum_crls)
+ self.assertRaises(WindowsError, ssl.enum_crls, "")
+
+ crls = ssl.enum_crls("CA")
+ self.assertIsInstance(crls, list)
+ for element in crls:
+ self.assertIsInstance(element, tuple)
+ self.assertEqual(len(element), 2)
+ self.assertIsInstance(element[0], bytes)
+ self.assertIn(element[1], {"x509_asn", "pkcs_7_asn"})
+
+
+ def test_asn1object(self):
+ expected = (129, 'serverAuth', 'TLS Web Server Authentication',
+ '1.3.6.1.5.5.7.3.1')
+
+ val = ssl._ASN1Object('1.3.6.1.5.5.7.3.1')
+ self.assertEqual(val, expected)
+ self.assertEqual(val.nid, 129)
+ self.assertEqual(val.shortname, 'serverAuth')
+ self.assertEqual(val.longname, 'TLS Web Server Authentication')
+ self.assertEqual(val.oid, '1.3.6.1.5.5.7.3.1')
+ self.assertIsInstance(val, ssl._ASN1Object)
+ self.assertRaises(ValueError, ssl._ASN1Object, 'serverAuth')
+
+ val = ssl._ASN1Object.fromnid(129)
+ self.assertEqual(val, expected)
+ self.assertIsInstance(val, ssl._ASN1Object)
+ self.assertRaises(ValueError, ssl._ASN1Object.fromnid, -1)
+ with self.assertRaisesRegex(ValueError, "unknown NID 100000"):
+ ssl._ASN1Object.fromnid(100000)
+ for i in range(1000):
+ try:
+ obj = ssl._ASN1Object.fromnid(i)
+ except ValueError:
+ pass
+ else:
+ self.assertIsInstance(obj.nid, int)
+ self.assertIsInstance(obj.shortname, str)
+ self.assertIsInstance(obj.longname, str)
+ self.assertIsInstance(obj.oid, (str, type(None)))
+
+ val = ssl._ASN1Object.fromname('TLS Web Server Authentication')
+ self.assertEqual(val, expected)
+ self.assertIsInstance(val, ssl._ASN1Object)
+ self.assertEqual(ssl._ASN1Object.fromname('serverAuth'), expected)
+ self.assertEqual(ssl._ASN1Object.fromname('1.3.6.1.5.5.7.3.1'),
+ expected)
+ with self.assertRaisesRegex(ValueError, "unknown object 'serverauth'"):
+ ssl._ASN1Object.fromname('serverauth')
+
+ def test_purpose_enum(self):
+ val = ssl._ASN1Object('1.3.6.1.5.5.7.3.1')
+ self.assertIsInstance(ssl.Purpose.SERVER_AUTH, ssl._ASN1Object)
+ self.assertEqual(ssl.Purpose.SERVER_AUTH, val)
+ self.assertEqual(ssl.Purpose.SERVER_AUTH.nid, 129)
+ self.assertEqual(ssl.Purpose.SERVER_AUTH.shortname, 'serverAuth')
+ self.assertEqual(ssl.Purpose.SERVER_AUTH.oid,
+ '1.3.6.1.5.5.7.3.1')
+
+ val = ssl._ASN1Object('1.3.6.1.5.5.7.3.2')
+ self.assertIsInstance(ssl.Purpose.CLIENT_AUTH, ssl._ASN1Object)
+ self.assertEqual(ssl.Purpose.CLIENT_AUTH, val)
+ self.assertEqual(ssl.Purpose.CLIENT_AUTH.nid, 130)
+ self.assertEqual(ssl.Purpose.CLIENT_AUTH.shortname, 'clientAuth')
+ self.assertEqual(ssl.Purpose.CLIENT_AUTH.oid,
+ '1.3.6.1.5.5.7.3.2')
+
+
class ContextTests(unittest.TestCase):
@skip_if_broken_ubuntu_ssl
def test_constructor(self):
- if hasattr(ssl, 'PROTOCOL_SSLv2'):
- ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv2)
- ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv3)
- ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ for protocol in PROTOCOLS:
+ ssl.SSLContext(protocol)
self.assertRaises(TypeError, ssl.SSLContext)
self.assertRaises(ValueError, ssl.SSLContext, -1)
self.assertRaises(ValueError, ssl.SSLContext, 42)
@@ -536,7 +673,7 @@ class ContextTests(unittest.TestCase):
with self.assertRaises(ValueError):
ctx.options = 0
- def test_verify(self):
+ def test_verify_mode(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
# Default value
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
@@ -551,13 +688,32 @@ class ContextTests(unittest.TestCase):
with self.assertRaises(ValueError):
ctx.verify_mode = 42
+ @unittest.skipUnless(have_verify_flags(),
+ "verify_flags need OpenSSL > 0.9.8")
+ def test_verify_flags(self):
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ # default value by OpenSSL
+ self.assertEqual(ctx.verify_flags, ssl.VERIFY_DEFAULT)
+ ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF
+ self.assertEqual(ctx.verify_flags, ssl.VERIFY_CRL_CHECK_LEAF)
+ ctx.verify_flags = ssl.VERIFY_CRL_CHECK_CHAIN
+ self.assertEqual(ctx.verify_flags, ssl.VERIFY_CRL_CHECK_CHAIN)
+ ctx.verify_flags = ssl.VERIFY_DEFAULT
+ self.assertEqual(ctx.verify_flags, ssl.VERIFY_DEFAULT)
+ # supports any value
+ ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF | ssl.VERIFY_X509_STRICT
+ self.assertEqual(ctx.verify_flags,
+ ssl.VERIFY_CRL_CHECK_LEAF | ssl.VERIFY_X509_STRICT)
+ with self.assertRaises(TypeError):
+ ctx.verify_flags = None
+
def test_load_cert_chain(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
# Combined key and cert in a single file
ctx.load_cert_chain(CERTFILE)
ctx.load_cert_chain(CERTFILE, keyfile=CERTFILE)
self.assertRaises(TypeError, ctx.load_cert_chain, keyfile=CERTFILE)
- with self.assertRaises(IOError) as cm:
+ with self.assertRaises(OSError) as cm:
ctx.load_cert_chain(WRONGCERT)
self.assertEqual(cm.exception.errno, errno.ENOENT)
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
@@ -641,8 +797,8 @@ class ContextTests(unittest.TestCase):
ctx.load_verify_locations(BYTES_CERTFILE)
ctx.load_verify_locations(cafile=BYTES_CERTFILE, capath=None)
self.assertRaises(TypeError, ctx.load_verify_locations)
- self.assertRaises(TypeError, ctx.load_verify_locations, None, None)
- with self.assertRaises(IOError) as cm:
+ self.assertRaises(TypeError, ctx.load_verify_locations, None, None, None)
+ with self.assertRaises(OSError) as cm:
ctx.load_verify_locations(WRONGCERT)
self.assertEqual(cm.exception.errno, errno.ENOENT)
with self.assertRaisesRegex(ssl.SSLError, "PEM lib"):
@@ -653,6 +809,64 @@ class ContextTests(unittest.TestCase):
# Issue #10989: crash if the second argument type is invalid
self.assertRaises(TypeError, ctx.load_verify_locations, None, True)
+ def test_load_verify_cadata(self):
+ # test cadata
+ with open(CAFILE_CACERT) as f:
+ cacert_pem = f.read()
+ cacert_der = ssl.PEM_cert_to_DER_cert(cacert_pem)
+ with open(CAFILE_NEURONIO) as f:
+ neuronio_pem = f.read()
+ neuronio_der = ssl.PEM_cert_to_DER_cert(neuronio_pem)
+
+ # test PEM
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 0)
+ ctx.load_verify_locations(cadata=cacert_pem)
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 1)
+ ctx.load_verify_locations(cadata=neuronio_pem)
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2)
+ # cert already in hash table
+ ctx.load_verify_locations(cadata=neuronio_pem)
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2)
+
+ # combined
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ combined = "\n".join((cacert_pem, neuronio_pem))
+ ctx.load_verify_locations(cadata=combined)
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2)
+
+ # with junk around the certs
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ combined = ["head", cacert_pem, "other", neuronio_pem, "again",
+ neuronio_pem, "tail"]
+ ctx.load_verify_locations(cadata="\n".join(combined))
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2)
+
+ # test DER
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ ctx.load_verify_locations(cadata=cacert_der)
+ ctx.load_verify_locations(cadata=neuronio_der)
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2)
+ # cert already in hash table
+ ctx.load_verify_locations(cadata=cacert_der)
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2)
+
+ # combined
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ combined = b"".join((cacert_der, neuronio_der))
+ ctx.load_verify_locations(cadata=combined)
+ self.assertEqual(ctx.cert_store_stats()["x509_ca"], 2)
+
+ # error cases
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ self.assertRaises(TypeError, ctx.load_verify_locations, cadata=object)
+
+ with self.assertRaisesRegex(ssl.SSLError, "no start line"):
+ ctx.load_verify_locations(cadata="broken")
+ with self.assertRaisesRegex(ssl.SSLError, "not enough data"):
+ ctx.load_verify_locations(cadata=b"broken")
+
+
def test_load_dh_params(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ctx.load_dh_params(DHFILE)
@@ -700,6 +914,156 @@ class ContextTests(unittest.TestCase):
self.assertRaises(ValueError, ctx.set_ecdh_curve, "foo")
self.assertRaises(ValueError, ctx.set_ecdh_curve, b"foo")
+ @needs_sni
+ def test_sni_callback(self):
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+
+ # set_servername_callback expects a callable, or None
+ self.assertRaises(TypeError, ctx.set_servername_callback)
+ self.assertRaises(TypeError, ctx.set_servername_callback, 4)
+ self.assertRaises(TypeError, ctx.set_servername_callback, "")
+ self.assertRaises(TypeError, ctx.set_servername_callback, ctx)
+
+ def dummycallback(sock, servername, ctx):
+ pass
+ ctx.set_servername_callback(None)
+ ctx.set_servername_callback(dummycallback)
+
+ @needs_sni
+ def test_sni_callback_refcycle(self):
+ # Reference cycles through the servername callback are detected
+ # and cleared.
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ def dummycallback(sock, servername, ctx, cycle=ctx):
+ pass
+ ctx.set_servername_callback(dummycallback)
+ wr = weakref.ref(ctx)
+ del ctx, dummycallback
+ gc.collect()
+ self.assertIs(wr(), None)
+
+ def test_cert_store_stats(self):
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.cert_store_stats(),
+ {'x509_ca': 0, 'crl': 0, 'x509': 0})
+ ctx.load_cert_chain(CERTFILE)
+ self.assertEqual(ctx.cert_store_stats(),
+ {'x509_ca': 0, 'crl': 0, 'x509': 0})
+ ctx.load_verify_locations(CERTFILE)
+ self.assertEqual(ctx.cert_store_stats(),
+ {'x509_ca': 0, 'crl': 0, 'x509': 1})
+ ctx.load_verify_locations(SVN_PYTHON_ORG_ROOT_CERT)
+ self.assertEqual(ctx.cert_store_stats(),
+ {'x509_ca': 1, 'crl': 0, 'x509': 2})
+
+ def test_get_ca_certs(self):
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.get_ca_certs(), [])
+ # CERTFILE is not flagged as X509v3 Basic Constraints: CA:TRUE
+ ctx.load_verify_locations(CERTFILE)
+ self.assertEqual(ctx.get_ca_certs(), [])
+ # but SVN_PYTHON_ORG_ROOT_CERT is a CA cert
+ ctx.load_verify_locations(SVN_PYTHON_ORG_ROOT_CERT)
+ self.assertEqual(ctx.get_ca_certs(),
+ [{'issuer': ((('organizationName', 'Root CA'),),
+ (('organizationalUnitName', 'http://www.cacert.org'),),
+ (('commonName', 'CA Cert Signing Authority'),),
+ (('emailAddress', 'support@cacert.org'),)),
+ 'notAfter': asn1time('Mar 29 12:29:49 2033 GMT'),
+ 'notBefore': asn1time('Mar 30 12:29:49 2003 GMT'),
+ 'serialNumber': '00',
+ 'crlDistributionPoints': ('https://www.cacert.org/revoke.crl',),
+ 'subject': ((('organizationName', 'Root CA'),),
+ (('organizationalUnitName', 'http://www.cacert.org'),),
+ (('commonName', 'CA Cert Signing Authority'),),
+ (('emailAddress', 'support@cacert.org'),)),
+ 'version': 3}])
+
+ with open(SVN_PYTHON_ORG_ROOT_CERT) as f:
+ pem = f.read()
+ der = ssl.PEM_cert_to_DER_cert(pem)
+ self.assertEqual(ctx.get_ca_certs(True), [der])
+
+ def test_load_default_certs(self):
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ ctx.load_default_certs()
+
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ ctx.load_default_certs(ssl.Purpose.SERVER_AUTH)
+ ctx.load_default_certs()
+
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ ctx.load_default_certs(ssl.Purpose.CLIENT_AUTH)
+
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ self.assertRaises(TypeError, ctx.load_default_certs, None)
+ self.assertRaises(TypeError, ctx.load_default_certs, 'SERVER_AUTH')
+
+ def test_create_default_context(self):
+ ctx = ssl.create_default_context()
+ self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
+ self.assertTrue(ctx.check_hostname)
+ self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+
+ with open(SIGNING_CA) as f:
+ cadata = f.read()
+ ctx = ssl.create_default_context(cafile=SIGNING_CA, capath=CAPATH,
+ cadata=cadata)
+ self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
+ self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+
+ ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
+ self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+
+ def test__create_stdlib_context(self):
+ ctx = ssl._create_stdlib_context()
+ self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
+ self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
+ self.assertFalse(ctx.check_hostname)
+ self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+
+ ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
+ self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+
+ ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1,
+ cert_reqs=ssl.CERT_REQUIRED)
+ self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
+ self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
+ self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+
+ ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH)
+ self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
+ self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
+ self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+
+ def test_check_hostname(self):
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ self.assertFalse(ctx.check_hostname)
+
+ # Requires CERT_REQUIRED or CERT_OPTIONAL
+ with self.assertRaises(ValueError):
+ ctx.check_hostname = True
+ ctx.verify_mode = ssl.CERT_REQUIRED
+ self.assertFalse(ctx.check_hostname)
+ ctx.check_hostname = True
+ self.assertTrue(ctx.check_hostname)
+
+ ctx.verify_mode = ssl.CERT_OPTIONAL
+ ctx.check_hostname = True
+ self.assertTrue(ctx.check_hostname)
+
+ # Cannot set CERT_NONE with check_hostname enabled
+ with self.assertRaises(ValueError):
+ ctx.verify_mode = ssl.CERT_NONE
+ ctx.check_hostname = False
+ self.assertFalse(ctx.check_hostname)
+
class SSLErrorTests(unittest.TestCase):
@@ -903,6 +1267,28 @@ class NetworkedTests(unittest.TestCase):
finally:
s.close()
+ def test_connect_cadata(self):
+ with open(CAFILE_CACERT) as f:
+ pem = f.read()
+ der = ssl.PEM_cert_to_DER_cert(pem)
+ with support.transient_internet("svn.python.org"):
+ ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ ctx.verify_mode = ssl.CERT_REQUIRED
+ ctx.load_verify_locations(cadata=pem)
+ with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
+ s.connect(("svn.python.org", 443))
+ cert = s.getpeercert()
+ self.assertTrue(cert)
+
+ # same with DER
+ ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ ctx.verify_mode = ssl.CERT_REQUIRED
+ ctx.load_verify_locations(cadata=der)
+ with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
+ s.connect(("svn.python.org", 443))
+ cert = s.getpeercert()
+ self.assertTrue(cert)
+
@unittest.skipIf(os.name == "nt", "Can't use a socket as a file under Windows")
def test_makefile_close(self):
# Issue #5238: creating a file-like object with makefile() shouldn't
@@ -1015,6 +1401,21 @@ class NetworkedTests(unittest.TestCase):
finally:
s.close()
+ def test_get_ca_certs_capath(self):
+ # capath certs are loaded on request
+ with support.transient_internet("svn.python.org"):
+ ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ ctx.verify_mode = ssl.CERT_REQUIRED
+ ctx.load_verify_locations(capath=CAPATH)
+ self.assertEqual(ctx.get_ca_certs(), [])
+ s = ctx.wrap_socket(socket.socket(socket.AF_INET))
+ s.connect(("svn.python.org", 443))
+ try:
+ cert = s.getpeercert()
+ self.assertTrue(cert)
+ finally:
+ s.close()
+ self.assertEqual(len(ctx.get_ca_certs()), 1)
try:
import threading
@@ -1142,7 +1543,7 @@ else:
sys.stdout.write(" server: read %r (%s), sending back %r (%s)...\n"
% (msg, ctype, msg.lower(), ctype))
self.write(msg.lower())
- except socket.error:
+ except OSError:
if self.server.chatty:
handle_error("Test server failure:\n")
self.close()
@@ -1252,7 +1653,7 @@ else:
return self.handle_close()
except ssl.SSLError:
raise
- except socket.error as err:
+ except OSError as err:
if err.args[0] == errno.ECONNABORTED:
return self.handle_close()
else:
@@ -1356,19 +1757,19 @@ else:
except ssl.SSLError as x:
if support.verbose:
sys.stdout.write("\nSSLError is %s\n" % x.args[1])
- except socket.error as x:
+ except OSError as x:
if support.verbose:
- sys.stdout.write("\nsocket.error is %s\n" % x.args[1])
- except IOError as x:
+ sys.stdout.write("\nOSError is %s\n" % x.args[1])
+ except OSError as x:
if x.errno != errno.ENOENT:
raise
if support.verbose:
- sys.stdout.write("\IOError is %s\n" % str(x))
+ sys.stdout.write("\OSError is %s\n" % str(x))
else:
raise AssertionError("Use of invalid cert should have failed!")
def server_params_test(client_context, server_context, indata=b"FOO\n",
- chatty=True, connectionchatty=False):
+ chatty=True, connectionchatty=False, sni_name=None):
"""
Launch a server, connect a client to it and try various reads
and writes.
@@ -1378,7 +1779,8 @@ else:
chatty=chatty,
connectionchatty=False)
with server:
- with client_context.wrap_socket(socket.socket()) as s:
+ with client_context.wrap_socket(socket.socket(),
+ server_hostname=sni_name) as s:
s.connect((HOST, server.port))
for arg in [indata, bytearray(indata), memoryview(indata)]:
if connectionchatty:
@@ -1402,6 +1804,7 @@ else:
stats.update({
'compression': s.compression(),
'cipher': s.cipher(),
+ 'peercert': s.getpeercert(),
'client_npn_protocol': s.selected_npn_protocol()
})
s.close()
@@ -1427,12 +1830,15 @@ else:
client_context.options = ssl.OP_ALL | client_options
server_context = ssl.SSLContext(server_protocol)
server_context.options = ssl.OP_ALL | server_options
+
+ # NOTE: we must enable "ALL" ciphers on the client, otherwise an
+ # SSLv23 client will send an SSLv3 hello (rather than SSLv2)
+ # starting from OpenSSL 1.0.0 (see issue #8322).
+ if client_context.protocol == ssl.PROTOCOL_SSLv23:
+ client_context.set_ciphers("ALL")
+
for ctx in (client_context, server_context):
ctx.verify_mode = certsreqs
- # NOTE: we must enable "ALL" ciphers, otherwise an SSLv23 client
- # will send an SSLv3 hello (rather than SSLv2) starting from
- # OpenSSL 1.0.0 (see issue #8322).
- ctx.set_ciphers("ALL")
ctx.load_cert_chain(CERTFILE)
ctx.load_verify_locations(CERTFILE)
try:
@@ -1443,7 +1849,7 @@ else:
except ssl.SSLError:
if expect_success:
raise
- except socket.error as e:
+ except OSError as e:
if expect_success or e.errno != errno.ECONNRESET:
raise
else:
@@ -1462,10 +1868,11 @@ else:
if support.verbose:
sys.stdout.write("\n")
for protocol in PROTOCOLS:
- context = ssl.SSLContext(protocol)
- context.load_cert_chain(CERTFILE)
- server_params_test(context, context,
- chatty=True, connectionchatty=True)
+ with self.subTest(protocol=ssl._PROTOCOL_NAMES[protocol]):
+ context = ssl.SSLContext(protocol)
+ context.load_cert_chain(CERTFILE)
+ server_params_test(context, context,
+ chatty=True, connectionchatty=True)
def test_getpeercert(self):
if support.verbose:
@@ -1476,8 +1883,14 @@ else:
context.load_cert_chain(CERTFILE)
server = ThreadedEchoServer(context=context, chatty=False)
with server:
- s = context.wrap_socket(socket.socket())
+ s = context.wrap_socket(socket.socket(),
+ do_handshake_on_connect=False)
s.connect((HOST, server.port))
+ # getpeercert() raise ValueError while the handshake isn't
+ # done.
+ with self.assertRaises(ValueError):
+ s.getpeercert()
+ s.do_handshake()
cert = s.getpeercert()
self.assertTrue(cert, "Can't get peer certificate.")
cipher = s.cipher()
@@ -1499,6 +1912,86 @@ else:
self.assertLess(before, after)
s.close()
+ @unittest.skipUnless(have_verify_flags(),
+ "verify_flags need OpenSSL > 0.9.8")
+ def test_crl_check(self):
+ if support.verbose:
+ sys.stdout.write("\n")
+
+ server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ server_context.load_cert_chain(SIGNED_CERTFILE)
+
+ context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ context.verify_mode = ssl.CERT_REQUIRED
+ context.load_verify_locations(SIGNING_CA)
+ self.assertEqual(context.verify_flags, ssl.VERIFY_DEFAULT)
+
+ # VERIFY_DEFAULT should pass
+ server = ThreadedEchoServer(context=server_context, chatty=True)
+ with server:
+ with context.wrap_socket(socket.socket()) as s:
+ s.connect((HOST, server.port))
+ cert = s.getpeercert()
+ self.assertTrue(cert, "Can't get peer certificate.")
+
+ # VERIFY_CRL_CHECK_LEAF without a loaded CRL file fails
+ context.verify_flags |= ssl.VERIFY_CRL_CHECK_LEAF
+
+ server = ThreadedEchoServer(context=server_context, chatty=True)
+ with server:
+ with context.wrap_socket(socket.socket()) as s:
+ with self.assertRaisesRegex(ssl.SSLError,
+ "certificate verify failed"):
+ s.connect((HOST, server.port))
+
+ # now load a CRL file. The CRL file is signed by the CA.
+ context.load_verify_locations(CRLFILE)
+
+ server = ThreadedEchoServer(context=server_context, chatty=True)
+ with server:
+ with context.wrap_socket(socket.socket()) as s:
+ s.connect((HOST, server.port))
+ cert = s.getpeercert()
+ self.assertTrue(cert, "Can't get peer certificate.")
+
+ def test_check_hostname(self):
+ if support.verbose:
+ sys.stdout.write("\n")
+
+ server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ server_context.load_cert_chain(SIGNED_CERTFILE)
+
+ context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ context.verify_mode = ssl.CERT_REQUIRED
+ context.check_hostname = True
+ context.load_verify_locations(SIGNING_CA)
+
+ # correct hostname should verify
+ server = ThreadedEchoServer(context=server_context, chatty=True)
+ with server:
+ with context.wrap_socket(socket.socket(),
+ server_hostname="localhost") as s:
+ s.connect((HOST, server.port))
+ cert = s.getpeercert()
+ self.assertTrue(cert, "Can't get peer certificate.")
+
+ # incorrect hostname should raise an exception
+ server = ThreadedEchoServer(context=server_context, chatty=True)
+ with server:
+ with context.wrap_socket(socket.socket(),
+ server_hostname="invalid") as s:
+ with self.assertRaisesRegex(ssl.CertificateError,
+ "hostname 'invalid' doesn't match 'localhost'"):
+ s.connect((HOST, server.port))
+
+ # missing server_hostname arg should cause an exception, too
+ server = ThreadedEchoServer(context=server_context, chatty=True)
+ with server:
+ with socket.socket() as s:
+ with self.assertRaisesRegex(ValueError,
+ "check_hostname requires server_hostname"):
+ context.wrap_socket(s)
+
def test_empty_cert(self):
"""Connecting with an empty cert file"""
bad_cert_test(os.path.join(os.path.dirname(__file__) or os.curdir,
@@ -1517,7 +2010,7 @@ else:
"badkey.pem"))
def test_rude_shutdown(self):
- """A brutal shutdown of an SSL server should raise an IOError
+ """A brutal shutdown of an SSL server should raise an OSError
in the client when attempting handshake.
"""
listener_ready = threading.Event()
@@ -1545,7 +2038,7 @@ else:
listener_gone.wait()
try:
ssl_sock = ssl.wrap_socket(c)
- except IOError:
+ except OSError:
pass
else:
self.fail('connecting to closed SSL socket should have failed')
@@ -1588,7 +2081,7 @@ else:
if hasattr(ssl, 'PROTOCOL_SSLv2'):
try:
try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv2, True)
- except (ssl.SSLError, socket.error) as x:
+ except OSError as x:
# this fails on some older versions of OpenSSL (0.9.7l, for instance)
if support.verbose:
sys.stdout.write(
@@ -1648,6 +2141,49 @@ else:
try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv23, False,
client_options=ssl.OP_NO_TLSv1)
+ @skip_if_broken_ubuntu_ssl
+ @unittest.skipUnless(hasattr(ssl, "PROTOCOL_TLSv1_1"),
+ "TLS version 1.1 not supported.")
+ def test_protocol_tlsv1_1(self):
+ """Connecting to a TLSv1.1 server with various client options.
+ Testing against older TLS versions."""
+ if support.verbose:
+ sys.stdout.write("\n")
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_1, True)
+ if hasattr(ssl, 'PROTOCOL_SSLv2'):
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv2, False)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv3, False)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv23, False,
+ client_options=ssl.OP_NO_TLSv1_1)
+
+ try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1_1, True)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1, False)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_1, False)
+
+
+ @skip_if_broken_ubuntu_ssl
+ @unittest.skipUnless(hasattr(ssl, "PROTOCOL_TLSv1_2"),
+ "TLS version 1.2 not supported.")
+ def test_protocol_tlsv1_2(self):
+ """Connecting to a TLSv1.2 server with various client options.
+ Testing against older TLS versions."""
+ if support.verbose:
+ sys.stdout.write("\n")
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_2, True,
+ server_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,
+ client_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,)
+ if hasattr(ssl, 'PROTOCOL_SSLv2'):
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv2, False)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv3, False)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv23, False,
+ client_options=ssl.OP_NO_TLSv1_2)
+
+ try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1_2, True)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1, False)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_2, False)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False)
+ try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_2, False)
+
def test_starttls(self):
"""Switching from clear text to encrypted and back again."""
msgs = (b"msg 1", b"MSG 2", b"STARTTLS", b"MSG 3", b"msg 4", b"ENDTLS", b"msg 5", b"msg 6")
@@ -1708,7 +2244,7 @@ else:
def test_socketserver(self):
"""Using a SocketServer to create and manage SSL connections."""
- server = make_https_server(self, CERTFILE)
+ server = make_https_server(self, certfile=CERTFILE)
# try to connect
if support.verbose:
sys.stdout.write('\n')
@@ -1964,6 +2500,20 @@ else:
self.assertIsInstance(remote, ssl.SSLSocket)
self.assertEqual(peer, client_addr)
+ def test_getpeercert_enotconn(self):
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ with context.wrap_socket(socket.socket()) as sock:
+ with self.assertRaises(OSError) as cm:
+ sock.getpeercert()
+ self.assertEqual(cm.exception.errno, errno.ENOTCONN)
+
+ def test_do_handshake_enotconn(self):
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ with context.wrap_socket(socket.socket()) as sock:
+ with self.assertRaises(OSError) as cm:
+ sock.do_handshake()
+ self.assertEqual(cm.exception.errno, errno.ENOTCONN)
+
def test_default_ciphers(self):
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
try:
@@ -1975,7 +2525,7 @@ else:
ssl_version=ssl.PROTOCOL_SSLv23,
chatty=False) as server:
with context.wrap_socket(socket.socket()) as s:
- with self.assertRaises((OSError, ssl.SSLError)):
+ with self.assertRaises(OSError):
s.connect((HOST, server.port))
self.assertIn("no shared cipher", str(server.conn_errors[0]))
@@ -2108,6 +2658,124 @@ else:
if len(stats['server_npn_protocols']) else 'nothing'
self.assertEqual(server_result, expected, msg % (server_result, "server"))
+ def sni_contexts(self):
+ server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ server_context.load_cert_chain(SIGNED_CERTFILE)
+ other_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ other_context.load_cert_chain(SIGNED_CERTFILE2)
+ client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ client_context.verify_mode = ssl.CERT_REQUIRED
+ client_context.load_verify_locations(SIGNING_CA)
+ return server_context, other_context, client_context
+
+ def check_common_name(self, stats, name):
+ cert = stats['peercert']
+ self.assertIn((('commonName', name),), cert['subject'])
+
+ @needs_sni
+ def test_sni_callback(self):
+ calls = []
+ server_context, other_context, client_context = self.sni_contexts()
+
+ def servername_cb(ssl_sock, server_name, initial_context):
+ calls.append((server_name, initial_context))
+ if server_name is not None:
+ ssl_sock.context = other_context
+ server_context.set_servername_callback(servername_cb)
+
+ stats = server_params_test(client_context, server_context,
+ chatty=True,
+ sni_name='supermessage')
+ # The hostname was fetched properly, and the certificate was
+ # changed for the connection.
+ self.assertEqual(calls, [("supermessage", server_context)])
+ # CERTFILE4 was selected
+ self.check_common_name(stats, 'fakehostname')
+
+ calls = []
+ # The callback is called with server_name=None
+ stats = server_params_test(client_context, server_context,
+ chatty=True,
+ sni_name=None)
+ self.assertEqual(calls, [(None, server_context)])
+ self.check_common_name(stats, 'localhost')
+
+ # Check disabling the callback
+ calls = []
+ server_context.set_servername_callback(None)
+
+ stats = server_params_test(client_context, server_context,
+ chatty=True,
+ sni_name='notfunny')
+ # Certificate didn't change
+ self.check_common_name(stats, 'localhost')
+ self.assertEqual(calls, [])
+
+ @needs_sni
+ def test_sni_callback_alert(self):
+ # Returning a TLS alert is reflected to the connecting client
+ server_context, other_context, client_context = self.sni_contexts()
+
+ def cb_returning_alert(ssl_sock, server_name, initial_context):
+ return ssl.ALERT_DESCRIPTION_ACCESS_DENIED
+ server_context.set_servername_callback(cb_returning_alert)
+
+ with self.assertRaises(ssl.SSLError) as cm:
+ stats = server_params_test(client_context, server_context,
+ chatty=False,
+ sni_name='supermessage')
+ self.assertEqual(cm.exception.reason, 'TLSV1_ALERT_ACCESS_DENIED')
+
+ @needs_sni
+ def test_sni_callback_raising(self):
+ # Raising fails the connection with a TLS handshake failure alert.
+ server_context, other_context, client_context = self.sni_contexts()
+
+ def cb_raising(ssl_sock, server_name, initial_context):
+ 1/0
+ server_context.set_servername_callback(cb_raising)
+
+ with self.assertRaises(ssl.SSLError) as cm, \
+ support.captured_stderr() as stderr:
+ stats = server_params_test(client_context, server_context,
+ chatty=False,
+ sni_name='supermessage')
+ self.assertEqual(cm.exception.reason, 'SSLV3_ALERT_HANDSHAKE_FAILURE')
+ self.assertIn("ZeroDivisionError", stderr.getvalue())
+
+ @needs_sni
+ def test_sni_callback_wrong_return_type(self):
+ # Returning the wrong return type terminates the TLS connection
+ # with an internal error alert.
+ server_context, other_context, client_context = self.sni_contexts()
+
+ def cb_wrong_return_type(ssl_sock, server_name, initial_context):
+ return "foo"
+ server_context.set_servername_callback(cb_wrong_return_type)
+
+ with self.assertRaises(ssl.SSLError) as cm, \
+ support.captured_stderr() as stderr:
+ stats = server_params_test(client_context, server_context,
+ chatty=False,
+ sni_name='supermessage')
+ self.assertEqual(cm.exception.reason, 'TLSV1_ALERT_INTERNAL_ERROR')
+ self.assertIn("TypeError", stderr.getvalue())
+
+ def test_read_write_after_close_raises_valuerror(self):
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.verify_mode = ssl.CERT_REQUIRED
+ context.load_verify_locations(CERTFILE)
+ context.load_cert_chain(CERTFILE)
+ server = ThreadedEchoServer(context=context, chatty=False)
+
+ with server:
+ s = context.wrap_socket(socket.socket())
+ s.connect((HOST, server.port))
+ s.close()
+
+ self.assertRaises(ValueError, s.read, 1024)
+ self.assertRaises(ValueError, s.write, b'hello')
+
def test_main(verbose=False):
if support.verbose:
@@ -2127,10 +2795,16 @@ def test_main(verbose=False):
(ssl.OPENSSL_VERSION, ssl.OPENSSL_VERSION_INFO))
print(" under %s" % plat)
print(" HAS_SNI = %r" % ssl.HAS_SNI)
+ print(" OP_ALL = 0x%8x" % ssl.OP_ALL)
+ try:
+ print(" OP_NO_TLSv1_1 = 0x%8x" % ssl.OP_NO_TLSv1_1)
+ except AttributeError:
+ pass
for filename in [
CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE,
ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY,
+ SIGNED_CERTFILE, SIGNED_CERTFILE2, SIGNING_CA,
BADCERT, BADKEY, EMPTYCERT]:
if not os.path.exists(filename):
raise support.TestFailed("Can't read certificate file %r" % filename)
diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py
index eb3f07a63d..af6ced4204 100644
--- a/Lib/test/test_stat.py
+++ b/Lib/test/test_stat.py
@@ -1,9 +1,13 @@
import unittest
import os
-from test.support import TESTFN, run_unittest, import_fresh_module
-import stat
+from test.support import TESTFN, import_fresh_module
+
+c_stat = import_fresh_module('stat', fresh=['_stat'])
+py_stat = import_fresh_module('stat', blocked=['_stat'])
+
+class TestFilemode:
+ statmod = None
-class TestFilemode(unittest.TestCase):
file_flags = {'SF_APPEND', 'SF_ARCHIVED', 'SF_IMMUTABLE', 'SF_NOUNLINK',
'SF_SNAPSHOT', 'UF_APPEND', 'UF_COMPRESSED', 'UF_HIDDEN',
'UF_IMMUTABLE', 'UF_NODUMP', 'UF_NOUNLINK', 'UF_OPAQUE'}
@@ -63,17 +67,17 @@ class TestFilemode(unittest.TestCase):
st_mode = os.lstat(fname).st_mode
else:
st_mode = os.stat(fname).st_mode
- modestr = stat.filemode(st_mode)
+ modestr = self.statmod.filemode(st_mode)
return st_mode, modestr
def assertS_IS(self, name, mode):
# test format, lstrip is for S_IFIFO
- fmt = getattr(stat, "S_IF" + name.lstrip("F"))
- self.assertEqual(stat.S_IFMT(mode), fmt)
+ fmt = getattr(self.statmod, "S_IF" + name.lstrip("F"))
+ self.assertEqual(self.statmod.S_IFMT(mode), fmt)
# test that just one function returns true
testname = "S_IS" + name
for funcname in self.format_funcs:
- func = getattr(stat, funcname, None)
+ func = getattr(self.statmod, funcname, None)
if func is None:
if funcname == testname:
raise ValueError(funcname)
@@ -91,35 +95,35 @@ class TestFilemode(unittest.TestCase):
st_mode, modestr = self.get_mode()
self.assertEqual(modestr, '-rwx------')
self.assertS_IS("REG", st_mode)
- self.assertEqual(stat.S_IMODE(st_mode),
- stat.S_IRWXU)
+ self.assertEqual(self.statmod.S_IMODE(st_mode),
+ self.statmod.S_IRWXU)
os.chmod(TESTFN, 0o070)
st_mode, modestr = self.get_mode()
self.assertEqual(modestr, '----rwx---')
self.assertS_IS("REG", st_mode)
- self.assertEqual(stat.S_IMODE(st_mode),
- stat.S_IRWXG)
+ self.assertEqual(self.statmod.S_IMODE(st_mode),
+ self.statmod.S_IRWXG)
os.chmod(TESTFN, 0o007)
st_mode, modestr = self.get_mode()
self.assertEqual(modestr, '-------rwx')
self.assertS_IS("REG", st_mode)
- self.assertEqual(stat.S_IMODE(st_mode),
- stat.S_IRWXO)
+ self.assertEqual(self.statmod.S_IMODE(st_mode),
+ self.statmod.S_IRWXO)
os.chmod(TESTFN, 0o444)
st_mode, modestr = self.get_mode()
self.assertS_IS("REG", st_mode)
self.assertEqual(modestr, '-r--r--r--')
- self.assertEqual(stat.S_IMODE(st_mode), 0o444)
+ self.assertEqual(self.statmod.S_IMODE(st_mode), 0o444)
else:
os.chmod(TESTFN, 0o700)
st_mode, modestr = self.get_mode()
self.assertEqual(modestr[:3], '-rw')
self.assertS_IS("REG", st_mode)
- self.assertEqual(stat.S_IFMT(st_mode),
- stat.S_IFREG)
+ self.assertEqual(self.statmod.S_IFMT(st_mode),
+ self.statmod.S_IFREG)
def test_directory(self):
os.mkdir(TESTFN)
@@ -165,25 +169,34 @@ class TestFilemode(unittest.TestCase):
def test_module_attributes(self):
for key, value in self.stat_struct.items():
- modvalue = getattr(stat, key)
+ modvalue = getattr(self.statmod, key)
self.assertEqual(value, modvalue, key)
for key, value in self.permission_bits.items():
- modvalue = getattr(stat, key)
+ modvalue = getattr(self.statmod, key)
self.assertEqual(value, modvalue, key)
for key in self.file_flags:
- modvalue = getattr(stat, key)
+ modvalue = getattr(self.statmod, key)
self.assertIsInstance(modvalue, int)
for key in self.formats:
- modvalue = getattr(stat, key)
+ modvalue = getattr(self.statmod, key)
self.assertIsInstance(modvalue, int)
for key in self.format_funcs:
- func = getattr(stat, key)
+ func = getattr(self.statmod, key)
self.assertTrue(callable(func))
self.assertEqual(func(0), 0)
-def test_main():
- run_unittest(TestFilemode)
+class TestFilemodeCStat(TestFilemode, unittest.TestCase):
+ statmod = c_stat
+
+ formats = TestFilemode.formats | {'S_IFDOOR', 'S_IFPORT', 'S_IFWHT'}
+ format_funcs = TestFilemode.format_funcs | {'S_ISDOOR', 'S_ISPORT',
+ 'S_ISWHT'}
+
+
+class TestFilemodePyStat(TestFilemode, unittest.TestCase):
+ statmod = py_stat
+
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py
new file mode 100644
index 0000000000..ee585e2c1c
--- /dev/null
+++ b/Lib/test/test_statistics.py
@@ -0,0 +1,1534 @@
+"""Test suite for statistics module, including helper NumericTestCase and
+approx_equal function.
+
+"""
+
+import collections
+import decimal
+import doctest
+import math
+import random
+import types
+import unittest
+
+from decimal import Decimal
+from fractions import Fraction
+
+
+# Module to be tested.
+import statistics
+
+
+# === Helper functions and class ===
+
+def _calc_errors(actual, expected):
+ """Return the absolute and relative errors between two numbers.
+
+ >>> _calc_errors(100, 75)
+ (25, 0.25)
+ >>> _calc_errors(100, 100)
+ (0, 0.0)
+
+ Returns the (absolute error, relative error) between the two arguments.
+ """
+ base = max(abs(actual), abs(expected))
+ abs_err = abs(actual - expected)
+ rel_err = abs_err/base if base else float('inf')
+ return (abs_err, rel_err)
+
+
+def approx_equal(x, y, tol=1e-12, rel=1e-7):
+ """approx_equal(x, y [, tol [, rel]]) => True|False
+
+ Return True if numbers x and y are approximately equal, to within some
+ margin of error, otherwise return False. Numbers which compare equal
+ will also compare approximately equal.
+
+ x is approximately equal to y if the difference between them is less than
+ an absolute error tol or a relative error rel, whichever is bigger.
+
+ If given, both tol and rel must be finite, non-negative numbers. If not
+ given, default values are tol=1e-12 and rel=1e-7.
+
+ >>> approx_equal(1.2589, 1.2587, tol=0.0003, rel=0)
+ True
+ >>> approx_equal(1.2589, 1.2587, tol=0.0001, rel=0)
+ False
+
+ Absolute error is defined as abs(x-y); if that is less than or equal to
+ tol, x and y are considered approximately equal.
+
+ Relative error is defined as abs((x-y)/x) or abs((x-y)/y), whichever is
+ smaller, provided x or y are not zero. If that figure is less than or
+ equal to rel, x and y are considered approximately equal.
+
+ Complex numbers are not directly supported. If you wish to compare to
+ complex numbers, extract their real and imaginary parts and compare them
+ individually.
+
+ NANs always compare unequal, even with themselves. Infinities compare
+ approximately equal if they have the same sign (both positive or both
+ negative). Infinities with different signs compare unequal; so do
+ comparisons of infinities with finite numbers.
+ """
+ if tol < 0 or rel < 0:
+ raise ValueError('error tolerances must be non-negative')
+ # NANs are never equal to anything, approximately or otherwise.
+ if math.isnan(x) or math.isnan(y):
+ return False
+ # Numbers which compare equal also compare approximately equal.
+ if x == y:
+ # This includes the case of two infinities with the same sign.
+ return True
+ if math.isinf(x) or math.isinf(y):
+ # This includes the case of two infinities of opposite sign, or
+ # one infinity and one finite number.
+ return False
+ # Two finite numbers.
+ actual_error = abs(x - y)
+ allowed_error = max(tol, rel*max(abs(x), abs(y)))
+ return actual_error <= allowed_error
+
+
+# This class exists only as somewhere to stick a docstring containing
+# doctests. The following docstring and tests were originally in a separate
+# module. Now that it has been merged in here, I need somewhere to hang the.
+# docstring. Ultimately, this class will die, and the information below will
+# either become redundant, or be moved into more appropriate places.
+class _DoNothing:
+ """
+ When doing numeric work, especially with floats, exact equality is often
+ not what you want. Due to round-off error, it is often a bad idea to try
+ to compare floats with equality. Instead the usual procedure is to test
+ them with some (hopefully small!) allowance for error.
+
+ The ``approx_equal`` function allows you to specify either an absolute
+ error tolerance, or a relative error, or both.
+
+ Absolute error tolerances are simple, but you need to know the magnitude
+ of the quantities being compared:
+
+ >>> approx_equal(12.345, 12.346, tol=1e-3)
+ True
+ >>> approx_equal(12.345e6, 12.346e6, tol=1e-3) # tol is too small.
+ False
+
+ Relative errors are more suitable when the values you are comparing can
+ vary in magnitude:
+
+ >>> approx_equal(12.345, 12.346, rel=1e-4)
+ True
+ >>> approx_equal(12.345e6, 12.346e6, rel=1e-4)
+ True
+
+ but a naive implementation of relative error testing can run into trouble
+ around zero.
+
+ If you supply both an absolute tolerance and a relative error, the
+ comparison succeeds if either individual test succeeds:
+
+ >>> approx_equal(12.345e6, 12.346e6, tol=1e-3, rel=1e-4)
+ True
+
+ """
+ pass
+
+
+
+# We prefer this for testing numeric values that may not be exactly equal,
+# and avoid using TestCase.assertAlmostEqual, because it sucks :-)
+
+class NumericTestCase(unittest.TestCase):
+ """Unit test class for numeric work.
+
+ This subclasses TestCase. In addition to the standard method
+ ``TestCase.assertAlmostEqual``, ``assertApproxEqual`` is provided.
+ """
+ # By default, we expect exact equality, unless overridden.
+ tol = rel = 0
+
+ def assertApproxEqual(
+ self, first, second, tol=None, rel=None, msg=None
+ ):
+ """Test passes if ``first`` and ``second`` are approximately equal.
+
+ This test passes if ``first`` and ``second`` are equal to
+ within ``tol``, an absolute error, or ``rel``, a relative error.
+
+ If either ``tol`` or ``rel`` are None or not given, they default to
+ test attributes of the same name (by default, 0).
+
+ The objects may be either numbers, or sequences of numbers. Sequences
+ are tested element-by-element.
+
+ >>> class MyTest(NumericTestCase):
+ ... def test_number(self):
+ ... x = 1.0/6
+ ... y = sum([x]*6)
+ ... self.assertApproxEqual(y, 1.0, tol=1e-15)
+ ... def test_sequence(self):
+ ... a = [1.001, 1.001e-10, 1.001e10]
+ ... b = [1.0, 1e-10, 1e10]
+ ... self.assertApproxEqual(a, b, rel=1e-3)
+ ...
+ >>> import unittest
+ >>> from io import StringIO # Suppress test runner output.
+ >>> suite = unittest.TestLoader().loadTestsFromTestCase(MyTest)
+ >>> unittest.TextTestRunner(stream=StringIO()).run(suite)
+ <unittest.runner.TextTestResult run=2 errors=0 failures=0>
+
+ """
+ if tol is None:
+ tol = self.tol
+ if rel is None:
+ rel = self.rel
+ if (
+ isinstance(first, collections.Sequence) and
+ isinstance(second, collections.Sequence)
+ ):
+ check = self._check_approx_seq
+ else:
+ check = self._check_approx_num
+ check(first, second, tol, rel, msg)
+
+ def _check_approx_seq(self, first, second, tol, rel, msg):
+ if len(first) != len(second):
+ standardMsg = (
+ "sequences differ in length: %d items != %d items"
+ % (len(first), len(second))
+ )
+ msg = self._formatMessage(msg, standardMsg)
+ raise self.failureException(msg)
+ for i, (a,e) in enumerate(zip(first, second)):
+ self._check_approx_num(a, e, tol, rel, msg, i)
+
+ def _check_approx_num(self, first, second, tol, rel, msg, idx=None):
+ if approx_equal(first, second, tol, rel):
+ # Test passes. Return early, we are done.
+ return None
+ # Otherwise we failed.
+ standardMsg = self._make_std_err_msg(first, second, tol, rel, idx)
+ msg = self._formatMessage(msg, standardMsg)
+ raise self.failureException(msg)
+
+ @staticmethod
+ def _make_std_err_msg(first, second, tol, rel, idx):
+ # Create the standard error message for approx_equal failures.
+ assert first != second
+ template = (
+ ' %r != %r\n'
+ ' values differ by more than tol=%r and rel=%r\n'
+ ' -> absolute error = %r\n'
+ ' -> relative error = %r'
+ )
+ if idx is not None:
+ header = 'numeric sequences first differ at index %d.\n' % idx
+ template = header + template
+ # Calculate actual errors:
+ abs_err, rel_err = _calc_errors(first, second)
+ return template % (first, second, tol, rel, abs_err, rel_err)
+
+
+# ========================
+# === Test the helpers ===
+# ========================
+
+
+# --- Tests for approx_equal ---
+
+class ApproxEqualSymmetryTest(unittest.TestCase):
+ # Test symmetry of approx_equal.
+
+ def test_relative_symmetry(self):
+ # Check that approx_equal treats relative error symmetrically.
+ # (a-b)/a is usually not equal to (a-b)/b. Ensure that this
+ # doesn't matter.
+ #
+ # Note: the reason for this test is that an early version
+ # of approx_equal was not symmetric. A relative error test
+ # would pass, or fail, depending on which value was passed
+ # as the first argument.
+ #
+ args1 = [2456, 37.8, -12.45, Decimal('2.54'), Fraction(17, 54)]
+ args2 = [2459, 37.2, -12.41, Decimal('2.59'), Fraction(15, 54)]
+ assert len(args1) == len(args2)
+ for a, b in zip(args1, args2):
+ self.do_relative_symmetry(a, b)
+
+ def do_relative_symmetry(self, a, b):
+ a, b = min(a, b), max(a, b)
+ assert a < b
+ delta = b - a # The absolute difference between the values.
+ rel_err1, rel_err2 = abs(delta/a), abs(delta/b)
+ # Choose an error margin halfway between the two.
+ rel = (rel_err1 + rel_err2)/2
+ # Now see that values a and b compare approx equal regardless of
+ # which is given first.
+ self.assertTrue(approx_equal(a, b, tol=0, rel=rel))
+ self.assertTrue(approx_equal(b, a, tol=0, rel=rel))
+
+ def test_symmetry(self):
+ # Test that approx_equal(a, b) == approx_equal(b, a)
+ args = [-23, -2, 5, 107, 93568]
+ delta = 2
+ for a in args:
+ for type_ in (int, float, Decimal, Fraction):
+ x = type_(a)*100
+ y = x + delta
+ r = abs(delta/max(x, y))
+ # There are five cases to check:
+ # 1) actual error <= tol, <= rel
+ self.do_symmetry_test(x, y, tol=delta, rel=r)
+ self.do_symmetry_test(x, y, tol=delta+1, rel=2*r)
+ # 2) actual error > tol, > rel
+ self.do_symmetry_test(x, y, tol=delta-1, rel=r/2)
+ # 3) actual error <= tol, > rel
+ self.do_symmetry_test(x, y, tol=delta, rel=r/2)
+ # 4) actual error > tol, <= rel
+ self.do_symmetry_test(x, y, tol=delta-1, rel=r)
+ self.do_symmetry_test(x, y, tol=delta-1, rel=2*r)
+ # 5) exact equality test
+ self.do_symmetry_test(x, x, tol=0, rel=0)
+ self.do_symmetry_test(x, y, tol=0, rel=0)
+
+ def do_symmetry_test(self, a, b, tol, rel):
+ template = "approx_equal comparisons don't match for %r"
+ flag1 = approx_equal(a, b, tol, rel)
+ flag2 = approx_equal(b, a, tol, rel)
+ self.assertEqual(flag1, flag2, template.format((a, b, tol, rel)))
+
+
+class ApproxEqualExactTest(unittest.TestCase):
+ # Test the approx_equal function with exactly equal values.
+ # Equal values should compare as approximately equal.
+ # Test cases for exactly equal values, which should compare approx
+ # equal regardless of the error tolerances given.
+
+ def do_exactly_equal_test(self, x, tol, rel):
+ result = approx_equal(x, x, tol=tol, rel=rel)
+ self.assertTrue(result, 'equality failure for x=%r' % x)
+ result = approx_equal(-x, -x, tol=tol, rel=rel)
+ self.assertTrue(result, 'equality failure for x=%r' % -x)
+
+ def test_exactly_equal_ints(self):
+ # Test that equal int values are exactly equal.
+ for n in [42, 19740, 14974, 230, 1795, 700245, 36587]:
+ self.do_exactly_equal_test(n, 0, 0)
+
+ def test_exactly_equal_floats(self):
+ # Test that equal float values are exactly equal.
+ for x in [0.42, 1.9740, 1497.4, 23.0, 179.5, 70.0245, 36.587]:
+ self.do_exactly_equal_test(x, 0, 0)
+
+ def test_exactly_equal_fractions(self):
+ # Test that equal Fraction values are exactly equal.
+ F = Fraction
+ for f in [F(1, 2), F(0), F(5, 3), F(9, 7), F(35, 36), F(3, 7)]:
+ self.do_exactly_equal_test(f, 0, 0)
+
+ def test_exactly_equal_decimals(self):
+ # Test that equal Decimal values are exactly equal.
+ D = Decimal
+ for d in map(D, "8.2 31.274 912.04 16.745 1.2047".split()):
+ self.do_exactly_equal_test(d, 0, 0)
+
+ def test_exactly_equal_absolute(self):
+ # Test that equal values are exactly equal with an absolute error.
+ for n in [16, 1013, 1372, 1198, 971, 4]:
+ # Test as ints.
+ self.do_exactly_equal_test(n, 0.01, 0)
+ # Test as floats.
+ self.do_exactly_equal_test(n/10, 0.01, 0)
+ # Test as Fractions.
+ f = Fraction(n, 1234)
+ self.do_exactly_equal_test(f, 0.01, 0)
+
+ def test_exactly_equal_absolute_decimals(self):
+ # Test equal Decimal values are exactly equal with an absolute error.
+ self.do_exactly_equal_test(Decimal("3.571"), Decimal("0.01"), 0)
+ self.do_exactly_equal_test(-Decimal("81.3971"), Decimal("0.01"), 0)
+
+ def test_exactly_equal_relative(self):
+ # Test that equal values are exactly equal with a relative error.
+ for x in [8347, 101.3, -7910.28, Fraction(5, 21)]:
+ self.do_exactly_equal_test(x, 0, 0.01)
+ self.do_exactly_equal_test(Decimal("11.68"), 0, Decimal("0.01"))
+
+ def test_exactly_equal_both(self):
+ # Test that equal values are equal when both tol and rel are given.
+ for x in [41017, 16.742, -813.02, Fraction(3, 8)]:
+ self.do_exactly_equal_test(x, 0.1, 0.01)
+ D = Decimal
+ self.do_exactly_equal_test(D("7.2"), D("0.1"), D("0.01"))
+
+
+class ApproxEqualUnequalTest(unittest.TestCase):
+ # Unequal values should compare unequal with zero error tolerances.
+ # Test cases for unequal values, with exact equality test.
+
+ def do_exactly_unequal_test(self, x):
+ for a in (x, -x):
+ result = approx_equal(a, a+1, tol=0, rel=0)
+ self.assertFalse(result, 'inequality failure for x=%r' % a)
+
+ def test_exactly_unequal_ints(self):
+ # Test unequal int values are unequal with zero error tolerance.
+ for n in [951, 572305, 478, 917, 17240]:
+ self.do_exactly_unequal_test(n)
+
+ def test_exactly_unequal_floats(self):
+ # Test unequal float values are unequal with zero error tolerance.
+ for x in [9.51, 5723.05, 47.8, 9.17, 17.24]:
+ self.do_exactly_unequal_test(x)
+
+ def test_exactly_unequal_fractions(self):
+ # Test that unequal Fractions are unequal with zero error tolerance.
+ F = Fraction
+ for f in [F(1, 5), F(7, 9), F(12, 11), F(101, 99023)]:
+ self.do_exactly_unequal_test(f)
+
+ def test_exactly_unequal_decimals(self):
+ # Test that unequal Decimals are unequal with zero error tolerance.
+ for d in map(Decimal, "3.1415 298.12 3.47 18.996 0.00245".split()):
+ self.do_exactly_unequal_test(d)
+
+
+class ApproxEqualInexactTest(unittest.TestCase):
+ # Inexact test cases for approx_error.
+ # Test cases when comparing two values that are not exactly equal.
+
+ # === Absolute error tests ===
+
+ def do_approx_equal_abs_test(self, x, delta):
+ template = "Test failure for x={!r}, y={!r}"
+ for y in (x + delta, x - delta):
+ msg = template.format(x, y)
+ self.assertTrue(approx_equal(x, y, tol=2*delta, rel=0), msg)
+ self.assertFalse(approx_equal(x, y, tol=delta/2, rel=0), msg)
+
+ def test_approx_equal_absolute_ints(self):
+ # Test approximate equality of ints with an absolute error.
+ for n in [-10737, -1975, -7, -2, 0, 1, 9, 37, 423, 9874, 23789110]:
+ self.do_approx_equal_abs_test(n, 10)
+ self.do_approx_equal_abs_test(n, 2)
+
+ def test_approx_equal_absolute_floats(self):
+ # Test approximate equality of floats with an absolute error.
+ for x in [-284.126, -97.1, -3.4, -2.15, 0.5, 1.0, 7.8, 4.23, 3817.4]:
+ self.do_approx_equal_abs_test(x, 1.5)
+ self.do_approx_equal_abs_test(x, 0.01)
+ self.do_approx_equal_abs_test(x, 0.0001)
+
+ def test_approx_equal_absolute_fractions(self):
+ # Test approximate equality of Fractions with an absolute error.
+ delta = Fraction(1, 29)
+ numerators = [-84, -15, -2, -1, 0, 1, 5, 17, 23, 34, 71]
+ for f in (Fraction(n, 29) for n in numerators):
+ self.do_approx_equal_abs_test(f, delta)
+ self.do_approx_equal_abs_test(f, float(delta))
+
+ def test_approx_equal_absolute_decimals(self):
+ # Test approximate equality of Decimals with an absolute error.
+ delta = Decimal("0.01")
+ for d in map(Decimal, "1.0 3.5 36.08 61.79 7912.3648".split()):
+ self.do_approx_equal_abs_test(d, delta)
+ self.do_approx_equal_abs_test(-d, delta)
+
+ def test_cross_zero(self):
+ # Test for the case of the two values having opposite signs.
+ self.assertTrue(approx_equal(1e-5, -1e-5, tol=1e-4, rel=0))
+
+ # === Relative error tests ===
+
+ def do_approx_equal_rel_test(self, x, delta):
+ template = "Test failure for x={!r}, y={!r}"
+ for y in (x*(1+delta), x*(1-delta)):
+ msg = template.format(x, y)
+ self.assertTrue(approx_equal(x, y, tol=0, rel=2*delta), msg)
+ self.assertFalse(approx_equal(x, y, tol=0, rel=delta/2), msg)
+
+ def test_approx_equal_relative_ints(self):
+ # Test approximate equality of ints with a relative error.
+ self.assertTrue(approx_equal(64, 47, tol=0, rel=0.36))
+ self.assertTrue(approx_equal(64, 47, tol=0, rel=0.37))
+ # ---
+ self.assertTrue(approx_equal(449, 512, tol=0, rel=0.125))
+ self.assertTrue(approx_equal(448, 512, tol=0, rel=0.125))
+ self.assertFalse(approx_equal(447, 512, tol=0, rel=0.125))
+
+ def test_approx_equal_relative_floats(self):
+ # Test approximate equality of floats with a relative error.
+ for x in [-178.34, -0.1, 0.1, 1.0, 36.97, 2847.136, 9145.074]:
+ self.do_approx_equal_rel_test(x, 0.02)
+ self.do_approx_equal_rel_test(x, 0.0001)
+
+ def test_approx_equal_relative_fractions(self):
+ # Test approximate equality of Fractions with a relative error.
+ F = Fraction
+ delta = Fraction(3, 8)
+ for f in [F(3, 84), F(17, 30), F(49, 50), F(92, 85)]:
+ for d in (delta, float(delta)):
+ self.do_approx_equal_rel_test(f, d)
+ self.do_approx_equal_rel_test(-f, d)
+
+ def test_approx_equal_relative_decimals(self):
+ # Test approximate equality of Decimals with a relative error.
+ for d in map(Decimal, "0.02 1.0 5.7 13.67 94.138 91027.9321".split()):
+ self.do_approx_equal_rel_test(d, Decimal("0.001"))
+ self.do_approx_equal_rel_test(-d, Decimal("0.05"))
+
+ # === Both absolute and relative error tests ===
+
+ # There are four cases to consider:
+ # 1) actual error <= both absolute and relative error
+ # 2) actual error <= absolute error but > relative error
+ # 3) actual error <= relative error but > absolute error
+ # 4) actual error > both absolute and relative error
+
+ def do_check_both(self, a, b, tol, rel, tol_flag, rel_flag):
+ check = self.assertTrue if tol_flag else self.assertFalse
+ check(approx_equal(a, b, tol=tol, rel=0))
+ check = self.assertTrue if rel_flag else self.assertFalse
+ check(approx_equal(a, b, tol=0, rel=rel))
+ check = self.assertTrue if (tol_flag or rel_flag) else self.assertFalse
+ check(approx_equal(a, b, tol=tol, rel=rel))
+
+ def test_approx_equal_both1(self):
+ # Test actual error <= both absolute and relative error.
+ self.do_check_both(7.955, 7.952, 0.004, 3.8e-4, True, True)
+ self.do_check_both(-7.387, -7.386, 0.002, 0.0002, True, True)
+
+ def test_approx_equal_both2(self):
+ # Test actual error <= absolute error but > relative error.
+ self.do_check_both(7.955, 7.952, 0.004, 3.7e-4, True, False)
+
+ def test_approx_equal_both3(self):
+ # Test actual error <= relative error but > absolute error.
+ self.do_check_both(7.955, 7.952, 0.001, 3.8e-4, False, True)
+
+ def test_approx_equal_both4(self):
+ # Test actual error > both absolute and relative error.
+ self.do_check_both(2.78, 2.75, 0.01, 0.001, False, False)
+ self.do_check_both(971.44, 971.47, 0.02, 3e-5, False, False)
+
+
+class ApproxEqualSpecialsTest(unittest.TestCase):
+ # Test approx_equal with NANs and INFs and zeroes.
+
+ def test_inf(self):
+ for type_ in (float, Decimal):
+ inf = type_('inf')
+ self.assertTrue(approx_equal(inf, inf))
+ self.assertTrue(approx_equal(inf, inf, 0, 0))
+ self.assertTrue(approx_equal(inf, inf, 1, 0.01))
+ self.assertTrue(approx_equal(-inf, -inf))
+ self.assertFalse(approx_equal(inf, -inf))
+ self.assertFalse(approx_equal(inf, 1000))
+
+ def test_nan(self):
+ for type_ in (float, Decimal):
+ nan = type_('nan')
+ for other in (nan, type_('inf'), 1000):
+ self.assertFalse(approx_equal(nan, other))
+
+ def test_float_zeroes(self):
+ nzero = math.copysign(0.0, -1)
+ self.assertTrue(approx_equal(nzero, 0.0, tol=0.1, rel=0.1))
+
+ def test_decimal_zeroes(self):
+ nzero = Decimal("-0.0")
+ self.assertTrue(approx_equal(nzero, Decimal(0), tol=0.1, rel=0.1))
+
+
+class TestApproxEqualErrors(unittest.TestCase):
+ # Test error conditions of approx_equal.
+
+ def test_bad_tol(self):
+ # Test negative tol raises.
+ self.assertRaises(ValueError, approx_equal, 100, 100, -1, 0.1)
+
+ def test_bad_rel(self):
+ # Test negative rel raises.
+ self.assertRaises(ValueError, approx_equal, 100, 100, 1, -0.1)
+
+
+# --- Tests for NumericTestCase ---
+
+# The formatting routine that generates the error messages is complex enough
+# that it too needs testing.
+
+class TestNumericTestCase(unittest.TestCase):
+ # The exact wording of NumericTestCase error messages is *not* guaranteed,
+ # but we need to give them some sort of test to ensure that they are
+ # generated correctly. As a compromise, we look for specific substrings
+ # that are expected to be found even if the overall error message changes.
+
+ def do_test(self, args):
+ actual_msg = NumericTestCase._make_std_err_msg(*args)
+ expected = self.generate_substrings(*args)
+ for substring in expected:
+ self.assertIn(substring, actual_msg)
+
+ def test_numerictestcase_is_testcase(self):
+ # Ensure that NumericTestCase actually is a TestCase.
+ self.assertTrue(issubclass(NumericTestCase, unittest.TestCase))
+
+ def test_error_msg_numeric(self):
+ # Test the error message generated for numeric comparisons.
+ args = (2.5, 4.0, 0.5, 0.25, None)
+ self.do_test(args)
+
+ def test_error_msg_sequence(self):
+ # Test the error message generated for sequence comparisons.
+ args = (3.75, 8.25, 1.25, 0.5, 7)
+ self.do_test(args)
+
+ def generate_substrings(self, first, second, tol, rel, idx):
+ """Return substrings we expect to see in error messages."""
+ abs_err, rel_err = _calc_errors(first, second)
+ substrings = [
+ 'tol=%r' % tol,
+ 'rel=%r' % rel,
+ 'absolute error = %r' % abs_err,
+ 'relative error = %r' % rel_err,
+ ]
+ if idx is not None:
+ substrings.append('differ at index %d' % idx)
+ return substrings
+
+
+# =======================================
+# === Tests for the statistics module ===
+# =======================================
+
+
+class GlobalsTest(unittest.TestCase):
+ module = statistics
+ expected_metadata = ["__doc__", "__all__"]
+
+ def test_meta(self):
+ # Test for the existence of metadata.
+ for meta in self.expected_metadata:
+ self.assertTrue(hasattr(self.module, meta),
+ "%s not present" % meta)
+
+ def test_check_all(self):
+ # Check everything in __all__ exists and is public.
+ module = self.module
+ for name in module.__all__:
+ # No private names in __all__:
+ self.assertFalse(name.startswith("_"),
+ 'private name "%s" in __all__' % name)
+ # And anything in __all__ must exist:
+ self.assertTrue(hasattr(module, name),
+ 'missing name "%s" in __all__' % name)
+
+
+class DocTests(unittest.TestCase):
+ def test_doc_tests(self):
+ failed, tried = doctest.testmod(statistics)
+ self.assertGreater(tried, 0)
+ self.assertEqual(failed, 0)
+
+class StatisticsErrorTest(unittest.TestCase):
+ def test_has_exception(self):
+ errmsg = (
+ "Expected StatisticsError to be a ValueError, but got a"
+ " subclass of %r instead."
+ )
+ self.assertTrue(hasattr(statistics, 'StatisticsError'))
+ self.assertTrue(
+ issubclass(statistics.StatisticsError, ValueError),
+ errmsg % statistics.StatisticsError.__base__
+ )
+
+
+# === Tests for private utility functions ===
+
+class ExactRatioTest(unittest.TestCase):
+ # Test _exact_ratio utility.
+
+ def test_int(self):
+ for i in (-20, -3, 0, 5, 99, 10**20):
+ self.assertEqual(statistics._exact_ratio(i), (i, 1))
+
+ def test_fraction(self):
+ numerators = (-5, 1, 12, 38)
+ for n in numerators:
+ f = Fraction(n, 37)
+ self.assertEqual(statistics._exact_ratio(f), (n, 37))
+
+ def test_float(self):
+ self.assertEqual(statistics._exact_ratio(0.125), (1, 8))
+ self.assertEqual(statistics._exact_ratio(1.125), (9, 8))
+ data = [random.uniform(-100, 100) for _ in range(100)]
+ for x in data:
+ num, den = statistics._exact_ratio(x)
+ self.assertEqual(x, num/den)
+
+ def test_decimal(self):
+ D = Decimal
+ _exact_ratio = statistics._exact_ratio
+ self.assertEqual(_exact_ratio(D("0.125")), (125, 1000))
+ self.assertEqual(_exact_ratio(D("12.345")), (12345, 1000))
+ self.assertEqual(_exact_ratio(D("-1.98")), (-198, 100))
+
+
+class DecimalToRatioTest(unittest.TestCase):
+ # Test _decimal_to_ratio private function.
+
+ def testSpecialsRaise(self):
+ # Test that NANs and INFs raise ValueError.
+ # Non-special values are covered by _exact_ratio above.
+ for d in (Decimal('NAN'), Decimal('sNAN'), Decimal('INF')):
+ self.assertRaises(ValueError, statistics._decimal_to_ratio, d)
+
+
+
+# === Tests for public functions ===
+
+class UnivariateCommonMixin:
+ # Common tests for most univariate functions that take a data argument.
+
+ def test_no_args(self):
+ # Fail if given no arguments.
+ self.assertRaises(TypeError, self.func)
+
+ def test_empty_data(self):
+ # Fail when the data argument (first argument) is empty.
+ for empty in ([], (), iter([])):
+ self.assertRaises(statistics.StatisticsError, self.func, empty)
+
+ def prepare_data(self):
+ """Return int data for various tests."""
+ data = list(range(10))
+ while data == sorted(data):
+ random.shuffle(data)
+ return data
+
+ def test_no_inplace_modifications(self):
+ # Test that the function does not modify its input data.
+ data = self.prepare_data()
+ assert len(data) != 1 # Necessary to avoid infinite loop.
+ assert data != sorted(data)
+ saved = data[:]
+ assert data is not saved
+ _ = self.func(data)
+ self.assertListEqual(data, saved, "data has been modified")
+
+ def test_order_doesnt_matter(self):
+ # Test that the order of data points doesn't change the result.
+
+ # CAUTION: due to floating point rounding errors, the result actually
+ # may depend on the order. Consider this test representing an ideal.
+ # To avoid this test failing, only test with exact values such as ints
+ # or Fractions.
+ data = [1, 2, 3, 3, 3, 4, 5, 6]*100
+ expected = self.func(data)
+ random.shuffle(data)
+ actual = self.func(data)
+ self.assertEqual(expected, actual)
+
+ def test_type_of_data_collection(self):
+ # Test that the type of iterable data doesn't effect the result.
+ class MyList(list):
+ pass
+ class MyTuple(tuple):
+ pass
+ def generator(data):
+ return (obj for obj in data)
+ data = self.prepare_data()
+ expected = self.func(data)
+ for kind in (list, tuple, iter, MyList, MyTuple, generator):
+ result = self.func(kind(data))
+ self.assertEqual(result, expected)
+
+ def test_range_data(self):
+ # Test that functions work with range objects.
+ data = range(20, 50, 3)
+ expected = self.func(list(data))
+ self.assertEqual(self.func(data), expected)
+
+ def test_bad_arg_types(self):
+ # Test that function raises when given data of the wrong type.
+
+ # Don't roll the following into a loop like this:
+ # for bad in list_of_bad:
+ # self.check_for_type_error(bad)
+ #
+ # Since assertRaises doesn't show the arguments that caused the test
+ # failure, it is very difficult to debug these test failures when the
+ # following are in a loop.
+ self.check_for_type_error(None)
+ self.check_for_type_error(23)
+ self.check_for_type_error(42.0)
+ self.check_for_type_error(object())
+
+ def check_for_type_error(self, *args):
+ self.assertRaises(TypeError, self.func, *args)
+
+ def test_type_of_data_element(self):
+ # Check the type of data elements doesn't affect the numeric result.
+ # This is a weaker test than UnivariateTypeMixin.testTypesConserved,
+ # because it checks the numeric result by equality, but not by type.
+ class MyFloat(float):
+ def __truediv__(self, other):
+ return type(self)(super().__truediv__(other))
+ def __add__(self, other):
+ return type(self)(super().__add__(other))
+ __radd__ = __add__
+
+ raw = self.prepare_data()
+ expected = self.func(raw)
+ for kind in (float, MyFloat, Decimal, Fraction):
+ data = [kind(x) for x in raw]
+ result = type(expected)(self.func(data))
+ self.assertEqual(result, expected)
+
+
+class UnivariateTypeMixin:
+ """Mixin class for type-conserving functions.
+
+ This mixin class holds test(s) for functions which conserve the type of
+ individual data points. E.g. the mean of a list of Fractions should itself
+ be a Fraction.
+
+ Not all tests to do with types need go in this class. Only those that
+ rely on the function returning the same type as its input data.
+ """
+ def test_types_conserved(self):
+ # Test that functions keeps the same type as their data points.
+ # (Excludes mixed data types.) This only tests the type of the return
+ # result, not the value.
+ class MyFloat(float):
+ def __truediv__(self, other):
+ return type(self)(super().__truediv__(other))
+ def __sub__(self, other):
+ return type(self)(super().__sub__(other))
+ def __rsub__(self, other):
+ return type(self)(super().__rsub__(other))
+ def __pow__(self, other):
+ return type(self)(super().__pow__(other))
+ def __add__(self, other):
+ return type(self)(super().__add__(other))
+ __radd__ = __add__
+
+ data = self.prepare_data()
+ for kind in (float, Decimal, Fraction, MyFloat):
+ d = [kind(x) for x in data]
+ result = self.func(d)
+ self.assertIs(type(result), kind)
+
+
+class TestSum(NumericTestCase, UnivariateCommonMixin, UnivariateTypeMixin):
+ # Test cases for statistics._sum() function.
+
+ def setUp(self):
+ self.func = statistics._sum
+
+ def test_empty_data(self):
+ # Override test for empty data.
+ for data in ([], (), iter([])):
+ self.assertEqual(self.func(data), 0)
+ self.assertEqual(self.func(data, 23), 23)
+ self.assertEqual(self.func(data, 2.3), 2.3)
+
+ def test_ints(self):
+ self.assertEqual(self.func([1, 5, 3, -4, -8, 20, 42, 1]), 60)
+ self.assertEqual(self.func([4, 2, 3, -8, 7], 1000), 1008)
+
+ def test_floats(self):
+ self.assertEqual(self.func([0.25]*20), 5.0)
+ self.assertEqual(self.func([0.125, 0.25, 0.5, 0.75], 1.5), 3.125)
+
+ def test_fractions(self):
+ F = Fraction
+ self.assertEqual(self.func([Fraction(1, 1000)]*500), Fraction(1, 2))
+
+ def test_decimals(self):
+ D = Decimal
+ data = [D("0.001"), D("5.246"), D("1.702"), D("-0.025"),
+ D("3.974"), D("2.328"), D("4.617"), D("2.843"),
+ ]
+ self.assertEqual(self.func(data), Decimal("20.686"))
+
+ def test_compare_with_math_fsum(self):
+ # Compare with the math.fsum function.
+ # Ideally we ought to get the exact same result, but sometimes
+ # we differ by a very slight amount :-(
+ data = [random.uniform(-100, 1000) for _ in range(1000)]
+ self.assertApproxEqual(self.func(data), math.fsum(data), rel=2e-16)
+
+ def test_start_argument(self):
+ # Test that the optional start argument works correctly.
+ data = [random.uniform(1, 1000) for _ in range(100)]
+ t = self.func(data)
+ self.assertEqual(t+42, self.func(data, 42))
+ self.assertEqual(t-23, self.func(data, -23))
+ self.assertEqual(t+1e20, self.func(data, 1e20))
+
+ def test_strings_fail(self):
+ # Sum of strings should fail.
+ self.assertRaises(TypeError, self.func, [1, 2, 3], '999')
+ self.assertRaises(TypeError, self.func, [1, 2, 3, '999'])
+
+ def test_bytes_fail(self):
+ # Sum of bytes should fail.
+ self.assertRaises(TypeError, self.func, [1, 2, 3], b'999')
+ self.assertRaises(TypeError, self.func, [1, 2, 3, b'999'])
+
+ def test_mixed_sum(self):
+ # Mixed sums are allowed.
+
+ # Careful here: order matters. Can't mix Fraction and Decimal directly,
+ # only after they're converted to float.
+ data = [1, 2, Fraction(1, 2), 3.0, Decimal("0.25")]
+ self.assertEqual(self.func(data), 6.75)
+
+
+class SumInternalsTest(NumericTestCase):
+ # Test internals of the sum function.
+
+ def test_ignore_instance_float_method(self):
+ # Test that __float__ methods on data instances are ignored.
+
+ # Python typically calls __dunder__ methods on the class, not the
+ # instance. The ``sum`` implementation calls __float__ directly. To
+ # better match the behaviour of Python, we call it only on the class,
+ # not the instance. This test will fail if somebody "fixes" that code.
+
+ # Create a fake __float__ method.
+ def __float__(self):
+ raise AssertionError('test fails')
+
+ # Inject it into an instance.
+ class MyNumber(Fraction):
+ pass
+ x = MyNumber(3)
+ x.__float__ = types.MethodType(__float__, x)
+
+ # Check it works as expected.
+ self.assertRaises(AssertionError, x.__float__)
+ self.assertEqual(float(x), 3.0)
+ # And now test the function.
+ self.assertEqual(statistics._sum([1.0, 2.0, x, 4.0]), 10.0)
+
+
+class SumTortureTest(NumericTestCase):
+ def test_torture(self):
+ # Tim Peters' torture test for sum, and variants of same.
+ self.assertEqual(statistics._sum([1, 1e100, 1, -1e100]*10000), 20000.0)
+ self.assertEqual(statistics._sum([1e100, 1, 1, -1e100]*10000), 20000.0)
+ self.assertApproxEqual(
+ statistics._sum([1e-100, 1, 1e-100, -1]*10000), 2.0e-96, rel=5e-16
+ )
+
+
+class SumSpecialValues(NumericTestCase):
+ # Test that sum works correctly with IEEE-754 special values.
+
+ def test_nan(self):
+ for type_ in (float, Decimal):
+ nan = type_('nan')
+ result = statistics._sum([1, nan, 2])
+ self.assertIs(type(result), type_)
+ self.assertTrue(math.isnan(result))
+
+ def check_infinity(self, x, inf):
+ """Check x is an infinity of the same type and sign as inf."""
+ self.assertTrue(math.isinf(x))
+ self.assertIs(type(x), type(inf))
+ self.assertEqual(x > 0, inf > 0)
+ assert x == inf
+
+ def do_test_inf(self, inf):
+ # Adding a single infinity gives infinity.
+ result = statistics._sum([1, 2, inf, 3])
+ self.check_infinity(result, inf)
+ # Adding two infinities of the same sign also gives infinity.
+ result = statistics._sum([1, 2, inf, 3, inf, 4])
+ self.check_infinity(result, inf)
+
+ def test_float_inf(self):
+ inf = float('inf')
+ for sign in (+1, -1):
+ self.do_test_inf(sign*inf)
+
+ def test_decimal_inf(self):
+ inf = Decimal('inf')
+ for sign in (+1, -1):
+ self.do_test_inf(sign*inf)
+
+ def test_float_mismatched_infs(self):
+ # Test that adding two infinities of opposite sign gives a NAN.
+ inf = float('inf')
+ result = statistics._sum([1, 2, inf, 3, -inf, 4])
+ self.assertTrue(math.isnan(result))
+
+ def test_decimal_mismatched_infs_to_nan(self):
+ # Test adding Decimal INFs with opposite sign returns NAN.
+ inf = Decimal('inf')
+ data = [1, 2, inf, 3, -inf, 4]
+ with decimal.localcontext(decimal.ExtendedContext):
+ self.assertTrue(math.isnan(statistics._sum(data)))
+
+ def test_decimal_mismatched_infs_to_nan(self):
+ # Test adding Decimal INFs with opposite sign raises InvalidOperation.
+ inf = Decimal('inf')
+ data = [1, 2, inf, 3, -inf, 4]
+ with decimal.localcontext(decimal.BasicContext):
+ self.assertRaises(decimal.InvalidOperation, statistics._sum, data)
+
+ def test_decimal_snan_raises(self):
+ # Adding sNAN should raise InvalidOperation.
+ sNAN = Decimal('sNAN')
+ data = [1, sNAN, 2]
+ self.assertRaises(decimal.InvalidOperation, statistics._sum, data)
+
+
+# === Tests for averages ===
+
+class AverageMixin(UnivariateCommonMixin):
+ # Mixin class holding common tests for averages.
+
+ def test_single_value(self):
+ # Average of a single value is the value itself.
+ for x in (23, 42.5, 1.3e15, Fraction(15, 19), Decimal('0.28')):
+ self.assertEqual(self.func([x]), x)
+
+ def test_repeated_single_value(self):
+ # The average of a single repeated value is the value itself.
+ for x in (3.5, 17, 2.5e15, Fraction(61, 67), Decimal('4.9712')):
+ for count in (2, 5, 10, 20):
+ data = [x]*count
+ self.assertEqual(self.func(data), x)
+
+
+class TestMean(NumericTestCase, AverageMixin, UnivariateTypeMixin):
+ def setUp(self):
+ self.func = statistics.mean
+
+ def test_torture_pep(self):
+ # "Torture Test" from PEP-450.
+ self.assertEqual(self.func([1e100, 1, 3, -1e100]), 1)
+
+ def test_ints(self):
+ # Test mean with ints.
+ data = [0, 1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 7, 7, 7, 8, 9]
+ random.shuffle(data)
+ self.assertEqual(self.func(data), 4.8125)
+
+ def test_floats(self):
+ # Test mean with floats.
+ data = [17.25, 19.75, 20.0, 21.5, 21.75, 23.25, 25.125, 27.5]
+ random.shuffle(data)
+ self.assertEqual(self.func(data), 22.015625)
+
+ def test_decimals(self):
+ # Test mean with ints.
+ D = Decimal
+ data = [D("1.634"), D("2.517"), D("3.912"), D("4.072"), D("5.813")]
+ random.shuffle(data)
+ self.assertEqual(self.func(data), D("3.5896"))
+
+ def test_fractions(self):
+ # Test mean with Fractions.
+ F = Fraction
+ data = [F(1, 2), F(2, 3), F(3, 4), F(4, 5), F(5, 6), F(6, 7), F(7, 8)]
+ random.shuffle(data)
+ self.assertEqual(self.func(data), F(1479, 1960))
+
+ def test_inf(self):
+ # Test mean with infinities.
+ raw = [1, 3, 5, 7, 9] # Use only ints, to avoid TypeError later.
+ for kind in (float, Decimal):
+ for sign in (1, -1):
+ inf = kind("inf")*sign
+ data = raw + [inf]
+ result = self.func(data)
+ self.assertTrue(math.isinf(result))
+ self.assertEqual(result, inf)
+
+ def test_mismatched_infs(self):
+ # Test mean with infinities of opposite sign.
+ data = [2, 4, 6, float('inf'), 1, 3, 5, float('-inf')]
+ result = self.func(data)
+ self.assertTrue(math.isnan(result))
+
+ def test_nan(self):
+ # Test mean with NANs.
+ raw = [1, 3, 5, 7, 9] # Use only ints, to avoid TypeError later.
+ for kind in (float, Decimal):
+ inf = kind("nan")
+ data = raw + [inf]
+ result = self.func(data)
+ self.assertTrue(math.isnan(result))
+
+ def test_big_data(self):
+ # Test adding a large constant to every data point.
+ c = 1e9
+ data = [3.4, 4.5, 4.9, 6.7, 6.8, 7.2, 8.0, 8.1, 9.4]
+ expected = self.func(data) + c
+ assert expected != c
+ result = self.func([x+c for x in data])
+ self.assertEqual(result, expected)
+
+ def test_doubled_data(self):
+ # Mean of [a,b,c...z] should be same as for [a,a,b,b,c,c...z,z].
+ data = [random.uniform(-3, 5) for _ in range(1000)]
+ expected = self.func(data)
+ actual = self.func(data*2)
+ self.assertApproxEqual(actual, expected)
+
+
+class TestMedian(NumericTestCase, AverageMixin):
+ # Common tests for median and all median.* functions.
+ def setUp(self):
+ self.func = statistics.median
+
+ def prepare_data(self):
+ """Overload method from UnivariateCommonMixin."""
+ data = super().prepare_data()
+ if len(data)%2 != 1:
+ data.append(2)
+ return data
+
+ def test_even_ints(self):
+ # Test median with an even number of int data points.
+ data = [1, 2, 3, 4, 5, 6]
+ assert len(data)%2 == 0
+ self.assertEqual(self.func(data), 3.5)
+
+ def test_odd_ints(self):
+ # Test median with an odd number of int data points.
+ data = [1, 2, 3, 4, 5, 6, 9]
+ assert len(data)%2 == 1
+ self.assertEqual(self.func(data), 4)
+
+ def test_odd_fractions(self):
+ # Test median works with an odd number of Fractions.
+ F = Fraction
+ data = [F(1, 7), F(2, 7), F(3, 7), F(4, 7), F(5, 7)]
+ assert len(data)%2 == 1
+ random.shuffle(data)
+ self.assertEqual(self.func(data), F(3, 7))
+
+ def test_even_fractions(self):
+ # Test median works with an even number of Fractions.
+ F = Fraction
+ data = [F(1, 7), F(2, 7), F(3, 7), F(4, 7), F(5, 7), F(6, 7)]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), F(1, 2))
+
+ def test_odd_decimals(self):
+ # Test median works with an odd number of Decimals.
+ D = Decimal
+ data = [D('2.5'), D('3.1'), D('4.2'), D('5.7'), D('5.8')]
+ assert len(data)%2 == 1
+ random.shuffle(data)
+ self.assertEqual(self.func(data), D('4.2'))
+
+ def test_even_decimals(self):
+ # Test median works with an even number of Decimals.
+ D = Decimal
+ data = [D('1.2'), D('2.5'), D('3.1'), D('4.2'), D('5.7'), D('5.8')]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), D('3.65'))
+
+
+class TestMedianDataType(NumericTestCase, UnivariateTypeMixin):
+ # Test conservation of data element type for median.
+ def setUp(self):
+ self.func = statistics.median
+
+ def prepare_data(self):
+ data = list(range(15))
+ assert len(data)%2 == 1
+ while data == sorted(data):
+ random.shuffle(data)
+ return data
+
+
+class TestMedianLow(TestMedian, UnivariateTypeMixin):
+ def setUp(self):
+ self.func = statistics.median_low
+
+ def test_even_ints(self):
+ # Test median_low with an even number of ints.
+ data = [1, 2, 3, 4, 5, 6]
+ assert len(data)%2 == 0
+ self.assertEqual(self.func(data), 3)
+
+ def test_even_fractions(self):
+ # Test median_low works with an even number of Fractions.
+ F = Fraction
+ data = [F(1, 7), F(2, 7), F(3, 7), F(4, 7), F(5, 7), F(6, 7)]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), F(3, 7))
+
+ def test_even_decimals(self):
+ # Test median_low works with an even number of Decimals.
+ D = Decimal
+ data = [D('1.1'), D('2.2'), D('3.3'), D('4.4'), D('5.5'), D('6.6')]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), D('3.3'))
+
+
+class TestMedianHigh(TestMedian, UnivariateTypeMixin):
+ def setUp(self):
+ self.func = statistics.median_high
+
+ def test_even_ints(self):
+ # Test median_high with an even number of ints.
+ data = [1, 2, 3, 4, 5, 6]
+ assert len(data)%2 == 0
+ self.assertEqual(self.func(data), 4)
+
+ def test_even_fractions(self):
+ # Test median_high works with an even number of Fractions.
+ F = Fraction
+ data = [F(1, 7), F(2, 7), F(3, 7), F(4, 7), F(5, 7), F(6, 7)]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), F(4, 7))
+
+ def test_even_decimals(self):
+ # Test median_high works with an even number of Decimals.
+ D = Decimal
+ data = [D('1.1'), D('2.2'), D('3.3'), D('4.4'), D('5.5'), D('6.6')]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), D('4.4'))
+
+
+class TestMedianGrouped(TestMedian):
+ # Test median_grouped.
+ # Doesn't conserve data element types, so don't use TestMedianType.
+ def setUp(self):
+ self.func = statistics.median_grouped
+
+ def test_odd_number_repeated(self):
+ # Test median.grouped with repeated median values.
+ data = [12, 13, 14, 14, 14, 15, 15]
+ assert len(data)%2 == 1
+ self.assertEqual(self.func(data), 14)
+ #---
+ data = [12, 13, 14, 14, 14, 14, 15]
+ assert len(data)%2 == 1
+ self.assertEqual(self.func(data), 13.875)
+ #---
+ data = [5, 10, 10, 15, 20, 20, 20, 20, 25, 25, 30]
+ assert len(data)%2 == 1
+ self.assertEqual(self.func(data, 5), 19.375)
+ #---
+ data = [16, 18, 18, 18, 18, 20, 20, 20, 22, 22, 22, 24, 24, 26, 28]
+ assert len(data)%2 == 1
+ self.assertApproxEqual(self.func(data, 2), 20.66666667, tol=1e-8)
+
+ def test_even_number_repeated(self):
+ # Test median.grouped with repeated median values.
+ data = [5, 10, 10, 15, 20, 20, 20, 25, 25, 30]
+ assert len(data)%2 == 0
+ self.assertApproxEqual(self.func(data, 5), 19.16666667, tol=1e-8)
+ #---
+ data = [2, 3, 4, 4, 4, 5]
+ assert len(data)%2 == 0
+ self.assertApproxEqual(self.func(data), 3.83333333, tol=1e-8)
+ #---
+ data = [2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6]
+ assert len(data)%2 == 0
+ self.assertEqual(self.func(data), 4.5)
+ #---
+ data = [3, 4, 4, 4, 5, 5, 5, 5, 6, 6]
+ assert len(data)%2 == 0
+ self.assertEqual(self.func(data), 4.75)
+
+ def test_repeated_single_value(self):
+ # Override method from AverageMixin.
+ # Yet again, failure of median_grouped to conserve the data type
+ # causes me headaches :-(
+ for x in (5.3, 68, 4.3e17, Fraction(29, 101), Decimal('32.9714')):
+ for count in (2, 5, 10, 20):
+ data = [x]*count
+ self.assertEqual(self.func(data), float(x))
+
+ def test_odd_fractions(self):
+ # Test median_grouped works with an odd number of Fractions.
+ F = Fraction
+ data = [F(5, 4), F(9, 4), F(13, 4), F(13, 4), F(17, 4)]
+ assert len(data)%2 == 1
+ random.shuffle(data)
+ self.assertEqual(self.func(data), 3.0)
+
+ def test_even_fractions(self):
+ # Test median_grouped works with an even number of Fractions.
+ F = Fraction
+ data = [F(5, 4), F(9, 4), F(13, 4), F(13, 4), F(17, 4), F(17, 4)]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), 3.25)
+
+ def test_odd_decimals(self):
+ # Test median_grouped works with an odd number of Decimals.
+ D = Decimal
+ data = [D('5.5'), D('6.5'), D('6.5'), D('7.5'), D('8.5')]
+ assert len(data)%2 == 1
+ random.shuffle(data)
+ self.assertEqual(self.func(data), 6.75)
+
+ def test_even_decimals(self):
+ # Test median_grouped works with an even number of Decimals.
+ D = Decimal
+ data = [D('5.5'), D('5.5'), D('6.5'), D('6.5'), D('7.5'), D('8.5')]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), 6.5)
+ #---
+ data = [D('5.5'), D('5.5'), D('6.5'), D('7.5'), D('7.5'), D('8.5')]
+ assert len(data)%2 == 0
+ random.shuffle(data)
+ self.assertEqual(self.func(data), 7.0)
+
+ def test_interval(self):
+ # Test median_grouped with interval argument.
+ data = [2.25, 2.5, 2.5, 2.75, 2.75, 3.0, 3.0, 3.25, 3.5, 3.75]
+ self.assertEqual(self.func(data, 0.25), 2.875)
+ data = [2.25, 2.5, 2.5, 2.75, 2.75, 2.75, 3.0, 3.0, 3.25, 3.5, 3.75]
+ self.assertApproxEqual(self.func(data, 0.25), 2.83333333, tol=1e-8)
+ data = [220, 220, 240, 260, 260, 260, 260, 280, 280, 300, 320, 340]
+ self.assertEqual(self.func(data, 20), 265.0)
+
+
+class TestMode(NumericTestCase, AverageMixin, UnivariateTypeMixin):
+ # Test cases for the discrete version of mode.
+ def setUp(self):
+ self.func = statistics.mode
+
+ def prepare_data(self):
+ """Overload method from UnivariateCommonMixin."""
+ # Make sure test data has exactly one mode.
+ return [1, 1, 1, 1, 3, 4, 7, 9, 0, 8, 2]
+
+ def test_range_data(self):
+ # Override test from UnivariateCommonMixin.
+ data = range(20, 50, 3)
+ self.assertRaises(statistics.StatisticsError, self.func, data)
+
+ def test_nominal_data(self):
+ # Test mode with nominal data.
+ data = 'abcbdb'
+ self.assertEqual(self.func(data), 'b')
+ data = 'fe fi fo fum fi fi'.split()
+ self.assertEqual(self.func(data), 'fi')
+
+ def test_discrete_data(self):
+ # Test mode with discrete numeric data.
+ data = list(range(10))
+ for i in range(10):
+ d = data + [i]
+ random.shuffle(d)
+ self.assertEqual(self.func(d), i)
+
+ def test_bimodal_data(self):
+ # Test mode with bimodal data.
+ data = [1, 1, 2, 2, 2, 2, 3, 4, 5, 6, 6, 6, 6, 7, 8, 9, 9]
+ assert data.count(2) == data.count(6) == 4
+ # Check for an exception.
+ self.assertRaises(statistics.StatisticsError, self.func, data)
+
+ def test_unique_data_failure(self):
+ # Test mode exception when data points are all unique.
+ data = list(range(10))
+ self.assertRaises(statistics.StatisticsError, self.func, data)
+
+ def test_none_data(self):
+ # Test that mode raises TypeError if given None as data.
+
+ # This test is necessary because the implementation of mode uses
+ # collections.Counter, which accepts None and returns an empty dict.
+ self.assertRaises(TypeError, self.func, None)
+
+
+# === Tests for variances and standard deviations ===
+
+class VarianceStdevMixin(UnivariateCommonMixin):
+ # Mixin class holding common tests for variance and std dev.
+
+ # Subclasses should inherit from this before NumericTestClass, in order
+ # to see the rel attribute below. See testShiftData for an explanation.
+
+ rel = 1e-12
+
+ def test_single_value(self):
+ # Deviation of a single value is zero.
+ for x in (11, 19.8, 4.6e14, Fraction(21, 34), Decimal('8.392')):
+ self.assertEqual(self.func([x]), 0)
+
+ def test_repeated_single_value(self):
+ # The deviation of a single repeated value is zero.
+ for x in (7.2, 49, 8.1e15, Fraction(3, 7), Decimal('62.4802')):
+ for count in (2, 3, 5, 15):
+ data = [x]*count
+ self.assertEqual(self.func(data), 0)
+
+ def test_domain_error_regression(self):
+ # Regression test for a domain error exception.
+ # (Thanks to Geremy Condra.)
+ data = [0.123456789012345]*10000
+ # All the items are identical, so variance should be exactly zero.
+ # We allow some small round-off error, but not much.
+ result = self.func(data)
+ self.assertApproxEqual(result, 0.0, tol=5e-17)
+ self.assertGreaterEqual(result, 0) # A negative result must fail.
+
+ def test_shift_data(self):
+ # Test that shifting the data by a constant amount does not affect
+ # the variance or stdev. Or at least not much.
+
+ # Due to rounding, this test should be considered an ideal. We allow
+ # some tolerance away from "no change at all" by setting tol and/or rel
+ # attributes. Subclasses may set tighter or looser error tolerances.
+ raw = [1.03, 1.27, 1.94, 2.04, 2.58, 3.14, 4.75, 4.98, 5.42, 6.78]
+ expected = self.func(raw)
+ # Don't set shift too high, the bigger it is, the more rounding error.
+ shift = 1e5
+ data = [x + shift for x in raw]
+ self.assertApproxEqual(self.func(data), expected)
+
+ def test_shift_data_exact(self):
+ # Like test_shift_data, but result is always exact.
+ raw = [1, 3, 3, 4, 5, 7, 9, 10, 11, 16]
+ assert all(x==int(x) for x in raw)
+ expected = self.func(raw)
+ shift = 10**9
+ data = [x + shift for x in raw]
+ self.assertEqual(self.func(data), expected)
+
+ def test_iter_list_same(self):
+ # Test that iter data and list data give the same result.
+
+ # This is an explicit test that iterators and lists are treated the
+ # same; justification for this test over and above the similar test
+ # in UnivariateCommonMixin is that an earlier design had variance and
+ # friends swap between one- and two-pass algorithms, which would
+ # sometimes give different results.
+ data = [random.uniform(-3, 8) for _ in range(1000)]
+ expected = self.func(data)
+ self.assertEqual(self.func(iter(data)), expected)
+
+
+class TestPVariance(VarianceStdevMixin, NumericTestCase, UnivariateTypeMixin):
+ # Tests for population variance.
+ def setUp(self):
+ self.func = statistics.pvariance
+
+ def test_exact_uniform(self):
+ # Test the variance against an exact result for uniform data.
+ data = list(range(10000))
+ random.shuffle(data)
+ expected = (10000**2 - 1)/12 # Exact value.
+ self.assertEqual(self.func(data), expected)
+
+ def test_ints(self):
+ # Test population variance with int data.
+ data = [4, 7, 13, 16]
+ exact = 22.5
+ self.assertEqual(self.func(data), exact)
+
+ def test_fractions(self):
+ # Test population variance with Fraction data.
+ F = Fraction
+ data = [F(1, 4), F(1, 4), F(3, 4), F(7, 4)]
+ exact = F(3, 8)
+ result = self.func(data)
+ self.assertEqual(result, exact)
+ self.assertIsInstance(result, Fraction)
+
+ def test_decimals(self):
+ # Test population variance with Decimal data.
+ D = Decimal
+ data = [D("12.1"), D("12.2"), D("12.5"), D("12.9")]
+ exact = D('0.096875')
+ result = self.func(data)
+ self.assertEqual(result, exact)
+ self.assertIsInstance(result, Decimal)
+
+
+class TestVariance(VarianceStdevMixin, NumericTestCase, UnivariateTypeMixin):
+ # Tests for sample variance.
+ def setUp(self):
+ self.func = statistics.variance
+
+ def test_single_value(self):
+ # Override method from VarianceStdevMixin.
+ for x in (35, 24.7, 8.2e15, Fraction(19, 30), Decimal('4.2084')):
+ self.assertRaises(statistics.StatisticsError, self.func, [x])
+
+ def test_ints(self):
+ # Test sample variance with int data.
+ data = [4, 7, 13, 16]
+ exact = 30
+ self.assertEqual(self.func(data), exact)
+
+ def test_fractions(self):
+ # Test sample variance with Fraction data.
+ F = Fraction
+ data = [F(1, 4), F(1, 4), F(3, 4), F(7, 4)]
+ exact = F(1, 2)
+ result = self.func(data)
+ self.assertEqual(result, exact)
+ self.assertIsInstance(result, Fraction)
+
+ def test_decimals(self):
+ # Test sample variance with Decimal data.
+ D = Decimal
+ data = [D(2), D(2), D(7), D(9)]
+ exact = 4*D('9.5')/D(3)
+ result = self.func(data)
+ self.assertEqual(result, exact)
+ self.assertIsInstance(result, Decimal)
+
+
+class TestPStdev(VarianceStdevMixin, NumericTestCase):
+ # Tests for population standard deviation.
+ def setUp(self):
+ self.func = statistics.pstdev
+
+ def test_compare_to_variance(self):
+ # Test that stdev is, in fact, the square root of variance.
+ data = [random.uniform(-17, 24) for _ in range(1000)]
+ expected = math.sqrt(statistics.pvariance(data))
+ self.assertEqual(self.func(data), expected)
+
+
+class TestStdev(VarianceStdevMixin, NumericTestCase):
+ # Tests for sample standard deviation.
+ def setUp(self):
+ self.func = statistics.stdev
+
+ def test_single_value(self):
+ # Override method from VarianceStdevMixin.
+ for x in (81, 203.74, 3.9e14, Fraction(5, 21), Decimal('35.719')):
+ self.assertRaises(statistics.StatisticsError, self.func, [x])
+
+ def test_compare_to_variance(self):
+ # Test that stdev is, in fact, the square root of variance.
+ data = [random.uniform(-2, 9) for _ in range(1000)]
+ expected = math.sqrt(statistics.variance(data))
+ self.assertEqual(self.func(data), expected)
+
+
+# === Run tests ===
+
+def load_tests(loader, tests, ignore):
+ """Used for doctest/unittest integration."""
+ tests.addTests(doctest.DocTestSuite())
+ return tests
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_strftime.py b/Lib/test/test_strftime.py
index 78c9c5bdb9..772cd0688c 100644
--- a/Lib/test/test_strftime.py
+++ b/Lib/test/test_strftime.py
@@ -182,26 +182,23 @@ class Y1900Tests(unittest.TestCase):
a date before 1900 is passed with a format string containing "%y"
"""
- @unittest.skipUnless(sys.platform == "win32", "Only applies to Windows")
- def test_y_before_1900_win(self):
- with self.assertRaises(ValueError):
- time.strftime("%y", (1899, 1, 1, 0, 0, 0, 0, 0, 0))
-
- @unittest.skipIf(sys.platform == "win32", "Doesn't apply on Windows")
- def test_y_before_1900_nonwin(self):
- self.assertEquals(
- time.strftime("%y", (1899, 1, 1, 0, 0, 0, 0, 0, 0)), "99")
+ def test_y_before_1900(self):
+ # Issue #13674, #19634
+ t = (1899, 1, 1, 0, 0, 0, 0, 0, 0)
+ if (sys.platform == "win32"
+ or sys.platform.startswith(("aix", "sunos", "solaris"))):
+ with self.assertRaises(ValueError):
+ time.strftime("%y", t)
+ else:
+ self.assertEqual(time.strftime("%y", t), "99")
def test_y_1900(self):
- self.assertEquals(
+ self.assertEqual(
time.strftime("%y", (1900, 1, 1, 0, 0, 0, 0, 0, 0)), "00")
def test_y_after_1900(self):
- self.assertEquals(
+ self.assertEqual(
time.strftime("%y", (2013, 1, 1, 0, 0, 0, 0, 0, 0)), "13")
-def test_main():
- support.run_unittest(StrftimeTest, Y1900Tests)
-
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_strtod.py b/Lib/test/test_strtod.py
index c5979fc989..41b6e5f833 100644
--- a/Lib/test/test_strtod.py
+++ b/Lib/test/test_strtod.py
@@ -248,7 +248,7 @@ class StrtodTests(unittest.TestCase):
else:
assert False, "expected ValueError"
- @test.support.bigmemtest(size=test.support._2G+10, memuse=4, dry_run=False)
+ @test.support.bigmemtest(size=test.support._2G+10, memuse=3, dry_run=False)
def test_oversized_digit_strings(self, maxsize):
# Input string whose length doesn't fit in an INT.
s = "1." + "1" * maxsize
diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py
index 50cae052f3..0107eebca6 100644
--- a/Lib/test/test_struct.py
+++ b/Lib/test/test_struct.py
@@ -1,4 +1,6 @@
+from collections import abc
import array
+import operator
import unittest
import struct
import sys
@@ -6,7 +8,6 @@ import sys
from test import support
ISBIGENDIAN = sys.byteorder == "big"
-IS32BIT = sys.maxsize == 0x7fffffff
integer_codes = 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q', 'n', 'N'
byteorders = '', '@', '=', '<', '>', '!'
@@ -489,7 +490,7 @@ class StructTest(unittest.TestCase):
def test_bool(self):
class ExplodingBool(object):
def __bool__(self):
- raise IOError
+ raise OSError
for prefix in tuple("<>!=")+('',):
false = (), [], [], '', 0
true = [1], 'test', 5, -1, 0xffffffff+1, 0xffffffff/2
@@ -520,10 +521,10 @@ class StructTest(unittest.TestCase):
try:
struct.pack(prefix + '?', ExplodingBool())
- except IOError:
+ except OSError:
pass
else:
- self.fail("Expected IOError: struct.pack(%r, "
+ self.fail("Expected OSError: struct.pack(%r, "
"ExplodingBool())" % (prefix + '?'))
for c in [b'\x01', b'\x7f', b'\xff', b'\x0f', b'\xf0']:
@@ -536,10 +537,6 @@ class StructTest(unittest.TestCase):
hugecount2 = '{}b{}H'.format(sys.maxsize//2, sys.maxsize//2)
self.assertRaises(struct.error, struct.calcsize, hugecount2)
- if IS32BIT:
- def test_crasher(self):
- self.assertRaises(MemoryError, struct.pack, "357913941b", "a")
-
def test_trailing_counter(self):
store = array.array('b', b' '*100)
@@ -576,7 +573,7 @@ class StructTest(unittest.TestCase):
# The size of 'PyStructObject'
totalsize = support.calcobjsize('2n3P')
# The size taken up by the 'formatcode' dynamic array
- totalsize += struct.calcsize('P2n0P') * (number_of_codes + 1)
+ totalsize += struct.calcsize('P3n0P') * (number_of_codes + 1)
support.check_sizeof(self, struct.Struct(format_str), totalsize)
@support.cpython_only
@@ -587,14 +584,84 @@ class StructTest(unittest.TestCase):
self.check_sizeof('B' * 1234, 1234)
self.check_sizeof('fd', 2)
self.check_sizeof('xxxxxxxxxxxxxx', 0)
- self.check_sizeof('100H', 100)
+ self.check_sizeof('100H', 1)
self.check_sizeof('187s', 1)
self.check_sizeof('20p', 1)
self.check_sizeof('0s', 1)
self.check_sizeof('0c', 0)
+
+class UnpackIteratorTest(unittest.TestCase):
+ """
+ Tests for iterative unpacking (struct.Struct.iter_unpack).
+ """
+
+ def test_construct(self):
+ def _check_iterator(it):
+ self.assertIsInstance(it, abc.Iterator)
+ self.assertIsInstance(it, abc.Iterable)
+ s = struct.Struct('>ibcp')
+ it = s.iter_unpack(b"")
+ _check_iterator(it)
+ it = s.iter_unpack(b"1234567")
+ _check_iterator(it)
+ # Wrong bytes length
+ with self.assertRaises(struct.error):
+ s.iter_unpack(b"123456")
+ with self.assertRaises(struct.error):
+ s.iter_unpack(b"12345678")
+ # Zero-length struct
+ s = struct.Struct('>')
+ with self.assertRaises(struct.error):
+ s.iter_unpack(b"")
+ with self.assertRaises(struct.error):
+ s.iter_unpack(b"12")
+
+ def test_iterate(self):
+ s = struct.Struct('>IB')
+ b = bytes(range(1, 16))
+ it = s.iter_unpack(b)
+ self.assertEqual(next(it), (0x01020304, 5))
+ self.assertEqual(next(it), (0x06070809, 10))
+ self.assertEqual(next(it), (0x0b0c0d0e, 15))
+ self.assertRaises(StopIteration, next, it)
+ self.assertRaises(StopIteration, next, it)
+
+ def test_arbitrary_buffer(self):
+ s = struct.Struct('>IB')
+ b = bytes(range(1, 11))
+ it = s.iter_unpack(memoryview(b))
+ self.assertEqual(next(it), (0x01020304, 5))
+ self.assertEqual(next(it), (0x06070809, 10))
+ self.assertRaises(StopIteration, next, it)
+ self.assertRaises(StopIteration, next, it)
+
+ def test_length_hint(self):
+ lh = operator.length_hint
+ s = struct.Struct('>IB')
+ b = bytes(range(1, 16))
+ it = s.iter_unpack(b)
+ self.assertEqual(lh(it), 3)
+ next(it)
+ self.assertEqual(lh(it), 2)
+ next(it)
+ self.assertEqual(lh(it), 1)
+ next(it)
+ self.assertEqual(lh(it), 0)
+ self.assertRaises(StopIteration, next, it)
+ self.assertEqual(lh(it), 0)
+
+ def test_module_func(self):
+ # Sanity check for the global struct.iter_unpack()
+ it = struct.iter_unpack('>IB', bytes(range(1, 11)))
+ self.assertEqual(next(it), (0x01020304, 5))
+ self.assertEqual(next(it), (0x06070809, 10))
+ self.assertRaises(StopIteration, next, it)
+ self.assertRaises(StopIteration, next, it)
+
+
def test_main():
- support.run_unittest(StructTest)
+ support.run_unittest(__name__)
if __name__ == '__main__':
test_main()
diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py
index a89e9556c1..353d0eadcc 100644
--- a/Lib/test/test_structseq.py
+++ b/Lib/test/test_structseq.py
@@ -38,7 +38,7 @@ class StructSeqTest(unittest.TestCase):
# os.stat() gives a complicated struct sequence.
st = os.stat(__file__)
rep = repr(st)
- self.assertTrue(rep.startswith(os.name + ".stat_result"))
+ self.assertTrue(rep.startswith("os.stat_result"))
self.assertIn("st_mode=", rep)
self.assertIn("st_ino=", rep)
self.assertIn("st_dev=", rep)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index f5a70a3873..46e012d142 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -11,6 +11,7 @@ import errno
import tempfile
import time
import re
+import selectors
import sysconfig
import warnings
import select
@@ -19,10 +20,6 @@ import gc
import textwrap
try:
- import resource
-except ImportError:
- resource = None
-try:
import threading
except ImportError:
threading = None
@@ -162,8 +159,28 @@ class ProcessTestCase(BaseTestCase):
stderr=subprocess.STDOUT)
self.assertIn(b'BDFL', output)
+ def test_check_output_stdin_arg(self):
+ # check_output() can be called with stdin set to a file
+ tf = tempfile.TemporaryFile()
+ self.addCleanup(tf.close)
+ tf.write(b'pear')
+ tf.seek(0)
+ output = subprocess.check_output(
+ [sys.executable, "-c",
+ "import sys; sys.stdout.write(sys.stdin.read().upper())"],
+ stdin=tf)
+ self.assertIn(b'PEAR', output)
+
+ def test_check_output_input_arg(self):
+ # check_output() can be called with input set to a string
+ output = subprocess.check_output(
+ [sys.executable, "-c",
+ "import sys; sys.stdout.write(sys.stdin.read().upper())"],
+ input=b'pear')
+ self.assertIn(b'PEAR', output)
+
def test_check_output_stdout_arg(self):
- # check_output() function stderr redirected to stdout
+ # check_output() refuses to accept 'stdout' argument
with self.assertRaises(ValueError) as c:
output = subprocess.check_output(
[sys.executable, "-c", "print('will not be run')"],
@@ -171,6 +188,20 @@ class ProcessTestCase(BaseTestCase):
self.fail("Expected ValueError when stdout arg supplied.")
self.assertIn('stdout', c.exception.args[0])
+ def test_check_output_stdin_with_input_arg(self):
+ # check_output() refuses to accept 'stdin' with 'input'
+ tf = tempfile.TemporaryFile()
+ self.addCleanup(tf.close)
+ tf.write(b'pear')
+ tf.seek(0)
+ with self.assertRaises(ValueError) as c:
+ output = subprocess.check_output(
+ [sys.executable, "-c", "print('will not be run')"],
+ stdin=tf, input=b'hare')
+ self.fail("Expected ValueError when stdin and input args supplied.")
+ self.assertIn('stdin', c.exception.args[0])
+ self.assertIn('input', c.exception.args[0])
+
def test_check_output_timeout(self):
# check_output() function with timeout arg
with self.assertRaises(subprocess.TimeoutExpired) as c:
@@ -853,8 +884,9 @@ class ProcessTestCase(BaseTestCase):
#
# UTF-16 and UTF-32-BE are sufficient to check both with BOM and
# without, and UTF-16 and UTF-32.
+ import _bootlocale
for encoding in ['utf-16', 'utf-32-be']:
- old_getpreferredencoding = locale.getpreferredencoding
+ old_getpreferredencoding = _bootlocale.getpreferredencoding
# Indirectly via io.TextIOWrapper, Popen() defaults to
# locale.getpreferredencoding(False) and earlier in Python 3.2 to
# locale.getpreferredencoding().
@@ -865,7 +897,7 @@ class ProcessTestCase(BaseTestCase):
encoding)
args = [sys.executable, '-c', code]
try:
- locale.getpreferredencoding = getpreferredencoding
+ _bootlocale.getpreferredencoding = getpreferredencoding
# We set stdin to be non-None because, as of this writing,
# a different code path is used when the number of pipes is
# zero or one.
@@ -874,7 +906,7 @@ class ProcessTestCase(BaseTestCase):
stdout=subprocess.PIPE)
stdout, stderr = popen.communicate(input='')
finally:
- locale.getpreferredencoding = old_getpreferredencoding
+ _bootlocale.getpreferredencoding = old_getpreferredencoding
self.assertEqual(stdout, '1\n2\n3\n4')
def test_no_leaking(self):
@@ -982,8 +1014,7 @@ class ProcessTestCase(BaseTestCase):
# value for that limit, but Windows has 2048, so we loop
# 1024 times (each call leaked two fds).
for i in range(1024):
- # Windows raises IOError. Others raise OSError.
- with self.assertRaises(EnvironmentError) as c:
+ with self.assertRaises(OSError) as c:
subprocess.Popen(['nonexisting_i_hope'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@@ -1114,47 +1145,6 @@ class ProcessTestCase(BaseTestCase):
fds_after_exception = os.listdir(fd_directory)
self.assertEqual(fds_before_popen, fds_after_exception)
-
-# context manager
-class _SuppressCoreFiles(object):
- """Try to prevent core files from being created."""
- old_limit = None
-
- def __enter__(self):
- """Try to save previous ulimit, then set it to (0, 0)."""
- if resource is not None:
- try:
- self.old_limit = resource.getrlimit(resource.RLIMIT_CORE)
- resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
- except (ValueError, resource.error):
- pass
-
- if sys.platform == 'darwin':
- # Check if the 'Crash Reporter' on OSX was configured
- # in 'Developer' mode and warn that it will get triggered
- # when it is.
- #
- # This assumes that this context manager is used in tests
- # that might trigger the next manager.
- value = subprocess.Popen(['/usr/bin/defaults', 'read',
- 'com.apple.CrashReporter', 'DialogType'],
- stdout=subprocess.PIPE).communicate()[0]
- if value.strip() == b'developer':
- print("this tests triggers the Crash Reporter, "
- "that is intentional", end='')
- sys.stdout.flush()
-
- def __exit__(self, *args):
- """Return core file behavior to default."""
- if self.old_limit is None:
- return
- if resource is not None:
- try:
- resource.setrlimit(resource.RLIMIT_CORE, self.old_limit)
- except (ValueError, resource.error):
- pass
-
-
@unittest.skipIf(mswindows, "POSIX specific tests")
class POSIXProcessTestCase(BaseTestCase):
@@ -1243,7 +1233,7 @@ class POSIXProcessTestCase(BaseTestCase):
def test_run_abort(self):
# returncode handles signal termination
- with _SuppressCoreFiles():
+ with support.SuppressCrashReport():
p = subprocess.Popen([sys.executable, "-c",
'import os; os.abort()'])
p.wait()
@@ -1266,7 +1256,7 @@ class POSIXProcessTestCase(BaseTestCase):
try:
p = subprocess.Popen([sys.executable, "-c", ""],
preexec_fn=raise_it)
- except RuntimeError as e:
+ except subprocess.SubprocessError as e:
self.assertTrue(
subprocess._posixsubprocess,
"Expected a ValueError from the preexec_fn")
@@ -1306,9 +1296,10 @@ class POSIXProcessTestCase(BaseTestCase):
"""Issue16140: Don't double close pipes on preexec error."""
def raise_it():
- raise RuntimeError("force the _execute_child() errpipe_data path.")
+ raise subprocess.SubprocessError(
+ "force the _execute_child() errpipe_data path.")
- with self.assertRaises(RuntimeError):
+ with self.assertRaises(subprocess.SubprocessError):
self._TestExecuteChildPopen(
self, [sys.executable, "-c", "pass"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
@@ -1507,16 +1498,28 @@ class POSIXProcessTestCase(BaseTestCase):
# Terminating a dead process
self._kill_dead_process('terminate')
+ def _save_fds(self, save_fds):
+ fds = []
+ for fd in save_fds:
+ inheritable = os.get_inheritable(fd)
+ saved = os.dup(fd)
+ fds.append((fd, saved, inheritable))
+ return fds
+
+ def _restore_fds(self, fds):
+ for fd, saved, inheritable in fds:
+ os.dup2(saved, fd, inheritable=inheritable)
+ os.close(saved)
+
def check_close_std_fds(self, fds):
# Issue #9905: test that subprocess pipes still work properly with
# some standard fds closed
stdin = 0
- newfds = []
- for a in fds:
- b = os.dup(a)
- newfds.append(b)
- if a == 0:
- stdin = b
+ saved_fds = self._save_fds(fds)
+ for fd, saved, inheritable in saved_fds:
+ if fd == 0:
+ stdin = saved
+ break
try:
for fd in fds:
os.close(fd)
@@ -1531,10 +1534,7 @@ class POSIXProcessTestCase(BaseTestCase):
err = support.strip_python_stderr(err)
self.assertEqual((out, err), (b'apple', b'orange'))
finally:
- for b, a in zip(newfds, fds):
- os.dup2(b, a)
- for b in newfds:
- os.close(b)
+ self._restore_fds(saved_fds)
def test_close_fd_0(self):
self.check_close_std_fds([0])
@@ -1595,7 +1595,7 @@ class POSIXProcessTestCase(BaseTestCase):
os.lseek(temp_fds[1], 0, 0)
# move the standard file descriptors out of the way
- saved_fds = [os.dup(fd) for fd in range(3)]
+ saved_fds = self._save_fds(range(3))
try:
# duplicate the file objects over the standard fd's
for fd, temp_fd in enumerate(temp_fds):
@@ -1611,10 +1611,7 @@ class POSIXProcessTestCase(BaseTestCase):
stderr=temp_fds[0])
p.wait()
finally:
- # restore the original fd's underneath sys.stdin, etc.
- for std, saved in enumerate(saved_fds):
- os.dup2(saved, std)
- os.close(saved)
+ self._restore_fds(saved_fds)
for fd in temp_fds:
os.lseek(fd, 0, 0)
@@ -1638,7 +1635,7 @@ class POSIXProcessTestCase(BaseTestCase):
os.unlink(fname)
# save a copy of the standard file descriptors
- saved_fds = [os.dup(fd) for fd in range(3)]
+ saved_fds = self._save_fds(range(3))
try:
# duplicate the temp files over the standard fd's 0, 1, 2
for fd, temp_fd in enumerate(temp_fds):
@@ -1664,9 +1661,7 @@ class POSIXProcessTestCase(BaseTestCase):
out = os.read(stdout_no, 1024)
err = support.strip_python_stderr(os.read(stderr_no, 1024))
finally:
- for std, saved in enumerate(saved_fds):
- os.dup2(saved, std)
- os.close(saved)
+ self._restore_fds(saved_fds)
self.assertEqual(out, b"got STDIN")
self.assertEqual(err, b"err")
@@ -1698,40 +1693,47 @@ class POSIXProcessTestCase(BaseTestCase):
# Pure Python implementations keeps the message
self.assertIsNone(subprocess._posixsubprocess)
self.assertEqual(str(err), "surrogate:\uDCff")
- except RuntimeError as err:
+ except subprocess.SubprocessError as err:
# _posixsubprocess uses a default message
self.assertIsNotNone(subprocess._posixsubprocess)
self.assertEqual(str(err), "Exception occurred in preexec_fn.")
else:
- self.fail("Expected ValueError or RuntimeError")
+ self.fail("Expected ValueError or subprocess.SubprocessError")
def test_undecodable_env(self):
for key, value in (('test', 'abc\uDCFF'), ('test\uDCFF', '42')):
+ encoded_value = value.encode("ascii", "surrogateescape")
+
# test str with surrogates
script = "import os; print(ascii(os.getenv(%s)))" % repr(key)
env = os.environ.copy()
env[key] = value
- # Use C locale to get ascii for the locale encoding to force
+ # Use C locale to get ASCII for the locale encoding to force
# surrogate-escaping of \xFF in the child process; otherwise it can
# be decoded as-is if the default locale is latin-1.
env['LC_ALL'] = 'C'
+ if sys.platform.startswith("aix"):
+ # On AIX, the C locale uses the Latin1 encoding
+ decoded_value = encoded_value.decode("latin1", "surrogateescape")
+ else:
+ # On other UNIXes, the C locale uses the ASCII encoding
+ decoded_value = value
stdout = subprocess.check_output(
[sys.executable, "-c", script],
env=env)
stdout = stdout.rstrip(b'\n\r')
- self.assertEqual(stdout.decode('ascii'), ascii(value))
+ self.assertEqual(stdout.decode('ascii'), ascii(decoded_value))
# test bytes
key = key.encode("ascii", "surrogateescape")
- value = value.encode("ascii", "surrogateescape")
script = "import os; print(ascii(os.getenvb(%s)))" % repr(key)
env = os.environ.copy()
- env[key] = value
+ env[key] = encoded_value
stdout = subprocess.check_output(
[sys.executable, "-c", script],
env=env)
stdout = stdout.rstrip(b'\n\r')
- self.assertEqual(stdout.decode('ascii'), ascii(value))
+ self.assertEqual(stdout.decode('ascii'), ascii(encoded_value))
def test_bytes_program(self):
abs_program = os.fsencode(sys.executable)
@@ -1837,6 +1839,9 @@ class POSIXProcessTestCase(BaseTestCase):
self.addCleanup(os.close, fd)
open_fds.add(fd)
+ for fd in open_fds:
+ os.set_inheritable(fd, True)
+
p = subprocess.Popen([sys.executable, fd_status],
stdout=subprocess.PIPE, close_fds=False)
output, ignored = p.communicate()
@@ -1881,6 +1886,8 @@ class POSIXProcessTestCase(BaseTestCase):
fds = os.pipe()
self.addCleanup(os.close, fds[0])
self.addCleanup(os.close, fds[1])
+ os.set_inheritable(fds[0], True)
+ os.set_inheritable(fds[1], True)
open_fds.update(fds)
for fd in open_fds:
@@ -1903,6 +1910,32 @@ class POSIXProcessTestCase(BaseTestCase):
close_fds=False, pass_fds=(fd, )))
self.assertIn('overriding close_fds', str(context.warning))
+ def test_pass_fds_inheritable(self):
+ script = support.findfile("fd_status.py", subdir="subprocessdata")
+
+ inheritable, non_inheritable = os.pipe()
+ self.addCleanup(os.close, inheritable)
+ self.addCleanup(os.close, non_inheritable)
+ os.set_inheritable(inheritable, True)
+ os.set_inheritable(non_inheritable, False)
+ pass_fds = (inheritable, non_inheritable)
+ args = [sys.executable, script]
+ args += list(map(str, pass_fds))
+
+ p = subprocess.Popen(args,
+ stdout=subprocess.PIPE, close_fds=True,
+ pass_fds=pass_fds)
+ output, ignored = p.communicate()
+ fds = set(map(int, output.split(b',')))
+
+ # the inheritable file descriptor must be inherited, so its inheritable
+ # flag must be set in the child process after fork() and before exec()
+ self.assertEqual(fds, set(pass_fds), "output=%a" % output)
+
+ # inheritable flag must not be changed in the parent process
+ self.assertEqual(os.get_inheritable(inheritable), True)
+ self.assertEqual(os.get_inheritable(non_inheritable), False)
+
def test_stdout_stdin_are_single_inout_fd(self):
with io.open(os.devnull, "r+") as inout:
p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
@@ -1990,7 +2023,7 @@ class POSIXProcessTestCase(BaseTestCase):
# let some time for the process to exit, and create a new Popen: this
# should trigger the wait() of p
time.sleep(0.2)
- with self.assertRaises(EnvironmentError) as c:
+ with self.assertRaises(OSError) as c:
with subprocess.Popen(['nonexisting_i_hope'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE) as proc:
@@ -2175,15 +2208,16 @@ class CommandTests(unittest.TestCase):
os.rmdir(dir)
-@unittest.skipUnless(getattr(subprocess, '_has_poll', False),
- "poll system call not supported")
+@unittest.skipUnless(hasattr(selectors, 'PollSelector'),
+ "Test needs selectors.PollSelector")
class ProcessTestCaseNoPoll(ProcessTestCase):
def setUp(self):
- subprocess._has_poll = False
+ self.orig_selector = subprocess._PopenSelector
+ subprocess._PopenSelector = selectors.SelectSelector
ProcessTestCase.setUp(self)
def tearDown(self):
- subprocess._has_poll = True
+ subprocess._PopenSelector = self.orig_selector
ProcessTestCase.tearDown(self)
diff --git a/Lib/test/test_sunau.py b/Lib/test/test_sunau.py
index 767314f5b2..af9ffec622 100644
--- a/Lib/test/test_sunau.py
+++ b/Lib/test/test_sunau.py
@@ -1,6 +1,7 @@
from test.support import TESTFN
import unittest
from test import audiotests
+from audioop import byteswap
import sys
import sunau
@@ -47,6 +48,34 @@ class SunauPCM16Test(audiotests.AudioWriteTests,
""")
+class SunauPCM24Test(audiotests.AudioWriteTests,
+ audiotests.AudioTestsWithSourceFile,
+ unittest.TestCase):
+ module = sunau
+ sndfilename = 'pluck-pcm24.au'
+ sndfilenframes = 3307
+ nchannels = 2
+ sampwidth = 3
+ framerate = 11025
+ nframes = 48
+ comptype = 'NONE'
+ compname = 'not compressed'
+ frames = bytes.fromhex("""\
+ 022D65FFEB9D 4B5A0F00FA54 3113C304EE2B 80DCD6084303 \
+ CBDEC006B261 48A99803F2F8 BFE82401B07D 036BFBFE7B5D \
+ B85756FA3EC9 B4B055F3502B 299830EBCB62 1A5CA7E6D99A \
+ EDFA3EE491BD C625EBE27884 0E05A9E0B6CF EF2929E02922 \
+ 5758D8E27067 FB3557E83E16 1377BFEF8402 D82C5BF7272A \
+ 978F16FB7745 F5F865FC1013 086635FB9C4E DF30FCFB40EE \
+ 117FE0FA3438 3EE6B8FB5AC3 BC77A3FCB2F4 66D6DAFF5F32 \
+ CF13B9041275 431D69097A8C C1BB600EC74E 5120B912A2BA \
+ EEDF641754C0 8207001664B7 7FFFFF14453F 8000001294E6 \
+ 499C1B0EB3B2 52B73E0DBCA0 EFB2B20F5FD8 CE3CDB0FBE12 \
+ E4B49C0CEA2D 6344A80A5A7C 08C8FE0A1FFE 2BB9860B0A0E \
+ 51486F0E44E1 8BCC64113B05 B6F4EC0EEB36 4413170A5B48 \
+ """)
+
+
class SunauPCM32Test(audiotests.AudioWriteTests,
audiotests.AudioTestsWithSourceFile,
unittest.TestCase):
@@ -96,7 +125,7 @@ class SunauULAWTest(audiotests.AudioWriteTests,
E5040CBC 617C0A3C 08BC0A3C 2C7C0B3C 517C0E3C 8A8410FC B6840EBC 457C0A3C \
""")
if sys.byteorder != 'big':
- frames = audiotests.byteswap2(frames)
+ frames = byteswap(frames, 2)
if __name__ == "__main__":
diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py
index e08cf0179d..2da3ac05dc 100644
--- a/Lib/test/test_sundry.py
+++ b/Lib/test/test_sundry.py
@@ -1,19 +1,26 @@
"""Do a minimal test of all the modules that aren't otherwise tested."""
-
-from test import support
+import importlib
import sys
+from test import support
import unittest
class TestUntestedModules(unittest.TestCase):
- def test_at_least_import_untested_modules(self):
+ def test_untested_modules_can_be_imported(self):
+ untested = ('bdb', 'encodings', 'formatter', 'imghdr',
+ 'nturl2path', 'tabnanny')
with support.check_warnings(quiet=True):
- import bdb
- import cgitb
+ for name in untested:
+ try:
+ support.import_module('test.test_{}'.format(name))
+ except unittest.SkipTest:
+ importlib.import_module(name)
+ else:
+ self.fail('{} has tests even though test_sundry claims '
+ 'otherwise'.format(name))
import distutils.bcppcompiler
import distutils.ccompiler
import distutils.cygwinccompiler
- import distutils.emxccompiler
import distutils.filelist
if sys.platform.startswith('win'):
import distutils.msvccompiler
@@ -39,28 +46,14 @@ class TestUntestedModules(unittest.TestCase):
import distutils.command.sdist
import distutils.command.upload
- import encodings
- import formatter
- import getpass
import html.entities
- import imghdr
- import keyword
- import mailcap
- import nturl2path
- import os2emxpath
- import pstats
- import py_compile
- import sndhdr
- import tabnanny
+
try:
- import tty # not available on Windows
+ import tty # Not available on Windows
except ImportError:
if support.verbose:
print("skipping tty")
-def test_main():
- support.run_unittest(TestUntestedModules)
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py
index 1e272ee2cf..37fc2d9134 100644
--- a/Lib/test/test_super.py
+++ b/Lib/test/test_super.py
@@ -44,6 +44,11 @@ class G(A):
class TestSuper(unittest.TestCase):
+ def tearDown(self):
+ # This fixes the damage that test_various___class___pathologies does.
+ nonlocal __class__
+ __class__ = TestSuper
+
def test_basics_working(self):
self.assertEqual(D().f(), 'ABCD')
@@ -81,8 +86,7 @@ class TestSuper(unittest.TestCase):
self.assertEqual(E().f(), 'AE')
- @unittest.expectedFailure
- def test___class___set(self):
+ def test_various___class___pathologies(self):
# See issue #12370
class X(A):
def f(self):
@@ -91,6 +95,31 @@ class TestSuper(unittest.TestCase):
x = X()
self.assertEqual(x.f(), 'A')
self.assertEqual(x.__class__, 413)
+ class X:
+ x = __class__
+ def f():
+ __class__
+ self.assertIs(X.x, type(self))
+ with self.assertRaises(NameError) as e:
+ exec("""class X:
+ __class__
+ def f():
+ __class__""", globals(), {})
+ self.assertIs(type(e.exception), NameError) # Not UnboundLocalError
+ class X:
+ global __class__
+ __class__ = 42
+ def f():
+ __class__
+ self.assertEqual(globals()["__class__"], 42)
+ del globals()["__class__"]
+ self.assertNotIn("__class__", X.__dict__)
+ class X:
+ nonlocal __class__
+ __class__ = 42
+ def f():
+ __class__
+ self.assertEqual(__class__, 42)
def test___class___instancemethod(self):
# See issue #14857
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
index 4edb1a8e01..16b660bce1 100644
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -306,6 +306,7 @@ class TestSupport(unittest.TestCase):
# args_from_interpreter_flags
# can_symlink
# skip_unless_symlink
+ # SuppressCrashReport
def test_main():
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 5926b69c93..a9d3628819 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -33,7 +33,7 @@ SyntaxError: invalid syntax
>>> None = 1
Traceback (most recent call last):
-SyntaxError: assignment to keyword
+SyntaxError: can't assign to keyword
It's a syntax error to assign to the empty tuple. Why isn't it an
error to assign to the empty list? It will always raise some error at
@@ -233,7 +233,7 @@ Traceback (most recent call last):
SyntaxError: can't assign to generator expression
>>> None += 1
Traceback (most recent call last):
-SyntaxError: assignment to keyword
+SyntaxError: can't assign to keyword
>>> f() += 1
Traceback (most recent call last):
SyntaxError: can't assign to function call
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index acf6d364f2..f0c71483ed 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -6,6 +6,9 @@ import textwrap
import warnings
import operator
import codecs
+import gc
+import sysconfig
+import platform
# count the number of test runs, used to create unique
# strings to intern in test_intern()
@@ -248,7 +251,7 @@ class SysModuleTest(unittest.TestCase):
sys.setrecursionlimit(%d)
f()""")
- with test.support.suppress_crash_popup():
+ with test.support.SuppressCrashReport():
for i in (50, 1000):
sub = subprocess.Popen([sys.executable, '-c', code % i],
stderr=subprocess.PIPE)
@@ -429,7 +432,7 @@ class SysModuleTest(unittest.TestCase):
self.assertEqual(type(sys.int_info.sizeof_digit), int)
self.assertIsInstance(sys.hexversion, int)
- self.assertEqual(len(sys.hash_info), 5)
+ self.assertEqual(len(sys.hash_info), 9)
self.assertLess(sys.hash_info.modulus, 2**sys.hash_info.width)
# sys.hash_info.modulus should be a prime; we do a quick
# probable primality test (doesn't exclude the possibility of
@@ -444,6 +447,26 @@ class SysModuleTest(unittest.TestCase):
self.assertIsInstance(sys.hash_info.inf, int)
self.assertIsInstance(sys.hash_info.nan, int)
self.assertIsInstance(sys.hash_info.imag, int)
+ algo = sysconfig.get_config_var("PY_HASH_ALGORITHM")
+ if sys.hash_info.algorithm in {"fnv", "siphash24"}:
+ self.assertIn(sys.hash_info.hash_bits, {32, 64})
+ self.assertIn(sys.hash_info.seed_bits, {32, 64, 128})
+
+ if algo == 1:
+ self.assertEqual(sys.hash_info.algorithm, "siphash24")
+ elif algo == 2:
+ self.assertEqual(sys.hash_info.algorithm, "fnv")
+ else:
+ processor = platform.processor().lower()
+ if processor in {"sparc", "mips"}:
+ self.assertEqual(sys.hash_info.algorithm, "fnv")
+ else:
+ self.assertEqual(sys.hash_info.algorithm, "siphash24")
+ else:
+ # PY_HASH_EXTERNAL
+ self.assertEqual(algo, 0)
+ self.assertGreaterEqual(sys.hash_info.cutoff, 0)
+ self.assertLess(sys.hash_info.cutoff, 8)
self.assertIsInstance(sys.maxsize, int)
self.assertIsInstance(sys.maxunicode, int)
@@ -481,7 +504,7 @@ class SysModuleTest(unittest.TestCase):
def test_thread_info(self):
info = sys.thread_info
self.assertEqual(len(info), 3)
- self.assertIn(info.name, ('nt', 'os2', 'pthread', 'solaris', None))
+ self.assertIn(info.name, ('nt', 'pthread', 'solaris', None))
self.assertIn(info.lock, ('semaphore', 'mutex+cond', None))
def test_43581(self):
@@ -514,7 +537,7 @@ class SysModuleTest(unittest.TestCase):
attrs = ("debug",
"inspect", "interactive", "optimize", "dont_write_bytecode",
"no_user_site", "no_site", "ignore_environment", "verbose",
- "bytes_warning", "quiet", "hash_randomization")
+ "bytes_warning", "quiet", "hash_randomization", "isolated")
for attr in attrs:
self.assertTrue(hasattr(sys.flags, attr), attr)
self.assertEqual(type(getattr(sys.flags, attr)), int, attr)
@@ -543,6 +566,42 @@ class SysModuleTest(unittest.TestCase):
out = p.communicate()[0].strip()
self.assertEqual(out, b'?')
+ env["PYTHONIOENCODING"] = "ascii"
+ p = subprocess.Popen([sys.executable, "-c", 'print(chr(0xa2))'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=env)
+ out, err = p.communicate()
+ self.assertEqual(out, b'')
+ self.assertIn(b'UnicodeEncodeError:', err)
+ self.assertIn(rb"'\xa2'", err)
+
+ env["PYTHONIOENCODING"] = "ascii:"
+ p = subprocess.Popen([sys.executable, "-c", 'print(chr(0xa2))'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=env)
+ out, err = p.communicate()
+ self.assertEqual(out, b'')
+ self.assertIn(b'UnicodeEncodeError:', err)
+ self.assertIn(rb"'\xa2'", err)
+
+ env["PYTHONIOENCODING"] = ":surrogateescape"
+ p = subprocess.Popen([sys.executable, "-c", 'print(chr(0xdcbd))'],
+ stdout=subprocess.PIPE, env=env)
+ out = p.communicate()[0].strip()
+ self.assertEqual(out, b'\xbd')
+
+ @unittest.skipUnless(test.support.FS_NONASCII,
+ 'requires OS support of non-ASCII encodings')
+ def test_ioencoding_nonascii(self):
+ env = dict(os.environ)
+
+ env["PYTHONIOENCODING"] = ""
+ p = subprocess.Popen([sys.executable, "-c",
+ 'print(%a)' % test.support.FS_NONASCII],
+ stdout=subprocess.PIPE, env=env)
+ out = p.communicate()[0].strip()
+ self.assertEqual(out, os.fsencode(test.support.FS_NONASCII))
+
@unittest.skipIf(sys.base_prefix != sys.prefix,
'Test is not venv-compatible')
def test_executable(self):
@@ -610,6 +669,36 @@ class SysModuleTest(unittest.TestCase):
ret, out, err = assert_python_ok(*args)
self.assertIn(b"free PyDictObjects", err)
+ @unittest.skipUnless(hasattr(sys, "getallocatedblocks"),
+ "sys.getallocatedblocks unavailable on this build")
+ def test_getallocatedblocks(self):
+ # Some sanity checks
+ with_pymalloc = sysconfig.get_config_var('WITH_PYMALLOC')
+ a = sys.getallocatedblocks()
+ self.assertIs(type(a), int)
+ if with_pymalloc:
+ self.assertGreater(a, 0)
+ else:
+ # When WITH_PYMALLOC isn't available, we don't know anything
+ # about the underlying implementation: the function might
+ # return 0 or something greater.
+ self.assertGreaterEqual(a, 0)
+ try:
+ # While we could imagine a Python session where the number of
+ # multiple buffer objects would exceed the sharing of references,
+ # it is unlikely to happen in a normal test run.
+ self.assertLess(a, sys.gettotalrefcount())
+ except AttributeError:
+ # gettotalrefcount() not available
+ pass
+ gc.collect()
+ b = sys.getallocatedblocks()
+ self.assertLessEqual(b, a)
+ gc.collect()
+ c = sys.getallocatedblocks()
+ self.assertIn(c, range(b - 50, b + 50))
+
+
class SizeofTest(unittest.TestCase):
def setUp(self):
@@ -654,7 +743,7 @@ class SizeofTest(unittest.TestCase):
samples = [b'', b'u'*100000]
for sample in samples:
x = bytearray(sample)
- check(x, vsize('inP') + x.__alloc__())
+ check(x, vsize('n2Pi') + x.__alloc__())
# bytearray_iterator
check(iter(bytearray()), size('nP'))
# cell
@@ -733,7 +822,7 @@ class SizeofTest(unittest.TestCase):
nfrees = len(x.f_code.co_freevars)
extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
ncells + nfrees - 1
- check(x, vsize('12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
+ check(x, vsize('13P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
# function
def func(): pass
check(func, size('12P'))
@@ -779,7 +868,7 @@ class SizeofTest(unittest.TestCase):
# memoryview
check(memoryview(b''), size('Pnin 2P2n2i5P 3cPn'))
# module
- check(unittest, size('PnP'))
+ check(unittest, size('PnPPP'))
# None
check(None, size(''))
# NotImplementedType
@@ -833,11 +922,11 @@ class SizeofTest(unittest.TestCase):
check((1,2,3), vsize('') + 3*self.P)
# type
# static type: PyTypeObject
- s = vsize('P2n15Pl4Pn9Pn11PI')
+ s = vsize('P2n15Pl4Pn9Pn11PIP')
check(int, s)
# (PyTypeObject + PyNumberMethods + PyMappingMethods +
# PySequenceMethods + PyBufferProcs + 4P)
- s = vsize('P2n15Pl4Pn9Pn11PI') + struct.calcsize('34P 3P 10P 2P 4P')
+ s = vsize('P2n15Pl4Pn9Pn11PIP') + struct.calcsize('34P 3P 10P 2P 4P')
# Separate block for PyDictKeysObject with 4 entries
s += struct.calcsize("2nPn") + 4*struct.calcsize("n2P")
# class
diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
index 03f67fd476..804c446716 100644
--- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py
@@ -5,7 +5,7 @@ import subprocess
import shutil
from copy import copy
-from test.support import (run_unittest, TESTFN, unlink,
+from test.support import (run_unittest, TESTFN, unlink, check_warnings,
captured_stdout, skip_unless_symlink)
import sysconfig
@@ -234,7 +234,7 @@ class TestSysConfig(unittest.TestCase):
self.assertTrue(os.path.isfile(config_h), config_h)
def test_get_scheme_names(self):
- wanted = ('nt', 'nt_user', 'os2', 'os2_home', 'osx_framework_user',
+ wanted = ('nt', 'nt_user', 'osx_framework_user',
'posix_home', 'posix_prefix', 'posix_user')
self.assertEqual(get_scheme_names(), wanted)
@@ -305,14 +305,13 @@ class TestSysConfig(unittest.TestCase):
if 'MACOSX_DEPLOYMENT_TARGET' in env:
del env['MACOSX_DEPLOYMENT_TARGET']
- with open('/dev/null', 'w') as devnull_fp:
- p = subprocess.Popen([
- sys.executable, '-c',
- 'import sysconfig; print(sysconfig.get_platform())',
- ],
- stdout=subprocess.PIPE,
- stderr=devnull_fp,
- env=env)
+ p = subprocess.Popen([
+ sys.executable, '-c',
+ 'import sysconfig; print(sysconfig.get_platform())',
+ ],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL,
+ env=env)
test_platform = p.communicate()[0].strip()
test_platform = test_platform.decode('utf-8')
status = p.wait()
@@ -325,20 +324,19 @@ class TestSysConfig(unittest.TestCase):
env = os.environ.copy()
env['MACOSX_DEPLOYMENT_TARGET'] = '10.1'
- with open('/dev/null') as dev_null:
- p = subprocess.Popen([
- sys.executable, '-c',
- 'import sysconfig; print(sysconfig.get_platform())',
- ],
- stdout=subprocess.PIPE,
- stderr=dev_null,
- env=env)
- test_platform = p.communicate()[0].strip()
- test_platform = test_platform.decode('utf-8')
- status = p.wait()
-
- self.assertEqual(status, 0)
- self.assertEqual(my_platform, test_platform)
+ p = subprocess.Popen([
+ sys.executable, '-c',
+ 'import sysconfig; print(sysconfig.get_platform())',
+ ],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL,
+ env=env)
+ test_platform = p.communicate()[0].strip()
+ test_platform = test_platform.decode('utf-8')
+ status = p.wait()
+
+ self.assertEqual(status, 0)
+ self.assertEqual(my_platform, test_platform)
def test_srcdir(self):
# See Issues #15322, #15364.
@@ -371,6 +369,26 @@ class TestSysConfig(unittest.TestCase):
os.chdir(cwd)
self.assertEqual(srcdir, srcdir2)
+ @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None,
+ 'EXT_SUFFIX required for this test')
+ def test_SO_deprecation(self):
+ self.assertWarns(DeprecationWarning,
+ sysconfig.get_config_var, 'SO')
+
+ @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None,
+ 'EXT_SUFFIX required for this test')
+ def test_SO_value(self):
+ with check_warnings(('', DeprecationWarning)):
+ self.assertEqual(sysconfig.get_config_var('SO'),
+ sysconfig.get_config_var('EXT_SUFFIX'))
+
+ @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None,
+ 'EXT_SUFFIX required for this test')
+ def test_SO_in_vars(self):
+ vars = sysconfig.get_config_vars()
+ self.assertIsNotNone(vars['SO'])
+ self.assertEqual(vars['SO'], vars['EXT_SUFFIX'])
+
class MakefileTests(unittest.TestCase):
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index 238175ff3c..9d318100fa 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -7,7 +7,7 @@ from hashlib import md5
import unittest
import tarfile
-from test import support
+from test import support, script_helper
# Check for our compression modules.
try:
@@ -27,11 +27,13 @@ def md5sum(data):
return md5(data).hexdigest()
TEMPDIR = os.path.abspath(support.TESTFN) + "-tardir"
+tarextdir = TEMPDIR + '-extract-test'
tarname = support.findfile("testtar.tar")
gzipname = os.path.join(TEMPDIR, "testtar.tar.gz")
bz2name = os.path.join(TEMPDIR, "testtar.tar.bz2")
xzname = os.path.join(TEMPDIR, "testtar.tar.xz")
tmpname = os.path.join(TEMPDIR, "tmp.tar")
+dotlessname = os.path.join(TEMPDIR, "testtar")
md5_regtype = "65f477c818ad9e15f7feab0c6d37742f"
md5_sparse = "a54fbc4ca4f4399a90e1b27164012fc6"
@@ -1724,6 +1726,168 @@ class MiscTest(unittest.TestCase):
tarfile.itn(0x10000000000, 6, tarfile.GNU_FORMAT)
+class CommandLineTest(unittest.TestCase):
+
+ def tarfilecmd(self, *args):
+ rc, out, err = script_helper.assert_python_ok('-m', 'tarfile', *args)
+ return out.replace(os.linesep.encode(), b'\n')
+
+ def tarfilecmd_failure(self, *args):
+ return script_helper.assert_python_failure('-m', 'tarfile', *args)
+
+ def make_simple_tarfile(self, tar_name):
+ files = [support.findfile('tokenize_tests.txt'),
+ support.findfile('tokenize_tests-no-coding-cookie-'
+ 'and-utf8-bom-sig-only.txt')]
+ self.addCleanup(support.unlink, tar_name)
+ with tarfile.open(tar_name, 'w') as tf:
+ for tardata in files:
+ tf.add(tardata, arcname=os.path.basename(tardata))
+
+ def test_test_command(self):
+ for tar_name in testtarnames:
+ for opt in '-t', '--test':
+ out = self.tarfilecmd(opt, tar_name)
+ self.assertEqual(out, b'')
+
+ def test_test_command_verbose(self):
+ for tar_name in testtarnames:
+ for opt in '-v', '--verbose':
+ out = self.tarfilecmd(opt, '-t', tar_name)
+ self.assertIn(b'is a tar archive.\n', out)
+
+ def test_test_command_invalid_file(self):
+ zipname = support.findfile('zipdir.zip')
+ rc, out, err = self.tarfilecmd_failure('-t', zipname)
+ self.assertIn(b' is not a tar archive.', err)
+ self.assertEqual(out, b'')
+ self.assertEqual(rc, 1)
+
+ for tar_name in testtarnames:
+ with self.subTest(tar_name=tar_name):
+ with open(tar_name, 'rb') as f:
+ data = f.read()
+ try:
+ with open(tmpname, 'wb') as f:
+ f.write(data[:511])
+ rc, out, err = self.tarfilecmd_failure('-t', tmpname)
+ self.assertEqual(out, b'')
+ self.assertEqual(rc, 1)
+ finally:
+ support.unlink(tmpname)
+
+ def test_list_command(self):
+ self.make_simple_tarfile(tmpname)
+ with support.captured_stdout() as t:
+ with tarfile.open(tmpname, 'r') as tf:
+ tf.list(verbose=False)
+ expected = t.getvalue().encode(sys.getfilesystemencoding())
+ for opt in '-l', '--list':
+ out = self.tarfilecmd(opt, tmpname)
+ self.assertEqual(out, expected)
+
+ def test_list_command_verbose(self):
+ self.make_simple_tarfile(tmpname)
+ with support.captured_stdout() as t:
+ with tarfile.open(tmpname, 'r') as tf:
+ tf.list(verbose=True)
+ expected = t.getvalue().encode(sys.getfilesystemencoding())
+ for opt in '-v', '--verbose':
+ out = self.tarfilecmd(opt, '-l', tmpname)
+ self.assertEqual(out, expected)
+
+ def test_list_command_invalid_file(self):
+ zipname = support.findfile('zipdir.zip')
+ rc, out, err = self.tarfilecmd_failure('-l', zipname)
+ self.assertIn(b' is not a tar archive.', err)
+ self.assertEqual(out, b'')
+ self.assertEqual(rc, 1)
+
+ def test_create_command(self):
+ files = [support.findfile('tokenize_tests.txt'),
+ support.findfile('tokenize_tests-no-coding-cookie-'
+ 'and-utf8-bom-sig-only.txt')]
+ for opt in '-c', '--create':
+ try:
+ out = self.tarfilecmd(opt, tmpname, *files)
+ self.assertEqual(out, b'')
+ with tarfile.open(tmpname) as tar:
+ tar.getmembers()
+ finally:
+ support.unlink(tmpname)
+
+ def test_create_command_verbose(self):
+ files = [support.findfile('tokenize_tests.txt'),
+ support.findfile('tokenize_tests-no-coding-cookie-'
+ 'and-utf8-bom-sig-only.txt')]
+ for opt in '-v', '--verbose':
+ try:
+ out = self.tarfilecmd(opt, '-c', tmpname, *files)
+ self.assertIn(b' file created.', out)
+ with tarfile.open(tmpname) as tar:
+ tar.getmembers()
+ finally:
+ support.unlink(tmpname)
+
+ def test_create_command_dotless_filename(self):
+ files = [support.findfile('tokenize_tests.txt')]
+ try:
+ out = self.tarfilecmd('-c', dotlessname, *files)
+ self.assertEqual(out, b'')
+ with tarfile.open(dotlessname) as tar:
+ tar.getmembers()
+ finally:
+ support.unlink(dotlessname)
+
+ def test_create_command_dot_started_filename(self):
+ tar_name = os.path.join(TEMPDIR, ".testtar")
+ files = [support.findfile('tokenize_tests.txt')]
+ try:
+ out = self.tarfilecmd('-c', tar_name, *files)
+ self.assertEqual(out, b'')
+ with tarfile.open(tar_name) as tar:
+ tar.getmembers()
+ finally:
+ support.unlink(tar_name)
+
+ def test_extract_command(self):
+ self.make_simple_tarfile(tmpname)
+ for opt in '-e', '--extract':
+ try:
+ with support.temp_cwd(tarextdir):
+ out = self.tarfilecmd(opt, tmpname)
+ self.assertEqual(out, b'')
+ finally:
+ support.rmtree(tarextdir)
+
+ def test_extract_command_verbose(self):
+ self.make_simple_tarfile(tmpname)
+ for opt in '-v', '--verbose':
+ try:
+ with support.temp_cwd(tarextdir):
+ out = self.tarfilecmd(opt, '-e', tmpname)
+ self.assertIn(b' file is extracted.', out)
+ finally:
+ support.rmtree(tarextdir)
+
+ def test_extract_command_different_directory(self):
+ self.make_simple_tarfile(tmpname)
+ try:
+ with support.temp_cwd(tarextdir):
+ out = self.tarfilecmd('-e', tmpname, 'spamdir')
+ self.assertEqual(out, b'')
+ finally:
+ support.rmtree(tarextdir)
+
+ def test_extract_command_invalid_file(self):
+ zipname = support.findfile('zipdir.zip')
+ with support.temp_cwd(tarextdir):
+ rc, out, err = self.tarfilecmd_failure('-e', zipname)
+ self.assertIn(b' is not a tar archive.', err)
+ self.assertEqual(out, b'')
+ self.assertEqual(rc, 1)
+
+
class ContextManagerTest(unittest.TestCase):
def test_basic(self):
@@ -1732,20 +1896,20 @@ class ContextManagerTest(unittest.TestCase):
self.assertTrue(tar.closed, "context manager failed")
def test_closed(self):
- # The __enter__() method is supposed to raise IOError
+ # The __enter__() method is supposed to raise OSError
# if the TarFile object is already closed.
tar = tarfile.open(tarname)
tar.close()
- with self.assertRaises(IOError):
+ with self.assertRaises(OSError):
with tar:
pass
def test_exception(self):
- # Test if the IOError exception is passed through properly.
+ # Test if the OSError exception is passed through properly.
with self.assertRaises(Exception) as exc:
with tarfile.open(tarname) as tar:
- raise IOError
- self.assertIsInstance(exc.exception, IOError,
+ raise OSError
+ self.assertIsInstance(exc.exception, OSError,
"wrong exception raised in context manager")
self.assertTrue(tar.closed, "context manager failed")
@@ -1851,6 +2015,8 @@ def setUpModule():
support.unlink(TEMPDIR)
os.makedirs(TEMPDIR)
+ global testtarnames
+ testtarnames = [tarname]
with open(tarname, "rb") as fobj:
data = fobj.read()
@@ -1858,6 +2024,7 @@ def setUpModule():
for c in GzipTest, Bz2Test, LzmaTest:
if c.open:
support.unlink(c.tarname)
+ testtarnames.append(c.tarname)
with c.open(c.tarname, "wb") as tar:
tar.write(data)
diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py
index 4cf3f28841..66c49ffedc 100644
--- a/Lib/test/test_tcl.py
+++ b/Lib/test/test_tcl.py
@@ -254,40 +254,6 @@ class TclTest(unittest.TestCase):
for arg, res in testcases:
self.assertEqual(split(arg), res, msg=arg)
- def test_merge(self):
- with support.check_warnings(('merge is deprecated',
- DeprecationWarning)):
- merge = self.interp.tk.merge
- call = self.interp.tk.call
- testcases = [
- ((), ''),
- (('a',), 'a'),
- ((2,), '2'),
- (('',), '{}'),
- ('{', '\\{'),
- (('a', 'b', 'c'), 'a b c'),
- ((' ', '\t', '\r', '\n'), '{ } {\t} {\r} {\n}'),
- (('a', ' ', 'c'), 'a { } c'),
- (('a', '€'), 'a €'),
- (('a', '\U000104a2'), 'a \U000104a2'),
- (('a', b'\xe2\x82\xac'), 'a €'),
- (('a', ('b', 'c')), 'a {b c}'),
- (('a', 2), 'a 2'),
- (('a', 3.4), 'a 3.4'),
- (('a', (2, 3.4)), 'a {2 3.4}'),
- ((), ''),
- ((call('list', 1, '2', (3.4,)),), '{1 2 3.4}'),
- ]
- if tcl_version >= (8, 5):
- testcases += [
- ((call('dict', 'create', 12, '\u20ac', b'\xe2\x82\xac', (3.4,)),),
- '{12 € € 3.4}'),
- ]
- for args, res in testcases:
- self.assertEqual(merge(*args), res, msg=args)
- self.assertRaises(UnicodeDecodeError, merge, b'\x80')
- self.assertRaises(UnicodeEncodeError, merge, '\udc80')
-
class BigmemTclTest(unittest.TestCase):
diff --git a/Lib/test/test_telnetlib.py b/Lib/test/test_telnetlib.py
index c9f2ccb462..4f61e44d2c 100644
--- a/Lib/test/test_telnetlib.py
+++ b/Lib/test/test_telnetlib.py
@@ -1,10 +1,9 @@
import socket
-import select
+import selectors
import telnetlib
import time
import contextlib
-import unittest
from unittest import TestCase
from test import support
threading = support.import_module('threading')
@@ -112,40 +111,33 @@ class TelnetAlike(telnetlib.Telnet):
self._messages += out.getvalue()
return
-def mock_select(*s_args):
- block = False
- for l in s_args:
- for fob in l:
- if isinstance(fob, TelnetAlike):
- block = fob.sock.block
- if block:
- return [[], [], []]
- else:
- return s_args
-
-class MockPoller(object):
- test_case = None # Set during TestCase setUp.
+class MockSelector(selectors.BaseSelector):
def __init__(self):
- self._file_objs = []
+ self.keys = {}
+
+ def register(self, fileobj, events, data=None):
+ key = selectors.SelectorKey(fileobj, 0, events, data)
+ self.keys[fileobj] = key
+ return key
- def register(self, fd, eventmask):
- self.test_case.assertTrue(hasattr(fd, 'fileno'), fd)
- self.test_case.assertEqual(eventmask, select.POLLIN|select.POLLPRI)
- self._file_objs.append(fd)
+ def unregister(self, fileobj):
+ return self.keys.pop(fileobj)
- def poll(self, timeout=None):
+ def select(self, timeout=None):
block = False
- for fob in self._file_objs:
- if isinstance(fob, TelnetAlike):
- block = fob.sock.block
+ for fileobj in self.keys:
+ if isinstance(fileobj, TelnetAlike):
+ block = fileobj.sock.block
+ break
if block:
return []
else:
- return zip(self._file_objs, [select.POLLIN]*len(self._file_objs))
+ return [(key, key.events) for key in self.keys.values()]
+
+ def get_map(self):
+ return self.keys
- def unregister(self, fd):
- self._file_objs.remove(fd)
@contextlib.contextmanager
def test_socket(reads):
@@ -159,7 +151,7 @@ def test_socket(reads):
socket.create_connection = old_conn
return
-def test_telnet(reads=(), cls=TelnetAlike, use_poll=None):
+def test_telnet(reads=(), cls=TelnetAlike):
''' return a telnetlib.Telnet object that uses a SocketStub with
reads queued up to be read '''
for x in reads:
@@ -167,29 +159,14 @@ def test_telnet(reads=(), cls=TelnetAlike, use_poll=None):
with test_socket(reads):
telnet = cls('dummy', 0)
telnet._messages = '' # debuglevel output
- if use_poll is not None:
- if use_poll and not telnet._has_poll:
- raise unittest.SkipTest('select.poll() required.')
- telnet._has_poll = use_poll
return telnet
-
class ExpectAndReadTestCase(TestCase):
def setUp(self):
- self.old_select = select.select
- select.select = mock_select
- self.old_poll = False
- if hasattr(select, 'poll'):
- self.old_poll = select.poll
- select.poll = MockPoller
- MockPoller.test_case = self
-
+ self.old_selector = telnetlib._TelnetSelector
+ telnetlib._TelnetSelector = MockSelector
def tearDown(self):
- if self.old_poll:
- MockPoller.test_case = None
- select.poll = self.old_poll
- select.select = self.old_select
-
+ telnetlib._TelnetSelector = self.old_selector
class ReadTests(ExpectAndReadTestCase):
def test_read_until(self):
@@ -208,22 +185,6 @@ class ReadTests(ExpectAndReadTestCase):
data = telnet.read_until(b'match')
self.assertEqual(data, expect)
- def test_read_until_with_poll(self):
- """Use select.poll() to implement telnet.read_until()."""
- want = [b'x' * 10, b'match', b'y' * 10]
- telnet = test_telnet(want, use_poll=True)
- select.select = lambda *_: self.fail('unexpected select() call.')
- data = telnet.read_until(b'match')
- self.assertEqual(data, b''.join(want[:-1]))
-
- def test_read_until_with_select(self):
- """Use select.select() to implement telnet.read_until()."""
- want = [b'x' * 10, b'match', b'y' * 10]
- telnet = test_telnet(want, use_poll=False)
- if self.old_poll:
- select.poll = lambda *_: self.fail('unexpected poll() call.')
- data = telnet.read_until(b'match')
- self.assertEqual(data, b''.join(want[:-1]))
def test_read_all(self):
"""
@@ -427,23 +388,6 @@ class ExpectTests(ExpectAndReadTestCase):
(_,_,data) = telnet.expect([b'match'])
self.assertEqual(data, b''.join(want[:-1]))
- def test_expect_with_poll(self):
- """Use select.poll() to implement telnet.expect()."""
- want = [b'x' * 10, b'match', b'y' * 10]
- telnet = test_telnet(want, use_poll=True)
- select.select = lambda *_: self.fail('unexpected select() call.')
- (_,_,data) = telnet.expect([b'match'])
- self.assertEqual(data, b''.join(want[:-1]))
-
- def test_expect_with_select(self):
- """Use select.select() to implement telnet.expect()."""
- want = [b'x' * 10, b'match', b'y' * 10]
- telnet = test_telnet(want, use_poll=False)
- if self.old_poll:
- select.poll = lambda *_: self.fail('unexpected poll() call.')
- (_,_,data) = telnet.expect([b'match'])
- self.assertEqual(data, b''.join(want[:-1]))
-
def test_main(verbose=None):
support.run_unittest(GeneralTests, ReadTests, WriteTests, OptionTests,
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index 4ff56b8bf6..ac4d8609df 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -36,7 +36,7 @@ else:
# Common functionality.
class BaseTestCase(unittest.TestCase):
- str_check = re.compile(r"[a-zA-Z0-9_-]{6}$")
+ str_check = re.compile(r"^[a-z0-9_-]{8}$")
def setUp(self):
self._warnings_manager = support.check_warnings()
@@ -63,7 +63,7 @@ class BaseTestCase(unittest.TestCase):
nbase = nbase[len(pre):len(nbase)-len(suf)]
self.assertTrue(self.str_check.match(nbase),
- "random string '%s' does not match /^[a-zA-Z0-9_-]{6}$/"
+ "random string '%s' does not match ^[a-z0-9_-]{8}$"
% nbase)
@@ -152,7 +152,7 @@ class TestRandomNameSequence(BaseTestCase):
# via any bugs above
try:
os.kill(pid, signal.SIGKILL)
- except EnvironmentError:
+ except OSError:
pass
os.close(read_fd)
os.close(write_fd)
@@ -191,7 +191,7 @@ class TestCandidateTempdirList(BaseTestCase):
try:
dirname = os.getcwd()
- except (AttributeError, os.error):
+ except (AttributeError, OSError):
dirname = os.curdir
self.assertIn(dirname, cand)
@@ -332,7 +332,7 @@ class TestMkstempInner(BaseTestCase):
file = self.do_create()
mode = stat.S_IMODE(os.stat(file.name).st_mode)
expected = 0o600
- if sys.platform in ('win32', 'os2emx'):
+ if sys.platform == 'win32':
# There's no distinction among 'user', 'group' and 'world';
# replicate the 'user' bits.
user = expected >> 6
@@ -350,6 +350,7 @@ class TestMkstempInner(BaseTestCase):
v="q"
file = self.do_create()
+ self.assertEqual(os.get_inheritable(file.fd), False)
fd = "%d" % file.fd
try:
@@ -366,7 +367,7 @@ class TestMkstempInner(BaseTestCase):
# On Windows a spawn* /path/ with embedded spaces shouldn't be quoted,
# but an arg with embedded spaces should be decorated with double
# quotes on each end
- if sys.platform in ('win32',):
+ if sys.platform == 'win32':
decorated = '"%s"' % sys.executable
tester = '"%s"' % tester
else:
@@ -477,6 +478,20 @@ class TestGetTempDir(BaseTestCase):
self.assertTrue(a is b)
+ def test_case_sensitive(self):
+ # gettempdir should not flatten its case
+ # even on a case-insensitive file system
+ case_sensitive_tempdir = tempfile.mkdtemp("-Temp")
+ _tempdir, tempfile.tempdir = tempfile.tempdir, None
+ try:
+ with support.EnvironmentVarGuard() as env:
+ # Fake the first env var which is checked as a candidate
+ env["TMPDIR"] = case_sensitive_tempdir
+ self.assertEqual(tempfile.gettempdir(), case_sensitive_tempdir)
+ finally:
+ tempfile.tempdir = _tempdir
+ support.rmdir(case_sensitive_tempdir)
+
class TestMkstemp(BaseTestCase):
"""Test mkstemp()."""
@@ -566,7 +581,7 @@ class TestMkdtemp(BaseTestCase):
mode = stat.S_IMODE(os.stat(dir).st_mode)
mode &= 0o777 # Mask off sticky bits inherited from /tmp
expected = 0o700
- if sys.platform in ('win32', 'os2emx'):
+ if sys.platform == 'win32':
# There's no distinction among 'user', 'group' and 'world';
# replicate the 'user' bits.
user = expected >> 6
diff --git a/Lib/test/test_textwrap.py b/Lib/test/test_textwrap.py
index c86f5cfae8..1bba77eb9e 100644
--- a/Lib/test/test_textwrap.py
+++ b/Lib/test/test_textwrap.py
@@ -9,9 +9,8 @@
#
import unittest
-from test import support
-from textwrap import TextWrapper, wrap, fill, dedent, indent
+from textwrap import TextWrapper, wrap, fill, dedent, indent, shorten
class BaseTestCase(unittest.TestCase):
@@ -430,6 +429,90 @@ What a mess!
self.check_wrap(text, 7, ["aa \xe4\xe4-", "\xe4\xe4"])
+class MaxLinesTestCase(BaseTestCase):
+ text = "Hello there, how are you this fine day? I'm glad to hear it!"
+
+ def test_simple(self):
+ self.check_wrap(self.text, 12,
+ ["Hello [...]"],
+ max_lines=0)
+ self.check_wrap(self.text, 12,
+ ["Hello [...]"],
+ max_lines=1)
+ self.check_wrap(self.text, 12,
+ ["Hello there,",
+ "how [...]"],
+ max_lines=2)
+ self.check_wrap(self.text, 13,
+ ["Hello there,",
+ "how are [...]"],
+ max_lines=2)
+ self.check_wrap(self.text, 80, [self.text], max_lines=1)
+ self.check_wrap(self.text, 12,
+ ["Hello there,",
+ "how are you",
+ "this fine",
+ "day? I'm",
+ "glad to hear",
+ "it!"],
+ max_lines=6)
+
+ def test_spaces(self):
+ # strip spaces before placeholder
+ self.check_wrap(self.text, 12,
+ ["Hello there,",
+ "how are you",
+ "this fine",
+ "day? [...]"],
+ max_lines=4)
+ # placeholder at the start of line
+ self.check_wrap(self.text, 6,
+ ["Hello",
+ "[...]"],
+ max_lines=2)
+ # final spaces
+ self.check_wrap(self.text + ' ' * 10, 12,
+ ["Hello there,",
+ "how are you",
+ "this fine",
+ "day? I'm",
+ "glad to hear",
+ "it!"],
+ max_lines=6)
+
+ def test_placeholder(self):
+ self.check_wrap(self.text, 12,
+ ["Hello..."],
+ max_lines=1,
+ placeholder='...')
+ self.check_wrap(self.text, 12,
+ ["Hello there,",
+ "how are..."],
+ max_lines=2,
+ placeholder='...')
+ # long placeholder and indentation
+ with self.assertRaises(ValueError):
+ wrap(self.text, 16, initial_indent=' ',
+ max_lines=1, placeholder=' [truncated]...')
+ with self.assertRaises(ValueError):
+ wrap(self.text, 16, subsequent_indent=' ',
+ max_lines=2, placeholder=' [truncated]...')
+ self.check_wrap(self.text, 16,
+ [" Hello there,",
+ " [truncated]..."],
+ max_lines=2,
+ initial_indent=' ',
+ subsequent_indent=' ',
+ placeholder=' [truncated]...')
+ self.check_wrap(self.text, 16,
+ [" [truncated]..."],
+ max_lines=1,
+ initial_indent=' ',
+ subsequent_indent=' ',
+ placeholder=' [truncated]...')
+ self.check_wrap(self.text, 80, [self.text], placeholder='.' * 1000)
+
+
class LongWordTestCase (BaseTestCase):
def setUp(self):
self.wrapper = TextWrapper()
@@ -490,6 +573,14 @@ How *do* you spell that odd word, anyways?
result = wrap(self.text, width=30, break_long_words=0)
self.check(result, expect)
+ def test_max_lines_long(self):
+ self.check_wrap(self.text, 12,
+ ['Did you say ',
+ '"supercalifr',
+ 'agilisticexp',
+ '[...]'],
+ max_lines=4)
+
class IndentTestCases(BaseTestCase):
@@ -777,12 +868,62 @@ class IndentTestCase(unittest.TestCase):
self.assertEqual(indent(text, prefix, predicate), expect)
-def test_main():
- support.run_unittest(WrapTestCase,
- LongWordTestCase,
- IndentTestCases,
- DedentTestCase,
- IndentTestCase)
+class ShortenTestCase(BaseTestCase):
+
+ def check_shorten(self, text, width, expect, **kwargs):
+ result = shorten(text, width, **kwargs)
+ self.check(result, expect)
+
+ def test_simple(self):
+ # Simple case: just words, spaces, and a bit of punctuation
+ text = "Hello there, how are you this fine day? I'm glad to hear it!"
+
+ self.check_shorten(text, 18, "Hello there, [...]")
+ self.check_shorten(text, len(text), text)
+ self.check_shorten(text, len(text) - 1,
+ "Hello there, how are you this fine day? "
+ "I'm glad to [...]")
+
+ def test_placeholder(self):
+ text = "Hello there, how are you this fine day? I'm glad to hear it!"
+
+ self.check_shorten(text, 17, "Hello there,$$", placeholder='$$')
+ self.check_shorten(text, 18, "Hello there, how$$", placeholder='$$')
+ self.check_shorten(text, 18, "Hello there, $$", placeholder=' $$')
+ self.check_shorten(text, len(text), text, placeholder='$$')
+ self.check_shorten(text, len(text) - 1,
+ "Hello there, how are you this fine day? "
+ "I'm glad to hear$$", placeholder='$$')
+
+ def test_empty_string(self):
+ self.check_shorten("", 6, "")
+
+ def test_whitespace(self):
+ # Whitespace collapsing
+ text = """
+ This is a paragraph that already has
+ line breaks and \t tabs too."""
+ self.check_shorten(text, 62,
+ "This is a paragraph that already has line "
+ "breaks and tabs too.")
+ self.check_shorten(text, 61,
+ "This is a paragraph that already has line "
+ "breaks and [...]")
+
+ self.check_shorten("hello world! ", 12, "hello world!")
+ self.check_shorten("hello world! ", 11, "hello [...]")
+ # The leading space is trimmed from the placeholder
+ # (it would be ugly otherwise).
+ self.check_shorten("hello world! ", 10, "[...]")
+
+ def test_width_too_small_for_placeholder(self):
+ shorten("x" * 20, width=8, placeholder="(......)")
+ with self.assertRaises(ValueError):
+ shorten("x" * 20, width=8, placeholder="(.......)")
+
+ def test_first_word_too_long_but_placeholder_fits(self):
+ self.check_shorten("Helloo", 5, "[...]")
+
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_thread.py b/Lib/test/test_thread.py
index a191e157bc..f9a721b03b 100644
--- a/Lib/test/test_thread.py
+++ b/Lib/test/test_thread.py
@@ -68,7 +68,7 @@ class ThreadRunningTests(BasicThreadTest):
thread.stack_size(0)
self.assertEqual(thread.stack_size(), 0, "stack_size not reset to default")
- if os.name not in ("nt", "os2", "posix"):
+ if os.name not in ("nt", "posix"):
return
tss_supported = True
diff --git a/Lib/test/test_threaded_import.py b/Lib/test/test_threaded_import.py
index 6c2965ba6e..3d961b5ee2 100644
--- a/Lib/test/test_threaded_import.py
+++ b/Lib/test/test_threaded_import.py
@@ -5,8 +5,8 @@
# complains several times about module random having no attribute
# randrange, and then Python hangs.
+import _imp as imp
import os
-import imp
import importlib
import sys
import time
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 0ebeb39cbd..a84577cc0f 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -11,6 +11,7 @@ import re
import sys
_thread = import_module('_thread')
threading = import_module('threading')
+import _testcapi
import time
import unittest
import weakref
@@ -20,6 +21,15 @@ import subprocess
from test import lock_tests
+
+# Between fork() and exec(), only async-safe functions are allowed (issues
+# #12316 and #11870), and fork() from a worker thread is known to trigger
+# problems with some operating systems (issue #3863): skip problematic tests
+# on platforms known to behave badly.
+platforms_to_skip = ('freebsd4', 'freebsd5', 'freebsd6', 'netbsd5',
+ 'hp-ux11')
+
+
# A trivial mutable counter.
class Counter(object):
def __init__(self):
@@ -99,7 +109,7 @@ class ThreadTests(BaseTestCase):
if verbose:
print('waiting for all tasks to complete')
for t in threads:
- t.join(NUMTASKS)
+ t.join()
self.assertTrue(not t.is_alive())
self.assertNotEqual(t.ident, 0)
self.assertFalse(t.ident is None)
@@ -467,6 +477,127 @@ class ThreadTests(BaseTestCase):
pid, status = os.waitpid(pid, 0)
self.assertEqual(0, status)
+ def test_main_thread(self):
+ main = threading.main_thread()
+ self.assertEqual(main.name, 'MainThread')
+ self.assertEqual(main.ident, threading.current_thread().ident)
+ self.assertEqual(main.ident, threading.get_ident())
+
+ def f():
+ self.assertNotEqual(threading.main_thread().ident,
+ threading.current_thread().ident)
+ th = threading.Thread(target=f)
+ th.start()
+ th.join()
+
+ @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
+ @unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
+ def test_main_thread_after_fork(self):
+ code = """if 1:
+ import os, threading
+
+ pid = os.fork()
+ if pid == 0:
+ main = threading.main_thread()
+ print(main.name)
+ print(main.ident == threading.current_thread().ident)
+ print(main.ident == threading.get_ident())
+ else:
+ os.waitpid(pid, 0)
+ """
+ _, out, err = assert_python_ok("-c", code)
+ data = out.decode().replace('\r', '')
+ self.assertEqual(err, b"")
+ self.assertEqual(data, "MainThread\nTrue\nTrue\n")
+
+ @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
+ @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
+ @unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
+ def test_main_thread_after_fork_from_nonmain_thread(self):
+ code = """if 1:
+ import os, threading, sys
+
+ def f():
+ pid = os.fork()
+ if pid == 0:
+ main = threading.main_thread()
+ print(main.name)
+ print(main.ident == threading.current_thread().ident)
+ print(main.ident == threading.get_ident())
+ # stdout is fully buffered because not a tty,
+ # we have to flush before exit.
+ sys.stdout.flush()
+ else:
+ os.waitpid(pid, 0)
+
+ th = threading.Thread(target=f)
+ th.start()
+ th.join()
+ """
+ _, out, err = assert_python_ok("-c", code)
+ data = out.decode().replace('\r', '')
+ self.assertEqual(err, b"")
+ self.assertEqual(data, "Thread-1\nTrue\nTrue\n")
+
+ def test_tstate_lock(self):
+ # Test an implementation detail of Thread objects.
+ started = _thread.allocate_lock()
+ finish = _thread.allocate_lock()
+ started.acquire()
+ finish.acquire()
+ def f():
+ started.release()
+ finish.acquire()
+ time.sleep(0.01)
+ # The tstate lock is None until the thread is started
+ t = threading.Thread(target=f)
+ self.assertIs(t._tstate_lock, None)
+ t.start()
+ started.acquire()
+ self.assertTrue(t.is_alive())
+ # The tstate lock can't be acquired when the thread is running
+ # (or suspended).
+ tstate_lock = t._tstate_lock
+ self.assertFalse(tstate_lock.acquire(timeout=0), False)
+ finish.release()
+ # When the thread ends, the state_lock can be successfully
+ # acquired.
+ self.assertTrue(tstate_lock.acquire(timeout=5), False)
+ # But is_alive() is still True: we hold _tstate_lock now, which
+ # prevents is_alive() from knowing the thread's end-of-life C code
+ # is done.
+ self.assertTrue(t.is_alive())
+ # Let is_alive() find out the C code is done.
+ tstate_lock.release()
+ self.assertFalse(t.is_alive())
+ # And verify the thread disposed of _tstate_lock.
+ self.assertTrue(t._tstate_lock is None)
+
+ def test_repr_stopped(self):
+ # Verify that "stopped" shows up in repr(Thread) appropriately.
+ started = _thread.allocate_lock()
+ finish = _thread.allocate_lock()
+ started.acquire()
+ finish.acquire()
+ def f():
+ started.release()
+ finish.acquire()
+ t = threading.Thread(target=f)
+ t.start()
+ started.acquire()
+ self.assertIn("started", repr(t))
+ finish.release()
+ # "stopped" should appear in the repr in a reasonable amount of time.
+ # Implementation detail: as of this writing, that's trivially true
+ # if .join() is called, and almost trivially true if .is_alive() is
+ # called. The detail we're testing here is that "stopped" shows up
+ # "all on its own".
+ LOOKING_FOR = "stopped"
+ for i in range(500):
+ if LOOKING_FOR in repr(t):
+ break
+ time.sleep(0.01)
+ self.assertIn(LOOKING_FOR, repr(t)) # we waited at least 5 seconds
def test_BoundedSemaphore_limit(self):
# BoundedSemaphore should raise ValueError if released too often.
@@ -486,14 +617,53 @@ class ThreadTests(BaseTestCase):
t.join()
self.assertRaises(ValueError, bs.release)
-class ThreadJoinOnShutdown(BaseTestCase):
+ def test_locals_at_exit(self):
+ # Issue #19466: thread locals must not be deleted before destructors
+ # are called
+ rc, out, err = assert_python_ok("-c", """if 1:
+ import threading
+
+ class Atexit:
+ def __del__(self):
+ print("thread_dict.atexit = %r" % thread_dict.atexit)
+
+ thread_dict = threading.local()
+ thread_dict.atexit = "atexit"
+
+ atexit = Atexit()
+ """)
+ self.assertEqual(out.rstrip(), b"thread_dict.atexit = 'atexit'")
+
+ def test_warnings_at_exit(self):
+ # Issue #19466: try to call most destructors at Python shutdown before
+ # destroying Python thread states
+ filename = __file__
+ rc, out, err = assert_python_ok("-Wd", "-c", """if 1:
+ import time
+ import threading
- # Between fork() and exec(), only async-safe functions are allowed (issues
- # #12316 and #11870), and fork() from a worker thread is known to trigger
- # problems with some operating systems (issue #3863): skip problematic tests
- # on platforms known to behave badly.
- platforms_to_skip = ('freebsd4', 'freebsd5', 'freebsd6', 'netbsd5',
- 'os2emx', 'hp-ux11')
+ def open_sleep():
+ # a warning will be emitted when the open file will be
+ # destroyed (without being explicitly closed) while the daemon
+ # thread is destroyed
+ fileobj = open(%a, 'rb')
+ start_event.set()
+ time.sleep(60.0)
+
+ start_event = threading.Event()
+
+ thread = threading.Thread(target=open_sleep)
+ thread.daemon = True
+ thread.start()
+
+ # wait until the thread started
+ start_event.wait()
+ """ % filename)
+ self.assertRegex(err.rstrip(),
+ b"^sys:1: ResourceWarning: unclosed file ")
+
+
+class ThreadJoinOnShutdown(BaseTestCase):
def _run_and_join(self, script):
script = """if 1:
@@ -566,144 +736,8 @@ class ThreadJoinOnShutdown(BaseTestCase):
"""
self._run_and_join(script)
- def assertScriptHasOutput(self, script, expected_output):
- rc, out, err = assert_python_ok("-c", script)
- data = out.decode().replace('\r', '')
- self.assertEqual(data, expected_output)
-
- @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
@unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
- def test_4_joining_across_fork_in_worker_thread(self):
- # There used to be a possible deadlock when forking from a child
- # thread. See http://bugs.python.org/issue6643.
-
- # The script takes the following steps:
- # - The main thread in the parent process starts a new thread and then
- # tries to join it.
- # - The join operation acquires the Lock inside the thread's _block
- # Condition. (See threading.py:Thread.join().)
- # - We stub out the acquire method on the condition to force it to wait
- # until the child thread forks. (See LOCK ACQUIRED HERE)
- # - The child thread forks. (See LOCK HELD and WORKER THREAD FORKS
- # HERE)
- # - The main thread of the parent process enters Condition.wait(),
- # which releases the lock on the child thread.
- # - The child process returns. Without the necessary fix, when the
- # main thread of the child process (which used to be the child thread
- # in the parent process) attempts to exit, it will try to acquire the
- # lock in the Thread._block Condition object and hang, because the
- # lock was held across the fork.
-
- script = """if 1:
- import os, time, threading
-
- finish_join = False
- start_fork = False
-
- def worker():
- # Wait until this thread's lock is acquired before forking to
- # create the deadlock.
- global finish_join
- while not start_fork:
- time.sleep(0.01)
- # LOCK HELD: Main thread holds lock across this call.
- childpid = os.fork()
- finish_join = True
- if childpid != 0:
- # Parent process just waits for child.
- os.waitpid(childpid, 0)
- # Child process should just return.
-
- w = threading.Thread(target=worker)
-
- # Stub out the private condition variable's lock acquire method.
- # This acquires the lock and then waits until the child has forked
- # before returning, which will release the lock soon after. If
- # someone else tries to fix this test case by acquiring this lock
- # before forking instead of resetting it, the test case will
- # deadlock when it shouldn't.
- condition = w._block
- orig_acquire = condition.acquire
- call_count_lock = threading.Lock()
- call_count = 0
- def my_acquire():
- global call_count
- global start_fork
- orig_acquire() # LOCK ACQUIRED HERE
- start_fork = True
- if call_count == 0:
- while not finish_join:
- time.sleep(0.01) # WORKER THREAD FORKS HERE
- with call_count_lock:
- call_count += 1
- condition.acquire = my_acquire
-
- w.start()
- w.join()
- print('end of main')
- """
- self.assertScriptHasOutput(script, "end of main\n")
-
- @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
- @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
- def test_5_clear_waiter_locks_to_avoid_crash(self):
- # Check that a spawned thread that forks doesn't segfault on certain
- # platforms, namely OS X. This used to happen if there was a waiter
- # lock in the thread's condition variable's waiters list. Even though
- # we know the lock will be held across the fork, it is not safe to
- # release locks held across forks on all platforms, so releasing the
- # waiter lock caused a segfault on OS X. Furthermore, since locks on
- # OS X are (as of this writing) implemented with a mutex + condition
- # variable instead of a semaphore, while we know that the Python-level
- # lock will be acquired, we can't know if the internal mutex will be
- # acquired at the time of the fork.
-
- script = """if True:
- import os, time, threading
-
- start_fork = False
-
- def worker():
- # Wait until the main thread has attempted to join this thread
- # before continuing.
- while not start_fork:
- time.sleep(0.01)
- childpid = os.fork()
- if childpid != 0:
- # Parent process just waits for child.
- (cpid, rc) = os.waitpid(childpid, 0)
- assert cpid == childpid
- assert rc == 0
- print('end of worker thread')
- else:
- # Child process should just return.
- pass
-
- w = threading.Thread(target=worker)
-
- # Stub out the private condition variable's _release_save method.
- # This releases the condition's lock and flips the global that
- # causes the worker to fork. At this point, the problematic waiter
- # lock has been acquired once by the waiter and has been put onto
- # the waiters list.
- condition = w._block
- orig_release_save = condition._release_save
- def my_release_save():
- global start_fork
- orig_release_save()
- # Waiter lock held here, condition lock released.
- start_fork = True
- condition._release_save = my_release_save
-
- w.start()
- w.join()
- print('end of main thread')
- """
- output = "end of worker thread\nend of main thread\n"
- self.assertScriptHasOutput(script, output)
-
- @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
- def test_6_daemon_threads(self):
+ def test_4_daemon_threads(self):
# Check that a daemon thread cannot crash the interpreter on shutdown
# by manipulating internal structures that are being disposed of in
# the main thread.
@@ -713,6 +747,10 @@ class ThreadJoinOnShutdown(BaseTestCase):
import sys
import time
import threading
+ import warnings
+
+ # ignore "unclosed file ..." warnings
+ warnings.filterwarnings('ignore', '', ResourceWarning)
thread_has_run = set()
@@ -769,6 +807,111 @@ class ThreadJoinOnShutdown(BaseTestCase):
for t in threads:
t.join()
+ @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
+ def test_clear_threads_states_after_fork(self):
+ # Issue #17094: check that threads states are cleared after fork()
+
+ # start a bunch of threads
+ threads = []
+ for i in range(16):
+ t = threading.Thread(target=lambda : time.sleep(0.3))
+ threads.append(t)
+ t.start()
+
+ pid = os.fork()
+ if pid == 0:
+ # check that threads states have been cleared
+ if len(sys._current_frames()) == 1:
+ os._exit(0)
+ else:
+ os._exit(1)
+ else:
+ _, status = os.waitpid(pid, 0)
+ self.assertEqual(0, status)
+
+ for t in threads:
+ t.join()
+
+
+class SubinterpThreadingTests(BaseTestCase):
+
+ def test_threads_join(self):
+ # Non-daemon threads should be joined at subinterpreter shutdown
+ # (issue #18808)
+ r, w = os.pipe()
+ self.addCleanup(os.close, r)
+ self.addCleanup(os.close, w)
+ code = r"""if 1:
+ import os
+ import threading
+ import time
+
+ def f():
+ # Sleep a bit so that the thread is still running when
+ # Py_EndInterpreter is called.
+ time.sleep(0.05)
+ os.write(%d, b"x")
+ threading.Thread(target=f).start()
+ """ % (w,)
+ ret = test.support.run_in_subinterp(code)
+ self.assertEqual(ret, 0)
+ # The thread was joined properly.
+ self.assertEqual(os.read(r, 1), b"x")
+
+ def test_threads_join_2(self):
+ # Same as above, but a delay gets introduced after the thread's
+ # Python code returned but before the thread state is deleted.
+ # To achieve this, we register a thread-local object which sleeps
+ # a bit when deallocated.
+ r, w = os.pipe()
+ self.addCleanup(os.close, r)
+ self.addCleanup(os.close, w)
+ code = r"""if 1:
+ import os
+ import threading
+ import time
+
+ class Sleeper:
+ def __del__(self):
+ time.sleep(0.05)
+
+ tls = threading.local()
+
+ def f():
+ # Sleep a bit so that the thread is still running when
+ # Py_EndInterpreter is called.
+ time.sleep(0.05)
+ tls.x = Sleeper()
+ os.write(%d, b"x")
+ threading.Thread(target=f).start()
+ """ % (w,)
+ ret = test.support.run_in_subinterp(code)
+ self.assertEqual(ret, 0)
+ # The thread was joined properly.
+ self.assertEqual(os.read(r, 1), b"x")
+
+ def test_daemon_threads_fatal_error(self):
+ subinterp_code = r"""if 1:
+ import os
+ import threading
+ import time
+
+ def f():
+ # Make sure the daemon thread is still running when
+ # Py_EndInterpreter is called.
+ time.sleep(10)
+ threading.Thread(target=f, daemon=True).start()
+ """
+ script = r"""if 1:
+ import _testcapi
+
+ _testcapi.run_in_subinterp(%r)
+ """ % (subinterp_code,)
+ with test.support.SuppressCrashReport():
+ rc, out, err = assert_python_failure("-c", script)
+ self.assertIn("Fatal Python error: Py_EndInterpreter: "
+ "not the last thread", err.decode())
+
class ThreadingExceptionTests(BaseTestCase):
# A RuntimeError should be raised if Thread.start() is called
diff --git a/Lib/test/test_threadsignals.py b/Lib/test/test_threadsignals.py
index f975a75e85..b1004e6689 100644
--- a/Lib/test/test_threadsignals.py
+++ b/Lib/test/test_threadsignals.py
@@ -8,7 +8,7 @@ from test.support import run_unittest, import_module
thread = import_module('_thread')
import time
-if sys.platform[:3] in ('win', 'os2') or sys.platform=='riscos':
+if (sys.platform[:3] == 'win'):
raise unittest.SkipTest("Can't test signal on %s" % sys.platform)
process_pid = os.getpid()
diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py
index d8d6029ea5..44bcb943cb 100644
--- a/Lib/test/test_time.py
+++ b/Lib/test/test_time.py
@@ -371,6 +371,14 @@ class TimeTestCase(unittest.TestCase):
@unittest.skipUnless(hasattr(time, 'monotonic'),
'need time.monotonic')
def test_monotonic(self):
+ # monotonic() should not go backward
+ times = [time.monotonic() for n in range(100)]
+ t1 = times[0]
+ for t2 in times[1:]:
+ self.assertGreaterEqual(t2, t1, "times=%s" % times)
+ t1 = t2
+
+ # monotonic() includes time elapsed during a sleep
t1 = time.monotonic()
time.sleep(0.5)
t2 = time.monotonic()
@@ -378,6 +386,7 @@ class TimeTestCase(unittest.TestCase):
self.assertGreater(t2, t1)
self.assertAlmostEqual(dt, 0.5, delta=0.2)
+ # monotonic() is a monotonic but non adjustable clock
info = time.get_clock_info('monotonic')
self.assertTrue(info.monotonic)
self.assertFalse(info.adjustable)
diff --git a/Lib/test/test_timeout.py b/Lib/test/test_timeout.py
index dcf201b507..703c43ab2b 100644
--- a/Lib/test/test_timeout.py
+++ b/Lib/test/test_timeout.py
@@ -207,7 +207,7 @@ class TCPTimeoutTestCase(TimeoutTestCase):
sock.connect((whitehole))
except socket.timeout:
pass
- except IOError as err:
+ except OSError as err:
if err.errno == errno.ECONNREFUSED:
skip = False
finally:
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 5bce2af68a..96ef951139 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -150,30 +150,74 @@ class SyntaxTracebackCases(unittest.TestCase):
class TracebackFormatTests(unittest.TestCase):
- def test_traceback_format(self):
+ def some_exception(self):
+ raise KeyError('blah')
+
+ def check_traceback_format(self, cleanup_func=None):
try:
- raise KeyError('blah')
+ self.some_exception()
except KeyError:
type_, value, tb = sys.exc_info()
+ if cleanup_func is not None:
+ # Clear the inner frames, not this one
+ cleanup_func(tb.tb_next)
traceback_fmt = 'Traceback (most recent call last):\n' + \
''.join(traceback.format_tb(tb))
file_ = StringIO()
traceback_print(tb, file_)
python_fmt = file_.getvalue()
+ # Call all _tb and _exc functions
+ with captured_output("stderr") as tbstderr:
+ traceback.print_tb(tb)
+ tbfile = StringIO()
+ traceback.print_tb(tb, file=tbfile)
+ with captured_output("stderr") as excstderr:
+ traceback.print_exc()
+ excfmt = traceback.format_exc()
+ excfile = StringIO()
+ traceback.print_exc(file=excfile)
else:
raise Error("unable to create test traceback string")
# Make sure that Python and the traceback module format the same thing
self.assertEqual(traceback_fmt, python_fmt)
+ # Now verify the _tb func output
+ self.assertEqual(tbstderr.getvalue(), tbfile.getvalue())
+ # Now verify the _exc func output
+ self.assertEqual(excstderr.getvalue(), excfile.getvalue())
+ self.assertEqual(excfmt, excfile.getvalue())
# Make sure that the traceback is properly indented.
tb_lines = python_fmt.splitlines()
- self.assertEqual(len(tb_lines), 3)
- banner, location, source_line = tb_lines
+ self.assertEqual(len(tb_lines), 5)
+ banner = tb_lines[0]
+ location, source_line = tb_lines[-2:]
self.assertTrue(banner.startswith('Traceback'))
self.assertTrue(location.startswith(' File'))
self.assertTrue(source_line.startswith(' raise'))
+ def test_traceback_format(self):
+ self.check_traceback_format()
+
+ def test_traceback_format_with_cleared_frames(self):
+ # Check that traceback formatting also works with a clear()ed frame
+ def cleanup_tb(tb):
+ tb.tb_frame.clear()
+ self.check_traceback_format(cleanup_tb)
+
+ def test_stack_format(self):
+ # Verify _stack functions. Note we have to use _getframe(1) to
+ # compare them without this frame appearing in the output
+ with captured_output("stderr") as ststderr:
+ traceback.print_stack(sys._getframe(1))
+ stfile = StringIO()
+ traceback.print_stack(sys._getframe(1), file=stfile)
+ self.assertEqual(ststderr.getvalue(), stfile.getvalue())
+
+ stfmt = traceback.format_stack(sys._getframe(1))
+
+ self.assertEqual(ststderr.getvalue(), "".join(stfmt))
+
cause_message = (
"\nThe above exception was the direct cause "
@@ -344,6 +388,36 @@ class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
return s.getvalue()
+class MiscTracebackCases(unittest.TestCase):
+ #
+ # Check non-printing functions in traceback module
+ #
+
+ def test_clear(self):
+ def outer():
+ middle()
+ def middle():
+ inner()
+ def inner():
+ i = 1
+ 1/0
+
+ try:
+ outer()
+ except:
+ type_, value, tb = sys.exc_info()
+
+ # Initial assertion: there's one local in the inner frame.
+ inner_frame = tb.tb_next.tb_next.tb_next.tb_frame
+ self.assertEqual(len(inner_frame.f_locals), 1)
+
+ # Clear traceback frames
+ traceback.clear_frames(tb)
+
+ # Local variable dict should now be empty.
+ self.assertEqual(len(inner_frame.f_locals), 0)
+
+
def test_main():
run_unittest(__name__)
diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py
new file mode 100644
index 0000000000..484e313a1a
--- /dev/null
+++ b/Lib/test/test_tracemalloc.py
@@ -0,0 +1,792 @@
+import contextlib
+import os
+import sys
+import tracemalloc
+import unittest
+from unittest.mock import patch
+from test.script_helper import assert_python_ok, assert_python_failure
+from test import support
+try:
+ import threading
+except ImportError:
+ threading = None
+
+EMPTY_STRING_SIZE = sys.getsizeof(b'')
+
+def get_frames(nframe, lineno_delta):
+ frames = []
+ frame = sys._getframe(1)
+ for index in range(nframe):
+ code = frame.f_code
+ lineno = frame.f_lineno + lineno_delta
+ frames.append((code.co_filename, lineno))
+ lineno_delta = 0
+ frame = frame.f_back
+ if frame is None:
+ break
+ return tuple(frames)
+
+def allocate_bytes(size):
+ nframe = tracemalloc.get_traceback_limit()
+ bytes_len = (size - EMPTY_STRING_SIZE)
+ frames = get_frames(nframe, 1)
+ data = b'x' * bytes_len
+ return data, tracemalloc.Traceback(frames)
+
+def create_snapshots():
+ traceback_limit = 2
+
+ raw_traces = [
+ (10, (('a.py', 2), ('b.py', 4))),
+ (10, (('a.py', 2), ('b.py', 4))),
+ (10, (('a.py', 2), ('b.py', 4))),
+
+ (2, (('a.py', 5), ('b.py', 4))),
+
+ (66, (('b.py', 1),)),
+
+ (7, (('<unknown>', 0),)),
+ ]
+ snapshot = tracemalloc.Snapshot(raw_traces, traceback_limit)
+
+ raw_traces2 = [
+ (10, (('a.py', 2), ('b.py', 4))),
+ (10, (('a.py', 2), ('b.py', 4))),
+ (10, (('a.py', 2), ('b.py', 4))),
+
+ (2, (('a.py', 5), ('b.py', 4))),
+ (5000, (('a.py', 5), ('b.py', 4))),
+
+ (400, (('c.py', 578),)),
+ ]
+ snapshot2 = tracemalloc.Snapshot(raw_traces2, traceback_limit)
+
+ return (snapshot, snapshot2)
+
+def frame(filename, lineno):
+ return tracemalloc._Frame((filename, lineno))
+
+def traceback(*frames):
+ return tracemalloc.Traceback(frames)
+
+def traceback_lineno(filename, lineno):
+ return traceback((filename, lineno))
+
+def traceback_filename(filename):
+ return traceback_lineno(filename, 0)
+
+
+class TestTracemallocEnabled(unittest.TestCase):
+ def setUp(self):
+ if tracemalloc.is_tracing():
+ self.skipTest("tracemalloc must be stopped before the test")
+
+ tracemalloc.start(1)
+
+ def tearDown(self):
+ tracemalloc.stop()
+
+ def test_get_tracemalloc_memory(self):
+ data = [allocate_bytes(123) for count in range(1000)]
+ size = tracemalloc.get_tracemalloc_memory()
+ self.assertGreaterEqual(size, 0)
+
+ tracemalloc.clear_traces()
+ size2 = tracemalloc.get_tracemalloc_memory()
+ self.assertGreaterEqual(size2, 0)
+ self.assertLessEqual(size2, size)
+
+ def test_get_object_traceback(self):
+ tracemalloc.clear_traces()
+ obj_size = 12345
+ obj, obj_traceback = allocate_bytes(obj_size)
+ traceback = tracemalloc.get_object_traceback(obj)
+ self.assertEqual(traceback, obj_traceback)
+
+ def test_set_traceback_limit(self):
+ obj_size = 10
+
+ tracemalloc.stop()
+ self.assertRaises(ValueError, tracemalloc.start, -1)
+
+ tracemalloc.stop()
+ tracemalloc.start(10)
+ obj2, obj2_traceback = allocate_bytes(obj_size)
+ traceback = tracemalloc.get_object_traceback(obj2)
+ self.assertEqual(len(traceback), 10)
+ self.assertEqual(traceback, obj2_traceback)
+
+ tracemalloc.stop()
+ tracemalloc.start(1)
+ obj, obj_traceback = allocate_bytes(obj_size)
+ traceback = tracemalloc.get_object_traceback(obj)
+ self.assertEqual(len(traceback), 1)
+ self.assertEqual(traceback, obj_traceback)
+
+
+ def find_trace(self, traces, traceback):
+ for trace in traces:
+ if trace[1] == traceback._frames:
+ return trace
+
+ self.fail("trace not found")
+
+ def test_get_traces(self):
+ tracemalloc.clear_traces()
+ obj_size = 12345
+ obj, obj_traceback = allocate_bytes(obj_size)
+
+ traces = tracemalloc._get_traces()
+ trace = self.find_trace(traces, obj_traceback)
+
+ self.assertIsInstance(trace, tuple)
+ size, traceback = trace
+ self.assertEqual(size, obj_size)
+ self.assertEqual(traceback, obj_traceback._frames)
+
+ tracemalloc.stop()
+ self.assertEqual(tracemalloc._get_traces(), [])
+
+
+ def test_get_traces_intern_traceback(self):
+ # dummy wrappers to get more useful and identical frames in the traceback
+ def allocate_bytes2(size):
+ return allocate_bytes(size)
+ def allocate_bytes3(size):
+ return allocate_bytes2(size)
+ def allocate_bytes4(size):
+ return allocate_bytes3(size)
+
+ # Ensure that two identical tracebacks are not duplicated
+ tracemalloc.stop()
+ tracemalloc.start(4)
+ obj_size = 123
+ obj1, obj1_traceback = allocate_bytes4(obj_size)
+ obj2, obj2_traceback = allocate_bytes4(obj_size)
+
+ traces = tracemalloc._get_traces()
+
+ trace1 = self.find_trace(traces, obj1_traceback)
+ trace2 = self.find_trace(traces, obj2_traceback)
+ size1, traceback1 = trace1
+ size2, traceback2 = trace2
+ self.assertEqual(traceback2, traceback1)
+ self.assertIs(traceback2, traceback1)
+
+ def test_get_traced_memory(self):
+ # Python allocates some internals objects, so the test must tolerate
+ # a small difference between the expected size and the real usage
+ max_error = 2048
+
+ # allocate one object
+ obj_size = 1024 * 1024
+ tracemalloc.clear_traces()
+ obj, obj_traceback = allocate_bytes(obj_size)
+ size, peak_size = tracemalloc.get_traced_memory()
+ self.assertGreaterEqual(size, obj_size)
+ self.assertGreaterEqual(peak_size, size)
+
+ self.assertLessEqual(size - obj_size, max_error)
+ self.assertLessEqual(peak_size - size, max_error)
+
+ # destroy the object
+ obj = None
+ size2, peak_size2 = tracemalloc.get_traced_memory()
+ self.assertLess(size2, size)
+ self.assertGreaterEqual(size - size2, obj_size - max_error)
+ self.assertGreaterEqual(peak_size2, peak_size)
+
+ # clear_traces() must reset traced memory counters
+ tracemalloc.clear_traces()
+ self.assertEqual(tracemalloc.get_traced_memory(), (0, 0))
+
+ # allocate another object
+ obj, obj_traceback = allocate_bytes(obj_size)
+ size, peak_size = tracemalloc.get_traced_memory()
+ self.assertGreaterEqual(size, obj_size)
+
+ # stop() also resets traced memory counters
+ tracemalloc.stop()
+ self.assertEqual(tracemalloc.get_traced_memory(), (0, 0))
+
+ def test_clear_traces(self):
+ obj, obj_traceback = allocate_bytes(123)
+ traceback = tracemalloc.get_object_traceback(obj)
+ self.assertIsNotNone(traceback)
+
+ tracemalloc.clear_traces()
+ traceback2 = tracemalloc.get_object_traceback(obj)
+ self.assertIsNone(traceback2)
+
+ def test_is_tracing(self):
+ tracemalloc.stop()
+ self.assertFalse(tracemalloc.is_tracing())
+
+ tracemalloc.start()
+ self.assertTrue(tracemalloc.is_tracing())
+
+ def test_snapshot(self):
+ obj, source = allocate_bytes(123)
+
+ # take a snapshot
+ snapshot = tracemalloc.take_snapshot()
+
+ # write on disk
+ snapshot.dump(support.TESTFN)
+ self.addCleanup(support.unlink, support.TESTFN)
+
+ # load from disk
+ snapshot2 = tracemalloc.Snapshot.load(support.TESTFN)
+ self.assertEqual(snapshot2.traces, snapshot.traces)
+
+ # tracemalloc must be tracing memory allocations to take a snapshot
+ tracemalloc.stop()
+ with self.assertRaises(RuntimeError) as cm:
+ tracemalloc.take_snapshot()
+ self.assertEqual(str(cm.exception),
+ "the tracemalloc module must be tracing memory "
+ "allocations to take a snapshot")
+
+ def test_snapshot_save_attr(self):
+ # take a snapshot with a new attribute
+ snapshot = tracemalloc.take_snapshot()
+ snapshot.test_attr = "new"
+ snapshot.dump(support.TESTFN)
+ self.addCleanup(support.unlink, support.TESTFN)
+
+ # load() should recreates the attribute
+ snapshot2 = tracemalloc.Snapshot.load(support.TESTFN)
+ self.assertEqual(snapshot2.test_attr, "new")
+
+ def fork_child(self):
+ if not tracemalloc.is_tracing():
+ return 2
+
+ obj_size = 12345
+ obj, obj_traceback = allocate_bytes(obj_size)
+ traceback = tracemalloc.get_object_traceback(obj)
+ if traceback is None:
+ return 3
+
+ # everything is fine
+ return 0
+
+ @unittest.skipUnless(hasattr(os, 'fork'), 'need os.fork()')
+ def test_fork(self):
+ # check that tracemalloc is still working after fork
+ pid = os.fork()
+ if not pid:
+ # child
+ exitcode = 1
+ try:
+ exitcode = self.fork_child()
+ finally:
+ os._exit(exitcode)
+ else:
+ pid2, status = os.waitpid(pid, 0)
+ self.assertTrue(os.WIFEXITED(status))
+ exitcode = os.WEXITSTATUS(status)
+ self.assertEqual(exitcode, 0)
+
+
+class TestSnapshot(unittest.TestCase):
+ maxDiff = 4000
+
+ def test_create_snapshot(self):
+ raw_traces = [(5, (('a.py', 2),))]
+
+ with contextlib.ExitStack() as stack:
+ stack.enter_context(patch.object(tracemalloc, 'is_tracing',
+ return_value=True))
+ stack.enter_context(patch.object(tracemalloc, 'get_traceback_limit',
+ return_value=5))
+ stack.enter_context(patch.object(tracemalloc, '_get_traces',
+ return_value=raw_traces))
+
+ snapshot = tracemalloc.take_snapshot()
+ self.assertEqual(snapshot.traceback_limit, 5)
+ self.assertEqual(len(snapshot.traces), 1)
+ trace = snapshot.traces[0]
+ self.assertEqual(trace.size, 5)
+ self.assertEqual(len(trace.traceback), 1)
+ self.assertEqual(trace.traceback[0].filename, 'a.py')
+ self.assertEqual(trace.traceback[0].lineno, 2)
+
+ def test_filter_traces(self):
+ snapshot, snapshot2 = create_snapshots()
+ filter1 = tracemalloc.Filter(False, "b.py")
+ filter2 = tracemalloc.Filter(True, "a.py", 2)
+ filter3 = tracemalloc.Filter(True, "a.py", 5)
+
+ original_traces = list(snapshot.traces._traces)
+
+ # exclude b.py
+ snapshot3 = snapshot.filter_traces((filter1,))
+ self.assertEqual(snapshot3.traces._traces, [
+ (10, (('a.py', 2), ('b.py', 4))),
+ (10, (('a.py', 2), ('b.py', 4))),
+ (10, (('a.py', 2), ('b.py', 4))),
+ (2, (('a.py', 5), ('b.py', 4))),
+ (7, (('<unknown>', 0),)),
+ ])
+
+ # filter_traces() must not touch the original snapshot
+ self.assertEqual(snapshot.traces._traces, original_traces)
+
+ # only include two lines of a.py
+ snapshot4 = snapshot3.filter_traces((filter2, filter3))
+ self.assertEqual(snapshot4.traces._traces, [
+ (10, (('a.py', 2), ('b.py', 4))),
+ (10, (('a.py', 2), ('b.py', 4))),
+ (10, (('a.py', 2), ('b.py', 4))),
+ (2, (('a.py', 5), ('b.py', 4))),
+ ])
+
+ # No filter: just duplicate the snapshot
+ snapshot5 = snapshot.filter_traces(())
+ self.assertIsNot(snapshot5, snapshot)
+ self.assertIsNot(snapshot5.traces, snapshot.traces)
+ self.assertEqual(snapshot5.traces, snapshot.traces)
+
+ def test_snapshot_group_by_line(self):
+ snapshot, snapshot2 = create_snapshots()
+ tb_0 = traceback_lineno('<unknown>', 0)
+ tb_a_2 = traceback_lineno('a.py', 2)
+ tb_a_5 = traceback_lineno('a.py', 5)
+ tb_b_1 = traceback_lineno('b.py', 1)
+ tb_c_578 = traceback_lineno('c.py', 578)
+
+ # stats per file and line
+ stats1 = snapshot.statistics('lineno')
+ self.assertEqual(stats1, [
+ tracemalloc.Statistic(tb_b_1, 66, 1),
+ tracemalloc.Statistic(tb_a_2, 30, 3),
+ tracemalloc.Statistic(tb_0, 7, 1),
+ tracemalloc.Statistic(tb_a_5, 2, 1),
+ ])
+
+ # stats per file and line (2)
+ stats2 = snapshot2.statistics('lineno')
+ self.assertEqual(stats2, [
+ tracemalloc.Statistic(tb_a_5, 5002, 2),
+ tracemalloc.Statistic(tb_c_578, 400, 1),
+ tracemalloc.Statistic(tb_a_2, 30, 3),
+ ])
+
+ # stats diff per file and line
+ statistics = snapshot2.compare_to(snapshot, 'lineno')
+ self.assertEqual(statistics, [
+ tracemalloc.StatisticDiff(tb_a_5, 5002, 5000, 2, 1),
+ tracemalloc.StatisticDiff(tb_c_578, 400, 400, 1, 1),
+ tracemalloc.StatisticDiff(tb_b_1, 0, -66, 0, -1),
+ tracemalloc.StatisticDiff(tb_0, 0, -7, 0, -1),
+ tracemalloc.StatisticDiff(tb_a_2, 30, 0, 3, 0),
+ ])
+
+ def test_snapshot_group_by_file(self):
+ snapshot, snapshot2 = create_snapshots()
+ tb_0 = traceback_filename('<unknown>')
+ tb_a = traceback_filename('a.py')
+ tb_b = traceback_filename('b.py')
+ tb_c = traceback_filename('c.py')
+
+ # stats per file
+ stats1 = snapshot.statistics('filename')
+ self.assertEqual(stats1, [
+ tracemalloc.Statistic(tb_b, 66, 1),
+ tracemalloc.Statistic(tb_a, 32, 4),
+ tracemalloc.Statistic(tb_0, 7, 1),
+ ])
+
+ # stats per file (2)
+ stats2 = snapshot2.statistics('filename')
+ self.assertEqual(stats2, [
+ tracemalloc.Statistic(tb_a, 5032, 5),
+ tracemalloc.Statistic(tb_c, 400, 1),
+ ])
+
+ # stats diff per file
+ diff = snapshot2.compare_to(snapshot, 'filename')
+ self.assertEqual(diff, [
+ tracemalloc.StatisticDiff(tb_a, 5032, 5000, 5, 1),
+ tracemalloc.StatisticDiff(tb_c, 400, 400, 1, 1),
+ tracemalloc.StatisticDiff(tb_b, 0, -66, 0, -1),
+ tracemalloc.StatisticDiff(tb_0, 0, -7, 0, -1),
+ ])
+
+ def test_snapshot_group_by_traceback(self):
+ snapshot, snapshot2 = create_snapshots()
+
+ # stats per file
+ tb1 = traceback(('a.py', 2), ('b.py', 4))
+ tb2 = traceback(('a.py', 5), ('b.py', 4))
+ tb3 = traceback(('b.py', 1))
+ tb4 = traceback(('<unknown>', 0))
+ stats1 = snapshot.statistics('traceback')
+ self.assertEqual(stats1, [
+ tracemalloc.Statistic(tb3, 66, 1),
+ tracemalloc.Statistic(tb1, 30, 3),
+ tracemalloc.Statistic(tb4, 7, 1),
+ tracemalloc.Statistic(tb2, 2, 1),
+ ])
+
+ # stats per file (2)
+ tb5 = traceback(('c.py', 578))
+ stats2 = snapshot2.statistics('traceback')
+ self.assertEqual(stats2, [
+ tracemalloc.Statistic(tb2, 5002, 2),
+ tracemalloc.Statistic(tb5, 400, 1),
+ tracemalloc.Statistic(tb1, 30, 3),
+ ])
+
+ # stats diff per file
+ diff = snapshot2.compare_to(snapshot, 'traceback')
+ self.assertEqual(diff, [
+ tracemalloc.StatisticDiff(tb2, 5002, 5000, 2, 1),
+ tracemalloc.StatisticDiff(tb5, 400, 400, 1, 1),
+ tracemalloc.StatisticDiff(tb3, 0, -66, 0, -1),
+ tracemalloc.StatisticDiff(tb4, 0, -7, 0, -1),
+ tracemalloc.StatisticDiff(tb1, 30, 0, 3, 0),
+ ])
+
+ self.assertRaises(ValueError,
+ snapshot.statistics, 'traceback', cumulative=True)
+
+ def test_snapshot_group_by_cumulative(self):
+ snapshot, snapshot2 = create_snapshots()
+ tb_0 = traceback_filename('<unknown>')
+ tb_a = traceback_filename('a.py')
+ tb_b = traceback_filename('b.py')
+ tb_a_2 = traceback_lineno('a.py', 2)
+ tb_a_5 = traceback_lineno('a.py', 5)
+ tb_b_1 = traceback_lineno('b.py', 1)
+ tb_b_4 = traceback_lineno('b.py', 4)
+
+ # per file
+ stats = snapshot.statistics('filename', True)
+ self.assertEqual(stats, [
+ tracemalloc.Statistic(tb_b, 98, 5),
+ tracemalloc.Statistic(tb_a, 32, 4),
+ tracemalloc.Statistic(tb_0, 7, 1),
+ ])
+
+ # per line
+ stats = snapshot.statistics('lineno', True)
+ self.assertEqual(stats, [
+ tracemalloc.Statistic(tb_b_1, 66, 1),
+ tracemalloc.Statistic(tb_b_4, 32, 4),
+ tracemalloc.Statistic(tb_a_2, 30, 3),
+ tracemalloc.Statistic(tb_0, 7, 1),
+ tracemalloc.Statistic(tb_a_5, 2, 1),
+ ])
+
+ def test_trace_format(self):
+ snapshot, snapshot2 = create_snapshots()
+ trace = snapshot.traces[0]
+ self.assertEqual(str(trace), 'a.py:2: 10 B')
+ traceback = trace.traceback
+ self.assertEqual(str(traceback), 'a.py:2')
+ frame = traceback[0]
+ self.assertEqual(str(frame), 'a.py:2')
+
+ def test_statistic_format(self):
+ snapshot, snapshot2 = create_snapshots()
+ stats = snapshot.statistics('lineno')
+ stat = stats[0]
+ self.assertEqual(str(stat),
+ 'b.py:1: size=66 B, count=1, average=66 B')
+
+ def test_statistic_diff_format(self):
+ snapshot, snapshot2 = create_snapshots()
+ stats = snapshot2.compare_to(snapshot, 'lineno')
+ stat = stats[0]
+ self.assertEqual(str(stat),
+ 'a.py:5: size=5002 B (+5000 B), count=2 (+1), average=2501 B')
+
+
+
+class TestFilters(unittest.TestCase):
+ maxDiff = 2048
+
+ def test_filter_attributes(self):
+ # test default values
+ f = tracemalloc.Filter(True, "abc")
+ self.assertEqual(f.inclusive, True)
+ self.assertEqual(f.filename_pattern, "abc")
+ self.assertIsNone(f.lineno)
+ self.assertEqual(f.all_frames, False)
+
+ # test custom values
+ f = tracemalloc.Filter(False, "test.py", 123, True)
+ self.assertEqual(f.inclusive, False)
+ self.assertEqual(f.filename_pattern, "test.py")
+ self.assertEqual(f.lineno, 123)
+ self.assertEqual(f.all_frames, True)
+
+ # parameters passed by keyword
+ f = tracemalloc.Filter(inclusive=False, filename_pattern="test.py", lineno=123, all_frames=True)
+ self.assertEqual(f.inclusive, False)
+ self.assertEqual(f.filename_pattern, "test.py")
+ self.assertEqual(f.lineno, 123)
+ self.assertEqual(f.all_frames, True)
+
+ # read-only attribute
+ self.assertRaises(AttributeError, setattr, f, "filename_pattern", "abc")
+
+ def test_filter_match(self):
+ # filter without line number
+ f = tracemalloc.Filter(True, "abc")
+ self.assertTrue(f._match_frame("abc", 0))
+ self.assertTrue(f._match_frame("abc", 5))
+ self.assertTrue(f._match_frame("abc", 10))
+ self.assertFalse(f._match_frame("12356", 0))
+ self.assertFalse(f._match_frame("12356", 5))
+ self.assertFalse(f._match_frame("12356", 10))
+
+ f = tracemalloc.Filter(False, "abc")
+ self.assertFalse(f._match_frame("abc", 0))
+ self.assertFalse(f._match_frame("abc", 5))
+ self.assertFalse(f._match_frame("abc", 10))
+ self.assertTrue(f._match_frame("12356", 0))
+ self.assertTrue(f._match_frame("12356", 5))
+ self.assertTrue(f._match_frame("12356", 10))
+
+ # filter with line number > 0
+ f = tracemalloc.Filter(True, "abc", 5)
+ self.assertFalse(f._match_frame("abc", 0))
+ self.assertTrue(f._match_frame("abc", 5))
+ self.assertFalse(f._match_frame("abc", 10))
+ self.assertFalse(f._match_frame("12356", 0))
+ self.assertFalse(f._match_frame("12356", 5))
+ self.assertFalse(f._match_frame("12356", 10))
+
+ f = tracemalloc.Filter(False, "abc", 5)
+ self.assertTrue(f._match_frame("abc", 0))
+ self.assertFalse(f._match_frame("abc", 5))
+ self.assertTrue(f._match_frame("abc", 10))
+ self.assertTrue(f._match_frame("12356", 0))
+ self.assertTrue(f._match_frame("12356", 5))
+ self.assertTrue(f._match_frame("12356", 10))
+
+ # filter with line number 0
+ f = tracemalloc.Filter(True, "abc", 0)
+ self.assertTrue(f._match_frame("abc", 0))
+ self.assertFalse(f._match_frame("abc", 5))
+ self.assertFalse(f._match_frame("abc", 10))
+ self.assertFalse(f._match_frame("12356", 0))
+ self.assertFalse(f._match_frame("12356", 5))
+ self.assertFalse(f._match_frame("12356", 10))
+
+ f = tracemalloc.Filter(False, "abc", 0)
+ self.assertFalse(f._match_frame("abc", 0))
+ self.assertTrue(f._match_frame("abc", 5))
+ self.assertTrue(f._match_frame("abc", 10))
+ self.assertTrue(f._match_frame("12356", 0))
+ self.assertTrue(f._match_frame("12356", 5))
+ self.assertTrue(f._match_frame("12356", 10))
+
+ def test_filter_match_filename(self):
+ def fnmatch(inclusive, filename, pattern):
+ f = tracemalloc.Filter(inclusive, pattern)
+ return f._match_frame(filename, 0)
+
+ self.assertTrue(fnmatch(True, "abc", "abc"))
+ self.assertFalse(fnmatch(True, "12356", "abc"))
+ self.assertFalse(fnmatch(True, "<unknown>", "abc"))
+
+ self.assertFalse(fnmatch(False, "abc", "abc"))
+ self.assertTrue(fnmatch(False, "12356", "abc"))
+ self.assertTrue(fnmatch(False, "<unknown>", "abc"))
+
+ def test_filter_match_filename_joker(self):
+ def fnmatch(filename, pattern):
+ filter = tracemalloc.Filter(True, pattern)
+ return filter._match_frame(filename, 0)
+
+ # empty string
+ self.assertFalse(fnmatch('abc', ''))
+ self.assertFalse(fnmatch('', 'abc'))
+ self.assertTrue(fnmatch('', ''))
+ self.assertTrue(fnmatch('', '*'))
+
+ # no *
+ self.assertTrue(fnmatch('abc', 'abc'))
+ self.assertFalse(fnmatch('abc', 'abcd'))
+ self.assertFalse(fnmatch('abc', 'def'))
+
+ # a*
+ self.assertTrue(fnmatch('abc', 'a*'))
+ self.assertTrue(fnmatch('abc', 'abc*'))
+ self.assertFalse(fnmatch('abc', 'b*'))
+ self.assertFalse(fnmatch('abc', 'abcd*'))
+
+ # a*b
+ self.assertTrue(fnmatch('abc', 'a*c'))
+ self.assertTrue(fnmatch('abcdcx', 'a*cx'))
+ self.assertFalse(fnmatch('abb', 'a*c'))
+ self.assertFalse(fnmatch('abcdce', 'a*cx'))
+
+ # a*b*c
+ self.assertTrue(fnmatch('abcde', 'a*c*e'))
+ self.assertTrue(fnmatch('abcbdefeg', 'a*bd*eg'))
+ self.assertFalse(fnmatch('abcdd', 'a*c*e'))
+ self.assertFalse(fnmatch('abcbdefef', 'a*bd*eg'))
+
+ # replace .pyc and .pyo suffix with .py
+ self.assertTrue(fnmatch('a.pyc', 'a.py'))
+ self.assertTrue(fnmatch('a.pyo', 'a.py'))
+ self.assertTrue(fnmatch('a.py', 'a.pyc'))
+ self.assertTrue(fnmatch('a.py', 'a.pyo'))
+
+ if os.name == 'nt':
+ # case insensitive
+ self.assertTrue(fnmatch('aBC', 'ABc'))
+ self.assertTrue(fnmatch('aBcDe', 'Ab*dE'))
+
+ self.assertTrue(fnmatch('a.pyc', 'a.PY'))
+ self.assertTrue(fnmatch('a.PYO', 'a.py'))
+ self.assertTrue(fnmatch('a.py', 'a.PYC'))
+ self.assertTrue(fnmatch('a.PY', 'a.pyo'))
+ else:
+ # case sensitive
+ self.assertFalse(fnmatch('aBC', 'ABc'))
+ self.assertFalse(fnmatch('aBcDe', 'Ab*dE'))
+
+ self.assertFalse(fnmatch('a.pyc', 'a.PY'))
+ self.assertFalse(fnmatch('a.PYO', 'a.py'))
+ self.assertFalse(fnmatch('a.py', 'a.PYC'))
+ self.assertFalse(fnmatch('a.PY', 'a.pyo'))
+
+ if os.name == 'nt':
+ # normalize alternate separator "/" to the standard separator "\"
+ self.assertTrue(fnmatch(r'a/b', r'a\b'))
+ self.assertTrue(fnmatch(r'a\b', r'a/b'))
+ self.assertTrue(fnmatch(r'a/b\c', r'a\b/c'))
+ self.assertTrue(fnmatch(r'a/b/c', r'a\b\c'))
+ else:
+ # there is no alternate separator
+ self.assertFalse(fnmatch(r'a/b', r'a\b'))
+ self.assertFalse(fnmatch(r'a\b', r'a/b'))
+ self.assertFalse(fnmatch(r'a/b\c', r'a\b/c'))
+ self.assertFalse(fnmatch(r'a/b/c', r'a\b\c'))
+
+ def test_filter_match_trace(self):
+ t1 = (("a.py", 2), ("b.py", 3))
+ t2 = (("b.py", 4), ("b.py", 5))
+ t3 = (("c.py", 5), ('<unknown>', 0))
+ unknown = (('<unknown>', 0),)
+
+ f = tracemalloc.Filter(True, "b.py", all_frames=True)
+ self.assertTrue(f._match_traceback(t1))
+ self.assertTrue(f._match_traceback(t2))
+ self.assertFalse(f._match_traceback(t3))
+ self.assertFalse(f._match_traceback(unknown))
+
+ f = tracemalloc.Filter(True, "b.py", all_frames=False)
+ self.assertFalse(f._match_traceback(t1))
+ self.assertTrue(f._match_traceback(t2))
+ self.assertFalse(f._match_traceback(t3))
+ self.assertFalse(f._match_traceback(unknown))
+
+ f = tracemalloc.Filter(False, "b.py", all_frames=True)
+ self.assertFalse(f._match_traceback(t1))
+ self.assertFalse(f._match_traceback(t2))
+ self.assertTrue(f._match_traceback(t3))
+ self.assertTrue(f._match_traceback(unknown))
+
+ f = tracemalloc.Filter(False, "b.py", all_frames=False)
+ self.assertTrue(f._match_traceback(t1))
+ self.assertFalse(f._match_traceback(t2))
+ self.assertTrue(f._match_traceback(t3))
+ self.assertTrue(f._match_traceback(unknown))
+
+ f = tracemalloc.Filter(False, "<unknown>", all_frames=False)
+ self.assertTrue(f._match_traceback(t1))
+ self.assertTrue(f._match_traceback(t2))
+ self.assertTrue(f._match_traceback(t3))
+ self.assertFalse(f._match_traceback(unknown))
+
+ f = tracemalloc.Filter(True, "<unknown>", all_frames=True)
+ self.assertFalse(f._match_traceback(t1))
+ self.assertFalse(f._match_traceback(t2))
+ self.assertTrue(f._match_traceback(t3))
+ self.assertTrue(f._match_traceback(unknown))
+
+ f = tracemalloc.Filter(False, "<unknown>", all_frames=True)
+ self.assertTrue(f._match_traceback(t1))
+ self.assertTrue(f._match_traceback(t2))
+ self.assertFalse(f._match_traceback(t3))
+ self.assertFalse(f._match_traceback(unknown))
+
+
+class TestCommandLine(unittest.TestCase):
+ def test_env_var(self):
+ # not tracing by default
+ code = 'import tracemalloc; print(tracemalloc.is_tracing())'
+ ok, stdout, stderr = assert_python_ok('-c', code)
+ stdout = stdout.rstrip()
+ self.assertEqual(stdout, b'False')
+
+ # PYTHON* environment variables must be ignored when -E option is
+ # present
+ code = 'import tracemalloc; print(tracemalloc.is_tracing())'
+ ok, stdout, stderr = assert_python_ok('-E', '-c', code, PYTHONTRACEMALLOC='1')
+ stdout = stdout.rstrip()
+ self.assertEqual(stdout, b'False')
+
+ # tracing at startup
+ code = 'import tracemalloc; print(tracemalloc.is_tracing())'
+ ok, stdout, stderr = assert_python_ok('-c', code, PYTHONTRACEMALLOC='1')
+ stdout = stdout.rstrip()
+ self.assertEqual(stdout, b'True')
+
+ # start and set the number of frames
+ code = 'import tracemalloc; print(tracemalloc.get_traceback_limit())'
+ ok, stdout, stderr = assert_python_ok('-c', code, PYTHONTRACEMALLOC='10')
+ stdout = stdout.rstrip()
+ self.assertEqual(stdout, b'10')
+
+ def test_env_var_invalid(self):
+ for nframe in (-1, 0, 2**30):
+ with self.subTest(nframe=nframe):
+ with support.SuppressCrashReport():
+ ok, stdout, stderr = assert_python_failure(
+ '-c', 'pass',
+ PYTHONTRACEMALLOC=str(nframe))
+ self.assertIn(b'PYTHONTRACEMALLOC: invalid '
+ b'number of frames',
+ stderr)
+
+ def test_sys_xoptions(self):
+ for xoptions, nframe in (
+ ('tracemalloc', 1),
+ ('tracemalloc=1', 1),
+ ('tracemalloc=15', 15),
+ ):
+ with self.subTest(xoptions=xoptions, nframe=nframe):
+ code = 'import tracemalloc; print(tracemalloc.get_traceback_limit())'
+ ok, stdout, stderr = assert_python_ok('-X', xoptions, '-c', code)
+ stdout = stdout.rstrip()
+ self.assertEqual(stdout, str(nframe).encode('ascii'))
+
+ def test_sys_xoptions_invalid(self):
+ for nframe in (-1, 0, 2**30):
+ with self.subTest(nframe=nframe):
+ with support.SuppressCrashReport():
+ args = ('-X', 'tracemalloc=%s' % nframe, '-c', 'pass')
+ ok, stdout, stderr = assert_python_failure(*args)
+ self.assertIn(b'-X tracemalloc=NFRAME: invalid '
+ b'number of frames',
+ stderr)
+
+
+def test_main():
+ support.run_unittest(
+ TestTracemallocEnabled,
+ TestSnapshot,
+ TestFilters,
+ TestCommandLine,
+ )
+
+if __name__ == "__main__":
+ test_main()
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index 3ee4c6bc5f..ec10752e6a 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -2,6 +2,7 @@
from test.support import run_unittest, run_with_locale
import collections
+import pickle
import locale
import sys
import types
@@ -1077,9 +1078,19 @@ class SimpleNamespaceTests(unittest.TestCase):
ns2 = types.SimpleNamespace()
ns2.x = "spam"
ns2._y = 5
+ name = "namespace"
- self.assertEqual(repr(ns1), "namespace(w=3, x=1, y=2)")
- self.assertEqual(repr(ns2), "namespace(_y=5, x='spam')")
+ self.assertEqual(repr(ns1), "{name}(w=3, x=1, y=2)".format(name=name))
+ self.assertEqual(repr(ns2), "{name}(_y=5, x='spam')".format(name=name))
+
+ def test_equal(self):
+ ns1 = types.SimpleNamespace(x=1)
+ ns2 = types.SimpleNamespace()
+ ns2.x = 1
+
+ self.assertEqual(types.SimpleNamespace(), types.SimpleNamespace())
+ self.assertEqual(ns1, ns2)
+ self.assertNotEqual(ns2, types.SimpleNamespace())
def test_nested(self):
ns1 = types.SimpleNamespace(a=1, b=2)
@@ -1117,11 +1128,12 @@ class SimpleNamespaceTests(unittest.TestCase):
ns1.spam = ns1
ns2.spam = ns3
ns3.spam = ns2
+ name = "namespace"
+ repr1 = "{name}(c='cookie', spam={name}(...))".format(name=name)
+ repr2 = "{name}(spam={name}(spam={name}(...), x=1))".format(name=name)
- self.assertEqual(repr(ns1),
- "namespace(c='cookie', spam=namespace(...))")
- self.assertEqual(repr(ns2),
- "namespace(spam=namespace(spam=namespace(...), x=1))")
+ self.assertEqual(repr(ns1), repr1)
+ self.assertEqual(repr(ns2), repr2)
def test_as_dict(self):
ns = types.SimpleNamespace(spam='spamspamspam')
@@ -1144,6 +1156,19 @@ class SimpleNamespaceTests(unittest.TestCase):
self.assertIs(type(spam), Spam)
self.assertEqual(vars(spam), {'ham': 8, 'eggs': 9})
+ def test_pickle(self):
+ ns = types.SimpleNamespace(breakfast="spam", lunch="spam")
+
+ for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
+ pname = "protocol {}".format(protocol)
+ try:
+ ns_pickled = pickle.dumps(ns, protocol)
+ except TypeError as e:
+ raise TypeError(pname) from e
+ ns_roundtrip = pickle.loads(ns_pickled)
+
+ self.assertEqual(ns, ns_roundtrip, pname)
+
def test_main():
run_unittest(TypesTests, MappingProxyTests, ClassCreationTests,
diff --git a/Lib/test/test_ucn.py b/Lib/test/test_ucn.py
index 2e6374561f..59bde7475a 100644
--- a/Lib/test/test_ucn.py
+++ b/Lib/test/test_ucn.py
@@ -173,7 +173,7 @@ class UnicodeNamesTest(unittest.TestCase):
try:
testdata = support.open_urlresource(url, encoding="utf-8",
check=check_version)
- except (IOError, HTTPException):
+ except (OSError, HTTPException):
self.skipTest("Could not retrieve " + url)
self.addCleanup(testdata.close)
for line in testdata:
diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py
index 502d39368d..575c4a5850 100644
--- a/Lib/test/test_unicode.py
+++ b/Lib/test/test_unicode.py
@@ -7,6 +7,7 @@ Written by Marc-Andre Lemburg (mal@lemburg.com).
"""#"
import _string
import codecs
+import itertools
import struct
import sys
import unittest
@@ -31,6 +32,16 @@ def search_function(encoding):
return None
codecs.register(search_function)
+def duplicate_string(text):
+ """
+ Try to get a fresh clone of the specified text:
+ new object with a reference count of 1.
+
+ This is a best-effort: latin1 single letters and the empty
+ string ('') are singletons and cannot be cloned.
+ """
+ return text.encode().decode()
+
class UnicodeTest(string_tests.CommonTest,
string_tests.MixinStrUnicodeUserStringTest,
string_tests.MixinStrUnicodeTest,
@@ -863,11 +874,9 @@ class UnicodeTest(string_tests.CommonTest,
self.assertEqual('{0:d}'.format(G('data')), 'G(data)')
self.assertEqual('{0!s}'.format(G('data')), 'string is data')
- msg = 'object.__format__ with a non-empty format string is deprecated'
- with support.check_warnings((msg, DeprecationWarning)):
- self.assertEqual('{0:^10}'.format(E('data')), ' E(data) ')
- self.assertEqual('{0:^10s}'.format(E('data')), ' E(data) ')
- self.assertEqual('{0:>15s}'.format(G('data')), ' string is data')
+ self.assertRaises(TypeError, '{0:^10}'.format, E('data'))
+ self.assertRaises(TypeError, '{0:^10s}'.format, E('data'))
+ self.assertRaises(TypeError, '{0:>15s}'.format, G('data'))
self.assertEqual("{0:date: %Y-%m-%d}".format(I(year=2007,
month=8,
@@ -903,7 +912,7 @@ class UnicodeTest(string_tests.CommonTest,
self.assertRaises(ValueError, "{0".format)
self.assertRaises(IndexError, "{0.}".format)
self.assertRaises(ValueError, "{0.}".format, 0)
- self.assertRaises(IndexError, "{0[}".format)
+ self.assertRaises(ValueError, "{0[}".format)
self.assertRaises(ValueError, "{0[}".format, [])
self.assertRaises(KeyError, "{0]}".format)
self.assertRaises(ValueError, "{0.[]}".format, 0)
@@ -955,6 +964,15 @@ class UnicodeTest(string_tests.CommonTest,
'')
self.assertEqual("{[{}]}".format({"{}": 5}), "5")
+ self.assertEqual("{[{}]}".format({"{}" : "a"}), "a")
+ self.assertEqual("{[{]}".format({"{" : "a"}), "a")
+ self.assertEqual("{[}]}".format({"}" : "a"}), "a")
+ self.assertEqual("{[[]}".format({"[" : "a"}), "a")
+ self.assertEqual("{[!]}".format({"!" : "a"}), "a")
+ self.assertRaises(ValueError, "{a{}b}".format, 42)
+ self.assertRaises(ValueError, "{a{b}".format, 42)
+ self.assertRaises(ValueError, "{[}".format, 42)
+
self.assertEqual("0x{:0{:d}X}".format(0x0,16), "0x0000000000000000")
def test_format_map(self):
@@ -1108,6 +1126,38 @@ class UnicodeTest(string_tests.CommonTest,
self.assertEqual('%.1s' % "a\xe9\u20ac", 'a')
self.assertEqual('%.2s' % "a\xe9\u20ac", 'a\xe9')
+ def test_formatting_with_enum(self):
+ # issue18780
+ import enum
+ class Float(float, enum.Enum):
+ PI = 3.1415926
+ class Int(enum.IntEnum):
+ IDES = 15
+ class Str(str, enum.Enum):
+ ABC = 'abc'
+ # Testing Unicode formatting strings...
+ self.assertEqual("%s, %s" % (Str.ABC, Str.ABC),
+ 'Str.ABC, Str.ABC')
+ self.assertEqual("%s, %s, %d, %i, %u, %f, %5.2f" %
+ (Str.ABC, Str.ABC,
+ Int.IDES, Int.IDES, Int.IDES,
+ Float.PI, Float.PI),
+ 'Str.ABC, Str.ABC, 15, 15, 15, 3.141593, 3.14')
+
+ # formatting jobs delegated from the string implementation:
+ self.assertEqual('...%(foo)s...' % {'foo':Str.ABC},
+ '...Str.ABC...')
+ self.assertEqual('...%(foo)s...' % {'foo':Int.IDES},
+ '...Int.IDES...')
+ self.assertEqual('...%(foo)i...' % {'foo':Int.IDES},
+ '...15...')
+ self.assertEqual('...%(foo)d...' % {'foo':Int.IDES},
+ '...15...')
+ self.assertEqual('...%(foo)u...' % {'foo':Int.IDES, 'def':Float.PI},
+ '...15...')
+ self.assertEqual('...%(foo)f...' % {'foo':Float.PI,'def':123},
+ '...3.141593...')
+
@support.cpython_only
def test_formatting_huge_precision(self):
from _testcapi import INT_MAX
@@ -1783,10 +1833,10 @@ class UnicodeTest(string_tests.CommonTest,
# 0-127
s = bytes(range(128))
for encoding in (
- 'cp037', 'cp1026',
+ 'cp037', 'cp1026', 'cp273',
'cp437', 'cp500', 'cp720', 'cp737', 'cp775', 'cp850',
'cp852', 'cp855', 'cp858', 'cp860', 'cp861', 'cp862',
- 'cp863', 'cp865', 'cp866',
+ 'cp863', 'cp865', 'cp866', 'cp1125',
'iso8859_10', 'iso8859_13', 'iso8859_14', 'iso8859_15',
'iso8859_2', 'iso8859_3', 'iso8859_4', 'iso8859_5', 'iso8859_6',
'iso8859_7', 'iso8859_9', 'koi8_r', 'latin_1',
@@ -1811,10 +1861,10 @@ class UnicodeTest(string_tests.CommonTest,
# 128-255
s = bytes(range(128, 256))
for encoding in (
- 'cp037', 'cp1026',
+ 'cp037', 'cp1026', 'cp273',
'cp437', 'cp500', 'cp720', 'cp737', 'cp775', 'cp850',
'cp852', 'cp855', 'cp858', 'cp860', 'cp861', 'cp862',
- 'cp863', 'cp865', 'cp866',
+ 'cp863', 'cp865', 'cp866', 'cp1125',
'iso8859_10', 'iso8859_13', 'iso8859_14', 'iso8859_15',
'iso8859_2', 'iso8859_4', 'iso8859_5',
'iso8859_9', 'koi8_r', 'latin_1',
@@ -2005,9 +2055,10 @@ class UnicodeTest(string_tests.CommonTest,
# Test PyUnicode_FromFormat()
def test_from_format(self):
support.import_module('ctypes')
- from ctypes import (pythonapi, py_object,
+ from ctypes import (
+ pythonapi, py_object, sizeof,
c_int, c_long, c_longlong, c_ssize_t,
- c_uint, c_ulong, c_ulonglong, c_size_t)
+ c_uint, c_ulong, c_ulonglong, c_size_t, c_void_p)
name = "PyUnicode_FromFormat"
_PyUnicode_FromFormat = getattr(pythonapi, name)
_PyUnicode_FromFormat.restype = py_object
@@ -2018,9 +2069,13 @@ class UnicodeTest(string_tests.CommonTest,
for arg in args)
return _PyUnicode_FromFormat(format, *cargs)
+ def check_format(expected, format, *args):
+ text = PyUnicode_FromFormat(format, *args)
+ self.assertEqual(expected, text)
+
# ascii format, non-ascii argument
- text = PyUnicode_FromFormat(b'ascii\x7f=%U', 'unicode\xe9')
- self.assertEqual(text, 'ascii\x7f=unicode\xe9')
+ check_format('ascii\x7f=unicode\xe9',
+ b'ascii\x7f=%U', 'unicode\xe9')
# non-ascii format, ascii argument: ensure that PyUnicode_FromFormatV()
# raises an error
@@ -2030,64 +2085,205 @@ class UnicodeTest(string_tests.CommonTest,
PyUnicode_FromFormat, b'unicode\xe9=%s', 'ascii')
# test "%c"
- self.assertEqual(PyUnicode_FromFormat(b'%c', c_int(0xabcd)), '\uabcd')
- self.assertEqual(PyUnicode_FromFormat(b'%c', c_int(0x10ffff)), '\U0010ffff')
+ check_format('\uabcd',
+ b'%c', c_int(0xabcd))
+ check_format('\U0010ffff',
+ b'%c', c_int(0x10ffff))
with self.assertRaises(OverflowError):
PyUnicode_FromFormat(b'%c', c_int(0x110000))
# Issue #18183
- self.assertEqual(
- PyUnicode_FromFormat(b'%c%c', c_int(0x10000), c_int(0x100000)),
- '\U00010000\U00100000')
+ check_format('\U00010000\U00100000',
+ b'%c%c', c_int(0x10000), c_int(0x100000))
# test "%"
- self.assertEqual(PyUnicode_FromFormat(b'%'), '%')
- self.assertEqual(PyUnicode_FromFormat(b'%%'), '%')
- self.assertEqual(PyUnicode_FromFormat(b'%%s'), '%s')
- self.assertEqual(PyUnicode_FromFormat(b'[%%]'), '[%]')
- self.assertEqual(PyUnicode_FromFormat(b'%%%s', b'abc'), '%abc')
+ check_format('%',
+ b'%')
+ check_format('%',
+ b'%%')
+ check_format('%s',
+ b'%%s')
+ check_format('[%]',
+ b'[%%]')
+ check_format('%abc',
+ b'%%%s', b'abc')
+
+ # truncated string
+ check_format('abc',
+ b'%.3s', b'abcdef')
+ check_format('abc[\ufffd',
+ b'%.5s', 'abc[\u20ac]'.encode('utf8'))
+ check_format("'\\u20acABC'",
+ b'%A', '\u20acABC')
+ check_format("'\\u20",
+ b'%.5A', '\u20acABCDEF')
+ check_format("'\u20acABC'",
+ b'%R', '\u20acABC')
+ check_format("'\u20acA",
+ b'%.3R', '\u20acABCDEF')
+ check_format('\u20acAB',
+ b'%.3S', '\u20acABCDEF')
+ check_format('\u20acAB',
+ b'%.3U', '\u20acABCDEF')
+ check_format('\u20acAB',
+ b'%.3V', '\u20acABCDEF', None)
+ check_format('abc[\ufffd',
+ b'%.5V', None, 'abc[\u20ac]'.encode('utf8'))
+
+ # following tests comes from #7330
+ # test width modifier and precision modifier with %S
+ check_format("repr= abc",
+ b'repr=%5S', 'abc')
+ check_format("repr=ab",
+ b'repr=%.2S', 'abc')
+ check_format("repr= ab",
+ b'repr=%5.2S', 'abc')
+
+ # test width modifier and precision modifier with %R
+ check_format("repr= 'abc'",
+ b'repr=%8R', 'abc')
+ check_format("repr='ab",
+ b'repr=%.3R', 'abc')
+ check_format("repr= 'ab",
+ b'repr=%5.3R', 'abc')
+
+ # test width modifier and precision modifier with %A
+ check_format("repr= 'abc'",
+ b'repr=%8A', 'abc')
+ check_format("repr='ab",
+ b'repr=%.3A', 'abc')
+ check_format("repr= 'ab",
+ b'repr=%5.3A', 'abc')
+
+ # test width modifier and precision modifier with %s
+ check_format("repr= abc",
+ b'repr=%5s', b'abc')
+ check_format("repr=ab",
+ b'repr=%.2s', b'abc')
+ check_format("repr= ab",
+ b'repr=%5.2s', b'abc')
+
+ # test width modifier and precision modifier with %U
+ check_format("repr= abc",
+ b'repr=%5U', 'abc')
+ check_format("repr=ab",
+ b'repr=%.2U', 'abc')
+ check_format("repr= ab",
+ b'repr=%5.2U', 'abc')
+
+ # test width modifier and precision modifier with %V
+ check_format("repr= abc",
+ b'repr=%5V', 'abc', b'123')
+ check_format("repr=ab",
+ b'repr=%.2V', 'abc', b'123')
+ check_format("repr= ab",
+ b'repr=%5.2V', 'abc', b'123')
+ check_format("repr= 123",
+ b'repr=%5V', None, b'123')
+ check_format("repr=12",
+ b'repr=%.2V', None, b'123')
+ check_format("repr= 12",
+ b'repr=%5.2V', None, b'123')
# test integer formats (%i, %d, %u)
- self.assertEqual(PyUnicode_FromFormat(b'%03i', c_int(10)), '010')
- self.assertEqual(PyUnicode_FromFormat(b'%0.4i', c_int(10)), '0010')
- self.assertEqual(PyUnicode_FromFormat(b'%i', c_int(-123)), '-123')
- self.assertEqual(PyUnicode_FromFormat(b'%li', c_long(-123)), '-123')
- self.assertEqual(PyUnicode_FromFormat(b'%lli', c_longlong(-123)), '-123')
- self.assertEqual(PyUnicode_FromFormat(b'%zi', c_ssize_t(-123)), '-123')
-
- self.assertEqual(PyUnicode_FromFormat(b'%d', c_int(-123)), '-123')
- self.assertEqual(PyUnicode_FromFormat(b'%ld', c_long(-123)), '-123')
- self.assertEqual(PyUnicode_FromFormat(b'%lld', c_longlong(-123)), '-123')
- self.assertEqual(PyUnicode_FromFormat(b'%zd', c_ssize_t(-123)), '-123')
-
- self.assertEqual(PyUnicode_FromFormat(b'%u', c_uint(123)), '123')
- self.assertEqual(PyUnicode_FromFormat(b'%lu', c_ulong(123)), '123')
- self.assertEqual(PyUnicode_FromFormat(b'%llu', c_ulonglong(123)), '123')
- self.assertEqual(PyUnicode_FromFormat(b'%zu', c_size_t(123)), '123')
+ check_format('010',
+ b'%03i', c_int(10))
+ check_format('0010',
+ b'%0.4i', c_int(10))
+ check_format('-123',
+ b'%i', c_int(-123))
+ check_format('-123',
+ b'%li', c_long(-123))
+ check_format('-123',
+ b'%lli', c_longlong(-123))
+ check_format('-123',
+ b'%zi', c_ssize_t(-123))
+
+ check_format('-123',
+ b'%d', c_int(-123))
+ check_format('-123',
+ b'%ld', c_long(-123))
+ check_format('-123',
+ b'%lld', c_longlong(-123))
+ check_format('-123',
+ b'%zd', c_ssize_t(-123))
+
+ check_format('123',
+ b'%u', c_uint(123))
+ check_format('123',
+ b'%lu', c_ulong(123))
+ check_format('123',
+ b'%llu', c_ulonglong(123))
+ check_format('123',
+ b'%zu', c_size_t(123))
+
+ # test long output
+ min_longlong = -(2 ** (8 * sizeof(c_longlong) - 1))
+ max_longlong = -min_longlong - 1
+ check_format(str(min_longlong),
+ b'%lld', c_longlong(min_longlong))
+ check_format(str(max_longlong),
+ b'%lld', c_longlong(max_longlong))
+ max_ulonglong = 2 ** (8 * sizeof(c_ulonglong)) - 1
+ check_format(str(max_ulonglong),
+ b'%llu', c_ulonglong(max_ulonglong))
+ PyUnicode_FromFormat(b'%p', c_void_p(-1))
+
+ # test padding (width and/or precision)
+ check_format('123'.rjust(10, '0'),
+ b'%010i', c_int(123))
+ check_format('123'.rjust(100),
+ b'%100i', c_int(123))
+ check_format('123'.rjust(100, '0'),
+ b'%.100i', c_int(123))
+ check_format('123'.rjust(80, '0').rjust(100),
+ b'%100.80i', c_int(123))
+
+ check_format('123'.rjust(10, '0'),
+ b'%010u', c_uint(123))
+ check_format('123'.rjust(100),
+ b'%100u', c_uint(123))
+ check_format('123'.rjust(100, '0'),
+ b'%.100u', c_uint(123))
+ check_format('123'.rjust(80, '0').rjust(100),
+ b'%100.80u', c_uint(123))
+
+ check_format('123'.rjust(10, '0'),
+ b'%010x', c_int(0x123))
+ check_format('123'.rjust(100),
+ b'%100x', c_int(0x123))
+ check_format('123'.rjust(100, '0'),
+ b'%.100x', c_int(0x123))
+ check_format('123'.rjust(80, '0').rjust(100),
+ b'%100.80x', c_int(0x123))
# test %A
- text = PyUnicode_FromFormat(b'%%A:%A', 'abc\xe9\uabcd\U0010ffff')
- self.assertEqual(text, r"%A:'abc\xe9\uabcd\U0010ffff'")
+ check_format(r"%A:'abc\xe9\uabcd\U0010ffff'",
+ b'%%A:%A', 'abc\xe9\uabcd\U0010ffff')
# test %V
- text = PyUnicode_FromFormat(b'repr=%V', 'abc', b'xyz')
- self.assertEqual(text, 'repr=abc')
+ check_format('repr=abc',
+ b'repr=%V', 'abc', b'xyz')
# Test string decode from parameter of %s using utf-8.
# b'\xe4\xba\xba\xe6\xb0\x91' is utf-8 encoded byte sequence of
# '\u4eba\u6c11'
- text = PyUnicode_FromFormat(b'repr=%V', None, b'\xe4\xba\xba\xe6\xb0\x91')
- self.assertEqual(text, 'repr=\u4eba\u6c11')
+ check_format('repr=\u4eba\u6c11',
+ b'repr=%V', None, b'\xe4\xba\xba\xe6\xb0\x91')
#Test replace error handler.
- text = PyUnicode_FromFormat(b'repr=%V', None, b'abc\xff')
- self.assertEqual(text, 'repr=abc\ufffd')
+ check_format('repr=abc\ufffd',
+ b'repr=%V', None, b'abc\xff')
# not supported: copy the raw format string. these tests are just here
# to check for crashs and should not be considered as specifications
- self.assertEqual(PyUnicode_FromFormat(b'%1%s', b'abc'), '%s')
- self.assertEqual(PyUnicode_FromFormat(b'%1abc'), '%1abc')
- self.assertEqual(PyUnicode_FromFormat(b'%+i', c_int(10)), '%+i')
- self.assertEqual(PyUnicode_FromFormat(b'%.%s', b'abc'), '%.%s')
+ check_format('%s',
+ b'%1%s', b'abc')
+ check_format('%1abc',
+ b'%1abc')
+ check_format('%+i',
+ b'%+i', c_int(10))
+ check_format('%.%s',
+ b'%.%s', b'abc')
# Test PyUnicode_AsWideChar()
def test_aswidechar(self):
@@ -2211,6 +2407,80 @@ class UnicodeTest(string_tests.CommonTest,
self.assertNotEqual(abc, abcdef)
self.assertEqual(abcdef.decode('unicode_internal'), text)
+ def test_compare(self):
+ # Issue #17615
+ N = 10
+ ascii = 'a' * N
+ ascii2 = 'z' * N
+ latin = '\x80' * N
+ latin2 = '\xff' * N
+ bmp = '\u0100' * N
+ bmp2 = '\uffff' * N
+ astral = '\U00100000' * N
+ astral2 = '\U0010ffff' * N
+ strings = (
+ ascii, ascii2,
+ latin, latin2,
+ bmp, bmp2,
+ astral, astral2)
+ for text1, text2 in itertools.combinations(strings, 2):
+ equal = (text1 is text2)
+ self.assertEqual(text1 == text2, equal)
+ self.assertEqual(text1 != text2, not equal)
+
+ if equal:
+ self.assertTrue(text1 <= text2)
+ self.assertTrue(text1 >= text2)
+
+ # text1 is text2: duplicate strings to skip the "str1 == str2"
+ # optimization in unicode_compare_eq() and really compare
+ # character per character
+ copy1 = duplicate_string(text1)
+ copy2 = duplicate_string(text2)
+ self.assertIsNot(copy1, copy2)
+
+ self.assertTrue(copy1 == copy2)
+ self.assertFalse(copy1 != copy2)
+
+ self.assertTrue(copy1 <= copy2)
+ self.assertTrue(copy2 >= copy2)
+
+ self.assertTrue(ascii < ascii2)
+ self.assertTrue(ascii < latin)
+ self.assertTrue(ascii < bmp)
+ self.assertTrue(ascii < astral)
+ self.assertFalse(ascii >= ascii2)
+ self.assertFalse(ascii >= latin)
+ self.assertFalse(ascii >= bmp)
+ self.assertFalse(ascii >= astral)
+
+ self.assertFalse(latin < ascii)
+ self.assertTrue(latin < latin2)
+ self.assertTrue(latin < bmp)
+ self.assertTrue(latin < astral)
+ self.assertTrue(latin >= ascii)
+ self.assertFalse(latin >= latin2)
+ self.assertFalse(latin >= bmp)
+ self.assertFalse(latin >= astral)
+
+ self.assertFalse(bmp < ascii)
+ self.assertFalse(bmp < latin)
+ self.assertTrue(bmp < bmp2)
+ self.assertTrue(bmp < astral)
+ self.assertTrue(bmp >= ascii)
+ self.assertTrue(bmp >= latin)
+ self.assertFalse(bmp >= bmp2)
+ self.assertFalse(bmp >= astral)
+
+ self.assertFalse(astral < ascii)
+ self.assertFalse(astral < latin)
+ self.assertFalse(astral < bmp2)
+ self.assertTrue(astral < astral2)
+ self.assertTrue(astral >= ascii)
+ self.assertTrue(astral >= latin)
+ self.assertTrue(astral >= bmp2)
+ self.assertFalse(astral >= astral2)
+
class StringModuleTest(unittest.TestCase):
def test_formatter_parser(self):
diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py
index 99aa0033cd..707b30e50c 100644
--- a/Lib/test/test_unicodedata.py
+++ b/Lib/test/test_unicodedata.py
@@ -21,7 +21,7 @@ errors = 'surrogatepass'
class UnicodeMethodsTest(unittest.TestCase):
# update this, if the database changes
- expectedchecksum = 'bf7a78f1a532421b5033600102e23a92044dbba9'
+ expectedchecksum = 'e74e878de71b6e780ffac271785c3cb58f6251f3'
def test_method_checksum(self):
h = hashlib.sha1()
@@ -80,7 +80,7 @@ class UnicodeDatabaseTest(unittest.TestCase):
class UnicodeFunctionsTest(UnicodeDatabaseTest):
# update this, if the database changes
- expectedchecksum = '17fe2f12b788e4fff5479b469c4404bb6ecf841f'
+ expectedchecksum = 'f0b74d26776331cc7bdc3a4698f037d73f2cee2b'
def test_function_checksum(self):
data = []
h = hashlib.sha1()
diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py
index 7a34a05d05..94f640b923 100644
--- a/Lib/test/test_urllib.py
+++ b/Lib/test/test_urllib.py
@@ -16,6 +16,7 @@ from nturl2path import url2pathname, pathname2url
from base64 import b64encode
import collections
+
def hexescape(char):
"""Escape char as RFC 2396 specifies"""
hex_repr = hex(ord(char))[2:].upper()
@@ -238,7 +239,7 @@ class urlopen_HttpTests(unittest.TestCase, FakeHTTPMixin):
self.check_read(b"1.1")
def test_read_bogus(self):
- # urlopen() should raise IOError for many error codes.
+ # urlopen() should raise OSError for many error codes.
self.fakehttp(b'''HTTP/1.1 401 Authentication Required
Date: Wed, 02 Jan 2008 03:03:54 GMT
Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e
@@ -246,12 +247,12 @@ Connection: close
Content-Type: text/html; charset=iso-8859-1
''')
try:
- self.assertRaises(IOError, urlopen, "http://python.org/")
+ self.assertRaises(OSError, urlopen, "http://python.org/")
finally:
self.unfakehttp()
def test_invalid_redirect(self):
- # urlopen() should raise IOError for many error codes.
+ # urlopen() should raise OSError for many error codes.
self.fakehttp(b'''HTTP/1.1 302 Found
Date: Wed, 02 Jan 2008 03:03:54 GMT
Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e
@@ -266,19 +267,20 @@ Content-Type: text/html; charset=iso-8859-1
self.unfakehttp()
def test_empty_socket(self):
- # urlopen() raises IOError if the underlying socket does not send any
+ # urlopen() raises OSError if the underlying socket does not send any
# data. (#1680230)
self.fakehttp(b'')
try:
- self.assertRaises(IOError, urlopen, "http://something")
+ self.assertRaises(OSError, urlopen, "http://something")
finally:
self.unfakehttp()
def test_missing_localfile(self):
# Test for #10836
- # 3.3 - URLError is not captured, explicit IOError is raised.
- with self.assertRaises(IOError):
+ with self.assertRaises(urllib.error.URLError) as e:
urlopen('file://localhost/a/file/which/doesnot/exists.py')
+ self.assertTrue(e.exception.filename)
+ self.assertTrue(e.exception.reason)
def test_file_notexists(self):
fd, tmp_file = tempfile.mkstemp()
@@ -291,20 +293,21 @@ Content-Type: text/html; charset=iso-8859-1
os.close(fd)
os.unlink(tmp_file)
self.assertFalse(os.path.exists(tmp_file))
- # 3.3 - IOError instead of URLError
- with self.assertRaises(IOError):
+ with self.assertRaises(urllib.error.URLError):
urlopen(tmp_fileurl)
def test_ftp_nohost(self):
test_ftp_url = 'ftp:///path'
- # 3.3 - IOError instead of URLError
- with self.assertRaises(IOError):
+ with self.assertRaises(urllib.error.URLError) as e:
urlopen(test_ftp_url)
+ self.assertFalse(e.exception.filename)
+ self.assertTrue(e.exception.reason)
def test_ftp_nonexisting(self):
- # 3.3 - IOError instead of URLError
- with self.assertRaises(IOError):
+ with self.assertRaises(urllib.error.URLError) as e:
urlopen('ftp://localhost/a/file/which/doesnot/exists.py')
+ self.assertFalse(e.exception.filename)
+ self.assertTrue(e.exception.reason)
def test_userpass_inurl(self):
@@ -341,6 +344,79 @@ Content-Type: text/html; charset=iso-8859-1
with support.check_warnings(('',DeprecationWarning)):
urllib.request.URLopener()
+class urlopen_DataTests(unittest.TestCase):
+ """Test urlopen() opening a data URL."""
+
+ def setUp(self):
+ # text containing URL special- and unicode-characters
+ self.text = "test data URLs :;,%=& \u00f6 \u00c4 "
+ # 2x1 pixel RGB PNG image with one black and one white pixel
+ self.image = (
+ b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x02\x00\x00\x00'
+ b'\x01\x08\x02\x00\x00\x00{@\xe8\xdd\x00\x00\x00\x01sRGB\x00\xae'
+ b'\xce\x1c\xe9\x00\x00\x00\x0fIDAT\x08\xd7c```\xf8\xff\xff?\x00'
+ b'\x06\x01\x02\xfe\no/\x1e\x00\x00\x00\x00IEND\xaeB`\x82')
+
+ self.text_url = (
+ "data:text/plain;charset=UTF-8,test%20data%20URLs%20%3A%3B%2C%25%3"
+ "D%26%20%C3%B6%20%C3%84%20")
+ self.text_url_base64 = (
+ "data:text/plain;charset=ISO-8859-1;base64,dGVzdCBkYXRhIFVSTHMgOjs"
+ "sJT0mIPYgxCA%3D")
+ # base64 encoded data URL that contains ignorable spaces,
+ # such as "\n", " ", "%0A", and "%20".
+ self.image_url = (
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7\n"
+ "QOjdAAAAAXNSR0IArs4c6QAAAA9JREFUCNdj%0AYGBg%2BP//PwAGAQL%2BCm8 "
+ "vHgAAAABJRU5ErkJggg%3D%3D%0A%20")
+
+ self.text_url_resp = urllib.request.urlopen(self.text_url)
+ self.text_url_base64_resp = urllib.request.urlopen(
+ self.text_url_base64)
+ self.image_url_resp = urllib.request.urlopen(self.image_url)
+
+ def test_interface(self):
+ # Make sure object returned by urlopen() has the specified methods
+ for attr in ("read", "readline", "readlines",
+ "close", "info", "geturl", "getcode", "__iter__"):
+ self.assertTrue(hasattr(self.text_url_resp, attr),
+ "object returned by urlopen() lacks %s attribute" %
+ attr)
+
+ def test_info(self):
+ self.assertIsInstance(self.text_url_resp.info(), email.message.Message)
+ self.assertEqual(self.text_url_base64_resp.info().get_params(),
+ [('text/plain', ''), ('charset', 'ISO-8859-1')])
+ self.assertEqual(self.image_url_resp.info()['content-length'],
+ str(len(self.image)))
+ self.assertEqual(urllib.request.urlopen("data:,").info().get_params(),
+ [('text/plain', ''), ('charset', 'US-ASCII')])
+
+ def test_geturl(self):
+ self.assertEqual(self.text_url_resp.geturl(), self.text_url)
+ self.assertEqual(self.text_url_base64_resp.geturl(),
+ self.text_url_base64)
+ self.assertEqual(self.image_url_resp.geturl(), self.image_url)
+
+ def test_read_text(self):
+ self.assertEqual(self.text_url_resp.read().decode(
+ dict(self.text_url_resp.info().get_params())['charset']), self.text)
+
+ def test_read_text_base64(self):
+ self.assertEqual(self.text_url_base64_resp.read().decode(
+ dict(self.text_url_base64_resp.info().get_params())['charset']),
+ self.text)
+
+ def test_read_image(self):
+ self.assertEqual(self.image_url_resp.read(), self.image)
+
+ def test_missing_comma(self):
+ self.assertRaises(ValueError,urllib.request.urlopen,'data:text/plain')
+
+ def test_invalid_base64_data(self):
+ # missing padding character
+ self.assertRaises(ValueError,urllib.request.urlopen,'data:;base64,Cg=')
+
class urlretrieve_FileTests(unittest.TestCase):
"""Test urllib.urlretrieve() on local files"""
@@ -1291,6 +1367,7 @@ class URLopener_Tests(unittest.TestCase):
# self.assertEqual(ftp.ftp.sock.gettimeout(), 30)
# ftp.close()
+
class RequestTests(unittest.TestCase):
"""Unit tests for urllib.request.Request."""
diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
index 33f90f48ca..dbd1c60ec2 100644
--- a/Lib/test/test_urllib2.py
+++ b/Lib/test/test_urllib2.py
@@ -11,6 +11,7 @@ import urllib.request
# The proxy bypass method imported below has logic specific to the OSX
# proxy config data structure but is testable on all platforms.
from urllib.request import Request, OpenerDirector, _proxy_bypass_macosx_sysconf
+from urllib.parse import urlparse
import urllib.error
# XXX
@@ -118,6 +119,15 @@ class RequestHdrsTests(unittest.TestCase):
self.assertIsNone(req.get_header("Not-there"))
self.assertEqual(req.get_header("Not-there", "default"), "default")
+ req.remove_header("Spam-eggs")
+ self.assertFalse(req.has_header("Spam-eggs"))
+
+ req.add_unredirected_header("Unredirected-spam", "Eggs")
+ self.assertTrue(req.has_header("Unredirected-spam"))
+
+ req.remove_header("Unredirected-spam")
+ self.assertFalse(req.has_header("Unredirected-spam"))
+
def test_password_manager(self):
mgr = urllib.request.HTTPPasswordMgr()
@@ -283,6 +293,7 @@ class MockHTTPClass:
self.req_headers = []
self.data = None
self.raise_on_endheaders = False
+ self.sock = None
self._tunnel_headers = {}
def __call__(self, host, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
@@ -310,8 +321,7 @@ class MockHTTPClass:
if body:
self.data = body
if self.raise_on_endheaders:
- import socket
- raise socket.error()
+ raise OSError()
def getresponse(self):
return MockHTTPResponse(MockFile(), {}, 200, "OK")
@@ -596,27 +606,6 @@ class OpenerDirectorTests(unittest.TestCase):
self.assertTrue(args[1] is None or
isinstance(args[1], MockResponse))
- def test_method_deprecations(self):
- req = Request("http://www.example.com")
-
- with self.assertWarns(DeprecationWarning):
- req.add_data("data")
- with self.assertWarns(DeprecationWarning):
- req.get_data()
- with self.assertWarns(DeprecationWarning):
- req.has_data()
- with self.assertWarns(DeprecationWarning):
- req.get_host()
- with self.assertWarns(DeprecationWarning):
- req.get_selector()
- with self.assertWarns(DeprecationWarning):
- req.is_unverifiable()
- with self.assertWarns(DeprecationWarning):
- req.get_origin_req_host()
- with self.assertWarns(DeprecationWarning):
- req.get_type()
-
-
def sanepathname2url(path):
try:
path.encode("utf-8")
@@ -811,7 +800,7 @@ class HandlerTests(unittest.TestCase):
("Foo", "bar"), ("Spam", "eggs")])
self.assertEqual(http.data, data)
- # check socket.error converted to URLError
+ # check OSError converted to URLError
http.raise_on_endheaders = True
self.assertRaises(urllib.error.URLError, h.do_open, http, req)
@@ -916,6 +905,36 @@ class HandlerTests(unittest.TestCase):
p_ds_req = h.do_request_(ds_req)
self.assertEqual(p_ds_req.unredirected_hdrs["Host"],"example.com")
+ def test_full_url_setter(self):
+ # Checks to ensure that components are set correctly after setting the
+ # full_url of a Request object
+
+ urls = [
+ 'http://example.com?foo=bar#baz',
+ 'http://example.com?foo=bar&spam=eggs#bash',
+ 'http://example.com',
+ ]
+
+ # testing a reusable request instance, but the url parameter is
+ # required, so just use a dummy one to instantiate
+ r = Request('http://example.com')
+ for url in urls:
+ r.full_url = url
+ parsed = urlparse(url)
+
+ self.assertEqual(r.get_full_url(), url)
+ # full_url setter uses splittag to split into components.
+ # splittag sets the fragment as None while urlparse sets it to ''
+ self.assertEqual(r.fragment or '', parsed.fragment)
+ self.assertEqual(urlparse(r.get_full_url()).query, parsed.query)
+
+ def test_full_url_deleter(self):
+ r = Request('http://www.example.com')
+ del r.full_url
+ self.assertIsNone(r.full_url)
+ self.assertIsNone(r.fragment)
+ self.assertEqual(r.selector, '')
+
def test_fixpath_in_weirdurls(self):
# Issue4493: urllib2 to supply '/' when to urls where path does not
# start with'/'
@@ -1415,6 +1434,21 @@ class MiscTests(unittest.TestCase):
self.opener_has_handler(o, MyHTTPHandler)
self.opener_has_handler(o, MyOtherHTTPHandler)
+ @unittest.skipUnless(support.is_resource_enabled('network'),
+ 'test requires network access')
+ def test_issue16464(self):
+ opener = urllib.request.build_opener()
+ request = urllib.request.Request("http://www.python.org/~jeremy/")
+ self.assertEqual(None, request.data)
+
+ opener.open(request, "1".encode("us-ascii"))
+ self.assertEqual(b"1", request.data)
+ self.assertEqual("1", request.get_header("Content-length"))
+
+ opener.open(request, "1234567890".encode("us-ascii"))
+ self.assertEqual(b"1234567890", request.data)
+ self.assertEqual("10", request.get_header("Content-length"))
+
def test_HTTPError_interface(self):
"""
Issue 13211 reveals that HTTPError didn't implement the URLError
@@ -1426,23 +1460,31 @@ class MiscTests(unittest.TestCase):
err = urllib.error.HTTPError(url, code, msg, hdrs, fp)
self.assertTrue(hasattr(err, 'reason'))
self.assertEqual(err.reason, 'something bad happened')
- self.assertTrue(hasattr(err, 'hdrs'))
- self.assertEqual(err.hdrs, 'Content-Length: 42')
+ self.assertTrue(hasattr(err, 'headers'))
+ self.assertEqual(err.headers, 'Content-Length: 42')
expected_errmsg = 'HTTP Error %s: %s' % (err.code, err.msg)
self.assertEqual(str(err), expected_errmsg)
-
class RequestTests(unittest.TestCase):
+ class PutRequest(Request):
+ method='PUT'
def setUp(self):
self.get = Request("http://www.python.org/~jeremy/")
self.post = Request("http://www.python.org/~jeremy/",
"data",
headers={"X-Test": "test"})
+ self.head = Request("http://www.python.org/~jeremy/", method='HEAD')
+ self.put = self.PutRequest("http://www.python.org/~jeremy/")
+ self.force_post = self.PutRequest("http://www.python.org/~jeremy/",
+ method="POST")
def test_method(self):
self.assertEqual("POST", self.post.get_method())
self.assertEqual("GET", self.get.get_method())
+ self.assertEqual("HEAD", self.head.get_method())
+ self.assertEqual("PUT", self.put.get_method())
+ self.assertEqual("POST", self.force_post.get_method())
def test_data(self):
self.assertFalse(self.get.data)
@@ -1451,6 +1493,25 @@ class RequestTests(unittest.TestCase):
self.assertTrue(self.get.data)
self.assertEqual("POST", self.get.get_method())
+ # issue 16464
+ # if we change data we need to remove content-length header
+ # (cause it's most probably calculated for previous value)
+ def test_setting_data_should_remove_content_length(self):
+ self.assertNotIn("Content-length", self.get.unredirected_hdrs)
+ self.get.add_unredirected_header("Content-length", 42)
+ self.assertEqual(42, self.get.unredirected_hdrs["Content-length"])
+ self.get.data = "spam"
+ self.assertNotIn("Content-length", self.get.unredirected_hdrs)
+
+ # issue 17485 same for deleting data.
+ def test_deleting_data_should_remove_content_length(self):
+ self.assertNotIn("Content-length", self.get.unredirected_hdrs)
+ self.get.data = 'foo'
+ self.get.add_unredirected_header("Content-length", 3)
+ self.assertEqual(3, self.get.unredirected_hdrs["Content-length"])
+ del self.get.data
+ self.assertNotIn("Content-length", self.get.unredirected_hdrs)
+
def test_get_full_url(self):
self.assertEqual("http://www.python.org/~jeremy/",
self.get.get_full_url())
@@ -1492,21 +1553,13 @@ class RequestTests(unittest.TestCase):
req = Request(url)
self.assertEqual(req.get_full_url(), url)
- def test_HTTPError_interface_call(self):
- """
- Issue 15701 - HTTPError interface has info method available from URLError
- """
- err = urllib.request.HTTPError(msg="something bad happened", url=None,
- code=None, hdrs='Content-Length:42', fp=None)
- self.assertTrue(hasattr(err, 'reason'))
- assert hasattr(err, 'reason')
- assert hasattr(err, 'info')
- assert callable(err.info)
- try:
- err.info()
- except AttributeError:
- self.fail('err.info call failed.')
- self.assertEqual(err.info(), "Content-Length:42")
+ def test_url_fullurl_get_full_url(self):
+ urls = ['http://docs.python.org',
+ 'http://docs.python.org/library/urllib2.html#OK',
+ 'http://www.python.org/?qs=query#fragment=true' ]
+ for url in urls:
+ req = Request(url)
+ self.assertEqual(req.get_full_url(), req.full_url)
def test_main(verbose=None):
from test import test_urllib2
diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py
index 9112e5062b..08250c36e5 100644
--- a/Lib/test/test_urllib2_localnet.py
+++ b/Lib/test/test_urllib2_localnet.py
@@ -9,7 +9,10 @@ import unittest
import hashlib
from test import support
threading = support.import_module('threading')
-
+try:
+ import ssl
+except ImportError:
+ ssl = None
here = os.path.dirname(__file__)
# Self-signed cert file for 'localhost'
@@ -17,6 +20,7 @@ CERT_localhost = os.path.join(here, 'keycert.pem')
# Self-signed cert file for 'fakehostname'
CERT_fakehostname = os.path.join(here, 'keycert2.pem')
+
# Loopback http server infrastructure
class LoopbackHttpServer(http.server.HTTPServer):
@@ -389,14 +393,14 @@ class TestUrlopen(unittest.TestCase):
handler.port = port
return handler
- def start_https_server(self, responses=None, certfile=CERT_localhost):
+ def start_https_server(self, responses=None, **kwargs):
if not hasattr(urllib.request, 'HTTPSHandler'):
self.skipTest('ssl support required')
from test.ssl_servers import make_https_server
if responses is None:
responses = [(200, [], b"we care a bit")]
handler = GetRequestHandler(responses)
- server = make_https_server(self, certfile=certfile, handler_class=handler)
+ server = make_https_server(self, handler_class=handler, **kwargs)
handler.port = server.port
return handler
@@ -486,6 +490,21 @@ class TestUrlopen(unittest.TestCase):
self.urlopen("https://localhost:%s/bizarre" % handler.port,
cadefault=True)
+ def test_https_sni(self):
+ if ssl is None:
+ self.skipTest("ssl module required")
+ if not ssl.HAS_SNI:
+ self.skipTest("SNI support required in OpenSSL")
+ sni_name = None
+ def cb_sni(ssl_sock, server_name, initial_context):
+ nonlocal sni_name
+ sni_name = server_name
+ context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+ context.set_servername_callback(cb_sni)
+ handler = self.start_https_server(context=context, certfile=CERT_localhost)
+ self.urlopen("https://localhost:%s" % handler.port)
+ self.assertEqual(sni_name, "localhost")
+
def test_sending_headers(self):
handler = self.start_server()
req = urllib.request.Request("http://localhost:%s/" % handler.port,
@@ -532,7 +551,7 @@ class TestUrlopen(unittest.TestCase):
# so we run the test only when -unetwork/-uall is specified to
# mitigate the problem a bit (see #17564)
support.requires('network')
- self.assertRaises(IOError,
+ self.assertRaises(OSError,
# Given that both VeriSign and various ISPs have in
# the past or are presently hijacking various invalid
# domain name requests in an attempt to boost traffic
diff --git a/Lib/test/test_urllib2net.py b/Lib/test/test_urllib2net.py
index 7f3c93adaa..fba3ceac83 100644
--- a/Lib/test/test_urllib2net.py
+++ b/Lib/test/test_urllib2net.py
@@ -164,6 +164,14 @@ class OtherNetworkTests(unittest.TestCase):
self.assertEqual(res.geturl(),
"http://docs.python.org/2/glossary.html#glossary")
+ def test_redirect_url_withfrag(self):
+ redirect_url_with_frag = "http://bitly.com/urllibredirecttest"
+ with support.transient_internet(redirect_url_with_frag):
+ req = urllib.request.Request(redirect_url_with_frag)
+ res = urllib.request.urlopen(req)
+ self.assertEqual(res.geturl(),
+ "http://docs.python.org/3.4/glossary.html#term-global-interpreter-lock")
+
def test_custom_headers(self):
url = "http://www.example.com"
with support.transient_internet(url):
@@ -216,7 +224,7 @@ class OtherNetworkTests(unittest.TestCase):
debug(url)
try:
f = urlopen(url, req, TIMEOUT)
- except EnvironmentError as err:
+ except OSError as err:
debug(err)
if expected_err:
msg = ("Didn't get expected error(s) %s for %s %s, got %s: %s" %
@@ -330,35 +338,6 @@ class TimeoutTest(unittest.TestCase):
self.assertEqual(u.fp.fp.raw._sock.gettimeout(), 60)
-@unittest.skipUnless(ssl, "requires SSL support")
-class HTTPSTests(unittest.TestCase):
-
- def test_sni(self):
- self.skipTest("test disabled - test server needed")
- # Checks that Server Name Indication works, if supported by the
- # OpenSSL linked to.
- # The ssl module itself doesn't have server-side support for SNI,
- # so we rely on a third-party test site.
- expect_sni = ssl.HAS_SNI
- with support.transient_internet("XXX"):
- u = urllib.request.urlopen("XXX")
- contents = u.readall()
- if expect_sni:
- self.assertIn(b"Great", contents)
- self.assertNotIn(b"Unfortunately", contents)
- else:
- self.assertNotIn(b"Great", contents)
- self.assertIn(b"Unfortunately", contents)
-
-
-def test_main():
- support.requires("network")
- support.run_unittest(AuthTests,
- HTTPSTests,
- OtherNetworkTests,
- CloseSocketTest,
- TimeoutTest,
- )
-
if __name__ == "__main__":
- test_main()
+ support.requires("network")
+ unittest.main()
diff --git a/Lib/test/test_urllibnet.py b/Lib/test/test_urllibnet.py
index 20efca6adb..b6888f3d7b 100644
--- a/Lib/test/test_urllibnet.py
+++ b/Lib/test/test_urllibnet.py
@@ -124,16 +124,15 @@ class urlopenNetworkTests(unittest.TestCase):
else:
# This happens with some overzealous DNS providers such as OpenDNS
self.skipTest("%r should not resolve for test to work" % bogus_domain)
- self.assertRaises(IOError,
- # SF patch 809915: In Sep 2003, VeriSign started
- # highjacking invalid .com and .net addresses to
- # boost traffic to their own site. This test
- # started failing then. One hopes the .invalid
- # domain will be spared to serve its defined
- # purpose.
- # urllib.urlopen, "http://www.sadflkjsasadf.com/")
- urllib.request.urlopen,
- "http://sadflkjsasf.i.nvali.d/")
+ failure_explanation = ('opening an invalid URL did not raise OSError; '
+ 'can be caused by a broken DNS server '
+ '(e.g. returns 404 or hijacks page)')
+ with self.assertRaises(OSError, msg=failure_explanation):
+ # SF patch 809915: In Sep 2003, VeriSign started highjacking
+ # invalid .com and .net addresses to boost traffic to their own
+ # site. This test started failing then. One hopes the .invalid
+ # domain will be spared to serve its defined purpose.
+ urllib.request.urlopen("http://sadflkjsasf.i.nvali.d/")
class urlretrieveNetworkTests(unittest.TestCase):
diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
index 378a427bc5..c938f09951 100755
--- a/Lib/test/test_urlparse.py
+++ b/Lib/test/test_urlparse.py
@@ -847,6 +847,14 @@ class UrlParseTestCase(unittest.TestCase):
self.assertEqual(p1.path, '863-1234')
self.assertEqual(p1.params, 'phone-context=+1-914-555')
+ def test_unwrap(self):
+ url = urllib.parse.unwrap('<URL:type://host/path>')
+ self.assertEqual(url, 'type://host/path')
+
+ def test_Quoter_repr(self):
+ quoter = urllib.parse.Quoter(urllib.parse._ALWAYS_SAFE)
+ self.assertIn('Quoter', repr(quoter))
+
def test_main():
support.run_unittest(UrlParseTestCase)
diff --git a/Lib/test/test_userdict.py b/Lib/test/test_userdict.py
index 137c445eae..2ca99298d4 100644
--- a/Lib/test/test_userdict.py
+++ b/Lib/test/test_userdict.py
@@ -45,7 +45,8 @@ class UserDictTest(mapping_tests.TestHashMappingProtocol):
# Test __repr__
self.assertEqual(str(u0), str(d0))
self.assertEqual(repr(u1), repr(d1))
- self.assertEqual(repr(u2), repr(d2))
+ self.assertIn(repr(u2), ("{'one': 1, 'two': 2}",
+ "{'two': 2, 'one': 1}"))
# Test rich comparison and __len__
all = [d0, d1, d2, u, u0, u1, u2, uu, uu0, uu1, uu2]
@@ -89,9 +90,9 @@ class UserDictTest(mapping_tests.TestHashMappingProtocol):
self.assertNotEqual(m2a, m2)
# Test keys, items, values
- self.assertEqual(u2.keys(), d2.keys())
- self.assertEqual(u2.items(), d2.items())
- self.assertEqual(list(u2.values()), list(d2.values()))
+ self.assertEqual(sorted(u2.keys()), sorted(d2.keys()))
+ self.assertEqual(sorted(u2.items()), sorted(d2.items()))
+ self.assertEqual(sorted(u2.values()), sorted(d2.values()))
# Test "in".
for i in u2.keys():
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index 0fae88bcf9..eb225fa4ae 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -5,6 +5,7 @@ Copyright (C) 2011-2012 Vinay Sajip.
Licensed to the PSF under a contributor agreement.
"""
+import ensurepip
import os
import os.path
import shutil
@@ -12,9 +13,18 @@ import subprocess
import sys
import tempfile
from test.support import (captured_stdout, captured_stderr, run_unittest,
- can_symlink)
+ can_symlink, EnvironmentVarGuard)
+import textwrap
import unittest
import venv
+try:
+ import ssl
+except ImportError:
+ ssl = None
+
+skipInVenv = unittest.skipIf(sys.prefix != sys.base_prefix,
+ 'Test not appropriate in a venv')
+
class BaseTest(unittest.TestCase):
"""Base class for venv tests."""
@@ -83,8 +93,7 @@ class BasicTest(BaseTest):
print(' %r' % os.listdir(bd))
self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
- @unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
- 'in a venv')
+ @skipInVenv
def test_prefixes(self):
"""
Test that the prefix values are as expected.
@@ -109,13 +118,68 @@ class BasicTest(BaseTest):
out, err = p.communicate()
self.assertEqual(out.strip(), expected.encode())
+ if sys.platform == 'win32':
+ ENV_SUBDIRS = (
+ ('Scripts',),
+ ('Include',),
+ ('Lib',),
+ ('Lib', 'site-packages'),
+ )
+ else:
+ ENV_SUBDIRS = (
+ ('bin',),
+ ('include',),
+ ('lib',),
+ ('lib', 'python%d.%d' % sys.version_info[:2]),
+ ('lib', 'python%d.%d' % sys.version_info[:2], 'site-packages'),
+ )
+
+ def create_contents(self, paths, filename):
+ """
+ Create some files in the environment which are unrelated
+ to the virtual environment.
+ """
+ for subdirs in paths:
+ d = os.path.join(self.env_dir, *subdirs)
+ os.mkdir(d)
+ fn = os.path.join(d, filename)
+ with open(fn, 'wb') as f:
+ f.write(b'Still here?')
+
def test_overwrite_existing(self):
"""
- Test control of overwriting an existing environment directory.
+ Test creating environment in an existing directory.
"""
- self.assertRaises(ValueError, venv.create, self.env_dir)
+ self.create_contents(self.ENV_SUBDIRS, 'foo')
+ venv.create(self.env_dir)
+ for subdirs in self.ENV_SUBDIRS:
+ fn = os.path.join(self.env_dir, *(subdirs + ('foo',)))
+ self.assertTrue(os.path.exists(fn))
+ with open(fn, 'rb') as f:
+ self.assertEqual(f.read(), b'Still here?')
+
builder = venv.EnvBuilder(clear=True)
builder.create(self.env_dir)
+ for subdirs in self.ENV_SUBDIRS:
+ fn = os.path.join(self.env_dir, *(subdirs + ('foo',)))
+ self.assertFalse(os.path.exists(fn))
+
+ def clear_directory(self, path):
+ for fn in os.listdir(path):
+ fn = os.path.join(path, fn)
+ if os.path.islink(fn) or os.path.isfile(fn):
+ os.remove(fn)
+ elif os.path.isdir(fn):
+ shutil.rmtree(fn)
+
+ def test_unoverwritable_fails(self):
+ #create a file clashing with directories in the env dir
+ for paths in self.ENV_SUBDIRS[:3]:
+ fn = os.path.join(self.env_dir, *paths)
+ with open(fn, 'wb') as f:
+ f.write(b'')
+ self.assertRaises((ValueError, OSError), venv.create, self.env_dir)
+ self.clear_directory(self.env_dir)
def test_upgrade(self):
"""
@@ -162,8 +226,7 @@ class BasicTest(BaseTest):
# run the test, the pyvenv.cfg in the venv created in the test will
# point to the venv being used to run the test, and we lose the link
# to the source build - so Python can't initialise properly.
- @unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
- 'in a venv')
+ @skipInVenv
def test_executable(self):
"""
Test that the sys.executable value is as expected.
@@ -192,8 +255,103 @@ class BasicTest(BaseTest):
out, err = p.communicate()
self.assertEqual(out.strip(), envpy.encode())
+
+@skipInVenv
+class EnsurePipTest(BaseTest):
+ """Test venv module installation of pip."""
+ def assert_pip_not_installed(self):
+ envpy = os.path.join(os.path.realpath(self.env_dir),
+ self.bindir, self.exe)
+ try_import = 'try:\n import pip\nexcept ImportError:\n print("OK")'
+ cmd = [envpy, '-c', try_import]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ # We force everything to text, so unittest gives the detailed diff
+ # if we get unexpected results
+ err = err.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertEqual(err, "")
+ out = out.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertEqual(out.strip(), "OK")
+
+
+ def test_no_pip_by_default(self):
+ shutil.rmtree(self.env_dir)
+ self.run_with_capture(venv.create, self.env_dir)
+ self.assert_pip_not_installed()
+
+ def test_explicit_no_pip(self):
+ shutil.rmtree(self.env_dir)
+ self.run_with_capture(venv.create, self.env_dir, with_pip=False)
+ self.assert_pip_not_installed()
+
+ # Temporary skip for http://bugs.python.org/issue19744
+ @unittest.skipIf(ssl is None, 'pip needs SSL support')
+ def test_with_pip(self):
+ shutil.rmtree(self.env_dir)
+ with EnvironmentVarGuard() as envvars:
+ # pip's cross-version compatibility may trigger deprecation
+ # warnings in current versions of Python. Ensure related
+ # environment settings don't cause venv to fail.
+ envvars["PYTHONWARNINGS"] = "e"
+ # pip doesn't ignore environment variables when running in
+ # isolated mode, and we don't have an active virtualenv here,
+ # we're relying on the native venv support in 3.3+
+ # See http://bugs.python.org/issue19734 for details
+ del envvars["PIP_REQUIRE_VIRTUALENV"]
+ try:
+ self.run_with_capture(venv.create, self.env_dir, with_pip=True)
+ except subprocess.CalledProcessError as exc:
+ # The output this produces can be a little hard to read, but
+ # least it has all the details
+ details = exc.output.decode(errors="replace")
+ msg = "{}\n\n**Subprocess Output**\n{}".format(exc, details)
+ self.fail(msg)
+ # Ensure pip is available in the virtual environment
+ envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
+ cmd = [envpy, '-Im', 'pip', '--version']
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ # We force everything to text, so unittest gives the detailed diff
+ # if we get unexpected results
+ err = err.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertEqual(err, "")
+ out = out.decode("latin-1") # Force to text, prevent decoding errors
+ expected_version = "pip {}".format(ensurepip.version())
+ self.assertEqual(out[:len(expected_version)], expected_version)
+ env_dir = os.fsencode(self.env_dir).decode("latin-1")
+ self.assertIn(env_dir, out)
+
+ # http://bugs.python.org/issue19728
+ # Check the private uninstall command provided for the Windows
+ # installers works (at least in a virtual environment)
+ cmd = [envpy, '-Im', 'ensurepip._uninstall']
+ with EnvironmentVarGuard() as envvars:
+ # pip doesn't ignore environment variables when running in
+ # isolated mode, and we don't have an active virtualenv here,
+ # we're relying on the native venv support in 3.3+
+ # See http://bugs.python.org/issue19734 for details
+ del envvars["PIP_REQUIRE_VIRTUALENV"]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ # We force everything to text, so unittest gives the detailed diff
+ # if we get unexpected results
+ err = err.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertEqual(err, "")
+ # Being fairly specific regarding the expected behaviour for the
+ # initial bundling phase in Python 3.4. If the output changes in
+ # future pip versions, this test can likely be relaxed further.
+ out = out.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertIn("Successfully uninstalled pip", out)
+ self.assertIn("Successfully uninstalled setuptools", out)
+ # Check pip is now gone from the virtual environment
+ self.assert_pip_not_installed()
+
+
def test_main():
- run_unittest(BasicTest)
+ run_unittest(BasicTest, EnsurePipTest)
if __name__ == "__main__":
test_main()
diff --git a/Lib/test/test_wait3.py b/Lib/test/test_wait3.py
index bd06c8d8bd..f6a065d850 100644
--- a/Lib/test/test_wait3.py
+++ b/Lib/test/test_wait3.py
@@ -7,15 +7,11 @@ import unittest
from test.fork_wait import ForkWait
from test.support import run_unittest, reap_children
-try:
- os.fork
-except AttributeError:
- raise unittest.SkipTest("os.fork not defined -- skipping test_wait3")
+if not hasattr(os, 'fork'):
+ raise unittest.SkipTest("os.fork not defined")
-try:
- os.wait3
-except AttributeError:
- raise unittest.SkipTest("os.wait3 not defined -- skipping test_wait3")
+if not hasattr(os, 'wait3'):
+ raise unittest.SkipTest("os.wait3 not defined")
class Wait3Test(ForkWait):
def wait_impl(self, cpid):
diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py
index 9f6d775c59..87463ac1a3 100644
--- a/Lib/test/test_warnings.py
+++ b/Lib/test/test_warnings.py
@@ -330,6 +330,19 @@ class WarnTests(BaseTest):
warning_tests.__name__ = module_name
sys.argv = argv
+ def test_warn_explicit_non_ascii_filename(self):
+ with original_warnings.catch_warnings(record=True,
+ module=self.module) as w:
+ self.module.resetwarnings()
+ self.module.filterwarnings("always", category=UserWarning)
+ for filename in ("nonascii\xe9\u20ac", "surrogate\udc80"):
+ try:
+ os.fsencode(filename)
+ except UnicodeEncodeError:
+ continue
+ self.module.warn_explicit("text", UserWarning, filename, 1)
+ self.assertEqual(w[-1].filename, filename)
+
def test_warn_explicit_type_errors(self):
# warn_explicit() should error out gracefully if it is given objects
# of the wrong types.
@@ -787,6 +800,25 @@ class BootstrapTest(unittest.TestCase):
env=env)
self.assertEqual(retcode, 0)
+class FinalizationTest(unittest.TestCase):
+ def test_finalization(self):
+ # Issue #19421: warnings.warn() should not crash
+ # during Python finalization
+ code = """
+import warnings
+warn = warnings.warn
+
+class A:
+ def __del__(self):
+ warn("test")
+
+a=A()
+ """
+ rc, out, err = assert_python_ok("-c", code)
+ # note: "__main__" filename is not correct, it should be the name
+ # of the script
+ self.assertEqual(err, b'__main__:7: UserWarning: test')
+
def setUpModule():
py_warnings.onceregistry.clear()
diff --git a/Lib/test/test_wave.py b/Lib/test/test_wave.py
index 5be12519d9..549ca89615 100644
--- a/Lib/test/test_wave.py
+++ b/Lib/test/test_wave.py
@@ -1,6 +1,7 @@
from test.support import TESTFN
import unittest
from test import audiotests
+from audioop import byteswap
import sys
import wave
@@ -46,13 +47,7 @@ class WavePCM16Test(audiotests.AudioWriteTests,
E4B50CEB 63440A5A 08CA0A1F 2BBA0B0B 51460E47 8BCB113C B6F50EEA 44150A59 \
""")
if sys.byteorder != 'big':
- frames = audiotests.byteswap2(frames)
-
- if sys.byteorder == 'big':
- @unittest.expectedFailure
- def test_unseekable_incompleted_write(self):
- super().test_unseekable_incompleted_write()
-
+ frames = byteswap(frames, 2)
class WavePCM24Test(audiotests.AudioWriteTests,
@@ -82,7 +77,7 @@ class WavePCM24Test(audiotests.AudioWriteTests,
51486F0E44E1 8BCC64113B05 B6F4EC0EEB36 4413170A5B48 \
""")
if sys.byteorder != 'big':
- frames = audiotests.byteswap3(frames)
+ frames = byteswap(frames, 3)
class WavePCM32Test(audiotests.AudioWriteTests,
@@ -112,12 +107,7 @@ class WavePCM32Test(audiotests.AudioWriteTests,
51486F800E44E190 8BCC6480113B0580 B6F4EC000EEB3630 441317800A5B48A0 \
""")
if sys.byteorder != 'big':
- frames = audiotests.byteswap4(frames)
-
- if sys.byteorder == 'big':
- @unittest.expectedFailure
- def test_unseekable_incompleted_write(self):
- super().test_unseekable_incompleted_write()
+ frames = byteswap(frames, 4)
if __name__ == '__main__':
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index 85ab717c69..cccb515252 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -7,11 +7,15 @@ import operator
import contextlib
import copy
-from test import support
+from test import support, script_helper
# Used in ReferencesTestCase.test_ref_created_during_del() .
ref_from_del = None
+# Used by FinalizeTestCase as a global that may be replaced by None
+# when the interpreter shuts down.
+_global_var = 'foobar'
+
class C:
def method(self):
pass
@@ -47,6 +51,11 @@ class Object:
return NotImplemented
def __hash__(self):
return hash(self.arg)
+ def some_method(self):
+ return 4
+ def other_method(self):
+ return 5
+
class RefCycle:
def __init__(self):
@@ -794,6 +803,30 @@ class ReferencesTestCase(TestBase):
del root
gc.collect()
+ def test_callback_attribute(self):
+ x = Object(1)
+ callback = lambda ref: None
+ ref1 = weakref.ref(x, callback)
+ self.assertIs(ref1.__callback__, callback)
+
+ ref2 = weakref.ref(x)
+ self.assertIsNone(ref2.__callback__)
+
+ def test_callback_attribute_after_deletion(self):
+ x = Object(1)
+ ref = weakref.ref(x, self.callback)
+ self.assertIsNotNone(ref.__callback__)
+ del x
+ support.gc_collect()
+ self.assertIsNone(ref.__callback__)
+
+ def test_set_callback_attribute(self):
+ x = Object(1)
+ callback = lambda ref: None
+ ref1 = weakref.ref(x, callback)
+ with self.assertRaises(AttributeError):
+ ref1.__callback__ = lambda ref: None
+
class SubclassableWeakrefTestCase(TestBase):
@@ -898,6 +931,140 @@ class SubclassableWeakrefTestCase(TestBase):
self.assertEqual(self.cbcalled, 0)
+class WeakMethodTestCase(unittest.TestCase):
+
+ def _subclass(self):
+ """Return a Object subclass overriding `some_method`."""
+ class C(Object):
+ def some_method(self):
+ return 6
+ return C
+
+ def test_alive(self):
+ o = Object(1)
+ r = weakref.WeakMethod(o.some_method)
+ self.assertIsInstance(r, weakref.ReferenceType)
+ self.assertIsInstance(r(), type(o.some_method))
+ self.assertIs(r().__self__, o)
+ self.assertIs(r().__func__, o.some_method.__func__)
+ self.assertEqual(r()(), 4)
+
+ def test_object_dead(self):
+ o = Object(1)
+ r = weakref.WeakMethod(o.some_method)
+ del o
+ gc.collect()
+ self.assertIs(r(), None)
+
+ def test_method_dead(self):
+ C = self._subclass()
+ o = C(1)
+ r = weakref.WeakMethod(o.some_method)
+ del C.some_method
+ gc.collect()
+ self.assertIs(r(), None)
+
+ def test_callback_when_object_dead(self):
+ # Test callback behaviour when object dies first.
+ C = self._subclass()
+ calls = []
+ def cb(arg):
+ calls.append(arg)
+ o = C(1)
+ r = weakref.WeakMethod(o.some_method, cb)
+ del o
+ gc.collect()
+ self.assertEqual(calls, [r])
+ # Callback is only called once.
+ C.some_method = Object.some_method
+ gc.collect()
+ self.assertEqual(calls, [r])
+
+ def test_callback_when_method_dead(self):
+ # Test callback behaviour when method dies first.
+ C = self._subclass()
+ calls = []
+ def cb(arg):
+ calls.append(arg)
+ o = C(1)
+ r = weakref.WeakMethod(o.some_method, cb)
+ del C.some_method
+ gc.collect()
+ self.assertEqual(calls, [r])
+ # Callback is only called once.
+ del o
+ gc.collect()
+ self.assertEqual(calls, [r])
+
+ @support.cpython_only
+ def test_no_cycles(self):
+ # A WeakMethod doesn't create any reference cycle to itself.
+ o = Object(1)
+ def cb(_):
+ pass
+ r = weakref.WeakMethod(o.some_method, cb)
+ wr = weakref.ref(r)
+ del r
+ self.assertIs(wr(), None)
+
+ def test_equality(self):
+ def _eq(a, b):
+ self.assertTrue(a == b)
+ self.assertFalse(a != b)
+ def _ne(a, b):
+ self.assertTrue(a != b)
+ self.assertFalse(a == b)
+ x = Object(1)
+ y = Object(1)
+ a = weakref.WeakMethod(x.some_method)
+ b = weakref.WeakMethod(y.some_method)
+ c = weakref.WeakMethod(x.other_method)
+ d = weakref.WeakMethod(y.other_method)
+ # Objects equal, same method
+ _eq(a, b)
+ _eq(c, d)
+ # Objects equal, different method
+ _ne(a, c)
+ _ne(a, d)
+ _ne(b, c)
+ _ne(b, d)
+ # Objects unequal, same or different method
+ z = Object(2)
+ e = weakref.WeakMethod(z.some_method)
+ f = weakref.WeakMethod(z.other_method)
+ _ne(a, e)
+ _ne(a, f)
+ _ne(b, e)
+ _ne(b, f)
+ del x, y, z
+ gc.collect()
+ # Dead WeakMethods compare by identity
+ refs = a, b, c, d, e, f
+ for q in refs:
+ for r in refs:
+ self.assertEqual(q == r, q is r)
+ self.assertEqual(q != r, q is not r)
+
+ def test_hashing(self):
+ # Alive WeakMethods are hashable if the underlying object is
+ # hashable.
+ x = Object(1)
+ y = Object(1)
+ a = weakref.WeakMethod(x.some_method)
+ b = weakref.WeakMethod(y.some_method)
+ c = weakref.WeakMethod(y.other_method)
+ # Since WeakMethod objects are equal, the hashes should be equal.
+ self.assertEqual(hash(a), hash(b))
+ ha = hash(a)
+ # Dead WeakMethods retain their old hash value
+ del x, y
+ gc.collect()
+ self.assertEqual(hash(a), ha)
+ self.assertEqual(hash(b), ha)
+ # If it wasn't hashed when alive, a dead WeakMethod cannot be hashed.
+ self.assertRaises(TypeError, hash, c)
+
+
class MappingTestCase(TestBase):
COUNT = 10
@@ -1385,6 +1552,151 @@ class WeakKeyDictionaryTestCase(mapping_tests.BasicTestMappingProtocol):
def _reference(self):
return self.__ref.copy()
+
+class FinalizeTestCase(unittest.TestCase):
+
+ class A:
+ pass
+
+ def _collect_if_necessary(self):
+ # we create no ref-cycles so in CPython no gc should be needed
+ if sys.implementation.name != 'cpython':
+ support.gc_collect()
+
+ def test_finalize(self):
+ def add(x,y,z):
+ res.append(x + y + z)
+ return x + y + z
+
+ a = self.A()
+
+ res = []
+ f = weakref.finalize(a, add, 67, 43, z=89)
+ self.assertEqual(f.alive, True)
+ self.assertEqual(f.peek(), (a, add, (67,43), {'z':89}))
+ self.assertEqual(f(), 199)
+ self.assertEqual(f(), None)
+ self.assertEqual(f(), None)
+ self.assertEqual(f.peek(), None)
+ self.assertEqual(f.detach(), None)
+ self.assertEqual(f.alive, False)
+ self.assertEqual(res, [199])
+
+ res = []
+ f = weakref.finalize(a, add, 67, 43, 89)
+ self.assertEqual(f.peek(), (a, add, (67,43,89), {}))
+ self.assertEqual(f.detach(), (a, add, (67,43,89), {}))
+ self.assertEqual(f(), None)
+ self.assertEqual(f(), None)
+ self.assertEqual(f.peek(), None)
+ self.assertEqual(f.detach(), None)
+ self.assertEqual(f.alive, False)
+ self.assertEqual(res, [])
+
+ res = []
+ f = weakref.finalize(a, add, x=67, y=43, z=89)
+ del a
+ self._collect_if_necessary()
+ self.assertEqual(f(), None)
+ self.assertEqual(f(), None)
+ self.assertEqual(f.peek(), None)
+ self.assertEqual(f.detach(), None)
+ self.assertEqual(f.alive, False)
+ self.assertEqual(res, [199])
+
+ def test_order(self):
+ a = self.A()
+ res = []
+
+ f1 = weakref.finalize(a, res.append, 'f1')
+ f2 = weakref.finalize(a, res.append, 'f2')
+ f3 = weakref.finalize(a, res.append, 'f3')
+ f4 = weakref.finalize(a, res.append, 'f4')
+ f5 = weakref.finalize(a, res.append, 'f5')
+
+ # make sure finalizers can keep themselves alive
+ del f1, f4
+
+ self.assertTrue(f2.alive)
+ self.assertTrue(f3.alive)
+ self.assertTrue(f5.alive)
+
+ self.assertTrue(f5.detach())
+ self.assertFalse(f5.alive)
+
+ f5() # nothing because previously unregistered
+ res.append('A')
+ f3() # => res.append('f3')
+ self.assertFalse(f3.alive)
+ res.append('B')
+ f3() # nothing because previously called
+ res.append('C')
+ del a
+ self._collect_if_necessary()
+ # => res.append('f4')
+ # => res.append('f2')
+ # => res.append('f1')
+ self.assertFalse(f2.alive)
+ res.append('D')
+ f2() # nothing because previously called by gc
+
+ expected = ['A', 'f3', 'B', 'C', 'f4', 'f2', 'f1', 'D']
+ self.assertEqual(res, expected)
+
+ def test_all_freed(self):
+ # we want a weakrefable subclass of weakref.finalize
+ class MyFinalizer(weakref.finalize):
+ pass
+
+ a = self.A()
+ res = []
+ def callback():
+ res.append(123)
+ f = MyFinalizer(a, callback)
+
+ wr_callback = weakref.ref(callback)
+ wr_f = weakref.ref(f)
+ del callback, f
+
+ self.assertIsNotNone(wr_callback())
+ self.assertIsNotNone(wr_f())
+
+ del a
+ self._collect_if_necessary()
+
+ self.assertIsNone(wr_callback())
+ self.assertIsNone(wr_f())
+ self.assertEqual(res, [123])
+
+ @classmethod
+ def run_in_child(cls):
+ def error():
+ # Create an atexit finalizer from inside a finalizer called
+ # at exit. This should be the next to be run.
+ g1 = weakref.finalize(cls, print, 'g1')
+ print('f3 error')
+ 1/0
+
+ # cls should stay alive till atexit callbacks run
+ f1 = weakref.finalize(cls, print, 'f1', _global_var)
+ f2 = weakref.finalize(cls, print, 'f2', _global_var)
+ f3 = weakref.finalize(cls, error)
+ f4 = weakref.finalize(cls, print, 'f4', _global_var)
+
+ assert f1.atexit == True
+ f2.atexit = False
+ assert f3.atexit == True
+ assert f4.atexit == True
+
+ def test_atexit(self):
+ prog = ('from test.test_weakref import FinalizeTestCase;'+
+ 'FinalizeTestCase.run_in_child()')
+ rc, out, err = script_helper.assert_python_ok('-c', prog)
+ out = out.decode('ascii').splitlines()
+ self.assertEqual(out, ['f4 foobar', 'f3 error', 'g1', 'f1 foobar'])
+ self.assertTrue(b'ZeroDivisionError' in err)
+
+
libreftest = """ Doctest for examples in the library reference: weakref.rst
>>> import weakref
@@ -1473,10 +1785,12 @@ __test__ = {'libreftest' : libreftest}
def test_main():
support.run_unittest(
ReferencesTestCase,
+ WeakMethodTestCase,
MappingTestCase,
WeakValueDictionaryTestCase,
WeakKeyDictionaryTestCase,
SubclassableWeakrefTestCase,
+ FinalizeTestCase,
)
support.run_doctest(sys.modules[__name__])
diff --git a/Lib/test/test_winreg.py b/Lib/test/test_winreg.py
index cb4cde9bc6..ef4ce552f1 100644
--- a/Lib/test/test_winreg.py
+++ b/Lib/test/test_winreg.py
@@ -8,7 +8,7 @@ threading = support.import_module("threading")
from platform import machine
# Do this first so test will be skipped if module doesn't exist
-support.import_module('winreg')
+support.import_module('winreg', required_on=['win'])
# Now import everything
from winreg import *
@@ -57,13 +57,13 @@ class BaseWinregTests(unittest.TestCase):
def delete_tree(self, root, subkey):
try:
hkey = OpenKey(root, subkey, KEY_ALL_ACCESS)
- except WindowsError:
+ except OSError:
# subkey does not exist
return
while True:
try:
subsubkey = EnumKey(hkey, 0)
- except WindowsError:
+ except OSError:
# no more subkeys
break
self.delete_tree(hkey, subsubkey)
@@ -100,7 +100,7 @@ class BaseWinregTests(unittest.TestCase):
QueryInfoKey(int_sub_key)
self.fail("It appears the CloseKey() function does "
"not close the actual key!")
- except EnvironmentError:
+ except OSError:
pass
# ... and close that key that way :-)
int_key = int(key)
@@ -109,7 +109,7 @@ class BaseWinregTests(unittest.TestCase):
QueryInfoKey(int_key)
self.fail("It appears the key.Close() function "
"does not close the actual key!")
- except EnvironmentError:
+ except OSError:
pass
def _read_test_data(self, root_key, subkeystr="sub_key", OpenKey=OpenKey):
@@ -126,7 +126,7 @@ class BaseWinregTests(unittest.TestCase):
while 1:
try:
data = EnumValue(sub_key, index)
- except EnvironmentError:
+ except OSError:
break
self.assertEqual(data in test_data, True,
"Didn't read back the correct test data")
@@ -147,7 +147,7 @@ class BaseWinregTests(unittest.TestCase):
try:
EnumKey(key, 1)
self.fail("Was able to get a second key when I only have one!")
- except EnvironmentError:
+ except OSError:
pass
key.Close()
@@ -171,7 +171,7 @@ class BaseWinregTests(unittest.TestCase):
# Shouldnt be able to delete it twice!
DeleteKey(key, subkeystr)
self.fail("Deleting the key twice succeeded")
- except EnvironmentError:
+ except OSError:
pass
key.Close()
DeleteKey(root_key, test_key_name)
@@ -179,7 +179,7 @@ class BaseWinregTests(unittest.TestCase):
try:
key = OpenKey(root_key, test_key_name)
self.fail("Could open the non-existent key")
- except WindowsError: # Use this error name this time
+ except OSError: # Use this error name this time
pass
def _test_all(self, root_key, subkeystr="sub_key"):
@@ -230,7 +230,7 @@ class LocalWinregTests(BaseWinregTests):
def test_inexistant_remote_registry(self):
connect = lambda: ConnectRegistry("abcdefghijkl", HKEY_CURRENT_USER)
- self.assertRaises(WindowsError, connect)
+ self.assertRaises(OSError, connect)
def testExpandEnvironmentStrings(self):
r = ExpandEnvironmentStrings("%windir%\\test")
@@ -242,8 +242,8 @@ class LocalWinregTests(BaseWinregTests):
try:
with ConnectRegistry(None, HKEY_LOCAL_MACHINE) as h:
self.assertNotEqual(h.handle, 0)
- raise WindowsError
- except WindowsError:
+ raise OSError
+ except OSError:
self.assertEqual(h.handle, 0)
def test_changing_value(self):
@@ -407,7 +407,7 @@ class Win64WinregTests(BaseWinregTests):
open_fail = lambda: OpenKey(HKEY_CURRENT_USER,
test_reflect_key_name, 0,
KEY_READ | KEY_WOW64_64KEY)
- self.assertRaises(WindowsError, open_fail)
+ self.assertRaises(OSError, open_fail)
# Now explicitly open the 64-bit version of the key
with OpenKey(HKEY_CURRENT_USER, test_reflect_key_name, 0,
@@ -447,7 +447,7 @@ class Win64WinregTests(BaseWinregTests):
open_fail = lambda: OpenKeyEx(HKEY_CURRENT_USER,
test_reflect_key_name, 0,
KEY_READ | KEY_WOW64_64KEY)
- self.assertRaises(WindowsError, open_fail)
+ self.assertRaises(OSError, open_fail)
# Make sure the 32-bit key is actually there
with OpenKeyEx(HKEY_CURRENT_USER, test_reflect_key_name, 0,
diff --git a/Lib/test/test_winsound.py b/Lib/test/test_winsound.py
index 27597874a8..d8a617e64f 100644
--- a/Lib/test/test_winsound.py
+++ b/Lib/test/test_winsound.py
@@ -22,7 +22,7 @@ def has_sound(sound):
key = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER,
"AppEvents\Schemes\Apps\.Default\{0}\.Default".format(sound))
return winreg.EnumValue(key, 0)[1] != ""
- except WindowsError:
+ except OSError:
return False
class BeepTest(unittest.TestCase):
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 7bd8a2c7ee..89971f16ca 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -10,6 +10,7 @@ import io
import operator
import pickle
import sys
+import types
import unittest
import weakref
@@ -240,7 +241,6 @@ class ElementTreeTest(unittest.TestCase):
self.assertEqual(ET.XML, ET.fromstring)
self.assertEqual(ET.PI, ET.ProcessingInstruction)
- self.assertEqual(ET.XMLParser, ET.XMLTreeBuilder)
def test_simpleops(self):
# Basic method sanity checks.
@@ -433,15 +433,6 @@ class ElementTreeTest(unittest.TestCase):
' <empty-element />\n'
'</root>')
- parser = ET.XMLTreeBuilder() # 1.2 compatibility
- parser.feed(data)
- self.serialize_check(parser.close(),
- '<root>\n'
- ' <element key="value">text</element>\n'
- ' <element>text</element>tail\n'
- ' <empty-element />\n'
- '</root>')
-
target = ET.TreeBuilder()
parser = ET.XMLParser(target=target)
parser.feed(data)
@@ -706,9 +697,9 @@ class ElementTreeTest(unittest.TestCase):
'iso8859-13', 'iso8859-14', 'iso8859-15', 'iso8859-16',
'cp437', 'cp720', 'cp737', 'cp775', 'cp850', 'cp852',
'cp855', 'cp856', 'cp857', 'cp858', 'cp860', 'cp861', 'cp862',
- 'cp863', 'cp865', 'cp866', 'cp869', 'cp874', 'cp1006', 'cp1250',
- 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256',
- 'cp1257', 'cp1258',
+ 'cp863', 'cp865', 'cp866', 'cp869', 'cp874', 'cp1006', 'cp1125',
+ 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255',
+ 'cp1256', 'cp1257', 'cp1258',
'mac-cyrillic', 'mac-greek', 'mac-iceland', 'mac-latin2',
'mac-roman', 'mac-turkish',
'iso2022-jp', 'iso2022-jp-1', 'iso2022-jp-2', 'iso2022-jp-2004',
@@ -964,6 +955,160 @@ class ElementTreeTest(unittest.TestCase):
self.assertEqual(serialized, expected)
+class XMLPullParserTest(unittest.TestCase):
+
+ def _feed(self, parser, data, chunk_size=None):
+ if chunk_size is None:
+ parser.feed(data)
+ else:
+ for i in range(0, len(data), chunk_size):
+ parser.feed(data[i:i+chunk_size])
+
+ def assert_event_tags(self, parser, expected):
+ events = parser.read_events()
+ self.assertEqual([(action, elem.tag) for action, elem in events],
+ expected)
+
+ def test_simple_xml(self):
+ for chunk_size in (None, 1, 5):
+ with self.subTest(chunk_size=chunk_size):
+ parser = ET.XMLPullParser()
+ self.assert_event_tags(parser, [])
+ self._feed(parser, "<!-- comment -->\n", chunk_size)
+ self.assert_event_tags(parser, [])
+ self._feed(parser,
+ "<root>\n <element key='value'>text</element",
+ chunk_size)
+ self.assert_event_tags(parser, [])
+ self._feed(parser, ">\n", chunk_size)
+ self.assert_event_tags(parser, [('end', 'element')])
+ self._feed(parser, "<element>text</element>tail\n", chunk_size)
+ self._feed(parser, "<empty-element/>\n", chunk_size)
+ self.assert_event_tags(parser, [
+ ('end', 'element'),
+ ('end', 'empty-element'),
+ ])
+ self._feed(parser, "</root>\n", chunk_size)
+ self.assert_event_tags(parser, [('end', 'root')])
+ self.assertIsNone(parser.close())
+
+ def test_feed_while_iterating(self):
+ parser = ET.XMLPullParser()
+ it = parser.read_events()
+ self._feed(parser, "<root>\n <element key='value'>text</element>\n")
+ action, elem = next(it)
+ self.assertEqual((action, elem.tag), ('end', 'element'))
+ self._feed(parser, "</root>\n")
+ action, elem = next(it)
+ self.assertEqual((action, elem.tag), ('end', 'root'))
+ with self.assertRaises(StopIteration):
+ next(it)
+
+ def test_simple_xml_with_ns(self):
+ parser = ET.XMLPullParser()
+ self.assert_event_tags(parser, [])
+ self._feed(parser, "<!-- comment -->\n")
+ self.assert_event_tags(parser, [])
+ self._feed(parser, "<root xmlns='namespace'>\n")
+ self.assert_event_tags(parser, [])
+ self._feed(parser, "<element key='value'>text</element")
+ self.assert_event_tags(parser, [])
+ self._feed(parser, ">\n")
+ self.assert_event_tags(parser, [('end', '{namespace}element')])
+ self._feed(parser, "<element>text</element>tail\n")
+ self._feed(parser, "<empty-element/>\n")
+ self.assert_event_tags(parser, [
+ ('end', '{namespace}element'),
+ ('end', '{namespace}empty-element'),
+ ])
+ self._feed(parser, "</root>\n")
+ self.assert_event_tags(parser, [('end', '{namespace}root')])
+ self.assertIsNone(parser.close())
+
+ def test_ns_events(self):
+ parser = ET.XMLPullParser(events=('start-ns', 'end-ns'))
+ self._feed(parser, "<!-- comment -->\n")
+ self._feed(parser, "<root xmlns='namespace'>\n")
+ self.assertEqual(
+ list(parser.read_events()),
+ [('start-ns', ('', 'namespace'))])
+ self._feed(parser, "<element key='value'>text</element")
+ self._feed(parser, ">\n")
+ self._feed(parser, "<element>text</element>tail\n")
+ self._feed(parser, "<empty-element/>\n")
+ self._feed(parser, "</root>\n")
+ self.assertEqual(list(parser.read_events()), [('end-ns', None)])
+ self.assertIsNone(parser.close())
+
+ def test_events(self):
+ parser = ET.XMLPullParser(events=())
+ self._feed(parser, "<root/>\n")
+ self.assert_event_tags(parser, [])
+
+ parser = ET.XMLPullParser(events=('start', 'end'))
+ self._feed(parser, "<!-- comment -->\n")
+ self.assert_event_tags(parser, [])
+ self._feed(parser, "<root>\n")
+ self.assert_event_tags(parser, [('start', 'root')])
+ self._feed(parser, "<element key='value'>text</element")
+ self.assert_event_tags(parser, [('start', 'element')])
+ self._feed(parser, ">\n")
+ self.assert_event_tags(parser, [('end', 'element')])
+ self._feed(parser,
+ "<element xmlns='foo'>text<empty-element/></element>tail\n")
+ self.assert_event_tags(parser, [
+ ('start', '{foo}element'),
+ ('start', '{foo}empty-element'),
+ ('end', '{foo}empty-element'),
+ ('end', '{foo}element'),
+ ])
+ self._feed(parser, "</root>")
+ self.assertIsNone(parser.close())
+ self.assert_event_tags(parser, [('end', 'root')])
+
+ parser = ET.XMLPullParser(events=('start',))
+ self._feed(parser, "<!-- comment -->\n")
+ self.assert_event_tags(parser, [])
+ self._feed(parser, "<root>\n")
+ self.assert_event_tags(parser, [('start', 'root')])
+ self._feed(parser, "<element key='value'>text</element")
+ self.assert_event_tags(parser, [('start', 'element')])
+ self._feed(parser, ">\n")
+ self.assert_event_tags(parser, [])
+ self._feed(parser,
+ "<element xmlns='foo'>text<empty-element/></element>tail\n")
+ self.assert_event_tags(parser, [
+ ('start', '{foo}element'),
+ ('start', '{foo}empty-element'),
+ ])
+ self._feed(parser, "</root>")
+ self.assertIsNone(parser.close())
+
+ def test_events_sequence(self):
+ # Test that events can be some sequence that's not just a tuple or list
+ eventset = {'end', 'start'}
+ parser = ET.XMLPullParser(events=eventset)
+ self._feed(parser, "<foo>bar</foo>")
+ self.assert_event_tags(parser, [('start', 'foo'), ('end', 'foo')])
+
+ class DummyIter:
+ def __init__(self):
+ self.events = iter(['start', 'end', 'start-ns'])
+ def __iter__(self):
+ return self
+ def __next__(self):
+ return next(self.events)
+
+ parser = ET.XMLPullParser(events=DummyIter())
+ self._feed(parser, "<foo>bar</foo>")
+ self.assert_event_tags(parser, [('start', 'foo'), ('end', 'foo')])
+
+
+ def test_unknown_event(self):
+ with self.assertRaises(ValueError):
+ ET.XMLPullParser(events=('start', 'end', 'bogus'))
+
+
#
# xinclude tests (samples from appendix C of the xinclude specification)
@@ -1305,7 +1450,7 @@ class BugsTest(unittest.TestCase):
# Don't crash when using custom entities.
ENTITIES = {'rsquo': '\u2019', 'lsquo': '\u2018'}
- parser = ET.XMLTreeBuilder()
+ parser = ET.XMLParser()
parser.entity.update(ENTITIES)
parser.feed("""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE patent-application-publication SYSTEM "pap-v15-2001-01-31.dtd" []>
@@ -1643,6 +1788,11 @@ class ElementFindTest(unittest.TestCase):
self.assertEqual(e.find('./tag[last()-1]').attrib['class'], 'c')
self.assertEqual(e.find('./tag[last()-2]').attrib['class'], 'b')
+ self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[0]')
+ self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[-1]')
+ self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()-0]')
+ self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()+1]')
+
def test_findall(self):
e = ET.XML(SAMPLE_XML)
e[2] = ET.XML(SAMPLE_SECTION)
@@ -1902,7 +2052,7 @@ class TreeBuilderTest(unittest.TestCase):
# Mimick SimpleTAL's behaviour (issue #16089): both versions of
# TreeBuilder should be able to cope with a subclass of the
# pure Python Element class.
- base = ET._Element
+ base = ET._Element_Py
# Not from a C extension
self.assertEqual(base.__module__, 'xml.etree.ElementTree')
# Force some multiple inheritance with a C class to make things
@@ -2261,6 +2411,18 @@ class IOTest(unittest.TestCase):
ET.tostring(root, 'utf-16'),
b''.join(ET.tostringlist(root, 'utf-16')))
+ def test_short_empty_elements(self):
+ root = ET.fromstring('<tag>a<x />b<y></y>c</tag>')
+ self.assertEqual(
+ ET.tostring(root, 'unicode'),
+ '<tag>a<x />b<y />c</tag>')
+ self.assertEqual(
+ ET.tostring(root, 'unicode', short_empty_elements=True),
+ '<tag>a<x />b<y />c</tag>')
+ self.assertEqual(
+ ET.tostring(root, 'unicode', short_empty_elements=False),
+ '<tag>a<x></x>b<y></y>c</tag>')
+
class ParseErrorTest(unittest.TestCase):
def test_subclass(self):
@@ -2326,8 +2488,11 @@ class NoAcceleratorTest(unittest.TestCase):
# Test that the C accelerator was not imported for pyET
def test_correct_import_pyET(self):
- self.assertEqual(pyET.Element.__module__, 'xml.etree.ElementTree')
- self.assertEqual(pyET.SubElement.__module__, 'xml.etree.ElementTree')
+ # The type of methods defined in Python code is types.FunctionType,
+ # while the type of methods defined inside _elementtree is
+ # <class 'wrapper_descriptor'>
+ self.assertIsInstance(pyET.Element.__init__, types.FunctionType)
+ self.assertIsInstance(pyET.XMLParser.__init__, types.FunctionType)
# --------------------------------------------------------------------
@@ -2397,6 +2562,7 @@ def test_main(module=None):
ElementIterTest,
TreeBuilderTest,
XMLParserTest,
+ XMLPullParserTest,
BugsTest,
]
diff --git a/Lib/test/test_xml_etree_c.py b/Lib/test/test_xml_etree_c.py
index bcaa724344..b3ff7ae37d 100644
--- a/Lib/test/test_xml_etree_c.py
+++ b/Lib/test/test_xml_etree_c.py
@@ -2,6 +2,7 @@
import sys, struct
from test import support
from test.support import import_fresh_module
+import types
import unittest
cET = import_fresh_module('xml.etree.ElementTree',
@@ -33,14 +34,22 @@ class TestAliasWorking(unittest.TestCase):
@unittest.skipUnless(cET, 'requires _elementtree')
+@support.cpython_only
class TestAcceleratorImported(unittest.TestCase):
# Test that the C accelerator was imported, as expected
def test_correct_import_cET(self):
+ # SubElement is a function so it retains _elementtree as its module.
self.assertEqual(cET.SubElement.__module__, '_elementtree')
def test_correct_import_cET_alias(self):
self.assertEqual(cET_alias.SubElement.__module__, '_elementtree')
+ def test_parser_comes_from_C(self):
+ # The type of methods defined in Python code is types.FunctionType,
+ # while the type of methods defined inside _elementtree is
+ # <class 'wrapper_descriptor'>
+ self.assertNotIsInstance(cET.Element.__init__, types.FunctionType)
+
@unittest.skipUnless(cET, 'requires _elementtree')
@support.cpython_only
diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py
index cc52259c82..99b3eda13e 100644
--- a/Lib/test/test_xmlrpc.py
+++ b/Lib/test/test_xmlrpc.py
@@ -15,6 +15,10 @@ import contextlib
from test import support
try:
+ import gzip
+except ImportError:
+ gzip = None
+try:
import threading
except ImportError:
threading = None
@@ -216,7 +220,7 @@ class XMLRPCTestCase(unittest.TestCase):
xmlrpc.client.ServerProxy('https://localhost:9999').bad_function()
except NotImplementedError:
self.assertFalse(has_ssl, "xmlrpc client's error with SSL support")
- except socket.error:
+ except OSError:
self.assertTrue(has_ssl)
class HelperTestCase(unittest.TestCase):
@@ -376,6 +380,11 @@ def http_server(evt, numrequests, requestHandler=None):
if name == 'div':
return 'This is the div function'
+ class Fixture:
+ @staticmethod
+ def getData():
+ return '42'
+
def my_function():
'''This is my function'''
return True
@@ -407,7 +416,8 @@ def http_server(evt, numrequests, requestHandler=None):
serv.register_function(pow)
serv.register_function(lambda x,y: x+y, 'add')
serv.register_function(my_function)
- serv.register_instance(TestInstanceClass())
+ testInstance = TestInstanceClass()
+ serv.register_instance(testInstance, allow_dotted_names=True)
evt.set()
# handle up to 'numrequests' requests
@@ -500,7 +510,7 @@ def is_unavailable_exception(e):
return True
exc_mess = e.headers.get('X-exception')
except AttributeError:
- # Ignore socket.errors here.
+ # Ignore OSErrors here.
exc_mess = str(e)
if exc_mess and 'temporarily unavailable' in exc_mess.lower():
@@ -515,7 +525,7 @@ def make_request_and_skipIf(condition, reason):
def make_request_and_skip(self):
try:
xmlrpclib.ServerProxy(URL).my_function()
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
if not is_unavailable_exception(e):
raise
raise unittest.SkipTest(reason)
@@ -553,7 +563,7 @@ class SimpleServerTestCase(BaseServerTestCase):
try:
p = xmlrpclib.ServerProxy(URL)
self.assertEqual(p.pow(6,8), 6**8)
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -566,7 +576,7 @@ class SimpleServerTestCase(BaseServerTestCase):
p = xmlrpclib.ServerProxy(URL)
self.assertEqual(p.add(start_string, end_string),
start_string + end_string)
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -587,12 +597,13 @@ class SimpleServerTestCase(BaseServerTestCase):
def test_introspection1(self):
expected_methods = set(['pow', 'div', 'my_function', 'add',
'system.listMethods', 'system.methodHelp',
- 'system.methodSignature', 'system.multicall'])
+ 'system.methodSignature', 'system.multicall',
+ 'Fixture'])
try:
p = xmlrpclib.ServerProxy(URL)
meth = p.system.listMethods()
self.assertEqual(set(meth), expected_methods)
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -605,7 +616,7 @@ class SimpleServerTestCase(BaseServerTestCase):
p = xmlrpclib.ServerProxy(URL)
divhelp = p.system.methodHelp('div')
self.assertEqual(divhelp, 'This is the div function')
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -619,7 +630,7 @@ class SimpleServerTestCase(BaseServerTestCase):
p = xmlrpclib.ServerProxy(URL)
myfunction = p.system.methodHelp('my_function')
self.assertEqual(myfunction, 'This is my function')
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -632,7 +643,7 @@ class SimpleServerTestCase(BaseServerTestCase):
p = xmlrpclib.ServerProxy(URL)
divsig = p.system.methodSignature('div')
self.assertEqual(divsig, 'signatures not supported')
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -649,7 +660,7 @@ class SimpleServerTestCase(BaseServerTestCase):
self.assertEqual(add_result, 2+3)
self.assertEqual(pow_result, 6**8)
self.assertEqual(div_result, 127//42)
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -670,7 +681,7 @@ class SimpleServerTestCase(BaseServerTestCase):
self.assertEqual(result.results[0]['faultString'],
'<class \'Exception\'>:method "this_is_not_exists" '
'is not supported')
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -686,6 +697,12 @@ class SimpleServerTestCase(BaseServerTestCase):
# This avoids waiting for the socket timeout.
self.test_simple1()
+ def test_allow_dotted_names_true(self):
+ # XXX also need allow_dotted_names_false test.
+ server = xmlrpclib.ServerProxy("http://%s:%d/RPC2" % (ADDR, PORT))
+ data = server.Fixture.getData()
+ self.assertEqual(data, '42')
+
def test_unicode_host(self):
server = xmlrpclib.ServerProxy("http://%s:%d/RPC2" % (ADDR, PORT))
self.assertEqual(server.add("a", "\xe9"), "a\xe9")
@@ -793,6 +810,7 @@ class KeepaliveServerTestCase2(BaseKeepaliveServerTestCase):
#A test case that verifies that gzip encoding works in both directions
#(for a request and the response)
+@unittest.skipIf(gzip is None, 'requires gzip')
class GzipServerTestCase(BaseServerTestCase):
#a request handler that supports keep-alive and logs requests into a
#class variable
@@ -923,7 +941,7 @@ class FailingServerTestCase(unittest.TestCase):
try:
p = xmlrpclib.ServerProxy(URL)
self.assertEqual(p.pow(6,8), 6**8)
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e):
# protocol error; provide additional information in test output
@@ -936,7 +954,7 @@ class FailingServerTestCase(unittest.TestCase):
try:
p = xmlrpclib.ServerProxy(URL)
p.pow(6,8)
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e) and hasattr(e, "headers"):
# The two server-side error headers shouldn't be sent back in this case
@@ -956,7 +974,7 @@ class FailingServerTestCase(unittest.TestCase):
try:
p = xmlrpclib.ServerProxy(URL)
p.pow(6,8)
- except (xmlrpclib.ProtocolError, socket.error) as e:
+ except (xmlrpclib.ProtocolError, OSError) as e:
# ignore failures due to non-blocking socket 'unavailable' errors
if not is_unavailable_exception(e) and hasattr(e, "headers"):
# We should get error info in the response
@@ -1084,23 +1102,13 @@ class UseBuiltinTypesTestCase(unittest.TestCase):
@support.reap_threads
def test_main():
- xmlrpc_tests = [XMLRPCTestCase, HelperTestCase, DateTimeTestCase,
- BinaryTestCase, FaultTestCase]
- xmlrpc_tests.append(UseBuiltinTypesTestCase)
- xmlrpc_tests.append(SimpleServerTestCase)
- xmlrpc_tests.append(KeepaliveServerTestCase1)
- xmlrpc_tests.append(KeepaliveServerTestCase2)
- try:
- import gzip
- xmlrpc_tests.append(GzipServerTestCase)
- except ImportError:
- pass #gzip not supported in this build
- xmlrpc_tests.append(MultiPathServerTestCase)
- xmlrpc_tests.append(ServerProxyTestCase)
- xmlrpc_tests.append(FailingServerTestCase)
- xmlrpc_tests.append(CGIHandlerTestCase)
-
- support.run_unittest(*xmlrpc_tests)
+ support.run_unittest(XMLRPCTestCase, HelperTestCase, DateTimeTestCase,
+ BinaryTestCase, FaultTestCase, UseBuiltinTypesTestCase,
+ SimpleServerTestCase, KeepaliveServerTestCase1,
+ KeepaliveServerTestCase2, GzipServerTestCase,
+ MultiPathServerTestCase, ServerProxyTestCase, FailingServerTestCase,
+ CGIHandlerTestCase)
+
if __name__ == "__main__":
test_main()
diff --git a/Lib/test/test_xmlrpc_net.py b/Lib/test/test_xmlrpc_net.py
index dfb5f9aa3d..00aca199df 100644
--- a/Lib/test/test_xmlrpc_net.py
+++ b/Lib/test/test_xmlrpc_net.py
@@ -9,32 +9,7 @@ from test import support
import xmlrpc.client as xmlrpclib
-class CurrentTimeTest(unittest.TestCase):
-
- def test_current_time(self):
- # Get the current time from xmlrpc.com. This code exercises
- # the minimal HTTP functionality in xmlrpclib.
- self.skipTest("time.xmlrpc.com is unreliable")
- server = xmlrpclib.ServerProxy("http://time.xmlrpc.com/RPC2")
- try:
- t0 = server.currentTime.getCurrentTime()
- except socket.error as e:
- self.skipTest("network error: %s" % e)
- return
-
- # Perform a minimal sanity check on the result, just to be sure
- # the request means what we think it means.
- t1 = xmlrpclib.DateTime()
-
- dt0 = xmlrpclib._datetime_type(t0.value)
- dt1 = xmlrpclib._datetime_type(t1.value)
- if dt0 > dt1:
- delta = dt0 - dt1
- else:
- delta = dt1 - dt0
- # The difference between the system time here and the system
- # time on the server should not be too big.
- self.assertTrue(delta.days <= 1)
+class PythonBuildersTest(unittest.TestCase):
def test_python_builders(self):
# Get the list of builders from the XMLRPC buildbot interface at
@@ -42,7 +17,7 @@ class CurrentTimeTest(unittest.TestCase):
server = xmlrpclib.ServerProxy("http://buildbot.python.org/all/xmlrpc/")
try:
builders = server.getAllBuilders()
- except socket.error as e:
+ except OSError as e:
self.skipTest("network error: %s" % e)
return
self.addCleanup(lambda: server('close')())
@@ -55,7 +30,7 @@ class CurrentTimeTest(unittest.TestCase):
def test_main():
support.requires("network")
- support.run_unittest(CurrentTimeTest)
+ support.run_unittest(PythonBuildersTest)
if __name__ == "__main__":
test_main()
diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py
index ad0c0b7b41..49eccbb1d7 100644
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -1,7 +1,7 @@
import io
import os
import sys
-import imp
+import importlib.util
import time
import shutil
import struct
@@ -14,7 +14,7 @@ from random import randint, random, getrandbits
from test.support import (TESTFN, findfile, unlink,
requires_zlib, requires_bz2, requires_lzma,
- captured_stdout)
+ captured_stdout, check_warnings)
TESTFN2 = TESTFN + "2"
TESTFNDIR = TESTFN + "d"
@@ -35,6 +35,10 @@ def get_files(test):
yield f
test.assertFalse(f.closed)
+def openU(zipfp, fn):
+ with check_warnings(('', DeprecationWarning)):
+ return zipfp.open(fn, 'rU')
+
class AbstractTestsWithSourceFile:
@classmethod
def setUpClass(cls):
@@ -506,12 +510,12 @@ class StoredTestZip64InSmallFiles(AbstractTestZip64InSmallFiles,
compression = zipfile.ZIP_STORED
def large_file_exception_test(self, f, compression):
- with zipfile.ZipFile(f, "w", compression) as zipfp:
+ with zipfile.ZipFile(f, "w", compression, allowZip64=False) as zipfp:
self.assertRaises(zipfile.LargeZipFile,
zipfp.write, TESTFN, "another.name")
def large_file_exception_test2(self, f, compression):
- with zipfile.ZipFile(f, "w", compression) as zipfp:
+ with zipfile.ZipFile(f, "w", compression, allowZip64=False) as zipfp:
self.assertRaises(zipfile.LargeZipFile,
zipfp.writestr, "another.name", self.data)
@@ -557,7 +561,7 @@ class PyZipFileTests(unittest.TestCase):
if os.altsep is not None:
path_split.extend(fn.split(os.altsep))
if '__pycache__' in path_split:
- fn = imp.source_from_cache(fn)
+ fn = importlib.util.source_from_cache(fn)
else:
fn = fn[:-1]
@@ -591,6 +595,34 @@ class PyZipFileTests(unittest.TestCase):
self.assertCompiledIn('email/__init__.py', names)
self.assertCompiledIn('email/mime/text.py', names)
+ def test_write_filtered_python_package(self):
+ import test
+ packagedir = os.path.dirname(test.__file__)
+
+ with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp:
+
+ # first make sure that the test folder gives error messages
+ # (on the badsyntax_... files)
+ with captured_stdout() as reportSIO:
+ zipfp.writepy(packagedir)
+ reportStr = reportSIO.getvalue()
+ self.assertTrue('SyntaxError' in reportStr)
+
+ # then check that the filter works on the whole package
+ with captured_stdout() as reportSIO:
+ zipfp.writepy(packagedir, filterfunc=lambda whatever: False)
+ reportStr = reportSIO.getvalue()
+ self.assertTrue('SyntaxError' not in reportStr)
+
+ # then check that the filter works on individual files
+ with captured_stdout() as reportSIO:
+ zipfp.writepy(packagedir, filterfunc=lambda fn:
+ 'bad' not in fn)
+ reportStr = reportSIO.getvalue()
+ if reportStr:
+ print(reportStr)
+ self.assertTrue('SyntaxError' not in reportStr)
+
def test_write_with_optimization(self):
import email
packagedir = os.path.dirname(email.__file__)
@@ -600,7 +632,7 @@ class PyZipFileTests(unittest.TestCase):
ext = '.pyo' if optlevel == 1 else '.pyc'
with TemporaryFile() as t, \
- zipfile.PyZipFile(t, "w", optimize=optlevel) as zipfp:
+ zipfile.PyZipFile(t, "w", optimize=optlevel) as zipfp:
zipfp.writepy(packagedir)
names = zipfp.namelist()
@@ -630,6 +662,26 @@ class PyZipFileTests(unittest.TestCase):
finally:
shutil.rmtree(TESTFN2)
+ def test_write_python_directory_filtered(self):
+ os.mkdir(TESTFN2)
+ try:
+ with open(os.path.join(TESTFN2, "mod1.py"), "w") as fp:
+ fp.write("print(42)\n")
+
+ with open(os.path.join(TESTFN2, "mod2.py"), "w") as fp:
+ fp.write("print(42 * 42)\n")
+
+ with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp:
+ zipfp.writepy(TESTFN2, filterfunc=lambda fn:
+ not fn.endswith('mod2.py'))
+
+ names = zipfp.namelist()
+ self.assertCompiledIn('mod1.py', names)
+ self.assertNotIn('mod2.py', names)
+
+ finally:
+ shutil.rmtree(TESTFN2)
+
def test_write_non_pyfile(self):
with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp:
with open(TESTFN, 'w') as f:
@@ -733,25 +785,25 @@ class ExtractTests(unittest.TestCase):
def test_extract_hackers_arcnames_windows_only(self):
"""Test combination of path fixing and windows name sanitization."""
windows_hacknames = [
- (r'..\foo\bar', 'foo/bar'),
- (r'..\/foo\/bar', 'foo/bar'),
- (r'foo/\..\/bar', 'foo/bar'),
- (r'foo\/../\bar', 'foo/bar'),
- (r'C:foo/bar', 'foo/bar'),
- (r'C:/foo/bar', 'foo/bar'),
- (r'C://foo/bar', 'foo/bar'),
- (r'C:\foo\bar', 'foo/bar'),
- (r'//conky/mountpoint/foo/bar', 'foo/bar'),
- (r'\\conky\mountpoint\foo\bar', 'foo/bar'),
- (r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
- (r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
- (r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
- (r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
- (r'//?/C:/foo/bar', 'foo/bar'),
- (r'\\?\C:\foo\bar', 'foo/bar'),
- (r'C:/../C:/foo/bar', 'C_/foo/bar'),
- (r'a:b\c<d>e|f"g?h*i', 'b/c_d_e_f_g_h_i'),
- ('../../foo../../ba..r', 'foo/ba..r'),
+ (r'..\foo\bar', 'foo/bar'),
+ (r'..\/foo\/bar', 'foo/bar'),
+ (r'foo/\..\/bar', 'foo/bar'),
+ (r'foo\/../\bar', 'foo/bar'),
+ (r'C:foo/bar', 'foo/bar'),
+ (r'C:/foo/bar', 'foo/bar'),
+ (r'C://foo/bar', 'foo/bar'),
+ (r'C:\foo\bar', 'foo/bar'),
+ (r'//conky/mountpoint/foo/bar', 'foo/bar'),
+ (r'\\conky\mountpoint\foo\bar', 'foo/bar'),
+ (r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
+ (r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
+ (r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
+ (r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
+ (r'//?/C:/foo/bar', 'foo/bar'),
+ (r'\\?\C:\foo\bar', 'foo/bar'),
+ (r'C:/../C:/foo/bar', 'C_/foo/bar'),
+ (r'a:b\c<d>e|f"g?h*i', 'b/c_d_e_f_g_h_i'),
+ ('../../foo../../ba..r', 'foo/ba..r'),
]
self._test_extract_hackers_arcnames(windows_hacknames)
@@ -827,6 +879,17 @@ class OtherTests(unittest.TestCase):
data += zipfp.read(info)
self.assertIn(data, {b"foobar", b"barfoo"})
+ def test_universal_deprecation(self):
+ f = io.BytesIO()
+ with zipfile.ZipFile(f, "w") as zipfp:
+ zipfp.writestr('spam.txt', b'ababagalamaga')
+
+ with zipfile.ZipFile(f, "r") as zipfp:
+ for mode in 'U', 'rU':
+ with self.assertWarns(DeprecationWarning):
+ zipopen = zipfp.open('spam.txt', mode)
+ zipopen.close()
+
def test_universal_readaheads(self):
f = io.BytesIO()
@@ -836,7 +899,7 @@ class OtherTests(unittest.TestCase):
data2 = b''
with zipfile.ZipFile(f, 'r') as zipfp, \
- zipfp.open(TESTFN, 'rU') as zipopen:
+ openU(zipfp, TESTFN) as zipopen:
for line in zipopen:
data2 += line
@@ -877,10 +940,10 @@ class OtherTests(unittest.TestCase):
def test_unsupported_version(self):
# File has an extract_version of 120
data = (b'PK\x03\x04x\x00\x00\x00\x00\x00!p\xa1@\x00\x00\x00\x00\x00\x00'
- b'\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00xPK\x01\x02x\x03x\x00\x00\x00\x00'
- b'\x00!p\xa1@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00'
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00\x00xPK\x05\x06'
- b'\x00\x00\x00\x00\x01\x00\x01\x00/\x00\x00\x00\x1f\x00\x00\x00\x00\x00')
+ b'\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00xPK\x01\x02x\x03x\x00\x00\x00\x00'
+ b'\x00!p\xa1@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00'
+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00\x00xPK\x05\x06'
+ b'\x00\x00\x00\x00\x01\x00\x01\x00/\x00\x00\x00\x1f\x00\x00\x00\x00\x00')
self.assertRaises(NotImplementedError, zipfile.ZipFile,
io.BytesIO(data), 'r')
@@ -913,7 +976,7 @@ class OtherTests(unittest.TestCase):
try:
with zipfile.ZipFile(TESTFN, 'a') as zf:
zf.writestr(filename, content)
- except IOError:
+ except OSError:
self.fail('Could not append data to a non-existent zip file.')
self.assertTrue(os.path.exists(TESTFN))
@@ -985,7 +1048,7 @@ class OtherTests(unittest.TestCase):
fp.seek(0, 0)
self.assertTrue(zipfile.is_zipfile(fp))
- def test_non_existent_file_raises_IOError(self):
+ def test_non_existent_file_raises_OSError(self):
# make sure we don't raise an AttributeError when a partially-constructed
# ZipFile instance is finalized; this tests for regression on SF tracker
# bug #403871.
@@ -997,7 +1060,7 @@ class OtherTests(unittest.TestCase):
# it is ignored, but the user should be sufficiently annoyed by
# the message on the output that regression will be noticed
# quickly.
- self.assertRaises(IOError, zipfile.ZipFile, TESTFN)
+ self.assertRaises(OSError, zipfile.ZipFile, TESTFN)
def test_empty_file_raises_BadZipFile(self):
f = open(TESTFN, 'w')
@@ -1066,11 +1129,11 @@ class OtherTests(unittest.TestCase):
def test_unsupported_compression(self):
# data is declared as shrunk, but actually deflated
data = (b'PK\x03\x04.\x00\x00\x00\x01\x00\xe4C\xa1@\x00\x00\x00'
- b'\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00x\x03\x00PK\x01'
- b'\x02.\x03.\x00\x00\x00\x01\x00\xe4C\xa1@\x00\x00\x00\x00\x02\x00\x00'
- b'\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
- b'\x80\x01\x00\x00\x00\x00xPK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00'
- b'/\x00\x00\x00!\x00\x00\x00\x00\x00')
+ b'\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00x\x03\x00PK\x01'
+ b'\x02.\x03.\x00\x00\x00\x01\x00\xe4C\xa1@\x00\x00\x00\x00\x02\x00\x00'
+ b'\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+ b'\x80\x01\x00\x00\x00\x00xPK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00'
+ b'/\x00\x00\x00!\x00\x00\x00\x00\x00')
with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf:
self.assertRaises(NotImplementedError, zipf.open, 'x')
@@ -1184,7 +1247,7 @@ class OtherTests(unittest.TestCase):
def test_open_empty_file(self):
# Issue 1710703: Check that opening a file with less than 22 bytes
# raises a BadZipFile exception (rather than the previously unhelpful
- # IOError)
+ # OSError)
f = open(TESTFN, 'w')
f.close()
self.assertRaises(zipfile.BadZipFile, zipfile.ZipFile, TESTFN, 'r')
@@ -1232,57 +1295,57 @@ class AbstractBadCrcTests:
class StoredBadCrcTests(AbstractBadCrcTests, unittest.TestCase):
compression = zipfile.ZIP_STORED
zip_with_bad_crc = (
- b'PK\003\004\024\0\0\0\0\0 \213\212;:r'
- b'\253\377\f\0\0\0\f\0\0\0\005\0\0\000af'
- b'ilehello,AworldP'
- b'K\001\002\024\003\024\0\0\0\0\0 \213\212;:'
- b'r\253\377\f\0\0\0\f\0\0\0\005\0\0\0\0'
- b'\0\0\0\0\0\0\0\200\001\0\0\0\000afi'
- b'lePK\005\006\0\0\0\0\001\0\001\0003\000'
- b'\0\0/\0\0\0\0\0')
+ b'PK\003\004\024\0\0\0\0\0 \213\212;:r'
+ b'\253\377\f\0\0\0\f\0\0\0\005\0\0\000af'
+ b'ilehello,AworldP'
+ b'K\001\002\024\003\024\0\0\0\0\0 \213\212;:'
+ b'r\253\377\f\0\0\0\f\0\0\0\005\0\0\0\0'
+ b'\0\0\0\0\0\0\0\200\001\0\0\0\000afi'
+ b'lePK\005\006\0\0\0\0\001\0\001\0003\000'
+ b'\0\0/\0\0\0\0\0')
@requires_zlib
class DeflateBadCrcTests(AbstractBadCrcTests, unittest.TestCase):
compression = zipfile.ZIP_DEFLATED
zip_with_bad_crc = (
- b'PK\x03\x04\x14\x00\x00\x00\x08\x00n}\x0c=FA'
- b'KE\x10\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00af'
- b'ile\xcbH\xcd\xc9\xc9W(\xcf/\xcaI\xc9\xa0'
- b'=\x13\x00PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00n'
- b'}\x0c=FAKE\x10\x00\x00\x00n\x00\x00\x00\x05'
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00'
- b'\x00afilePK\x05\x06\x00\x00\x00\x00\x01\x00'
- b'\x01\x003\x00\x00\x003\x00\x00\x00\x00\x00')
+ b'PK\x03\x04\x14\x00\x00\x00\x08\x00n}\x0c=FA'
+ b'KE\x10\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00af'
+ b'ile\xcbH\xcd\xc9\xc9W(\xcf/\xcaI\xc9\xa0'
+ b'=\x13\x00PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00n'
+ b'}\x0c=FAKE\x10\x00\x00\x00n\x00\x00\x00\x05'
+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00'
+ b'\x00afilePK\x05\x06\x00\x00\x00\x00\x01\x00'
+ b'\x01\x003\x00\x00\x003\x00\x00\x00\x00\x00')
@requires_bz2
class Bzip2BadCrcTests(AbstractBadCrcTests, unittest.TestCase):
compression = zipfile.ZIP_BZIP2
zip_with_bad_crc = (
- b'PK\x03\x04\x14\x03\x00\x00\x0c\x00nu\x0c=FA'
- b'KE8\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00af'
- b'ileBZh91AY&SY\xd4\xa8\xca'
- b'\x7f\x00\x00\x0f\x11\x80@\x00\x06D\x90\x80 \x00 \xa5'
- b'P\xd9!\x03\x03\x13\x13\x13\x89\xa9\xa9\xc2u5:\x9f'
- b'\x8b\xb9"\x9c(HjTe?\x80PK\x01\x02\x14'
- b'\x03\x14\x03\x00\x00\x0c\x00nu\x0c=FAKE8'
- b'\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00'
- b'\x00 \x80\x80\x81\x00\x00\x00\x00afilePK'
- b'\x05\x06\x00\x00\x00\x00\x01\x00\x01\x003\x00\x00\x00[\x00'
- b'\x00\x00\x00\x00')
+ b'PK\x03\x04\x14\x03\x00\x00\x0c\x00nu\x0c=FA'
+ b'KE8\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00af'
+ b'ileBZh91AY&SY\xd4\xa8\xca'
+ b'\x7f\x00\x00\x0f\x11\x80@\x00\x06D\x90\x80 \x00 \xa5'
+ b'P\xd9!\x03\x03\x13\x13\x13\x89\xa9\xa9\xc2u5:\x9f'
+ b'\x8b\xb9"\x9c(HjTe?\x80PK\x01\x02\x14'
+ b'\x03\x14\x03\x00\x00\x0c\x00nu\x0c=FAKE8'
+ b'\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00'
+ b'\x00 \x80\x80\x81\x00\x00\x00\x00afilePK'
+ b'\x05\x06\x00\x00\x00\x00\x01\x00\x01\x003\x00\x00\x00[\x00'
+ b'\x00\x00\x00\x00')
@requires_lzma
class LzmaBadCrcTests(AbstractBadCrcTests, unittest.TestCase):
compression = zipfile.ZIP_LZMA
zip_with_bad_crc = (
- b'PK\x03\x04\x14\x03\x00\x00\x0e\x00nu\x0c=FA'
- b'KE\x1b\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00af'
- b'ile\t\x04\x05\x00]\x00\x00\x00\x04\x004\x19I'
- b'\xee\x8d\xe9\x17\x89:3`\tq!.8\x00PK'
- b'\x01\x02\x14\x03\x14\x03\x00\x00\x0e\x00nu\x0c=FA'
- b'KE\x1b\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00\x00\x00'
- b'\x00\x00\x00\x00 \x80\x80\x81\x00\x00\x00\x00afil'
- b'ePK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x003\x00\x00'
- b'\x00>\x00\x00\x00\x00\x00')
+ b'PK\x03\x04\x14\x03\x00\x00\x0e\x00nu\x0c=FA'
+ b'KE\x1b\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00af'
+ b'ile\t\x04\x05\x00]\x00\x00\x00\x04\x004\x19I'
+ b'\xee\x8d\xe9\x17\x89:3`\tq!.8\x00PK'
+ b'\x01\x02\x14\x03\x14\x03\x00\x00\x0e\x00nu\x0c=FA'
+ b'KE\x1b\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00\x00\x00'
+ b'\x00\x00\x00\x00 \x80\x80\x81\x00\x00\x00\x00afil'
+ b'ePK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x003\x00\x00'
+ b'\x00>\x00\x00\x00\x00\x00')
class DecryptionTests(unittest.TestCase):
@@ -1291,22 +1354,22 @@ class DecryptionTests(unittest.TestCase):
ZIP file."""
data = (
- b'PK\x03\x04\x14\x00\x01\x00\x00\x00n\x92i.#y\xef?&\x00\x00\x00\x1a\x00'
- b'\x00\x00\x08\x00\x00\x00test.txt\xfa\x10\xa0gly|\xfa-\xc5\xc0=\xf9y'
- b'\x18\xe0\xa8r\xb3Z}Lg\xbc\xae\xf9|\x9b\x19\xe4\x8b\xba\xbb)\x8c\xb0\xdbl'
- b'PK\x01\x02\x14\x00\x14\x00\x01\x00\x00\x00n\x92i.#y\xef?&\x00\x00\x00'
- b'\x1a\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x01\x00 \x00\xb6\x81'
- b'\x00\x00\x00\x00test.txtPK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x006\x00'
- b'\x00\x00L\x00\x00\x00\x00\x00' )
+ b'PK\x03\x04\x14\x00\x01\x00\x00\x00n\x92i.#y\xef?&\x00\x00\x00\x1a\x00'
+ b'\x00\x00\x08\x00\x00\x00test.txt\xfa\x10\xa0gly|\xfa-\xc5\xc0=\xf9y'
+ b'\x18\xe0\xa8r\xb3Z}Lg\xbc\xae\xf9|\x9b\x19\xe4\x8b\xba\xbb)\x8c\xb0\xdbl'
+ b'PK\x01\x02\x14\x00\x14\x00\x01\x00\x00\x00n\x92i.#y\xef?&\x00\x00\x00'
+ b'\x1a\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x01\x00 \x00\xb6\x81'
+ b'\x00\x00\x00\x00test.txtPK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x006\x00'
+ b'\x00\x00L\x00\x00\x00\x00\x00' )
data2 = (
- b'PK\x03\x04\x14\x00\t\x00\x08\x00\xcf}38xu\xaa\xb2\x14\x00\x00\x00\x00\x02'
- b'\x00\x00\x04\x00\x15\x00zeroUT\t\x00\x03\xd6\x8b\x92G\xda\x8b\x92GUx\x04'
- b'\x00\xe8\x03\xe8\x03\xc7<M\xb5a\xceX\xa3Y&\x8b{oE\xd7\x9d\x8c\x98\x02\xc0'
- b'PK\x07\x08xu\xaa\xb2\x14\x00\x00\x00\x00\x02\x00\x00PK\x01\x02\x17\x03'
- b'\x14\x00\t\x00\x08\x00\xcf}38xu\xaa\xb2\x14\x00\x00\x00\x00\x02\x00\x00'
- b'\x04\x00\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00ze'
- b'roUT\x05\x00\x03\xd6\x8b\x92GUx\x00\x00PK\x05\x06\x00\x00\x00\x00\x01'
- b'\x00\x01\x00?\x00\x00\x00[\x00\x00\x00\x00\x00' )
+ b'PK\x03\x04\x14\x00\t\x00\x08\x00\xcf}38xu\xaa\xb2\x14\x00\x00\x00\x00\x02'
+ b'\x00\x00\x04\x00\x15\x00zeroUT\t\x00\x03\xd6\x8b\x92G\xda\x8b\x92GUx\x04'
+ b'\x00\xe8\x03\xe8\x03\xc7<M\xb5a\xceX\xa3Y&\x8b{oE\xd7\x9d\x8c\x98\x02\xc0'
+ b'PK\x07\x08xu\xaa\xb2\x14\x00\x00\x00\x00\x02\x00\x00PK\x01\x02\x17\x03'
+ b'\x14\x00\t\x00\x08\x00\xcf}38xu\xaa\xb2\x14\x00\x00\x00\x00\x02\x00\x00'
+ b'\x04\x00\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00ze'
+ b'roUT\x05\x00\x03\xd6\x8b\x92GUx\x00\x00PK\x05\x06\x00\x00\x00\x00\x01'
+ b'\x00\x01\x00?\x00\x00\x00[\x00\x00\x00\x00\x00' )
plain = b'zipfile.py encryption test'
plain2 = b'\x00'*512
@@ -1565,7 +1628,7 @@ class AbstractUniversalNewlineTests:
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
- with zipfp.open(fn, "rU") as fp:
+ with openU(zipfp, fn) as fp:
zipdata = fp.read()
self.assertEqual(self.arcdata[sep], zipdata)
@@ -1579,7 +1642,7 @@ class AbstractUniversalNewlineTests:
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
- with zipfp.open(fn, "rU") as zipopen:
+ with openU(zipfp, fn) as zipopen:
data = b''
while True:
read = zipopen.readline()
@@ -1604,7 +1667,7 @@ class AbstractUniversalNewlineTests:
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
- with zipfp.open(fn, "rU") as zipopen:
+ with openU(zipfp, fn) as zipopen:
for line in self.line_gen:
linedata = zipopen.readline()
self.assertEqual(linedata, line + b'\n')
@@ -1619,7 +1682,7 @@ class AbstractUniversalNewlineTests:
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
- with zipfp.open(fn, "rU") as fp:
+ with openU(zipfp, fn) as fp:
ziplines = fp.readlines()
for line, zipline in zip(self.line_gen, ziplines):
self.assertEqual(zipline, line + b'\n')
@@ -1634,7 +1697,7 @@ class AbstractUniversalNewlineTests:
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
- with zipfp.open(fn, "rU") as fp:
+ with openU(zipfp, fn) as fp:
for line, zipline in zip(self.line_gen, fp):
self.assertEqual(zipline, line + b'\n')
@@ -1668,6 +1731,5 @@ class LzmaUniversalNewlineTests(AbstractUniversalNewlineTests,
unittest.TestCase):
compression = zipfile.ZIP_LZMA
-
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_zipfile64.py b/Lib/test/test_zipfile64.py
index a8fb7aba54..498d464db3 100644
--- a/Lib/test/test_zipfile64.py
+++ b/Lib/test/test_zipfile64.py
@@ -38,7 +38,7 @@ class TestsWithSourceFile(unittest.TestCase):
def zipTest(self, f, compression):
# Create the ZIP archive.
- zipfp = zipfile.ZipFile(f, "w", compression, allowZip64=True)
+ zipfp = zipfile.ZipFile(f, "w", compression)
# It will contain enough copies of self.data to reach about 6GB of
# raw data to store.
@@ -92,7 +92,7 @@ class OtherTests(unittest.TestCase):
def testMoreThan64kFiles(self):
# This test checks that more than 64k files can be added to an archive,
# and that the resulting archive can be read properly by ZipFile
- zipf = zipfile.ZipFile(TESTFN, mode="w")
+ zipf = zipfile.ZipFile(TESTFN, mode="w", allowZip64=False)
zipf.debug = 100
numfiles = (1 << 16) * 3//2
for i in range(numfiles):
diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py
index f7cb8b977f..3f16041f51 100644
--- a/Lib/test/test_zipimport.py
+++ b/Lib/test/test_zipimport.py
@@ -1,13 +1,12 @@
import sys
import os
import marshal
-import imp
+import importlib.util
import struct
import time
import unittest
from test import support
-from test.test_importhooks import ImportHooksBaseTestCase, test_src, test_co
from zipfile import ZipFile, ZipInfo, ZIP_STORED, ZIP_DEFLATED
@@ -17,6 +16,14 @@ import doctest
import inspect
import io
from traceback import extract_tb, extract_stack, print_tb
+
+test_src = """\
+def get_name():
+ return __name__
+def get_file():
+ return __file__
+"""
+test_co = compile(test_src, "<???>", "exec")
raise_src = 'def do_raise(): raise TypeError\n'
def make_pyc(co, mtime, size):
@@ -27,7 +34,8 @@ def make_pyc(co, mtime, size):
mtime = int(mtime)
else:
mtime = int(-0x100000000 + int(mtime))
- pyc = imp.get_magic() + struct.pack("<ii", int(mtime), size & 0xFFFFFFFF) + data
+ pyc = (importlib.util.MAGIC_NUMBER +
+ struct.pack("<ii", int(mtime), size & 0xFFFFFFFF) + data)
return pyc
def module_path_to_dotted_name(path):
@@ -42,10 +50,27 @@ TESTPACK = "ziptestpackage"
TESTPACK2 = "ziptestpackage2"
TEMP_ZIP = os.path.abspath("junk95142.zip")
-pyc_file = imp.cache_from_source(TESTMOD + '.py')
+pyc_file = importlib.util.cache_from_source(TESTMOD + '.py')
pyc_ext = ('.pyc' if __debug__ else '.pyo')
+class ImportHooksBaseTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.path = sys.path[:]
+ self.meta_path = sys.meta_path[:]
+ self.path_hooks = sys.path_hooks[:]
+ sys.path_importer_cache.clear()
+ self.modules_before = support.modules_setup()
+
+ def tearDown(self):
+ sys.path[:] = self.path
+ sys.meta_path[:] = self.meta_path
+ sys.path_hooks[:] = self.path_hooks
+ sys.path_importer_cache.clear()
+ support.modules_cleanup(*self.modules_before)
+
+
class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
compression = ZIP_STORED
@@ -196,6 +221,7 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
for name, (mtime, data) in files.items():
zinfo = ZipInfo(name, time.localtime(mtime))
zinfo.compress_type = self.compression
+ zinfo.comment = b"spam"
z.writestr(zinfo, data)
z.close()
@@ -245,6 +271,7 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
for name, (mtime, data) in files.items():
zinfo = ZipInfo(name, time.localtime(mtime))
zinfo.compress_type = self.compression
+ zinfo.comment = b"eggs"
z.writestr(zinfo, data)
z.close()
@@ -459,7 +486,7 @@ class BadFileZipImportTestCase(unittest.TestCase):
self.assertRaises(error, z.load_module, 'abc')
self.assertRaises(error, z.get_code, 'abc')
- self.assertRaises(IOError, z.get_data, 'abc')
+ self.assertRaises(OSError, z.get_data, 'abc')
self.assertRaises(error, z.get_source, 'abc')
self.assertRaises(error, z.is_package, 'abc')
finally:
diff --git a/Lib/test/test_zipimport_support.py b/Lib/test/test_zipimport_support.py
index f7f3398015..84ba5e047a 100644
--- a/Lib/test/test_zipimport_support.py
+++ b/Lib/test/test_zipimport_support.py
@@ -227,13 +227,15 @@ class ZipSupportTests(unittest.TestCase):
p = spawn_python(script_name)
p.stdin.write(b'l\n')
data = kill_python(p)
- self.assertIn(script_name.encode('utf-8'), data)
+ # bdb/pdb applies normcase to its filename before displaying
+ self.assertIn(os.path.normcase(script_name.encode('utf-8')), data)
zip_name, run_name = make_zip_script(d, "test_zip",
script_name, '__main__.py')
p = spawn_python(zip_name)
p.stdin.write(b'l\n')
data = kill_python(p)
- self.assertIn(run_name.encode('utf-8'), data)
+ # bdb/pdb applies normcase to its filename before displaying
+ self.assertIn(os.path.normcase(run_name.encode('utf-8')), data)
def test_main():
diff --git a/Lib/test/tf_inherit_check.py b/Lib/test/tf_inherit_check.py
index 92ebd95e52..afe50d2325 100644
--- a/Lib/test/tf_inherit_check.py
+++ b/Lib/test/tf_inherit_check.py
@@ -11,7 +11,7 @@ try:
try:
os.write(fd, b"blat")
- except os.error:
+ except OSError:
# Success -- could not write to fd.
sys.exit(0)
else:
diff --git a/Lib/textwrap.py b/Lib/textwrap.py
index 7024d4d245..24891804dd 100644
--- a/Lib/textwrap.py
+++ b/Lib/textwrap.py
@@ -7,7 +7,7 @@
import re
-__all__ = ['TextWrapper', 'wrap', 'fill', 'dedent', 'indent']
+__all__ = ['TextWrapper', 'wrap', 'fill', 'dedent', 'indent', 'shorten']
# Hardcode the recognized whitespace characters to the US-ASCII
# whitespace characters. The main reason for doing this is that in
@@ -62,6 +62,10 @@ class TextWrapper:
compound words.
drop_whitespace (default: true)
Drop leading and trailing whitespace from lines.
+ max_lines (default: None)
+ Truncate wrapped lines.
+ placeholder (default: ' [...]')
+ Append to the last line of truncated text.
"""
unicode_whitespace_trans = {}
@@ -104,7 +108,10 @@ class TextWrapper:
break_long_words=True,
drop_whitespace=True,
break_on_hyphens=True,
- tabsize=8):
+ tabsize=8,
+ *,
+ max_lines=None,
+ placeholder=' [...]'):
self.width = width
self.initial_indent = initial_indent
self.subsequent_indent = subsequent_indent
@@ -115,6 +122,8 @@ class TextWrapper:
self.drop_whitespace = drop_whitespace
self.break_on_hyphens = break_on_hyphens
self.tabsize = tabsize
+ self.max_lines = max_lines
+ self.placeholder = placeholder
# -- Private methods -----------------------------------------------
@@ -223,6 +232,13 @@ class TextWrapper:
lines = []
if self.width <= 0:
raise ValueError("invalid width %r (must be > 0)" % self.width)
+ if self.max_lines is not None:
+ if self.max_lines > 1:
+ indent = self.subsequent_indent
+ else:
+ indent = self.initial_indent
+ if len(indent) + len(self.placeholder.lstrip()) > self.width:
+ raise ValueError("placeholder too large for max width")
# Arrange in reverse order so items can be efficiently popped
# from a stack of chucks.
@@ -265,18 +281,47 @@ class TextWrapper:
# fit on *any* line (not just this one).
if chunks and len(chunks[-1]) > width:
self._handle_long_word(chunks, cur_line, cur_len, width)
+ cur_len = sum(map(len, cur_line))
# If the last chunk on this line is all whitespace, drop it.
if self.drop_whitespace and cur_line and cur_line[-1].strip() == '':
+ cur_len -= len(cur_line[-1])
del cur_line[-1]
- # Convert current line back to a string and store it in list
- # of all lines (return value).
if cur_line:
- lines.append(indent + ''.join(cur_line))
+ if (self.max_lines is None or
+ len(lines) + 1 < self.max_lines or
+ (not chunks or
+ self.drop_whitespace and
+ len(chunks) == 1 and
+ not chunks[0].strip()) and cur_len <= width):
+ # Convert current line back to a string and store it in
+ # list of all lines (return value).
+ lines.append(indent + ''.join(cur_line))
+ else:
+ while cur_line:
+ if (cur_line[-1].strip() and
+ cur_len + len(self.placeholder) <= width):
+ cur_line.append(self.placeholder)
+ lines.append(indent + ''.join(cur_line))
+ break
+ cur_len -= len(cur_line[-1])
+ del cur_line[-1]
+ else:
+ if lines:
+ prev_line = lines[-1].rstrip()
+ if (len(prev_line) + len(self.placeholder) <=
+ self.width):
+ lines[-1] = prev_line + self.placeholder
+ break
+ lines.append(indent + self.placeholder.lstrip())
+ break
return lines
+ def _split_chunks(self, text):
+ text = self._munge_whitespace(text)
+ return self._split(text)
# -- Public interface ----------------------------------------------
@@ -289,8 +334,7 @@ class TextWrapper:
and all other whitespace characters (including newline) are
converted to space.
"""
- text = self._munge_whitespace(text)
- chunks = self._split(text)
+ chunks = self._split_chunks(text)
if self.fix_sentence_endings:
self._fix_sentence_endings(chunks)
return self._wrap_chunks(chunks)
@@ -332,6 +376,21 @@ def fill(text, width=70, **kwargs):
w = TextWrapper(width=width, **kwargs)
return w.fill(text)
+def shorten(text, width, **kwargs):
+ """Collapse and truncate the given text to fit in the given width.
+
+ The text first has its whitespace collapsed. If it then fits in
+ the *width*, it is returned as is. Otherwise, as many words
+ as possible are joined and then the placeholder is appended::
+
+ >>> textwrap.shorten("Hello world!", width=12)
+ 'Hello world!'
+ >>> textwrap.shorten("Hello world!", width=11)
+ 'Hello [...]'
+ """
+ w = TextWrapper(width=width, max_lines=1, **kwargs)
+ return w.fill(' '.join(text.strip().split()))
+
# -- Loosely related functionality -------------------------------------
diff --git a/Lib/threading.py b/Lib/threading.py
index 625c9b9d7b..d907c89f3f 100644
--- a/Lib/threading.py
+++ b/Lib/threading.py
@@ -10,6 +10,11 @@ except ImportError:
from time import time as _time
from traceback import format_exc as _format_exc
from _weakrefset import WeakSet
+from itertools import islice as _islice
+try:
+ from _collections import deque as _deque
+except ImportError:
+ from collections import deque as _deque
# Note regarding PEP 8 compliant names
# This threading model was originally inspired by Java, and inherited
@@ -28,6 +33,7 @@ __all__ = ['active_count', 'Condition', 'current_thread', 'enumerate', 'Event',
# Rename some stuff so "from threading import *" is safe
_start_new_thread = _thread.start_new_thread
_allocate_lock = _thread.allocate_lock
+_set_sentinel = _thread._set_sentinel
get_ident = _thread.get_ident
ThreadError = _thread.error
try:
@@ -132,7 +138,7 @@ class _RLock:
"""
me = get_ident()
if self._owner == me:
- self._count = self._count + 1
+ self._count += 1
return 1
rc = self._block.acquire(blocking, timeout)
if rc:
@@ -224,7 +230,7 @@ class Condition:
self._is_owned = lock._is_owned
except AttributeError:
pass
- self._waiters = []
+ self._waiters = _deque()
def __enter__(self):
return self._lock.__enter__()
@@ -332,14 +338,14 @@ class Condition:
"""
if not self._is_owned():
raise RuntimeError("cannot notify on un-acquired lock")
- __waiters = self._waiters
- waiters = __waiters[:n]
- if not waiters:
+ all_waiters = self._waiters
+ waiters_to_notify = _deque(_islice(all_waiters, n))
+ if not waiters_to_notify:
return
- for waiter in waiters:
+ for waiter in waiters_to_notify:
waiter.release()
try:
- __waiters.remove(waiter)
+ all_waiters.remove(waiter)
except ValueError:
pass
@@ -414,7 +420,7 @@ class Semaphore:
break
self._cond.wait(timeout)
else:
- self._value = self._value - 1
+ self._value -= 1
rc = True
return rc
@@ -428,7 +434,7 @@ class Semaphore:
"""
with self._cond:
- self._value = self._value + 1
+ self._value += 1
self._cond.notify()
def __exit__(self, t, v, tb):
@@ -723,15 +729,13 @@ class BrokenBarrierError(RuntimeError):
_counter = 0
def _newname(template="Thread-%d"):
global _counter
- _counter = _counter + 1
+ _counter += 1
return template % _counter
# Active thread administration
_active_limbo_lock = _allocate_lock()
_active = {} # maps thread id to Thread object
_limbo = {}
-
-# For debug and leak testing
_dangling = WeakSet()
# Main class for threads
@@ -790,28 +794,35 @@ class Thread:
else:
self._daemonic = current_thread().daemon
self._ident = None
+ self._tstate_lock = None
self._started = Event()
- self._stopped = False
- self._block = Condition(Lock())
+ self._is_stopped = False
self._initialized = True
# sys.stderr is not stored in the class like
# sys.exc_info since it can be changed between instances
self._stderr = _sys.stderr
+ # For debugging and _after_fork()
_dangling.add(self)
- def _reset_internal_locks(self):
+ def _reset_internal_locks(self, is_alive):
# private! Called by _after_fork() to reset our internal locks as
# they may be in an invalid state leading to a deadlock or crash.
- if hasattr(self, '_block'): # DummyThread deletes _block
- self._block.__init__()
self._started._reset_internal_locks()
+ if is_alive:
+ self._set_tstate_lock()
+ else:
+ # The thread isn't alive after fork: it doesn't have a tstate
+ # anymore.
+ self._is_stopped = True
+ self._tstate_lock = None
def __repr__(self):
assert self._initialized, "Thread.__init__() was not called"
status = "initial"
if self._started.is_set():
status = "started"
- if self._stopped:
+ self.is_alive() # easy way to get ._is_stopped set when appropriate
+ if self._is_stopped:
status = "stopped"
if self._daemonic:
status += " daemon"
@@ -884,9 +895,18 @@ class Thread:
def _set_ident(self):
self._ident = get_ident()
+ def _set_tstate_lock(self):
+ """
+ Set a lock object which will be released by the interpreter when
+ the underlying thread state (see pystate.h) gets deleted.
+ """
+ self._tstate_lock = _set_sentinel()
+ self._tstate_lock.acquire()
+
def _bootstrap_inner(self):
try:
self._set_ident()
+ self._set_tstate_lock()
self._started.set()
with _active_limbo_lock:
_active[self._ident] = self
@@ -941,7 +961,6 @@ class Thread:
pass
finally:
with _active_limbo_lock:
- self._stop()
try:
# We don't call self._delete() because it also
# grabs _active_limbo_lock.
@@ -950,10 +969,27 @@ class Thread:
pass
def _stop(self):
- self._block.acquire()
- self._stopped = True
- self._block.notify_all()
- self._block.release()
+ # After calling ._stop(), .is_alive() returns False and .join() returns
+ # immediately. ._tstate_lock must be released before calling ._stop().
+ #
+ # Normal case: C code at the end of the thread's life
+ # (release_sentinel in _threadmodule.c) releases ._tstate_lock, and
+ # that's detected by our ._wait_for_tstate_lock(), called by .join()
+ # and .is_alive(). Any number of threads _may_ call ._stop()
+ # simultaneously (for example, if multiple threads are blocked in
+ # .join() calls), and they're not serialized. That's harmless -
+ # they'll just make redundant rebindings of ._is_stopped and
+ # ._tstate_lock. Obscure: we rebind ._tstate_lock last so that the
+ # "assert self._is_stopped" in ._wait_for_tstate_lock() always works
+ # (the assert is executed only if ._tstate_lock is None).
+ #
+ # Special case: _main_thread releases ._tstate_lock via this
+ # module's _shutdown() function.
+ lock = self._tstate_lock
+ if lock is not None:
+ assert not lock.locked()
+ self._is_stopped = True
+ self._tstate_lock = None
def _delete(self):
"Remove current thread from the dict of currently running threads."
@@ -1021,20 +1057,26 @@ class Thread:
if self is current_thread():
raise RuntimeError("cannot join current thread")
- self._block.acquire()
- try:
- if timeout is None:
- while not self._stopped:
- self._block.wait()
- else:
- deadline = _time() + timeout
- while not self._stopped:
- delay = deadline - _time()
- if delay <= 0:
- break
- self._block.wait(delay)
- finally:
- self._block.release()
+ if timeout is None:
+ self._wait_for_tstate_lock()
+ else:
+ # the behavior of a negative timeout isn't documented, but
+ # historically .join(timeout=x) for x<0 has acted as if timeout=0
+ self._wait_for_tstate_lock(timeout=max(timeout, 0))
+
+ def _wait_for_tstate_lock(self, block=True, timeout=-1):
+ # Issue #18808: wait for the thread state to be gone.
+ # At the end of the thread's life, after all knowledge of the thread
+ # is removed from C data structures, C code releases our _tstate_lock.
+ # This method passes its arguments to _tstate_lock.aquire().
+ # If the lock is acquired, the C code is done, and self._stop() is
+ # called. That sets ._is_stopped to True, and ._tstate_lock to None.
+ lock = self._tstate_lock
+ if lock is None: # already determined that the C code is done
+ assert self._is_stopped
+ elif lock.acquire(block, timeout):
+ lock.release()
+ self._stop()
@property
def name(self):
@@ -1073,7 +1115,10 @@ class Thread:
"""
assert self._initialized, "Thread.__init__() not called"
- return self._started.is_set() and not self._stopped
+ if self._is_stopped or not self._started.is_set():
+ return False
+ self._wait_for_tstate_lock(False)
+ return not self._is_stopped
isAlive = is_alive
@@ -1149,25 +1194,12 @@ class _MainThread(Thread):
def __init__(self):
Thread.__init__(self, name="MainThread", daemon=False)
+ self._set_tstate_lock()
self._started.set()
self._set_ident()
with _active_limbo_lock:
_active[self._ident] = self
- def _exitfunc(self):
- self._stop()
- t = _pickSomeNonDaemonThread()
- while t:
- t.join()
- t = _pickSomeNonDaemonThread()
- self._delete()
-
-def _pickSomeNonDaemonThread():
- for t in enumerate():
- if not t.daemon and t.is_alive():
- return t
- return None
-
# Dummy thread class to represent threads not started here.
# These aren't garbage collected when they die, nor can they be waited for.
@@ -1182,11 +1214,6 @@ class _DummyThread(Thread):
def __init__(self):
Thread.__init__(self, name=_newname("Dummy-%d"), daemon=True)
- # Thread._block consumes an OS-level locking primitive, which
- # can never be used by a _DummyThread. Since a _DummyThread
- # instance is immortal, that's bad, so release this resource.
- del self._block
-
self._started.set()
self._set_ident()
with _active_limbo_lock:
@@ -1248,7 +1275,40 @@ from _thread import stack_size
# and make it available for the interpreter
# (Py_Main) as threading._shutdown.
-_shutdown = _MainThread()._exitfunc
+_main_thread = _MainThread()
+
+def _shutdown():
+ # Obscure: other threads may be waiting to join _main_thread. That's
+ # dubious, but some code does it. We can't wait for C code to release
+ # the main thread's tstate_lock - that won't happen until the interpreter
+ # is nearly dead. So we release it here. Note that just calling _stop()
+ # isn't enough: other threads may already be waiting on _tstate_lock.
+ tlock = _main_thread._tstate_lock
+ # The main thread isn't finished yet, so its thread state lock can't have
+ # been released.
+ assert tlock is not None
+ assert tlock.locked()
+ tlock.release()
+ _main_thread._stop()
+ t = _pickSomeNonDaemonThread()
+ while t:
+ t.join()
+ t = _pickSomeNonDaemonThread()
+ _main_thread._delete()
+
+def _pickSomeNonDaemonThread():
+ for t in enumerate():
+ if not t.daemon and t.is_alive():
+ return t
+ return None
+
+def main_thread():
+ """Return the main thread object.
+
+ In normal conditions, the main thread is the thread from which the
+ Python interpreter was started.
+ """
+ return _main_thread
# get thread-local implementation, either from the thread
# module, or from the python fallback
@@ -1266,25 +1326,31 @@ def _after_fork():
# Reset _active_limbo_lock, in case we forked while the lock was held
# by another (non-forked) thread. http://bugs.python.org/issue874900
- global _active_limbo_lock
+ global _active_limbo_lock, _main_thread
_active_limbo_lock = _allocate_lock()
# fork() only copied the current thread; clear references to others.
new_active = {}
current = current_thread()
+ _main_thread = current
with _active_limbo_lock:
- for thread in _enumerate():
+ # Dangling thread instances must still have their locks reset,
+ # because someone may join() them.
+ threads = set(_enumerate())
+ threads.update(_dangling)
+ for thread in threads:
# Any lock/condition variable may be currently locked or in an
# invalid state, so we reinitialize them.
- thread._reset_internal_locks()
if thread is current:
# There is only one active thread. We reset the ident to
# its new value since it can have changed.
+ thread._reset_internal_locks(True)
ident = get_ident()
thread._ident = ident
new_active[ident] = thread
else:
# All the others are already stopped.
+ thread._reset_internal_locks(False)
thread._stop()
_limbo.clear()
diff --git a/Lib/timeit.py b/Lib/timeit.py
index 4f7d28fbef..ead2030515 100644
--- a/Lib/timeit.py
+++ b/Lib/timeit.py
@@ -31,38 +31,29 @@ treated similarly.
If -n is not given, a suitable number of loops is calculated by trying
successive powers of 10 until the total time is at least 0.2 seconds.
-The difference in default timer function is because on Windows,
-clock() has microsecond granularity but time()'s granularity is 1/60th
-of a second; on Unix, clock() has 1/100th of a second granularity and
-time() is much more precise. On either platform, the default timer
-functions measure wall clock time, not the CPU time. This means that
-other processes running on the same computer may interfere with the
-timing. The best thing to do when accurate timing is necessary is to
-repeat the timing a few times and use the best time. The -r option is
-good for this; the default of 3 repetitions is probably enough in most
-cases. On Unix, you can use clock() to measure CPU time.
-
Note: there is a certain baseline overhead associated with executing a
-pass statement. The code here doesn't try to hide it, but you should
-be aware of it. The baseline overhead can be measured by invoking the
-program without arguments.
-
-The baseline overhead differs between Python versions! Also, to
-fairly compare older Python versions to Python 2.3, you may want to
-use python -O for the older versions to avoid timing SET_LINENO
-instructions.
+pass statement. It differs between versions. The code here doesn't try
+to hide it, but you should be aware of it. The baseline overhead can be
+measured by invoking the program without arguments.
+
+Classes:
+
+ Timer
+
+Functions:
+
+ timeit(string, string) -> float
+ repeat(string, string) -> list
+ default_timer() -> float
+
"""
import gc
import sys
import time
-try:
- import itertools
-except ImportError:
- # Must be an older Python version (see timeit() below)
- itertools = None
+import itertools
-__all__ = ["Timer"]
+__all__ = ["Timer", "timeit", "repeat", "default_timer"]
dummy_src_name = "<timeit-src>"
default_number = 1000000
@@ -180,10 +171,7 @@ class Timer:
to one million. The main statement, the setup statement and
the timer function to be used are passed to the constructor.
"""
- if itertools:
- it = itertools.repeat(None, number)
- else:
- it = [None] * number
+ it = itertools.repeat(None, number)
gcold = gc.isenabled()
gc.disable()
try:
diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py
index 9cb228429d..22a41acc0d 100644
--- a/Lib/tkinter/__init__.py
+++ b/Lib/tkinter/__init__.py
@@ -2181,45 +2181,6 @@ class Button(Widget):
"""
return self.tk.call(self._w, 'invoke')
-
-# Indices:
-# XXX I don't like these -- take them away
-def AtEnd():
- warnings.warn("tkinter.AtEnd will be removed in 3.4",
- DeprecationWarning, stacklevel=2)
- return 'end'
-
-
-def AtInsert(*args):
- warnings.warn("tkinter.AtInsert will be removed in 3.4",
- DeprecationWarning, stacklevel=2)
- s = 'insert'
- for a in args:
- if a: s = s + (' ' + a)
- return s
-
-
-def AtSelFirst():
- warnings.warn("tkinter.AtSelFirst will be removed in 3.4",
- DeprecationWarning, stacklevel=2)
- return 'sel.first'
-
-
-def AtSelLast():
- warnings.warn("tkinter.AtSelLast will be removed in 3.4",
- DeprecationWarning, stacklevel=2)
- return 'sel.last'
-
-
-def At(x, y=None):
- warnings.warn("tkinter.At will be removed in 3.4",
- DeprecationWarning, stacklevel=2)
- if y is None:
- return '@%r' % (x,)
- else:
- return '@%r,%r' % (x, y)
-
-
class Canvas(Widget, XView, YView):
"""Canvas widget to display graphical elements like lines or text."""
def __init__(self, master=None, cnf={}, **kw):
@@ -2950,11 +2911,11 @@ class Text(Widget, XView, YView):
"""
Widget.__init__(self, master, 'text', cnf, kw)
- def bbox(self, *args):
+ def bbox(self, index):
"""Return a tuple of (x,y,width,height) which gives the bounding
- box of the visible part of the character at the index in ARGS."""
+ box of the visible part of the character at the given index."""
return self._getints(
- self.tk.call((self._w, 'bbox') + args)) or None
+ self.tk.call(self._w, 'bbox', index)) or None
def tk_textSelectTo(self, index):
self.tk.call('tk_textSelectTo', self._w, index)
def tk_textBackspace(self):
diff --git a/Lib/tkinter/filedialog.py b/Lib/tkinter/filedialog.py
index 3ffb2528f0..a71afb25b9 100644
--- a/Lib/tkinter/filedialog.py
+++ b/Lib/tkinter/filedialog.py
@@ -166,7 +166,7 @@ class FileDialog:
dir, pat = self.get_filter()
try:
names = os.listdir(dir)
- except os.error:
+ except OSError:
self.master.bell()
return
self.directory = dir
@@ -209,7 +209,7 @@ class FileDialog:
if not os.path.isabs(dir):
try:
pwd = os.getcwd()
- except os.error:
+ except OSError:
pwd = None
if pwd:
dir = os.path.join(pwd, dir)
diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py
index 2c4cb0540a..4de65313bb 100644
--- a/Lib/tkinter/test/test_tkinter/test_widgets.py
+++ b/Lib/tkinter/test/test_tkinter/test_widgets.py
@@ -630,8 +630,8 @@ class TextTest(AbstractWidgetTest, unittest.TestCase):
self.assertIsNone(widget.bbox('end'))
self.assertRaises(tkinter.TclError, widget.bbox, 'noindex')
self.assertRaises(tkinter.TclError, widget.bbox, None)
- self.assertRaises(tkinter.TclError, widget.bbox)
- self.assertRaises(tkinter.TclError, widget.bbox, '1.1', 'end')
+ self.assertRaises(TypeError, widget.bbox)
+ self.assertRaises(TypeError, widget.bbox, '1.1', 'end')
@add_standard_options(PixelSizeTests, StandardOptionsTests)
diff --git a/Lib/token.py b/Lib/token.py
index 31fae0a078..7470c8c376 100755
--- a/Lib/token.py
+++ b/Lib/token.py
@@ -93,7 +93,7 @@ def _main():
outFileName = args[1]
try:
fp = open(inFileName)
- except IOError as err:
+ except OSError as err:
sys.stdout.write("I/O error: %s\n" % str(err))
sys.exit(1)
lines = fp.read().split("\n")
@@ -112,7 +112,7 @@ def _main():
# load the output skeleton from the target:
try:
fp = open(outFileName)
- except IOError as err:
+ except OSError as err:
sys.stderr.write("I/O error: %s\n" % str(err))
sys.exit(2)
format = fp.read().split("\n")
@@ -129,7 +129,7 @@ def _main():
format[start:end] = lines
try:
fp = open(outFileName, 'w')
- except IOError as err:
+ except OSError as err:
sys.stderr.write("I/O error: %s\n" % str(err))
sys.exit(4)
fp.write("\n".join(format))
diff --git a/Lib/tokenize.py b/Lib/tokenize.py
index d0609e8545..f614aeb164 100644
--- a/Lib/tokenize.py
+++ b/Lib/tokenize.py
@@ -670,7 +670,7 @@ def main():
error(err.args[0], filename, (line, column))
except SyntaxError as err:
error(err, filename)
- except IOError as err:
+ except OSError as err:
error(err)
except KeyboardInterrupt:
print("interrupted\n")
diff --git a/Lib/trace.py b/Lib/trace.py
index c09b365a01..09fe9ee0e4 100644
--- a/Lib/trace.py
+++ b/Lib/trace.py
@@ -238,7 +238,7 @@ class CoverageResults:
counts, calledfuncs, callers = \
pickle.load(open(self.infile, 'rb'))
self.update(self.__class__(counts, calledfuncs, callers))
- except (IOError, EOFError, ValueError) as err:
+ except (OSError, EOFError, ValueError) as err:
print(("Skipping counts file %r: %s"
% (self.infile, err)), file=sys.stderr)
@@ -348,7 +348,7 @@ class CoverageResults:
try:
pickle.dump((self.counts, self.calledfuncs, self.callers),
open(self.outfile, 'wb'), 1)
- except IOError as err:
+ except OSError as err:
print("Can't save counts files because %s" % err, file=sys.stderr)
def write_results_file(self, path, lines, lnotab, lines_hit, encoding=None):
@@ -356,7 +356,7 @@ class CoverageResults:
try:
outfile = open(path, "w", encoding=encoding)
- except IOError as err:
+ except OSError as err:
print(("trace: Could not open %r for writing: %s"
"- skipping" % (path, err)), file=sys.stderr)
return 0, 0
@@ -437,7 +437,7 @@ def _find_executable_linenos(filename):
with tokenize.open(filename) as f:
prog = f.read()
encoding = f.encoding
- except IOError as err:
+ except OSError as err:
print(("Not printing coverage data for %r: %s"
% (filename, err)), file=sys.stderr)
return {}
@@ -802,7 +802,7 @@ def main(argv=None):
'__cached__': None,
}
t.runctx(code, globs, globs)
- except IOError as err:
+ except OSError as err:
_err_exit("Cannot run file %r because: %s" % (sys.argv[0], err))
except SystemExit:
pass
diff --git a/Lib/traceback.py b/Lib/traceback.py
index b13bfe2497..3b2cae748e 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -2,26 +2,32 @@
import linecache
import sys
+import operator
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
'format_exception_only', 'format_list', 'format_stack',
'format_tb', 'print_exc', 'format_exc', 'print_exception',
- 'print_last', 'print_stack', 'print_tb']
+ 'print_last', 'print_stack', 'print_tb',
+ 'clear_frames']
-def _print(file, str='', terminator='\n'):
- file.write(str+terminator)
+#
+# Formatting and printing lists of traceback lines.
+#
+def _format_list_iter(extracted_list):
+ for filename, lineno, name, line in extracted_list:
+ item = ' File "{}", line {}, in {}\n'.format(filename, lineno, name)
+ if line:
+ item = item + ' {}\n'.format(line.strip())
+ yield item
def print_list(extracted_list, file=None):
"""Print the list of tuples as returned by extract_tb() or
extract_stack() as a formatted stack trace to the given file."""
if file is None:
file = sys.stderr
- for filename, lineno, name, line in extracted_list:
- _print(file,
- ' File "%s", line %d, in %s' % (filename,lineno,name))
- if line:
- _print(file, ' %s' % line.strip())
+ for item in _format_list_iter(extracted_list):
+ print(item, file=file, end="")
def format_list(extracted_list):
"""Format a list of traceback entry tuples for printing.
@@ -33,14 +39,44 @@ def format_list(extracted_list):
the strings may contain internal newlines as well, for those items
whose source text line is not None.
"""
- list = []
- for filename, lineno, name, line in extracted_list:
- item = ' File "%s", line %d, in %s\n' % (filename,lineno,name)
+ return list(_format_list_iter(extracted_list))
+
+#
+# Printing and Extracting Tracebacks.
+#
+
+# extractor takes curr and needs to return a tuple of:
+# - Frame object
+# - Line number
+# - Next item (same type as curr)
+# In practice, curr is either a traceback or a frame.
+def _extract_tb_or_stack_iter(curr, limit, extractor):
+ if limit is None:
+ limit = getattr(sys, 'tracebacklimit', None)
+
+ n = 0
+ while curr is not None and (limit is None or n < limit):
+ f, lineno, next_item = extractor(curr)
+ co = f.f_code
+ filename = co.co_filename
+ name = co.co_name
+
+ linecache.checkcache(filename)
+ line = linecache.getline(filename, lineno, f.f_globals)
+
if line:
- item = item + ' %s\n' % line.strip()
- list.append(item)
- return list
+ line = line.strip()
+ else:
+ line = None
+
+ yield (filename, lineno, name, line)
+ curr = next_item
+ n += 1
+def _extract_tb_iter(tb, limit):
+ return _extract_tb_or_stack_iter(
+ tb, limit,
+ operator.attrgetter("tb_frame", "tb_lineno", "tb_next"))
def print_tb(tb, limit=None, file=None):
"""Print up to 'limit' stack trace entries from the traceback 'tb'.
@@ -50,29 +86,11 @@ def print_tb(tb, limit=None, file=None):
'file' should be an open file or file-like object with a write()
method.
"""
- if file is None:
- file = sys.stderr
- if limit is None:
- if hasattr(sys, 'tracebacklimit'):
- limit = sys.tracebacklimit
- n = 0
- while tb is not None and (limit is None or n < limit):
- f = tb.tb_frame
- lineno = tb.tb_lineno
- co = f.f_code
- filename = co.co_filename
- name = co.co_name
- _print(file,
- ' File "%s", line %d, in %s' % (filename, lineno, name))
- linecache.checkcache(filename)
- line = linecache.getline(filename, lineno, f.f_globals)
- if line: _print(file, ' ' + line.strip())
- tb = tb.tb_next
- n = n+1
+ print_list(extract_tb(tb, limit=limit), file=file)
def format_tb(tb, limit=None):
"""A shorthand for 'format_list(extract_tb(tb, limit))'."""
- return format_list(extract_tb(tb, limit))
+ return format_list(extract_tb(tb, limit=limit))
def extract_tb(tb, limit=None):
"""Return list of up to limit pre-processed entries from traceback.
@@ -85,26 +103,11 @@ def extract_tb(tb, limit=None):
leading and trailing whitespace stripped; if the source is not
available it is None.
"""
- if limit is None:
- if hasattr(sys, 'tracebacklimit'):
- limit = sys.tracebacklimit
- list = []
- n = 0
- while tb is not None and (limit is None or n < limit):
- f = tb.tb_frame
- lineno = tb.tb_lineno
- co = f.f_code
- filename = co.co_filename
- name = co.co_name
- linecache.checkcache(filename)
- line = linecache.getline(filename, lineno, f.f_globals)
- if line: line = line.strip()
- else: line = None
- list.append((filename, lineno, name, line))
- tb = tb.tb_next
- n = n+1
- return list
+ return list(_extract_tb_iter(tb, limit=limit))
+#
+# Exception formatting and output.
+#
_cause_message = (
"\nThe above exception was the direct cause "
@@ -132,9 +135,23 @@ def _iter_chain(exc, custom_tb=None, seen=None):
its.append([(exc, custom_tb or exc.__traceback__)])
# itertools.chain is in an extension module and may be unavailable
for it in its:
- for x in it:
- yield x
+ yield from it
+def _format_exception_iter(etype, value, tb, limit, chain):
+ if chain:
+ values = _iter_chain(value, tb)
+ else:
+ values = [(value, tb)]
+
+ for value, tb in values:
+ if isinstance(value, str):
+ # This is a cause/context message line
+ yield value + '\n'
+ continue
+ if tb:
+ yield 'Traceback (most recent call last):\n'
+ yield from _format_list_iter(_extract_tb_iter(tb, limit=limit))
+ yield from _format_exception_only_iter(type(value), value)
def print_exception(etype, value, tb, limit=None, file=None, chain=True):
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
@@ -149,20 +166,8 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True):
"""
if file is None:
file = sys.stderr
- if chain:
- values = _iter_chain(value, tb)
- else:
- values = [(value, tb)]
- for value, tb in values:
- if isinstance(value, str):
- _print(file, value)
- continue
- if tb:
- _print(file, 'Traceback (most recent call last):')
- print_tb(tb, limit, file)
- lines = format_exception_only(type(value), value)
- for line in lines:
- _print(file, line, '')
+ for line in _format_exception_iter(etype, value, tb, limit, chain):
+ print(line, file=file, end="")
def format_exception(etype, value, tb, limit=None, chain=True):
"""Format a stack trace and the exception information.
@@ -173,20 +178,7 @@ def format_exception(etype, value, tb, limit=None, chain=True):
these lines are concatenated and printed, exactly the same text is
printed as does print_exception().
"""
- list = []
- if chain:
- values = _iter_chain(value, tb)
- else:
- values = [(value, tb)]
- for value, tb in values:
- if isinstance(value, str):
- list.append(value + '\n')
- continue
- if tb:
- list.append('Traceback (most recent call last):\n')
- list.extend(format_tb(tb, limit))
- list.extend(format_exception_only(type(value), value))
- return list
+ return list(_format_exception_iter(etype, value, tb, limit, chain))
def format_exception_only(etype, value):
"""Format the exception part of a traceback.
@@ -204,10 +196,14 @@ def format_exception_only(etype, value):
string in the list.
"""
+ return list(_format_exception_only_iter(etype, value))
+
+def _format_exception_only_iter(etype, value):
# Gracefully handle (the way Python 2.4 and earlier did) the case of
# being called with (None, None).
if etype is None:
- return [_format_final_exc_line(etype, value)]
+ yield _format_final_exc_line(etype, value)
+ return
stype = etype.__name__
smod = etype.__module__
@@ -215,26 +211,26 @@ def format_exception_only(etype, value):
stype = smod + '.' + stype
if not issubclass(etype, SyntaxError):
- return [_format_final_exc_line(stype, value)]
+ yield _format_final_exc_line(stype, value)
+ return
# It was a syntax error; show exactly where the problem was found.
- lines = []
filename = value.filename or "<string>"
lineno = str(value.lineno) or '?'
- lines.append(' File "%s", line %s\n' % (filename, lineno))
+ yield ' File "{}", line {}\n'.format(filename, lineno)
+
badline = value.text
offset = value.offset
if badline is not None:
- lines.append(' %s\n' % badline.strip())
+ yield ' {}\n'.format(badline.strip())
if offset is not None:
caretspace = badline.rstrip('\n')[:offset].lstrip()
# non-space whitespace (likes tabs) must be kept for alignment
caretspace = ((c.isspace() and c or ' ') for c in caretspace)
# only three spaces to account for offset1 == pos 0
- lines.append(' %s^\n' % ''.join(caretspace))
+ yield ' {}^\n'.format(''.join(caretspace))
msg = value.msg or "<no detail available>"
- lines.append("%s: %s\n" % (stype, msg))
- return lines
+ yield "{}: {}\n".format(stype, msg)
def _format_final_exc_line(etype, value):
valuestr = _some_str(value)
@@ -250,38 +246,34 @@ def _some_str(value):
except:
return '<unprintable %s object>' % type(value).__name__
-
def print_exc(limit=None, file=None, chain=True):
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
- if file is None:
- file = sys.stderr
- try:
- etype, value, tb = sys.exc_info()
- print_exception(etype, value, tb, limit, file, chain)
- finally:
- etype = value = tb = None
-
+ print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain)
def format_exc(limit=None, chain=True):
"""Like print_exc() but return a string."""
- try:
- etype, value, tb = sys.exc_info()
- return ''.join(
- format_exception(etype, value, tb, limit, chain))
- finally:
- etype = value = tb = None
-
+ return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
def print_last(limit=None, file=None, chain=True):
"""This is a shorthand for 'print_exception(sys.last_type,
sys.last_value, sys.last_traceback, limit, file)'."""
if not hasattr(sys, "last_type"):
raise ValueError("no last exception")
- if file is None:
- file = sys.stderr
print_exception(sys.last_type, sys.last_value, sys.last_traceback,
limit, file, chain)
+#
+# Printing and Extracting Stacks.
+#
+
+def _extract_stack_iter(f, limit=None):
+ return _extract_tb_or_stack_iter(
+ f, limit, lambda f: (f, f.f_lineno, f.f_back))
+
+def _get_stack(f):
+ if f is None:
+ f = sys._getframe().f_back.f_back
+ return f
def print_stack(f=None, limit=None, file=None):
"""Print a stack trace from its invocation point.
@@ -290,21 +282,11 @@ def print_stack(f=None, limit=None, file=None):
stack frame at which to start. The optional 'limit' and 'file'
arguments have the same meaning as for print_exception().
"""
- if f is None:
- try:
- raise ZeroDivisionError
- except ZeroDivisionError:
- f = sys.exc_info()[2].tb_frame.f_back
- print_list(extract_stack(f, limit), file)
+ print_list(extract_stack(_get_stack(f), limit=limit), file=file)
def format_stack(f=None, limit=None):
"""Shorthand for 'format_list(extract_stack(f, limit))'."""
- if f is None:
- try:
- raise ZeroDivisionError
- except ZeroDivisionError:
- f = sys.exc_info()[2].tb_frame.f_back
- return format_list(extract_stack(f, limit))
+ return format_list(extract_stack(_get_stack(f), limit=limit))
def extract_stack(f=None, limit=None):
"""Extract the raw traceback from the current stack frame.
@@ -315,27 +297,16 @@ def extract_stack(f=None, limit=None):
line number, function name, text), and the entries are in order
from oldest to newest stack frame.
"""
- if f is None:
+ stack = list(_extract_stack_iter(_get_stack(f), limit=limit))
+ stack.reverse()
+ return stack
+
+def clear_frames(tb):
+ "Clear all references to local variables in the frames of a traceback."
+ while tb is not None:
try:
- raise ZeroDivisionError
- except ZeroDivisionError:
- f = sys.exc_info()[2].tb_frame.f_back
- if limit is None:
- if hasattr(sys, 'tracebacklimit'):
- limit = sys.tracebacklimit
- list = []
- n = 0
- while f is not None and (limit is None or n < limit):
- lineno = f.f_lineno
- co = f.f_code
- filename = co.co_filename
- name = co.co_name
- linecache.checkcache(filename)
- line = linecache.getline(filename, lineno, f.f_globals)
- if line: line = line.strip()
- else: line = None
- list.append((filename, lineno, name, line))
- f = f.f_back
- n = n+1
- list.reverse()
- return list
+ tb.tb_frame.clear()
+ except RuntimeError:
+ # Ignore the exception raised if the frame is still executing.
+ pass
+ tb = tb.tb_next
diff --git a/Lib/tracemalloc.py b/Lib/tracemalloc.py
new file mode 100644
index 0000000000..816f7346d6
--- /dev/null
+++ b/Lib/tracemalloc.py
@@ -0,0 +1,459 @@
+from collections import Sequence
+from functools import total_ordering
+import fnmatch
+import os.path
+import pickle
+
+# Import types and functions implemented in C
+from _tracemalloc import *
+from _tracemalloc import _get_object_traceback, _get_traces
+
+
+def _format_size(size, sign):
+ for unit in ('B', 'KiB', 'MiB', 'GiB', 'TiB'):
+ if abs(size) < 100 and unit != 'B':
+ # 3 digits (xx.x UNIT)
+ if sign:
+ return "%+.1f %s" % (size, unit)
+ else:
+ return "%.1f %s" % (size, unit)
+ if abs(size) < 10 * 1024 or unit == 'TiB':
+ # 4 or 5 digits (xxxx UNIT)
+ if sign:
+ return "%+.0f %s" % (size, unit)
+ else:
+ return "%.0f %s" % (size, unit)
+ size /= 1024
+
+
+class Statistic:
+ """
+ Statistic difference on memory allocations between two Snapshot instance.
+ """
+
+ __slots__ = ('traceback', 'size', 'count')
+
+ def __init__(self, traceback, size, count):
+ self.traceback = traceback
+ self.size = size
+ self.count = count
+
+ def __hash__(self):
+ return hash((self.traceback, self.size, self.count))
+
+ def __eq__(self, other):
+ return (self.traceback == other.traceback
+ and self.size == other.size
+ and self.count == other.count)
+
+ def __str__(self):
+ text = ("%s: size=%s, count=%i"
+ % (self.traceback,
+ _format_size(self.size, False),
+ self.count))
+ if self.count:
+ average = self.size / self.count
+ text += ", average=%s" % _format_size(average, False)
+ return text
+
+ def __repr__(self):
+ return ('<Statistic traceback=%r size=%i count=%i>'
+ % (self.traceback, self.size, self.count))
+
+ def _sort_key(self):
+ return (self.size, self.count, self.traceback)
+
+
+class StatisticDiff:
+ """
+ Statistic difference on memory allocations between an old and a new
+ Snapshot instance.
+ """
+ __slots__ = ('traceback', 'size', 'size_diff', 'count', 'count_diff')
+
+ def __init__(self, traceback, size, size_diff, count, count_diff):
+ self.traceback = traceback
+ self.size = size
+ self.size_diff = size_diff
+ self.count = count
+ self.count_diff = count_diff
+
+ def __hash__(self):
+ return hash((self.traceback, self.size, self.size_diff,
+ self.count, self.count_diff))
+
+ def __eq__(self, other):
+ return (self.traceback == other.traceback
+ and self.size == other.size
+ and self.size_diff == other.size_diff
+ and self.count == other.count
+ and self.count_diff == other.count_diff)
+
+ def __str__(self):
+ text = ("%s: size=%s (%s), count=%i (%+i)"
+ % (self.traceback,
+ _format_size(self.size, False),
+ _format_size(self.size_diff, True),
+ self.count,
+ self.count_diff))
+ if self.count:
+ average = self.size / self.count
+ text += ", average=%s" % _format_size(average, False)
+ return text
+
+ def __repr__(self):
+ return ('<StatisticDiff traceback=%r size=%i (%+i) count=%i (%+i)>'
+ % (self.traceback, self.size, self.size_diff,
+ self.count, self.count_diff))
+
+ def _sort_key(self):
+ return (abs(self.size_diff), self.size,
+ abs(self.count_diff), self.count,
+ self.traceback)
+
+
+def _compare_grouped_stats(old_group, new_group):
+ statistics = []
+ for traceback, stat in new_group.items():
+ previous = old_group.pop(traceback, None)
+ if previous is not None:
+ stat = StatisticDiff(traceback,
+ stat.size, stat.size - previous.size,
+ stat.count, stat.count - previous.count)
+ else:
+ stat = StatisticDiff(traceback,
+ stat.size, stat.size,
+ stat.count, stat.count)
+ statistics.append(stat)
+
+ for traceback, stat in old_group.items():
+ stat = StatisticDiff(traceback, 0, -stat.size, 0, -stat.count)
+ statistics.append(stat)
+ return statistics
+
+
+@total_ordering
+class Frame:
+ """
+ Frame of a traceback.
+ """
+ __slots__ = ("_frame",)
+
+ def __init__(self, frame):
+ self._frame = frame
+
+ @property
+ def filename(self):
+ return self._frame[0]
+
+ @property
+ def lineno(self):
+ return self._frame[1]
+
+ def __eq__(self, other):
+ return (self._frame == other._frame)
+
+ def __lt__(self, other):
+ return (self._frame < other._frame)
+
+ def __hash__(self):
+ return hash(self._frame)
+
+ def __str__(self):
+ return "%s:%s" % (self.filename, self.lineno)
+
+ def __repr__(self):
+ return "<Frame filename=%r lineno=%r>" % (self.filename, self.lineno)
+
+
+@total_ordering
+class Traceback(Sequence):
+ """
+ Sequence of Frame instances sorted from the most recent frame
+ to the oldest frame.
+ """
+ __slots__ = ("_frames",)
+
+ def __init__(self, frames):
+ Sequence.__init__(self)
+ self._frames = frames
+
+ def __len__(self):
+ return len(self._frames)
+
+ def __getitem__(self, index):
+ trace = self._frames[index]
+ return Frame(trace)
+
+ def __contains__(self, frame):
+ return frame._frame in self._frames
+
+ def __hash__(self):
+ return hash(self._frames)
+
+ def __eq__(self, other):
+ return (self._frames == other._frames)
+
+ def __lt__(self, other):
+ return (self._frames < other._frames)
+
+ def __str__(self):
+ return str(self[0])
+
+ def __repr__(self):
+ return "<Traceback %r>" % (tuple(self),)
+
+
+def get_object_traceback(obj):
+ """
+ Get the traceback where the Python object *obj* was allocated.
+ Return a Traceback instance.
+
+ Return None if the tracemalloc module is not tracing memory allocations or
+ did not trace the allocation of the object.
+ """
+ frames = _get_object_traceback(obj)
+ if frames is not None:
+ return Traceback(frames)
+ else:
+ return None
+
+
+class Trace:
+ """
+ Trace of a memory block.
+ """
+ __slots__ = ("_trace",)
+
+ def __init__(self, trace):
+ self._trace = trace
+
+ @property
+ def size(self):
+ return self._trace[0]
+
+ @property
+ def traceback(self):
+ return Traceback(self._trace[1])
+
+ def __eq__(self, other):
+ return (self._trace == other._trace)
+
+ def __hash__(self):
+ return hash(self._trace)
+
+ def __str__(self):
+ return "%s: %s" % (self.traceback, _format_size(self.size, False))
+
+ def __repr__(self):
+ return ("<Trace size=%s, traceback=%r>"
+ % (_format_size(self.size, False), self.traceback))
+
+
+class _Traces(Sequence):
+ def __init__(self, traces):
+ Sequence.__init__(self)
+ self._traces = traces
+
+ def __len__(self):
+ return len(self._traces)
+
+ def __getitem__(self, index):
+ trace = self._traces[index]
+ return Trace(trace)
+
+ def __contains__(self, trace):
+ return trace._trace in self._traces
+
+ def __eq__(self, other):
+ return (self._traces == other._traces)
+
+ def __repr__(self):
+ return "<Traces len=%s>" % len(self)
+
+
+def _normalize_filename(filename):
+ filename = os.path.normcase(filename)
+ if filename.endswith(('.pyc', '.pyo')):
+ filename = filename[:-1]
+ return filename
+
+
+class Filter:
+ def __init__(self, inclusive, filename_pattern,
+ lineno=None, all_frames=False):
+ self.inclusive = inclusive
+ self._filename_pattern = _normalize_filename(filename_pattern)
+ self.lineno = lineno
+ self.all_frames = all_frames
+
+ @property
+ def filename_pattern(self):
+ return self._filename_pattern
+
+ def __match_frame(self, filename, lineno):
+ filename = _normalize_filename(filename)
+ if not fnmatch.fnmatch(filename, self._filename_pattern):
+ return False
+ if self.lineno is None:
+ return True
+ else:
+ return (lineno == self.lineno)
+
+ def _match_frame(self, filename, lineno):
+ return self.__match_frame(filename, lineno) ^ (not self.inclusive)
+
+ def _match_traceback(self, traceback):
+ if self.all_frames:
+ if any(self.__match_frame(filename, lineno)
+ for filename, lineno in traceback):
+ return self.inclusive
+ else:
+ return (not self.inclusive)
+ else:
+ filename, lineno = traceback[0]
+ return self._match_frame(filename, lineno)
+
+
+class Snapshot:
+ """
+ Snapshot of traces of memory blocks allocated by Python.
+ """
+
+ def __init__(self, traces, traceback_limit):
+ self.traces = _Traces(traces)
+ self.traceback_limit = traceback_limit
+
+ def dump(self, filename):
+ """
+ Write the snapshot into a file.
+ """
+ with open(filename, "wb") as fp:
+ pickle.dump(self, fp, pickle.HIGHEST_PROTOCOL)
+
+ @staticmethod
+ def load(filename):
+ """
+ Load a snapshot from a file.
+ """
+ with open(filename, "rb") as fp:
+ return pickle.load(fp)
+
+ def _filter_trace(self, include_filters, exclude_filters, trace):
+ traceback = trace[1]
+ if include_filters:
+ if not any(trace_filter._match_traceback(traceback)
+ for trace_filter in include_filters):
+ return False
+ if exclude_filters:
+ if any(not trace_filter._match_traceback(traceback)
+ for trace_filter in exclude_filters):
+ return False
+ return True
+
+ def filter_traces(self, filters):
+ """
+ Create a new Snapshot instance with a filtered traces sequence, filters
+ is a list of Filter instances. If filters is an empty list, return a
+ new Snapshot instance with a copy of the traces.
+ """
+ if filters:
+ include_filters = []
+ exclude_filters = []
+ for trace_filter in filters:
+ if trace_filter.inclusive:
+ include_filters.append(trace_filter)
+ else:
+ exclude_filters.append(trace_filter)
+ new_traces = [trace for trace in self.traces._traces
+ if self._filter_trace(include_filters,
+ exclude_filters,
+ trace)]
+ else:
+ new_traces = self.traces._traces.copy()
+ return Snapshot(new_traces, self.traceback_limit)
+
+ def _group_by(self, key_type, cumulative):
+ if key_type not in ('traceback', 'filename', 'lineno'):
+ raise ValueError("unknown key_type: %r" % (key_type,))
+ if cumulative and key_type not in ('lineno', 'filename'):
+ raise ValueError("cumulative mode cannot by used "
+ "with key type %r" % key_type)
+
+ stats = {}
+ tracebacks = {}
+ if not cumulative:
+ for trace in self.traces._traces:
+ size, trace_traceback = trace
+ try:
+ traceback = tracebacks[trace_traceback]
+ except KeyError:
+ if key_type == 'traceback':
+ frames = trace_traceback
+ elif key_type == 'lineno':
+ frames = trace_traceback[:1]
+ else: # key_type == 'filename':
+ frames = ((trace_traceback[0][0], 0),)
+ traceback = Traceback(frames)
+ tracebacks[trace_traceback] = traceback
+ try:
+ stat = stats[traceback]
+ stat.size += size
+ stat.count += 1
+ except KeyError:
+ stats[traceback] = Statistic(traceback, size, 1)
+ else:
+ # cumulative statistics
+ for trace in self.traces._traces:
+ size, trace_traceback = trace
+ for frame in trace_traceback:
+ try:
+ traceback = tracebacks[frame]
+ except KeyError:
+ if key_type == 'lineno':
+ frames = (frame,)
+ else: # key_type == 'filename':
+ frames = ((frame[0], 0),)
+ traceback = Traceback(frames)
+ tracebacks[frame] = traceback
+ try:
+ stat = stats[traceback]
+ stat.size += size
+ stat.count += 1
+ except KeyError:
+ stats[traceback] = Statistic(traceback, size, 1)
+ return stats
+
+ def statistics(self, key_type, cumulative=False):
+ """
+ Group statistics by key_type. Return a sorted list of Statistic
+ instances.
+ """
+ grouped = self._group_by(key_type, cumulative)
+ statistics = list(grouped.values())
+ statistics.sort(reverse=True, key=Statistic._sort_key)
+ return statistics
+
+ def compare_to(self, old_snapshot, key_type, cumulative=False):
+ """
+ Compute the differences with an old snapshot old_snapshot. Get
+ statistics as a sorted list of StatisticDiff instances, grouped by
+ group_by.
+ """
+ new_group = self._group_by(key_type, cumulative)
+ old_group = old_snapshot._group_by(key_type, cumulative)
+ statistics = _compare_grouped_stats(old_group, new_group)
+ statistics.sort(reverse=True, key=StatisticDiff._sort_key)
+ return statistics
+
+
+def take_snapshot():
+ """
+ Take a snapshot of traces of memory blocks allocated by Python.
+ """
+ if not is_tracing():
+ raise RuntimeError("the tracemalloc module must be tracing memory "
+ "allocations to take a snapshot")
+ traces = _get_traces()
+ traceback_limit = get_traceback_limit()
+ return Snapshot(traces, traceback_limit)
diff --git a/Lib/turtle.py b/Lib/turtle.py
index c6ebfa5c6d..ab7b6ba42a 100644
--- a/Lib/turtle.py
+++ b/Lib/turtle.py
@@ -3843,18 +3843,18 @@ def write_docstringdict(filename="turtle_docstringdict"):
key = "Turtle."+methodname
docsdict[key] = eval(key).__doc__
- f = open("%s.py" % filename,"w")
- keys = sorted([x for x in docsdict.keys()
- if x.split('.')[1] not in _alias_list])
- f.write('docsdict = {\n\n')
- for key in keys[:-1]:
+ with open("%s.py" % filename,"w") as f:
+ keys = sorted([x for x in docsdict.keys()
+ if x.split('.')[1] not in _alias_list])
+ f.write('docsdict = {\n\n')
+ for key in keys[:-1]:
+ f.write('%s :\n' % repr(key))
+ f.write(' """%s\n""",\n\n' % docsdict[key])
+ key = keys[-1]
f.write('%s :\n' % repr(key))
- f.write(' """%s\n""",\n\n' % docsdict[key])
- key = keys[-1]
- f.write('%s :\n' % repr(key))
- f.write(' """%s\n"""\n\n' % docsdict[key])
- f.write("}\n")
- f.close()
+ f.write(' """%s\n"""\n\n' % docsdict[key])
+ f.write("}\n")
+ f.close()
def read_docstrings(lang):
"""Read in docstrings from lang-specific docstring dictionary.
diff --git a/Lib/turtledemo/__main__.py b/Lib/turtledemo/__main__.py
index cbf3aeb692..a14684c356 100755
--- a/Lib/turtledemo/__main__.py
+++ b/Lib/turtledemo/__main__.py
@@ -6,7 +6,7 @@ from tkinter import *
from idlelib.Percolator import Percolator
from idlelib.ColorDelegator import ColorDelegator
from idlelib.textView import view_file # TextViewer
-from imp import reload
+from importlib import reload
import turtle
import time
diff --git a/Lib/types.py b/Lib/types.py
index cfd09eaaff..7e4fec2d25 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -99,3 +99,60 @@ def _calculate_meta(meta, bases):
"must be a (non-strict) subclass "
"of the metaclasses of all its bases")
return winner
+
+class DynamicClassAttribute:
+ """Route attribute access on a class to __getattr__.
+
+ This is a descriptor, used to define attributes that act differently when
+ accessed through an instance and through a class. Instance access remains
+ normal, but access to an attribute through a class will be routed to the
+ class's __getattr__ method; this is done by raising AttributeError.
+
+ This allows one to have properties active on an instance, and have virtual
+ attributes on the class with the same name (see Enum for an example).
+
+ """
+ def __init__(self, fget=None, fset=None, fdel=None, doc=None):
+ self.fget = fget
+ self.fset = fset
+ self.fdel = fdel
+ # next two lines make DynamicClassAttribute act the same as property
+ self.__doc__ = doc or fget.__doc__
+ self.overwrite_doc = doc is None
+ # support for abstract methods
+ self.__isabstractmethod__ = bool(getattr(fget, '__isabstractmethod__', False))
+
+ def __get__(self, instance, ownerclass=None):
+ if instance is None:
+ if self.__isabstractmethod__:
+ return self
+ raise AttributeError()
+ elif self.fget is None:
+ raise AttributeError("unreadable attribute")
+ return self.fget(instance)
+
+ def __set__(self, instance, value):
+ if self.fset is None:
+ raise AttributeError("can't set attribute")
+ self.fset(instance, value)
+
+ def __delete__(self, instance):
+ if self.fdel is None:
+ raise AttributeError("can't delete attribute")
+ self.fdel(instance)
+
+ def getter(self, fget):
+ fdoc = fget.__doc__ if self.overwrite_doc else None
+ result = type(self)(fget, self.fset, self.fdel, fdoc or self.__doc__)
+ result.overwrite_doc = self.overwrite_doc
+ return result
+
+ def setter(self, fset):
+ result = type(self)(self.fget, fset, self.fdel, self.__doc__)
+ result.overwrite_doc = self.overwrite_doc
+ return result
+
+ def deleter(self, fdel):
+ result = type(self)(self.fget, self.fset, fdel, self.__doc__)
+ result.overwrite_doc = self.overwrite_doc
+ return result
diff --git a/Lib/unittest/__main__.py b/Lib/unittest/__main__.py
index 798ebc0f53..2663178d3f 100644
--- a/Lib/unittest/__main__.py
+++ b/Lib/unittest/__main__.py
@@ -13,7 +13,6 @@ if sys.argv[0].endswith("__main__.py"):
__unittest = True
-from .main import main, TestProgram, USAGE_AS_MAIN
-TestProgram.USAGE = USAGE_AS_MAIN
+from .main import main, TestProgram
main(module=None)
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index f56af5574b..7ed932fafd 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -3,14 +3,16 @@
import sys
import functools
import difflib
+import logging
import pprint
import re
import warnings
import collections
+import contextlib
from . import result
from .util import (strclass, safe_repr, _count_diff_all_purpose,
- _count_diff_hashable)
+ _count_diff_hashable, _common_shorten_repr)
__unittest = True
@@ -26,17 +28,11 @@ class SkipTest(Exception):
instead of raising this directly.
"""
-class _ExpectedFailure(Exception):
+class _ShouldStop(Exception):
"""
- Raise this when a test is expected to fail.
-
- This is an implementation detail.
+ The test should stop.
"""
- def __init__(self, exc_info):
- super(_ExpectedFailure, self).__init__()
- self.exc_info = exc_info
-
class _UnexpectedSuccess(Exception):
"""
The test was supposed to fail, but it didn't!
@@ -44,13 +40,40 @@ class _UnexpectedSuccess(Exception):
class _Outcome(object):
- def __init__(self):
+ def __init__(self, result=None):
+ self.expecting_failure = False
+ self.result = result
+ self.result_supports_subtests = hasattr(result, "addSubTest")
self.success = True
- self.skipped = None
- self.unexpectedSuccess = None
+ self.skipped = []
self.expectedFailure = None
self.errors = []
- self.failures = []
+
+ @contextlib.contextmanager
+ def testPartExecutor(self, test_case, isTest=False):
+ old_success = self.success
+ self.success = True
+ try:
+ yield
+ except KeyboardInterrupt:
+ raise
+ except SkipTest as e:
+ self.success = False
+ self.skipped.append((test_case, str(e)))
+ except _ShouldStop:
+ pass
+ except:
+ exc_info = sys.exc_info()
+ if self.expecting_failure:
+ self.expectedFailure = exc_info
+ else:
+ self.success = False
+ self.errors.append((test_case, exc_info))
+ else:
+ if self.result_supports_subtests and self.success:
+ self.errors.append((test_case, None))
+ finally:
+ self.success = self.success and old_success
def _id(obj):
@@ -88,22 +111,26 @@ def skipUnless(condition, reason):
return skip(reason)
return _id
+def expectedFailure(test_item):
+ test_item.__unittest_expecting_failure__ = True
+ return test_item
-def expectedFailure(func):
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- try:
- func(*args, **kwargs)
- except Exception:
- raise _ExpectedFailure(sys.exc_info())
- raise _UnexpectedSuccess
- return wrapper
+class _BaseTestCaseContext:
-class _AssertRaisesBaseContext(object):
+ def __init__(self, test_case):
+ self.test_case = test_case
+
+ def _raiseFailure(self, standardMsg):
+ msg = self.test_case._formatMessage(self.msg, standardMsg)
+ raise self.test_case.failureException(msg)
+
+
+class _AssertRaisesBaseContext(_BaseTestCaseContext):
def __init__(self, expected, test_case, callable_obj=None,
expected_regex=None):
+ _BaseTestCaseContext.__init__(self, test_case)
self.expected = expected
self.test_case = test_case
if callable_obj is not None:
@@ -118,10 +145,6 @@ class _AssertRaisesBaseContext(object):
self.expected_regex = expected_regex
self.msg = None
- def _raiseFailure(self, standardMsg):
- msg = self.test_case._formatMessage(self.msg, standardMsg)
- raise self.test_case.failureException(msg)
-
def handle(self, name, callable_obj, args, kwargs):
"""
If callable_obj is None, assertRaises/Warns is being used as a
@@ -135,7 +158,6 @@ class _AssertRaisesBaseContext(object):
callable_obj(*args, **kwargs)
-
class _AssertRaisesContext(_AssertRaisesBaseContext):
"""A context manager used to implement TestCase.assertRaises* methods."""
@@ -217,6 +239,74 @@ class _AssertWarnsContext(_AssertRaisesBaseContext):
self._raiseFailure("{} not triggered".format(exc_name))
+
+_LoggingWatcher = collections.namedtuple("_LoggingWatcher",
+ ["records", "output"])
+
+
+class _CapturingHandler(logging.Handler):
+ """
+ A logging handler capturing all (raw and formatted) logging output.
+ """
+
+ def __init__(self):
+ logging.Handler.__init__(self)
+ self.watcher = _LoggingWatcher([], [])
+
+ def flush(self):
+ pass
+
+ def emit(self, record):
+ self.watcher.records.append(record)
+ msg = self.format(record)
+ self.watcher.output.append(msg)
+
+
+
+class _AssertLogsContext(_BaseTestCaseContext):
+ """A context manager used to implement TestCase.assertLogs()."""
+
+ LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
+
+ def __init__(self, test_case, logger_name, level):
+ _BaseTestCaseContext.__init__(self, test_case)
+ self.logger_name = logger_name
+ if level:
+ self.level = logging._nameToLevel.get(level, level)
+ else:
+ self.level = logging.INFO
+ self.msg = None
+
+ def __enter__(self):
+ if isinstance(self.logger_name, logging.Logger):
+ logger = self.logger = self.logger_name
+ else:
+ logger = self.logger = logging.getLogger(self.logger_name)
+ formatter = logging.Formatter(self.LOGGING_FORMAT)
+ handler = _CapturingHandler()
+ handler.setFormatter(formatter)
+ self.watcher = handler.watcher
+ self.old_handlers = logger.handlers[:]
+ self.old_level = logger.level
+ self.old_propagate = logger.propagate
+ logger.handlers = [handler]
+ logger.setLevel(self.level)
+ logger.propagate = False
+ return handler.watcher
+
+ def __exit__(self, exc_type, exc_value, tb):
+ self.logger.handlers = self.old_handlers
+ self.logger.propagate = self.old_propagate
+ self.logger.setLevel(self.old_level)
+ if exc_type is not None:
+ # let unexpected exceptions pass through
+ return False
+ if len(self.watcher.records) == 0:
+ self._raiseFailure(
+ "no logs of level {} or higher triggered on {}"
+ .format(logging.getLevelName(self.level), self.logger.name))
+
+
class TestCase(object):
"""A class whose instances are single test cases.
@@ -270,7 +360,7 @@ class TestCase(object):
not have a method with the specified name.
"""
self._testMethodName = methodName
- self._outcomeForDoCleanups = None
+ self._outcome = None
self._testMethodDoc = 'No test'
try:
testMethod = getattr(self, methodName)
@@ -283,6 +373,7 @@ class TestCase(object):
else:
self._testMethodDoc = testMethod.__doc__
self._cleanups = []
+ self._subtest = None
# Map types to custom assertEqual functions that will compare
# instances of said type in more detail to generate a more useful
@@ -370,44 +461,80 @@ class TestCase(object):
return "<%s testMethod=%s>" % \
(strclass(self.__class__), self._testMethodName)
- def _addSkip(self, result, reason):
+ def _addSkip(self, result, test_case, reason):
addSkip = getattr(result, 'addSkip', None)
if addSkip is not None:
- addSkip(self, reason)
+ addSkip(test_case, reason)
else:
warnings.warn("TestResult has no addSkip method, skips not reported",
RuntimeWarning, 2)
+ result.addSuccess(test_case)
+
+ @contextlib.contextmanager
+ def subTest(self, msg=None, **params):
+ """Return a context manager that will return the enclosed block
+ of code in a subtest identified by the optional message and
+ keyword parameters. A failure in the subtest marks the test
+ case as failed but resumes execution at the end of the enclosed
+ block, allowing further test code to be executed.
+ """
+ if not self._outcome.result_supports_subtests:
+ yield
+ return
+ parent = self._subtest
+ if parent is None:
+ params_map = collections.ChainMap(params)
+ else:
+ params_map = parent.params.new_child(params)
+ self._subtest = _SubTest(self, msg, params_map)
+ try:
+ with self._outcome.testPartExecutor(self._subtest, isTest=True):
+ yield
+ if not self._outcome.success:
+ result = self._outcome.result
+ if result is not None and result.failfast:
+ raise _ShouldStop
+ elif self._outcome.expectedFailure:
+ # If the test is expecting a failure, we really want to
+ # stop now and register the expected failure.
+ raise _ShouldStop
+ finally:
+ self._subtest = parent
+
+ def _feedErrorsToResult(self, result, errors):
+ for test, exc_info in errors:
+ if isinstance(test, _SubTest):
+ result.addSubTest(test.test_case, test, exc_info)
+ elif exc_info is not None:
+ if issubclass(exc_info[0], self.failureException):
+ result.addFailure(test, exc_info)
+ else:
+ result.addError(test, exc_info)
+
+ def _addExpectedFailure(self, result, exc_info):
+ try:
+ addExpectedFailure = result.addExpectedFailure
+ except AttributeError:
+ warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
+ RuntimeWarning)
result.addSuccess(self)
+ else:
+ addExpectedFailure(self, exc_info)
- def _executeTestPart(self, function, outcome, isTest=False):
+ def _addUnexpectedSuccess(self, result):
try:
- function()
- except KeyboardInterrupt:
- raise
- except SkipTest as e:
- outcome.success = False
- outcome.skipped = str(e)
- except _UnexpectedSuccess:
- exc_info = sys.exc_info()
- outcome.success = False
- if isTest:
- outcome.unexpectedSuccess = exc_info
- else:
- outcome.errors.append(exc_info)
- except _ExpectedFailure:
- outcome.success = False
- exc_info = sys.exc_info()
- if isTest:
- outcome.expectedFailure = exc_info
- else:
- outcome.errors.append(exc_info)
- except self.failureException:
- outcome.success = False
- outcome.failures.append(sys.exc_info())
- exc_info = sys.exc_info()
- except:
- outcome.success = False
- outcome.errors.append(sys.exc_info())
+ addUnexpectedSuccess = result.addUnexpectedSuccess
+ except AttributeError:
+ warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failure",
+ RuntimeWarning)
+ # We need to pass an actual exception and traceback to addFailure,
+ # otherwise the legacy result can choke.
+ try:
+ raise _UnexpectedSuccess from None
+ except _UnexpectedSuccess:
+ result.addFailure(self, sys.exc_info())
+ else:
+ addUnexpectedSuccess(self)
def run(self, result=None):
orig_result = result
@@ -426,46 +553,38 @@ class TestCase(object):
try:
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
or getattr(testMethod, '__unittest_skip_why__', ''))
- self._addSkip(result, skip_why)
+ self._addSkip(result, self, skip_why)
finally:
result.stopTest(self)
return
+ expecting_failure = getattr(testMethod,
+ "__unittest_expecting_failure__", False)
try:
- outcome = _Outcome()
- self._outcomeForDoCleanups = outcome
+ outcome = _Outcome(result)
+ self._outcome = outcome
- self._executeTestPart(self.setUp, outcome)
+ with outcome.testPartExecutor(self):
+ self.setUp()
if outcome.success:
- self._executeTestPart(testMethod, outcome, isTest=True)
- self._executeTestPart(self.tearDown, outcome)
+ outcome.expecting_failure = expecting_failure
+ with outcome.testPartExecutor(self, isTest=True):
+ testMethod()
+ outcome.expecting_failure = False
+ with outcome.testPartExecutor(self):
+ self.tearDown()
self.doCleanups()
+ for test, reason in outcome.skipped:
+ self._addSkip(result, test, reason)
+ self._feedErrorsToResult(result, outcome.errors)
if outcome.success:
- result.addSuccess(self)
- else:
- if outcome.skipped is not None:
- self._addSkip(result, outcome.skipped)
- for exc_info in outcome.errors:
- result.addError(self, exc_info)
- for exc_info in outcome.failures:
- result.addFailure(self, exc_info)
- if outcome.unexpectedSuccess is not None:
- addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
- if addUnexpectedSuccess is not None:
- addUnexpectedSuccess(self)
+ if expecting_failure:
+ if outcome.expectedFailure:
+ self._addExpectedFailure(result, outcome.expectedFailure)
else:
- warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
- RuntimeWarning)
- result.addFailure(self, outcome.unexpectedSuccess)
-
- if outcome.expectedFailure is not None:
- addExpectedFailure = getattr(result, 'addExpectedFailure', None)
- if addExpectedFailure is not None:
- addExpectedFailure(self, outcome.expectedFailure)
- else:
- warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
- RuntimeWarning)
- result.addSuccess(self)
+ self._addUnexpectedSuccess(result)
+ else:
+ result.addSuccess(self)
return result
finally:
result.stopTest(self)
@@ -477,11 +596,11 @@ class TestCase(object):
def doCleanups(self):
"""Execute all cleanup functions. Normally called for you after
tearDown."""
- outcome = self._outcomeForDoCleanups or _Outcome()
+ outcome = self._outcome or _Outcome()
while self._cleanups:
function, args, kwargs = self._cleanups.pop()
- part = lambda: function(*args, **kwargs)
- self._executeTestPart(part, outcome)
+ with outcome.testPartExecutor(self):
+ function(*args, **kwargs)
# return this for backwards compatibility
# even though we no longer us it internally
@@ -600,6 +719,28 @@ class TestCase(object):
context = _AssertWarnsContext(expected_warning, self, callable_obj)
return context.handle('assertWarns', callable_obj, args, kwargs)
+ def assertLogs(self, logger=None, level=None):
+ """Fail unless a log message of level *level* or higher is emitted
+ on *logger_name* or its children. If omitted, *level* defaults to
+ INFO and *logger* defaults to the root logger.
+
+ This method must be used as a context manager, and will yield
+ a recording object with two attributes: `output` and `records`.
+ At the end of the context manager, the `output` attribute will
+ be a list of the matching formatted log messages and the
+ `records` attribute will be a list of the corresponding LogRecord
+ objects.
+
+ Example::
+
+ with self.assertLogs('foo', level='INFO') as cm:
+ logging.getLogger('foo').info('first message')
+ logging.getLogger('foo.bar').error('second message')
+ self.assertEqual(cm.output, ['INFO:foo:first message',
+ 'ERROR:foo.bar:second message'])
+ """
+ return _AssertLogsContext(self, logger, level)
+
def _getAssertEqualityFunc(self, first, second):
"""Get a detailed comparison function for the types of the two args.
@@ -629,7 +770,7 @@ class TestCase(object):
def _baseAssertEqual(self, first, second, msg=None):
"""The default assertEqual implementation, not type specific."""
if not first == second:
- standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second))
+ standardMsg = '%s != %s' % _common_shorten_repr(first, second)
msg = self._formatMessage(msg, standardMsg)
raise self.failureException(msg)
@@ -764,14 +905,9 @@ class TestCase(object):
if seq1 == seq2:
return
- seq1_repr = safe_repr(seq1)
- seq2_repr = safe_repr(seq2)
- if len(seq1_repr) > 30:
- seq1_repr = seq1_repr[:30] + '...'
- if len(seq2_repr) > 30:
- seq2_repr = seq2_repr[:30] + '...'
- elements = (seq_type_name.capitalize(), seq1_repr, seq2_repr)
- differing = '%ss differ: %s != %s\n' % elements
+ differing = '%ss differ: %s != %s\n' % (
+ (seq_type_name.capitalize(),) +
+ _common_shorten_repr(seq1, seq2))
for i in range(min(len1, len2)):
try:
@@ -929,7 +1065,7 @@ class TestCase(object):
self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
if d1 != d2:
- standardMsg = '%s != %s' % (safe_repr(d1, True), safe_repr(d2, True))
+ standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
diff = ('\n' + '\n'.join(difflib.ndiff(
pprint.pformat(d1).splitlines(),
pprint.pformat(d2).splitlines())))
@@ -1013,8 +1149,7 @@ class TestCase(object):
if len(firstlines) == 1 and first.strip('\r\n') == first:
firstlines = [first + '\n']
secondlines = [second + '\n']
- standardMsg = '%s != %s' % (safe_repr(first, True),
- safe_repr(second, True))
+ standardMsg = '%s != %s' % _common_shorten_repr(first, second)
diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines))
standardMsg = self._truncateMessage(standardMsg, diff)
self.fail(self._formatMessage(msg, standardMsg))
@@ -1212,3 +1347,39 @@ class FunctionTestCase(TestCase):
return self._description
doc = self._testFunc.__doc__
return doc and doc.split("\n")[0].strip() or None
+
+
+class _SubTest(TestCase):
+
+ def __init__(self, test_case, message, params):
+ super().__init__()
+ self._message = message
+ self.test_case = test_case
+ self.params = params
+ self.failureException = test_case.failureException
+
+ def runTest(self):
+ raise NotImplementedError("subtests cannot be run directly")
+
+ def _subDescription(self):
+ parts = []
+ if self._message:
+ parts.append("[{}]".format(self._message))
+ if self.params:
+ params_desc = ', '.join(
+ "{}={!r}".format(k, v)
+ for (k, v) in sorted(self.params.items()))
+ parts.append("({})".format(params_desc))
+ return " ".join(parts) or '(<subtest>)'
+
+ def id(self):
+ return "{} {}".format(self.test_case.id(), self._subDescription())
+
+ def shortDescription(self):
+ """Returns a one-line description of the subtest, or None if no
+ description has been provided.
+ """
+ return self.test_case.shortDescription()
+
+ def __str__(self):
+ return "{} {}".format(self.test_case, self._subDescription())
diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py
index 9ab26c1efb..808c50eb66 100644
--- a/Lib/unittest/loader.py
+++ b/Lib/unittest/loader.py
@@ -34,6 +34,14 @@ def _make_failed_test(classname, methodname, exception, suiteClass):
TestClass = type(classname, (case.TestCase,), attrs)
return suiteClass((TestClass(methodname),))
+def _make_skipped_test(methodname, exception, suiteClass):
+ @case.skip(str(exception))
+ def testSkipped(self):
+ pass
+ attrs = {methodname: testSkipped}
+ TestClass = type("ModuleSkipped", (case.TestCase,), attrs)
+ return suiteClass((TestClass(methodname),))
+
def _jython_aware_splitext(path):
if path.lower().endswith('$py.class'):
return path[:-9]
@@ -53,8 +61,9 @@ class TestLoader(object):
def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all tests cases contained in testCaseClass"""
if issubclass(testCaseClass, suite.TestSuite):
- raise TypeError("Test cases should not be derived from TestSuite." \
- " Maybe you meant to derive from TestCase?")
+ raise TypeError("Test cases should not be derived from "
+ "TestSuite. Maybe you meant to derive from "
+ "TestCase?")
testCaseNames = self.getTestCaseNames(testCaseClass)
if not testCaseNames and hasattr(testCaseClass, 'runTest'):
testCaseNames = ['runTest']
@@ -169,6 +178,9 @@ class TestLoader(object):
The pattern is deliberately not stored as a loader attribute so that
packages can continue discovery themselves. top_level_dir is stored so
load_tests does not need to pass this argument in to loader.discover().
+
+ Paths are sorted before being imported to ensure reproducible execution
+ order even on filesystems with non-alphabetical ordering like ext3/4.
"""
set_implicit_top = False
if top_level_dir is None and self._top_level_dir is not None:
@@ -189,6 +201,8 @@ class TestLoader(object):
self._top_level_dir = top_level_dir
is_not_importable = False
+ is_namespace = False
+ tests = []
if os.path.isdir(os.path.abspath(start_dir)):
start_dir = os.path.abspath(start_dir)
if start_dir != top_level_dir:
@@ -202,15 +216,52 @@ class TestLoader(object):
else:
the_module = sys.modules[start_dir]
top_part = start_dir.split('.')[0]
- start_dir = os.path.abspath(os.path.dirname((the_module.__file__)))
+ try:
+ start_dir = os.path.abspath(
+ os.path.dirname((the_module.__file__)))
+ except AttributeError:
+ # look for namespace packages
+ try:
+ spec = the_module.__spec__
+ except AttributeError:
+ spec = None
+
+ if spec and spec.loader is None:
+ if spec.submodule_search_locations is not None:
+ is_namespace = True
+
+ for path in the_module.__path__:
+ if (not set_implicit_top and
+ not path.startswith(top_level_dir)):
+ continue
+ self._top_level_dir = \
+ (path.split(the_module.__name__
+ .replace(".", os.path.sep))[0])
+ tests.extend(self._find_tests(path,
+ pattern,
+ namespace=True))
+ elif the_module.__name__ in sys.builtin_module_names:
+ # builtin module
+ raise TypeError('Can not use builtin modules '
+ 'as dotted module names') from None
+ else:
+ raise TypeError(
+ 'don\'t know how to discover from {!r}'
+ .format(the_module)) from None
+
if set_implicit_top:
- self._top_level_dir = self._get_directory_containing_module(top_part)
- sys.path.remove(top_level_dir)
+ if not is_namespace:
+ self._top_level_dir = \
+ self._get_directory_containing_module(top_part)
+ sys.path.remove(top_level_dir)
+ else:
+ sys.path.remove(top_level_dir)
if is_not_importable:
raise ImportError('Start directory is not importable: %r' % start_dir)
- tests = list(self._find_tests(start_dir, pattern))
+ if not is_namespace:
+ tests = list(self._find_tests(start_dir, pattern))
return self.suiteClass(tests)
def _get_directory_containing_module(self, module_name):
@@ -243,9 +294,9 @@ class TestLoader(object):
# override this method to use alternative matching strategy
return fnmatch(path, pattern)
- def _find_tests(self, start_dir, pattern):
+ def _find_tests(self, start_dir, pattern, namespace=False):
"""Used by discovery. Yields test suites it loads."""
- paths = os.listdir(start_dir)
+ paths = sorted(os.listdir(start_dir))
for path in paths:
full_path = os.path.join(start_dir, path)
@@ -259,6 +310,8 @@ class TestLoader(object):
name = self._get_name_from_path(full_path)
try:
module = self._get_module_from_name(name)
+ except case.SkipTest as e:
+ yield _make_skipped_test(name, e, self.suiteClass)
except:
yield _make_failed_import_test(name, self.suiteClass)
else:
@@ -274,7 +327,8 @@ class TestLoader(object):
raise ImportError(msg % (mod_name, module_dir, expected_dir))
yield self.loadTestsFromModule(module)
elif os.path.isdir(full_path):
- if not os.path.isfile(os.path.join(full_path, '__init__.py')):
+ if (not namespace and
+ not os.path.isfile(os.path.join(full_path, '__init__.py'))):
continue
load_tests = None
@@ -291,8 +345,8 @@ class TestLoader(object):
# tests loaded from package file
yield tests
# recurse into the package
- for test in self._find_tests(full_path, pattern):
- yield test
+ yield from self._find_tests(full_path, pattern,
+ namespace=namespace)
else:
try:
yield load_tests(self, tests, pattern)
diff --git a/Lib/unittest/main.py b/Lib/unittest/main.py
index ead64936a0..180df8676e 100644
--- a/Lib/unittest/main.py
+++ b/Lib/unittest/main.py
@@ -1,7 +1,7 @@
"""Unittest main program"""
import sys
-import optparse
+import argparse
import os
from . import loader, runner
@@ -9,53 +9,20 @@ from .signals import installHandler
__unittest = True
-FAILFAST = " -f, --failfast Stop on first failure\n"
-CATCHBREAK = " -c, --catch Catch control-C and display results\n"
-BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n"
-
-USAGE_AS_MAIN = """\
-Usage: %(progName)s [options] [tests]
-
-Options:
- -h, --help Show this message
- -v, --verbose Verbose output
- -q, --quiet Minimal output
-%(failfast)s%(catchbreak)s%(buffer)s
+MAIN_EXAMPLES = """\
Examples:
- %(progName)s test_module - run tests from test_module
- %(progName)s module.TestClass - run tests from module.TestClass
- %(progName)s module.Class.test_method - run specified test method
-
-[tests] can be a list of any number of test modules, classes and test
-methods.
-
-Alternative Usage: %(progName)s discover [options]
-
-Options:
- -v, --verbose Verbose output
-%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default)
- -p pattern Pattern to match test files ('test*.py' default)
- -t directory Top level directory of project (default to
- start directory)
-
-For test discovery all test modules must be importable from the top
-level directory of the project.
+ %(prog)s test_module - run tests from test_module
+ %(prog)s module.TestClass - run tests from module.TestClass
+ %(prog)s module.Class.test_method - run specified test method
"""
-USAGE_FROM_MODULE = """\
-Usage: %(progName)s [options] [test] [...]
-
-Options:
- -h, --help Show this message
- -v, --verbose Verbose output
- -q, --quiet Minimal output
-%(failfast)s%(catchbreak)s%(buffer)s
+MODULE_EXAMPLES = """\
Examples:
- %(progName)s - run default set of tests
- %(progName)s MyTestSuite - run suite 'MyTestSuite'
- %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
- %(progName)s MyTestCase - run all 'test*' test methods
- in MyTestCase
+ %(prog)s - run default set of tests
+ %(prog)s MyTestSuite - run suite 'MyTestSuite'
+ %(prog)s MyTestCase.testSomething - run MyTestCase.testSomething
+ %(prog)s MyTestCase - run all 'test*' test methods
+ in MyTestCase
"""
def _convert_name(name):
@@ -82,10 +49,11 @@ class TestProgram(object):
"""A command-line program that runs a set of tests; this is primarily
for making test modules conveniently executable.
"""
- USAGE = USAGE_FROM_MODULE
-
# defaults for testing
+ module=None
+ verbosity = 1
failfast = catchbreak = buffer = progName = warnings = None
+ _discovery_parser = None
def __init__(self, module='__main__', defaultTest=None, argv=None,
testRunner=None, testLoader=loader.defaultTestLoader,
@@ -127,44 +95,47 @@ class TestProgram(object):
def usageExit(self, msg=None):
if msg:
print(msg)
- usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
- 'buffer': ''}
- if self.failfast != False:
- usage['failfast'] = FAILFAST
- if self.catchbreak != False:
- usage['catchbreak'] = CATCHBREAK
- if self.buffer != False:
- usage['buffer'] = BUFFEROUTPUT
- print(self.USAGE % usage)
+ if self._discovery_parser is None:
+ self._initArgParsers()
+ self._print_help()
sys.exit(2)
- def parseArgs(self, argv):
- if ((len(argv) > 1 and argv[1].lower() == 'discover') or
- (len(argv) == 1 and self.module is None)):
- self._do_discovery(argv[2:])
- return
-
- parser = self._getOptParser()
- options, args = parser.parse_args(argv[1:])
- self._setAttributesFromOptions(options)
+ def _print_help(self, *args, **kwargs):
+ if self.module is None:
+ print(self._main_parser.format_help())
+ print(MAIN_EXAMPLES % {'prog': self.progName})
+ self._discovery_parser.print_help()
+ else:
+ print(self._main_parser.format_help())
+ print(MODULE_EXAMPLES % {'prog': self.progName})
- if len(args) == 0 and self.module is None:
- # this allows "python -m unittest -v" to still work for
- # test discovery. This means -c / -b / -v / -f options will
- # be handled twice, which is harmless but not ideal.
- self._do_discovery(argv[1:])
- return
+ def parseArgs(self, argv):
+ self._initArgParsers()
+ if self.module is None:
+ if len(argv) > 1 and argv[1].lower() == 'discover':
+ self._do_discovery(argv[2:])
+ return
+ self._main_parser.parse_args(argv[1:], self)
+ if not self.tests:
+ # this allows "python -m unittest -v" to still work for
+ # test discovery.
+ self._do_discovery([])
+ return
+ else:
+ self._main_parser.parse_args(argv[1:], self)
- if len(args) == 0 and self.defaultTest is None:
- # createTests will load tests from self.module
- self.testNames = None
- elif len(args) > 0:
- self.testNames = _convert_names(args)
+ if self.tests:
+ self.testNames = _convert_names(self.tests)
if __name__ == '__main__':
# to support python -m unittest ...
self.module = None
- else:
+ elif self.defaultTest is None:
+ # createTests will load tests from self.module
+ self.testNames = None
+ elif isinstance(self.defaultTest, str):
self.testNames = (self.defaultTest,)
+ else:
+ self.testNames = list(self.defaultTest)
self.createTests()
def createTests(self):
@@ -174,76 +145,84 @@ class TestProgram(object):
self.test = self.testLoader.loadTestsFromNames(self.testNames,
self.module)
- def _getOptParser(self):
- import optparse
- parser = optparse.OptionParser()
- parser.prog = self.progName
- parser.add_option('-v', '--verbose', dest='verbose', default=False,
- help='Verbose output', action='store_true')
- parser.add_option('-q', '--quiet', dest='quiet', default=False,
- help='Quiet output', action='store_true')
+ def _initArgParsers(self):
+ parent_parser = self._getParentArgParser()
+ self._main_parser = self._getMainArgParser(parent_parser)
+ self._discovery_parser = self._getDiscoveryArgParser(parent_parser)
- if self.failfast != False:
- parser.add_option('-f', '--failfast', dest='failfast', default=False,
- help='Stop on first fail or error',
- action='store_true')
- if self.catchbreak != False:
- parser.add_option('-c', '--catch', dest='catchbreak', default=False,
- help='Catch ctrl-C and display results so far',
- action='store_true')
- if self.buffer != False:
- parser.add_option('-b', '--buffer', dest='buffer', default=False,
- help='Buffer stdout and stderr during tests',
- action='store_true')
- return parser
+ def _getParentArgParser(self):
+ parser = argparse.ArgumentParser(add_help=False)
+
+ parser.add_argument('-v', '--verbose', dest='verbosity',
+ action='store_const', const=2,
+ help='Verbose output')
+ parser.add_argument('-q', '--quiet', dest='verbosity',
+ action='store_const', const=0,
+ help='Quiet output')
- def _setAttributesFromOptions(self, options):
- # only set options from the parsing here
- # if they weren't set explicitly in the constructor
if self.failfast is None:
- self.failfast = options.failfast
+ parser.add_argument('-f', '--failfast', dest='failfast',
+ action='store_true',
+ help='Stop on first fail or error')
+ self.failfast = False
if self.catchbreak is None:
- self.catchbreak = options.catchbreak
+ parser.add_argument('-c', '--catch', dest='catchbreak',
+ action='store_true',
+ help='Catch ctrl-C and display results so far')
+ self.catchbreak = False
if self.buffer is None:
- self.buffer = options.buffer
-
- if options.verbose:
- self.verbosity = 2
- elif options.quiet:
- self.verbosity = 0
+ parser.add_argument('-b', '--buffer', dest='buffer',
+ action='store_true',
+ help='Buffer stdout and stderr during tests')
+ self.buffer = False
- def _addDiscoveryOptions(self, parser):
- parser.add_option('-s', '--start-directory', dest='start', default='.',
- help="Directory to start discovery ('.' default)")
- parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
- help="Pattern to match tests ('test*.py' default)")
- parser.add_option('-t', '--top-level-directory', dest='top', default=None,
- help='Top level directory of project (defaults to start directory)')
-
- def _do_discovery(self, argv, Loader=None):
- if Loader is None:
- Loader = lambda: self.testLoader
+ return parser
- # handle command line args for test discovery
- self.progName = '%s discover' % self.progName
- parser = self._getOptParser()
- self._addDiscoveryOptions(parser)
+ def _getMainArgParser(self, parent):
+ parser = argparse.ArgumentParser(parents=[parent])
+ parser.prog = self.progName
+ parser.print_help = self._print_help
- options, args = parser.parse_args(argv)
- if len(args) > 3:
- self.usageExit()
+ parser.add_argument('tests', nargs='*',
+ help='a list of any number of test modules, '
+ 'classes and test methods.')
- for name, value in zip(('start', 'pattern', 'top'), args):
- setattr(options, name, value)
+ return parser
- self._setAttributesFromOptions(options)
+ def _getDiscoveryArgParser(self, parent):
+ parser = argparse.ArgumentParser(parents=[parent])
+ parser.prog = '%s discover' % self.progName
+ parser.epilog = ('For test discovery all test modules must be '
+ 'importable from the top level directory of the '
+ 'project.')
+
+ parser.add_argument('-s', '--start-directory', dest='start',
+ help="Directory to start discovery ('.' default)")
+ parser.add_argument('-p', '--pattern', dest='pattern',
+ help="Pattern to match tests ('test*.py' default)")
+ parser.add_argument('-t', '--top-level-directory', dest='top',
+ help='Top level directory of project (defaults to '
+ 'start directory)')
+ for arg in ('start', 'pattern', 'top'):
+ parser.add_argument(arg, nargs='?',
+ default=argparse.SUPPRESS,
+ help=argparse.SUPPRESS)
- start_dir = options.start
- pattern = options.pattern
- top_level_dir = options.top
+ return parser
- loader = Loader()
- self.test = loader.discover(start_dir, pattern, top_level_dir)
+ def _do_discovery(self, argv, Loader=None):
+ self.start = '.'
+ self.pattern = 'test*.py'
+ self.top = None
+ if argv is not None:
+ # handle command line args for test discovery
+ if self._discovery_parser is None:
+ # for testing
+ self._initArgParsers()
+ self._discovery_parser.parse_args(argv, self)
+
+ loader = self.testLoader if Loader is None else Loader()
+ self.test = loader.discover(self.start, self.pattern, self.top)
def runTests(self):
if self.catchbreak:
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 073869a1f1..dc5c033739 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -27,7 +27,7 @@ __version__ = '1.0'
import inspect
import pprint
import sys
-from functools import wraps
+from functools import wraps, partial
BaseExceptions = (BaseException,)
@@ -66,55 +66,45 @@ DescriptorTypes = (
)
-def _getsignature(func, skipfirst, instance=False):
- if isinstance(func, type) and not instance:
+def _get_signature_object(func, as_instance, eat_self):
+ """
+ Given an arbitrary, possibly callable object, try to create a suitable
+ signature object.
+ Return a (reduced func, signature) tuple, or None.
+ """
+ if isinstance(func, type) and not as_instance:
+ # If it's a type and should be modelled as a type, use __init__.
try:
func = func.__init__
except AttributeError:
- return
- skipfirst = True
+ return None
+ # Skip the `self` argument in __init__
+ eat_self = True
elif not isinstance(func, FunctionTypes):
- # for classes where instance is True we end up here too
+ # If we really want to model an instance of the passed type,
+ # __call__ should be looked up, not __init__.
try:
func = func.__call__
except AttributeError:
- return
-
+ return None
+ if eat_self:
+ sig_func = partial(func, None)
+ else:
+ sig_func = func
try:
- argspec = inspect.getfullargspec(func)
- except TypeError:
- # C function / method, possibly inherited object().__init__
- return
-
- regargs, varargs, varkw, defaults, kwonly, kwonlydef, ann = argspec
-
-
- # instance methods and classmethods need to lose the self argument
- if getattr(func, '__self__', None) is not None:
- regargs = regargs[1:]
- if skipfirst:
- # this condition and the above one are never both True - why?
- regargs = regargs[1:]
-
- signature = inspect.formatargspec(
- regargs, varargs, varkw, defaults,
- kwonly, kwonlydef, ann, formatvalue=lambda value: "")
- return signature[1:-1], func
+ return func, inspect.signature(sig_func)
+ except ValueError:
+ # Certain callable types are not supported by inspect.signature()
+ return None
def _check_signature(func, mock, skipfirst, instance=False):
- if not _callable(func):
+ sig = _get_signature_object(func, instance, skipfirst)
+ if sig is None:
return
-
- result = _getsignature(func, skipfirst, instance)
- if result is None:
- return
- signature, func = result
-
- # can't use self because "self" is common as an argument name
- # unfortunately even not in the first place
- src = "lambda _mock_self, %s: None" % signature
- checksig = eval(src, {})
+ func, sig = sig
+ def checksig(_mock_self, *args, **kwargs):
+ sig.bind(*args, **kwargs)
_copy_func_details(func, checksig)
type(mock)._mock_check_sig = checksig
@@ -166,15 +156,12 @@ def _set_signature(mock, original, instance=False):
return
skipfirst = isinstance(original, type)
- result = _getsignature(original, skipfirst, instance)
+ result = _get_signature_object(original, instance, skipfirst)
if result is None:
- # was a C function (e.g. object().__init__ ) that can't be mocked
return
-
- signature, func = result
-
- src = "lambda %s: None" % signature
- checksig = eval(src, {})
+ func, sig = result
+ def checksig(*args, **kwargs):
+ sig.bind(*args, **kwargs)
_copy_func_details(func, checksig)
name = original.__name__
@@ -368,7 +355,7 @@ class NonCallableMock(Base):
def __init__(
self, spec=None, wraps=None, name=None, spec_set=None,
parent=None, _spec_state=None, _new_name='', _new_parent=None,
- **kwargs
+ _spec_as_instance=False, _eat_self=None, **kwargs
):
if _new_parent is None:
_new_parent = parent
@@ -382,8 +369,10 @@ class NonCallableMock(Base):
if spec_set is not None:
spec = spec_set
spec_set = True
+ if _eat_self is None:
+ _eat_self = parent is not None
- self._mock_add_spec(spec, spec_set)
+ self._mock_add_spec(spec, spec_set, _spec_as_instance, _eat_self)
__dict__['_mock_children'] = {}
__dict__['_mock_wraps'] = wraps
@@ -428,20 +417,26 @@ class NonCallableMock(Base):
self._mock_add_spec(spec, spec_set)
- def _mock_add_spec(self, spec, spec_set):
+ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
+ _eat_self=False):
_spec_class = None
+ _spec_signature = None
if spec is not None and not _is_list(spec):
if isinstance(spec, type):
_spec_class = spec
else:
_spec_class = _get_class(spec)
+ res = _get_signature_object(spec,
+ _spec_as_instance, _eat_self)
+ _spec_signature = res and res[1]
spec = dir(spec)
__dict__ = self.__dict__
__dict__['_spec_class'] = _spec_class
__dict__['_spec_set'] = spec_set
+ __dict__['_spec_signature'] = _spec_signature
__dict__['_mock_methods'] = spec
@@ -695,7 +690,6 @@ class NonCallableMock(Base):
self._mock_children[name] = _deleted
-
def _format_mock_call_signature(self, args, kwargs):
name = self._mock_name or 'mock'
return _format_call_signature(name, args, kwargs)
@@ -711,6 +705,28 @@ class NonCallableMock(Base):
return message % (expected_string, actual_string)
+ def _call_matcher(self, _call):
+ """
+ Given a call (or simply a (args, kwargs) tuple), return a
+ comparison key suitable for matching with other calls.
+ This is a best effort method which relies on the spec's signature,
+ if available, or falls back on the arguments themselves.
+ """
+ sig = self._spec_signature
+ if sig is not None:
+ if len(_call) == 2:
+ name = ''
+ args, kwargs = _call
+ else:
+ name, args, kwargs = _call
+ try:
+ return name, sig.bind(*args, **kwargs)
+ except TypeError as e:
+ return e.with_traceback(None)
+ else:
+ return _call
+
+
def assert_called_with(_mock_self, *args, **kwargs):
"""assert that the mock was called with the specified arguments.
@@ -721,9 +737,14 @@ class NonCallableMock(Base):
expected = self._format_mock_call_signature(args, kwargs)
raise AssertionError('Expected call: %s\nNot called' % (expected,))
- if self.call_args != (args, kwargs):
+ def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
- raise AssertionError(msg)
+ return msg
+ expected = self._call_matcher((args, kwargs))
+ actual = self._call_matcher(self.call_args)
+ if expected != actual:
+ cause = expected if isinstance(expected, Exception) else None
+ raise AssertionError(_error_message()) from cause
def assert_called_once_with(_mock_self, *args, **kwargs):
@@ -747,18 +768,21 @@ class NonCallableMock(Base):
If `any_order` is True then the calls can be in any order, but
they must all appear in `mock_calls`."""
+ expected = [self._call_matcher(c) for c in calls]
+ cause = expected if isinstance(expected, Exception) else None
+ all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls)
if not any_order:
- if calls not in self.mock_calls:
+ if expected not in all_calls:
raise AssertionError(
'Calls not found.\nExpected: %r\n'
'Actual: %r' % (calls, self.mock_calls)
- )
+ ) from cause
return
- all_calls = list(self.mock_calls)
+ all_calls = list(all_calls)
not_found = []
- for kall in calls:
+ for kall in expected:
try:
all_calls.remove(kall)
except ValueError:
@@ -766,7 +790,7 @@ class NonCallableMock(Base):
if not_found:
raise AssertionError(
'%r not all found in call list' % (tuple(not_found),)
- )
+ ) from cause
def assert_any_call(self, *args, **kwargs):
@@ -775,12 +799,14 @@ class NonCallableMock(Base):
The assert passes if the mock has *ever* been called, unlike
`assert_called_with` and `assert_called_once_with` that only pass if
the call is the most recent one."""
- kall = call(*args, **kwargs)
- if kall not in self.call_args_list:
+ expected = self._call_matcher((args, kwargs))
+ actual = [self._call_matcher(c) for c in self.call_args_list]
+ if expected not in actual:
+ cause = expected if isinstance(expected, Exception) else None
expected_string = self._format_mock_call_signature(args, kwargs)
raise AssertionError(
'%s call not found' % expected_string
- )
+ ) from cause
def _get_child_mock(self, **kw):
@@ -850,11 +876,12 @@ class CallableMixin(Base):
self = _mock_self
self.called = True
self.call_count += 1
- self.call_args = _Call((args, kwargs), two=True)
- self.call_args_list.append(_Call((args, kwargs), two=True))
-
_new_name = self._mock_new_name
_new_parent = self._mock_new_parent
+
+ _call = _Call((args, kwargs), two=True)
+ self.call_args = _call
+ self.call_args_list.append(_call)
self.mock_calls.append(_Call(('', args, kwargs)))
seen = set()
@@ -909,8 +936,6 @@ class CallableMixin(Base):
return result
ret_val = effect(*args, **kwargs)
- if ret_val is DEFAULT:
- ret_val = self.return_value
if (self._mock_wraps is not None and
self._mock_return_value is DEFAULT):
@@ -2030,6 +2055,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
elif spec is None:
# None we mock with a normal mock without a spec
_kwargs = {}
+ if _kwargs and instance:
+ _kwargs['_spec_as_instance'] = True
_kwargs.update(kwargs)
@@ -2096,10 +2123,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
if isinstance(spec, FunctionTypes):
parent = mock.mock
+ skipfirst = _must_skip(spec, entry, is_type)
+ kwargs['_eat_self'] = skipfirst
new = MagicMock(parent=parent, name=entry, _new_name=entry,
- _new_parent=parent, **kwargs)
+ _new_parent=parent,
+ **kwargs)
mock._mock_children[entry] = new
- skipfirst = _must_skip(spec, entry, is_type)
_check_signature(original, new, skipfirst=skipfirst)
# so functions created with _set_signature become instance attributes,
@@ -2113,6 +2142,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
def _must_skip(spec, entry, is_type):
+ """
+ Return whether we should skip the first argument on spec's `entry`
+ attribute.
+ """
if not isinstance(spec, type):
if entry in getattr(spec, '__dict__', {}):
# instance attribute - shouldn't skip
@@ -2125,7 +2158,12 @@ def _must_skip(spec, entry, is_type):
continue
if isinstance(result, (staticmethod, classmethod)):
return False
- return is_type
+ elif isinstance(getattr(result, '__get__', None), MethodWrapperTypes):
+ # Normal method => skip if looked up on type
+ # (if looked up on instance, self is already skipped)
+ return is_type
+ else:
+ return False
# shouldn't get here unless function is a dynamically provided attribute
# XXXX untested behaviour
@@ -2159,9 +2197,31 @@ FunctionTypes = (
type(ANY.__eq__),
)
+MethodWrapperTypes = (
+ type(ANY.__eq__.__get__),
+)
+
file_spec = None
+def _iterate_read_data(read_data):
+ # Helper for mock_open:
+ # Retrieve lines from read_data via a generator so that separate calls to
+ # readline, read, and readlines are properly interleaved
+ data_as_list = ['{}\n'.format(l) for l in read_data.split('\n')]
+
+ if data_as_list[-1] == '\n':
+ # If the last line ended in a newline, the list comprehension will have an
+ # extra entry that's just a newline. Remove this.
+ data_as_list = data_as_list[:-1]
+ else:
+ # If there wasn't an extra newline by itself, then the file being
+ # emulated doesn't have a newline to end the last line remove the
+ # newline that our naive format() added
+ data_as_list[-1] = data_as_list[-1][:-1]
+
+ for line in data_as_list:
+ yield line
def mock_open(mock=None, read_data=''):
"""
@@ -2172,9 +2232,27 @@ def mock_open(mock=None, read_data=''):
default) then a `MagicMock` will be created for you, with the API limited
to methods or attributes available on standard file handles.
- `read_data` is a string for the `read` method of the file handle to return.
- This is an empty string by default.
+ `read_data` is a string for the `read` methoddline`, and `readlines` of the
+ file handle to return. This is an empty string by default.
"""
+ def _readlines_side_effect(*args, **kwargs):
+ if handle.readlines.return_value is not None:
+ return handle.readlines.return_value
+ return list(_data)
+
+ def _read_side_effect(*args, **kwargs):
+ if handle.read.return_value is not None:
+ return handle.read.return_value
+ return ''.join(_data)
+
+ def _readline_side_effect():
+ if handle.readline.return_value is not None:
+ while True:
+ yield handle.readline.return_value
+ for line in _data:
+ yield line
+
+
global file_spec
if file_spec is None:
import _io
@@ -2184,9 +2262,18 @@ def mock_open(mock=None, read_data=''):
mock = MagicMock(name='open', spec=open)
handle = MagicMock(spec=file_spec)
- handle.write.return_value = None
handle.__enter__.return_value = handle
- handle.read.return_value = read_data
+
+ _data = _iterate_read_data(read_data)
+
+ handle.write.return_value = None
+ handle.read.return_value = None
+ handle.readline.return_value = None
+ handle.readlines.return_value = None
+
+ handle.read.side_effect = _read_side_effect
+ handle.readline.side_effect = _readline_side_effect()
+ handle.readlines.side_effect = _readlines_side_effect
mock.return_value = handle
return mock
diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py
index 97e5426927..f3f4b676a3 100644
--- a/Lib/unittest/result.py
+++ b/Lib/unittest/result.py
@@ -121,6 +121,22 @@ class TestResult(object):
self.failures.append((test, self._exc_info_to_string(err, test)))
self._mirrorOutput = True
+ @failfast
+ def addSubTest(self, test, subtest, err):
+ """Called at the end of a subtest.
+ 'err' is None if the subtest ended successfully, otherwise it's a
+ tuple of values as returned by sys.exc_info().
+ """
+ # By default, we don't do anything with successful subtests, but
+ # more sophisticated test results might want to record them.
+ if err is not None:
+ if issubclass(err[0], test.failureException):
+ errors = self.failures
+ else:
+ errors = self.errors
+ errors.append((subtest, self._exc_info_to_string(err, test)))
+ self._mirrorOutput = True
+
def addSuccess(self, test):
"Called when a test has completed successfully"
pass
diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py
index cde5d385ed..ca82765b9c 100644
--- a/Lib/unittest/suite.py
+++ b/Lib/unittest/suite.py
@@ -16,6 +16,8 @@ def _call_if_exists(parent, attr):
class BaseTestSuite(object):
"""A simple test suite that doesn't provide class or module shared fixtures.
"""
+ _cleanup = True
+
def __init__(self, tests=()):
self._tests = []
self.addTests(tests)
@@ -57,12 +59,22 @@ class BaseTestSuite(object):
self.addTest(test)
def run(self, result):
- for test in self:
+ for index, test in enumerate(self):
if result.shouldStop:
break
test(result)
+ if self._cleanup:
+ self._removeTestAtIndex(index)
return result
+ def _removeTestAtIndex(self, index):
+ """Stop holding a reference to the TestCase at index."""
+ try:
+ self._tests[index] = None
+ except TypeError:
+ # support for suite implementations that have overriden self._test
+ pass
+
def __call__(self, *args, **kwds):
return self.run(*args, **kwds)
@@ -87,7 +99,7 @@ class TestSuite(BaseTestSuite):
if getattr(result, '_testRunEntered', False) is False:
result._testRunEntered = topLevel = True
- for test in self:
+ for index, test in enumerate(self):
if result.shouldStop:
break
@@ -106,6 +118,9 @@ class TestSuite(BaseTestSuite):
else:
test.debug()
+ if self._cleanup:
+ self._removeTestAtIndex(index)
+
if topLevel:
self._tearDownPreviousClass(None, result)
self._handleModuleTearDown(result)
diff --git a/Lib/unittest/test/__main__.py b/Lib/unittest/test/__main__.py
new file mode 100644
index 0000000000..44d0591e84
--- /dev/null
+++ b/Lib/unittest/test/__main__.py
@@ -0,0 +1,18 @@
+import os
+import unittest
+
+
+def load_tests(loader, standard_tests, pattern):
+ # top level directory cached on loader instance
+ this_dir = os.path.dirname(__file__)
+ pattern = pattern or "test_*.py"
+ # We are inside unittest.test, so the top-level is two notches up
+ top_level_dir = os.path.dirname(os.path.dirname(this_dir))
+ package_tests = loader.discover(start_dir=this_dir, pattern=pattern,
+ top_level_dir=top_level_dir)
+ standard_tests.addTests(package_tests)
+ return standard_tests
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/unittest/test/support.py b/Lib/unittest/test/support.py
index dbe4ddcd0e..02e8f3a00b 100644
--- a/Lib/unittest/test/support.py
+++ b/Lib/unittest/test/support.py
@@ -41,7 +41,7 @@ class TestHashing(object):
self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e))
-class LoggingResult(unittest.TestResult):
+class _BaseLoggingResult(unittest.TestResult):
def __init__(self, log):
self._events = log
super().__init__()
@@ -52,7 +52,7 @@ class LoggingResult(unittest.TestResult):
def startTestRun(self):
self._events.append('startTestRun')
- super(LoggingResult, self).startTestRun()
+ super().startTestRun()
def stopTest(self, test):
self._events.append('stopTest')
@@ -60,7 +60,7 @@ class LoggingResult(unittest.TestResult):
def stopTestRun(self):
self._events.append('stopTestRun')
- super(LoggingResult, self).stopTestRun()
+ super().stopTestRun()
def addFailure(self, *args):
self._events.append('addFailure')
@@ -68,7 +68,7 @@ class LoggingResult(unittest.TestResult):
def addSuccess(self, *args):
self._events.append('addSuccess')
- super(LoggingResult, self).addSuccess(*args)
+ super().addSuccess(*args)
def addError(self, *args):
self._events.append('addError')
@@ -76,15 +76,39 @@ class LoggingResult(unittest.TestResult):
def addSkip(self, *args):
self._events.append('addSkip')
- super(LoggingResult, self).addSkip(*args)
+ super().addSkip(*args)
def addExpectedFailure(self, *args):
self._events.append('addExpectedFailure')
- super(LoggingResult, self).addExpectedFailure(*args)
+ super().addExpectedFailure(*args)
def addUnexpectedSuccess(self, *args):
self._events.append('addUnexpectedSuccess')
- super(LoggingResult, self).addUnexpectedSuccess(*args)
+ super().addUnexpectedSuccess(*args)
+
+
+class LegacyLoggingResult(_BaseLoggingResult):
+ """
+ A legacy TestResult implementation, without an addSubTest method,
+ which records its method calls.
+ """
+
+ @property
+ def addSubTest(self):
+ raise AttributeError
+
+
+class LoggingResult(_BaseLoggingResult):
+ """
+ A TestResult implementation which records its method calls.
+ """
+
+ def addSubTest(self, test, subtest, err):
+ if err is None:
+ self._events.append('addSubTestSuccess')
+ else:
+ self._events.append('addSubTestFailure')
+ super().addSubTest(test, subtest, err)
class ResultWithNoStartTestRunStopTestRun(object):
diff --git a/Lib/unittest/test/test_assertions.py b/Lib/unittest/test/test_assertions.py
index 7931cadaf3..af08d5ad65 100644
--- a/Lib/unittest/test/test_assertions.py
+++ b/Lib/unittest/test/test_assertions.py
@@ -361,3 +361,7 @@ class TestLongMessage(unittest.TestCase):
['^"regex" does not match "foo"$', '^oops$',
'^"regex" does not match "foo"$',
'^"regex" does not match "foo" : oops$'])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/unittest/test/test_break.py b/Lib/unittest/test/test_break.py
index 75532f42d3..0bf1a229b8 100644
--- a/Lib/unittest/test/test_break.py
+++ b/Lib/unittest/test/test_break.py
@@ -282,3 +282,7 @@ class TestBreakSignalIgnored(TestBreak):
"if threads have been used")
class TestBreakSignalDefault(TestBreak):
int_handler = signal.SIG_DFL
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py
index b8cb0c76b7..4b93179335 100644
--- a/Lib/unittest/test/test_case.py
+++ b/Lib/unittest/test/test_case.py
@@ -1,8 +1,10 @@
+import contextlib
import difflib
import pprint
import pickle
import re
import sys
+import logging
import warnings
import weakref
import inspect
@@ -13,9 +15,15 @@ from test import support
import unittest
from .support import (
- TestEquality, TestHashing, LoggingResult,
+ TestEquality, TestHashing, LoggingResult, LegacyLoggingResult,
ResultWithNoStartTestRunStopTestRun
)
+from test.support import captured_stderr
+
+
+log_foo = logging.getLogger('foo')
+log_foobar = logging.getLogger('foo.bar')
+log_quux = logging.getLogger('quux')
class Test(object):
@@ -297,6 +305,98 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
Foo('test').run()
+ def _check_call_order__subtests(self, result, events, expected_events):
+ class Foo(Test.LoggingTestCase):
+ def test(self):
+ super(Foo, self).test()
+ for i in [1, 2, 3]:
+ with self.subTest(i=i):
+ if i == 1:
+ self.fail('failure')
+ for j in [2, 3]:
+ with self.subTest(j=j):
+ if i * j == 6:
+ raise RuntimeError('raised by Foo.test')
+ 1 / 0
+
+ # Order is the following:
+ # i=1 => subtest failure
+ # i=2, j=2 => subtest success
+ # i=2, j=3 => subtest error
+ # i=3, j=2 => subtest error
+ # i=3, j=3 => subtest success
+ # toplevel => error
+ Foo(events).run(result)
+ self.assertEqual(events, expected_events)
+
+ def test_run_call_order__subtests(self):
+ events = []
+ result = LoggingResult(events)
+ expected = ['startTest', 'setUp', 'test', 'tearDown',
+ 'addSubTestFailure', 'addSubTestSuccess',
+ 'addSubTestFailure', 'addSubTestFailure',
+ 'addSubTestSuccess', 'addError', 'stopTest']
+ self._check_call_order__subtests(result, events, expected)
+
+ def test_run_call_order__subtests_legacy(self):
+ # With a legacy result object (without a addSubTest method),
+ # text execution stops after the first subtest failure.
+ events = []
+ result = LegacyLoggingResult(events)
+ expected = ['startTest', 'setUp', 'test', 'tearDown',
+ 'addFailure', 'stopTest']
+ self._check_call_order__subtests(result, events, expected)
+
+ def _check_call_order__subtests_success(self, result, events, expected_events):
+ class Foo(Test.LoggingTestCase):
+ def test(self):
+ super(Foo, self).test()
+ for i in [1, 2]:
+ with self.subTest(i=i):
+ for j in [2, 3]:
+ with self.subTest(j=j):
+ pass
+
+ Foo(events).run(result)
+ self.assertEqual(events, expected_events)
+
+ def test_run_call_order__subtests_success(self):
+ events = []
+ result = LoggingResult(events)
+ # The 6 subtest successes are individually recorded, in addition
+ # to the whole test success.
+ expected = (['startTest', 'setUp', 'test', 'tearDown']
+ + 6 * ['addSubTestSuccess']
+ + ['addSuccess', 'stopTest'])
+ self._check_call_order__subtests_success(result, events, expected)
+
+ def test_run_call_order__subtests_success_legacy(self):
+ # With a legacy result, only the whole test success is recorded.
+ events = []
+ result = LegacyLoggingResult(events)
+ expected = ['startTest', 'setUp', 'test', 'tearDown',
+ 'addSuccess', 'stopTest']
+ self._check_call_order__subtests_success(result, events, expected)
+
+ def test_run_call_order__subtests_failfast(self):
+ events = []
+ result = LoggingResult(events)
+ result.failfast = True
+
+ class Foo(Test.LoggingTestCase):
+ def test(self):
+ super(Foo, self).test()
+ with self.subTest(i=1):
+ self.fail('failure')
+ with self.subTest(i=2):
+ self.fail('failure')
+ self.fail('failure')
+
+ expected = ['startTest', 'setUp', 'test', 'tearDown',
+ 'addSubTestFailure', 'stopTest']
+ Foo(events).run(result)
+ self.assertEqual(events, expected)
+
# "This class attribute gives the exception raised by the test() method.
# If a test framework needs to use a specialized exception, possibly to
# carry additional information, it must subclass this exception in
@@ -729,18 +829,18 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
# set a lower threshold value and add a cleanup to restore it
old_threshold = self._diffThreshold
- self._diffThreshold = 2**8
+ self._diffThreshold = 2**5
self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold))
# under the threshold: diff marker (^) in error message
- s = 'x' * (2**7)
+ s = 'x' * (2**4)
with self.assertRaises(self.failureException) as cm:
self.assertEqual(s + 'a', s + 'b')
self.assertIn('^', str(cm.exception))
self.assertEqual(s + 'a', s + 'a')
# over the threshold: diff not used and marker (^) not in error message
- s = 'x' * (2**9)
+ s = 'x' * (2**6)
# if the path that uses difflib is taken, _truncateMessage will be
# called -- replace it with explodingTruncation to verify that this
# doesn't happen
@@ -757,6 +857,37 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
self.assertEqual(str(cm.exception), '%r != %r' % (s1, s2))
self.assertEqual(s + 'a', s + 'a')
+ def testAssertEqual_shorten(self):
+ # set a lower threshold value and add a cleanup to restore it
+ old_threshold = self._diffThreshold
+ self._diffThreshold = 0
+ self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold))
+
+ s = 'x' * 100
+ s1, s2 = s + 'a', s + 'b'
+ with self.assertRaises(self.failureException) as cm:
+ self.assertEqual(s1, s2)
+ c = 'xxxx[35 chars]' + 'x' * 61
+ self.assertEqual(str(cm.exception), "'%sa' != '%sb'" % (c, c))
+ self.assertEqual(s + 'a', s + 'a')
+
+ p = 'y' * 50
+ s1, s2 = s + 'a' + p, s + 'b' + p
+ with self.assertRaises(self.failureException) as cm:
+ self.assertEqual(s1, s2)
+ c = 'xxxx[85 chars]xxxxxxxxxxx'
+ #print()
+ #print(str(cm.exception))
+ self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, p, c, p))
+
+ p = 'y' * 100
+ s1, s2 = s + 'a' + p, s + 'b' + p
+ with self.assertRaises(self.failureException) as cm:
+ self.assertEqual(s1, s2)
+ c = 'xxxx[91 chars]xxxxx'
+ d = 'y' * 40 + '[56 chars]yyyy'
+ self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, d, c, d))
+
def testAssertCountEqual(self):
a = object()
self.assertCountEqual([1, 2, 3], [3, 2, 1])
@@ -1159,6 +1290,94 @@ test case
with self.assertWarnsRegex(RuntimeWarning, "o+"):
_runtime_warn("barz")
+ @contextlib.contextmanager
+ def assertNoStderr(self):
+ with captured_stderr() as buf:
+ yield
+ self.assertEqual(buf.getvalue(), "")
+
+ def assertLogRecords(self, records, matches):
+ self.assertEqual(len(records), len(matches))
+ for rec, match in zip(records, matches):
+ self.assertIsInstance(rec, logging.LogRecord)
+ for k, v in match.items():
+ self.assertEqual(getattr(rec, k), v)
+
+ def testAssertLogsDefaults(self):
+ # defaults: root logger, level INFO
+ with self.assertNoStderr():
+ with self.assertLogs() as cm:
+ log_foo.info("1")
+ log_foobar.debug("2")
+ self.assertEqual(cm.output, ["INFO:foo:1"])
+ self.assertLogRecords(cm.records, [{'name': 'foo'}])
+
+ def testAssertLogsTwoMatchingMessages(self):
+ # Same, but with two matching log messages
+ with self.assertNoStderr():
+ with self.assertLogs() as cm:
+ log_foo.info("1")
+ log_foobar.debug("2")
+ log_quux.warning("3")
+ self.assertEqual(cm.output, ["INFO:foo:1", "WARNING:quux:3"])
+ self.assertLogRecords(cm.records,
+ [{'name': 'foo'}, {'name': 'quux'}])
+
+ def checkAssertLogsPerLevel(self, level):
+ # Check level filtering
+ with self.assertNoStderr():
+ with self.assertLogs(level=level) as cm:
+ log_foo.warning("1")
+ log_foobar.error("2")
+ log_quux.critical("3")
+ self.assertEqual(cm.output, ["ERROR:foo.bar:2", "CRITICAL:quux:3"])
+ self.assertLogRecords(cm.records,
+ [{'name': 'foo.bar'}, {'name': 'quux'}])
+
+ def testAssertLogsPerLevel(self):
+ self.checkAssertLogsPerLevel(logging.ERROR)
+ self.checkAssertLogsPerLevel('ERROR')
+
+ def checkAssertLogsPerLogger(self, logger):
+ # Check per-logger fitering
+ with self.assertNoStderr():
+ with self.assertLogs(level='DEBUG') as outer_cm:
+ with self.assertLogs(logger, level='DEBUG') as cm:
+ log_foo.info("1")
+ log_foobar.debug("2")
+ log_quux.warning("3")
+ self.assertEqual(cm.output, ["INFO:foo:1", "DEBUG:foo.bar:2"])
+ self.assertLogRecords(cm.records,
+ [{'name': 'foo'}, {'name': 'foo.bar'}])
+ # The outer catchall caught the quux log
+ self.assertEqual(outer_cm.output, ["WARNING:quux:3"])
+
+ def testAssertLogsPerLogger(self):
+ self.checkAssertLogsPerLogger(logging.getLogger('foo'))
+ self.checkAssertLogsPerLogger('foo')
+
+ def testAssertLogsFailureNoLogs(self):
+ # Failure due to no logs
+ with self.assertNoStderr():
+ with self.assertRaises(self.failureException):
+ with self.assertLogs():
+ pass
+
+ def testAssertLogsFailureLevelTooHigh(self):
+ # Failure due to level too high
+ with self.assertNoStderr():
+ with self.assertRaises(self.failureException):
+ with self.assertLogs(level='WARNING'):
+ log_foo.info("1")
+
+ def testAssertLogsFailureMismatchingLogger(self):
+ # Failure due to mismatching logger (and the logged message is
+ # passed through)
+ with self.assertLogs('quux', level='ERROR'):
+ with self.assertRaises(self.failureException):
+ with self.assertLogs('foo'):
+ log_quux.error("1")
+
def testDeprecatedMethodNames(self):
"""
Test that the deprecated methods raise a DeprecationWarning. See #9424.
@@ -1313,3 +1532,7 @@ test case
with support.disable_gc():
del case
self.assertFalse(wr())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/unittest/test/test_discovery.py b/Lib/unittest/test/test_discovery.py
index ccc7db249e..6b7b1280f8 100644
--- a/Lib/unittest/test/test_discovery.py
+++ b/Lib/unittest/test/test_discovery.py
@@ -1,12 +1,15 @@
import os
import re
import sys
+import types
+import builtins
+from test import support
import unittest
class TestableTestProgram(unittest.TestProgram):
- module = '__main__'
+ module = None
exit = True
defaultTest = failfast = catchbreak = buffer = None
verbosity = 1
@@ -46,9 +49,9 @@ class TestDiscovery(unittest.TestCase):
def restore_isdir():
os.path.isdir = original_isdir
- path_lists = [['test1.py', 'test2.py', 'not_a_test.py', 'test_dir',
+ path_lists = [['test2.py', 'test1.py', 'not_a_test.py', 'test_dir',
'test.foo', 'test-not-a-module.py', 'another_dir'],
- ['test3.py', 'test4.py', ]]
+ ['test4.py', 'test3.py', ]]
os.listdir = lambda path: path_lists.pop(0)
self.addCleanup(restore_listdir)
@@ -70,6 +73,8 @@ class TestDiscovery(unittest.TestCase):
loader._top_level_dir = top_level
suite = list(loader._find_tests(top_level, 'test*.py'))
+ # The test suites found should be sorted alphabetically for reliable
+ # execution order.
expected = [name + ' module tests' for name in
('test1', 'test2')]
expected.extend([('test_dir.%s' % name) + ' module tests' for name in
@@ -132,6 +137,7 @@ class TestDiscovery(unittest.TestCase):
# and directly from the test_directory2 package
self.assertEqual(suite,
['load_tests', 'test_directory2' + ' module tests'])
+ # The test module paths should be sorted for reliable execution order
self.assertEqual(Module.paths, ['test_directory', 'test_directory2'])
# load_tests should have been called once with loader, tests and pattern
@@ -169,7 +175,7 @@ class TestDiscovery(unittest.TestCase):
self.addCleanup(restore_isdir)
_find_tests_args = []
- def _find_tests(start_dir, pattern):
+ def _find_tests(start_dir, pattern, namespace=None):
_find_tests_args.append((start_dir, pattern))
return ['tests']
loader._find_tests = _find_tests
@@ -184,11 +190,9 @@ class TestDiscovery(unittest.TestCase):
self.assertEqual(_find_tests_args, [(start_dir, 'pattern')])
self.assertIn(top_level_dir, sys.path)
- def test_discover_with_modules_that_fail_to_import(self):
- loader = unittest.TestLoader()
-
+ def setup_import_issue_tests(self, fakefile):
listdir = os.listdir
- os.listdir = lambda _: ['test_this_does_not_exist.py']
+ os.listdir = lambda _: [fakefile]
isfile = os.path.isfile
os.path.isfile = lambda _: True
orig_sys_path = sys.path[:]
@@ -198,6 +202,11 @@ class TestDiscovery(unittest.TestCase):
sys.path[:] = orig_sys_path
self.addCleanup(restore)
+ def test_discover_with_modules_that_fail_to_import(self):
+ loader = unittest.TestLoader()
+
+ self.setup_import_issue_tests('test_this_does_not_exist.py')
+
suite = loader.discover('.')
self.assertIn(os.getcwd(), sys.path)
self.assertEqual(suite.countTestCases(), 1)
@@ -206,62 +215,74 @@ class TestDiscovery(unittest.TestCase):
with self.assertRaises(ImportError):
test.test_this_does_not_exist()
+ def test_discover_with_module_that_raises_SkipTest_on_import(self):
+ loader = unittest.TestLoader()
+
+ def _get_module_from_name(name):
+ raise unittest.SkipTest('skipperoo')
+ loader._get_module_from_name = _get_module_from_name
+
+ self.setup_import_issue_tests('test_skip_dummy.py')
+
+ suite = loader.discover('.')
+ self.assertEqual(suite.countTestCases(), 1)
+
+ result = unittest.TestResult()
+ suite.run(result)
+ self.assertEqual(len(result.skipped), 1)
+
def test_command_line_handling_parseArgs(self):
program = TestableTestProgram()
args = []
- def do_discovery(argv):
- args.extend(argv)
- program._do_discovery = do_discovery
+ program._do_discovery = args.append
program.parseArgs(['something', 'discover'])
- self.assertEqual(args, [])
+ self.assertEqual(args, [[]])
+ args[:] = []
program.parseArgs(['something', 'discover', 'foo', 'bar'])
- self.assertEqual(args, ['foo', 'bar'])
+ self.assertEqual(args, [['foo', 'bar']])
def test_command_line_handling_discover_by_default(self):
program = TestableTestProgram()
- program.module = None
- self.called = False
- def do_discovery(argv):
- self.called = True
- self.assertEqual(argv, [])
- program._do_discovery = do_discovery
+ args = []
+ program._do_discovery = args.append
program.parseArgs(['something'])
- self.assertTrue(self.called)
+ self.assertEqual(args, [[]])
+ self.assertEqual(program.verbosity, 1)
+ self.assertIs(program.buffer, False)
+ self.assertIs(program.catchbreak, False)
+ self.assertIs(program.failfast, False)
def test_command_line_handling_discover_by_default_with_options(self):
program = TestableTestProgram()
- program.module = None
- args = ['something', '-v', '-b', '-v', '-c', '-f']
- self.called = False
- def do_discovery(argv):
- self.called = True
- self.assertEqual(argv, args[1:])
- program._do_discovery = do_discovery
- program.parseArgs(args)
- self.assertTrue(self.called)
+ args = []
+ program._do_discovery = args.append
+ program.parseArgs(['something', '-v', '-b', '-v', '-c', '-f'])
+ self.assertEqual(args, [[]])
+ self.assertEqual(program.verbosity, 2)
+ self.assertIs(program.buffer, True)
+ self.assertIs(program.catchbreak, True)
+ self.assertIs(program.failfast, True)
def test_command_line_handling_do_discovery_too_many_arguments(self):
- class Stop(Exception):
- pass
- def usageExit():
- raise Stop
-
program = TestableTestProgram()
- program.usageExit = usageExit
program.testLoader = None
- with self.assertRaises(Stop):
+ with support.captured_stderr() as stderr, \
+ self.assertRaises(SystemExit) as cm:
# too many args
program._do_discovery(['one', 'two', 'three', 'four'])
+ self.assertEqual(cm.exception.args, (2,))
+ self.assertIn('usage:', stderr.getvalue())
def test_command_line_handling_do_discovery_uses_default_loader(self):
program = object.__new__(unittest.TestProgram)
+ program._initArgParsers()
class Loader(object):
args = []
@@ -417,7 +438,7 @@ class TestDiscovery(unittest.TestCase):
expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__))
self.wasRun = False
- def _find_tests(start_dir, pattern):
+ def _find_tests(start_dir, pattern, namespace=None):
self.wasRun = True
self.assertEqual(start_dir, expectedPath)
return tests
@@ -427,5 +448,79 @@ class TestDiscovery(unittest.TestCase):
self.assertEqual(suite._tests, tests)
+ def test_discovery_from_dotted_path_builtin_modules(self):
+
+ loader = unittest.TestLoader()
+
+ listdir = os.listdir
+ os.listdir = lambda _: ['test_this_does_not_exist.py']
+ isfile = os.path.isfile
+ isdir = os.path.isdir
+ os.path.isdir = lambda _: False
+ orig_sys_path = sys.path[:]
+ def restore():
+ os.path.isfile = isfile
+ os.path.isdir = isdir
+ os.listdir = listdir
+ sys.path[:] = orig_sys_path
+ self.addCleanup(restore)
+
+ with self.assertRaises(TypeError) as cm:
+ loader.discover('sys')
+ self.assertEqual(str(cm.exception),
+ 'Can not use builtin modules '
+ 'as dotted module names')
+
+ def test_discovery_from_dotted_namespace_packages(self):
+ loader = unittest.TestLoader()
+
+ orig_import = __import__
+ package = types.ModuleType('package')
+ package.__path__ = ['/a', '/b']
+ package.__spec__ = types.SimpleNamespace(
+ loader=None,
+ submodule_search_locations=['/a', '/b']
+ )
+
+ def _import(packagename, *args, **kwargs):
+ sys.modules[packagename] = package
+ return package
+
+ def cleanup():
+ builtins.__import__ = orig_import
+ self.addCleanup(cleanup)
+ builtins.__import__ = _import
+
+ _find_tests_args = []
+ def _find_tests(start_dir, pattern, namespace=None):
+ _find_tests_args.append((start_dir, pattern))
+ return ['%s/tests' % start_dir]
+
+ loader._find_tests = _find_tests
+ loader.suiteClass = list
+ suite = loader.discover('package')
+ self.assertEqual(suite, ['/a/tests', '/b/tests'])
+
+ def test_discovery_failed_discovery(self):
+ loader = unittest.TestLoader()
+ package = types.ModuleType('package')
+ orig_import = __import__
+
+ def _import(packagename, *args, **kwargs):
+ sys.modules[packagename] = package
+ return package
+
+ def cleanup():
+ builtins.__import__ = orig_import
+ self.addCleanup(cleanup)
+ builtins.__import__ = _import
+
+ with self.assertRaises(TypeError) as cm:
+ loader.discover('package')
+ self.assertEqual(str(cm.exception),
+ 'don\'t know how to discover from {!r}'
+ .format(package))
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/unittest/test/test_functiontestcase.py b/Lib/unittest/test/test_functiontestcase.py
index 9ce5ee3556..d7fe07a53b 100644
--- a/Lib/unittest/test/test_functiontestcase.py
+++ b/Lib/unittest/test/test_functiontestcase.py
@@ -142,3 +142,7 @@ class Test_FunctionTestCase(unittest.TestCase):
test = unittest.FunctionTestCase(lambda: None, description=desc)
self.assertEqual(test.shortDescription(), "this tests foo")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/unittest/test/test_loader.py b/Lib/unittest/test/test_loader.py
index fcd2e07624..b62a1b5c54 100644
--- a/Lib/unittest/test/test_loader.py
+++ b/Lib/unittest/test/test_loader.py
@@ -1306,3 +1306,7 @@ class Test_TestLoader(unittest.TestCase):
def test_suiteClass__default_value(self):
loader = unittest.TestLoader()
self.assertIs(loader.suiteClass, unittest.TestSuite)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/unittest/test/test_program.py b/Lib/unittest/test/test_program.py
index 8a4b3fad58..3294298030 100644
--- a/Lib/unittest/test/test_program.py
+++ b/Lib/unittest/test/test_program.py
@@ -2,6 +2,7 @@ import io
import os
import sys
+from test import support
import unittest
@@ -64,6 +65,41 @@ class Test_TestProgram(unittest.TestCase):
return self.suiteClass(
[self.loadTestsFromTestCase(Test_TestProgram.FooBar)])
+ def loadTestsFromNames(self, names, module):
+ return self.suiteClass(
+ [self.loadTestsFromTestCase(Test_TestProgram.FooBar)])
+
+ def test_defaultTest_with_string(self):
+ class FakeRunner(object):
+ def run(self, test):
+ self.test = test
+ return True
+
+ old_argv = sys.argv
+ sys.argv = ['faketest']
+ runner = FakeRunner()
+ program = unittest.TestProgram(testRunner=runner, exit=False,
+ defaultTest='unittest.test',
+ testLoader=self.FooBarLoader())
+ sys.argv = old_argv
+ self.assertEqual(('unittest.test',), program.testNames)
+
+ def test_defaultTest_with_iterable(self):
+ class FakeRunner(object):
+ def run(self, test):
+ self.test = test
+ return True
+
+ old_argv = sys.argv
+ sys.argv = ['faketest']
+ runner = FakeRunner()
+ program = unittest.TestProgram(
+ testRunner=runner, exit=False,
+ defaultTest=['unittest.test', 'unittest.test2'],
+ testLoader=self.FooBarLoader())
+ sys.argv = old_argv
+ self.assertEqual(['unittest.test', 'unittest.test2'],
+ program.testNames)
def test_NonExit(self):
program = unittest.main(exit=False,
@@ -151,20 +187,38 @@ class TestCommandLineArgs(unittest.TestCase):
if attr == 'catch' and not hasInstallHandler:
continue
+ setattr(program, attr, None)
+ program.parseArgs([None])
+ self.assertIs(getattr(program, attr), False)
+
+ false = []
+ setattr(program, attr, false)
+ program.parseArgs([None])
+ self.assertIs(getattr(program, attr), false)
+
+ true = [42]
+ setattr(program, attr, true)
+ program.parseArgs([None])
+ self.assertIs(getattr(program, attr), true)
+
short_opt = '-%s' % arg[0]
long_opt = '--%s' % arg
for opt in short_opt, long_opt:
setattr(program, attr, None)
-
- program.parseArgs([None, opt])
- self.assertTrue(getattr(program, attr))
-
- for opt in short_opt, long_opt:
- not_none = object()
- setattr(program, attr, not_none)
-
program.parseArgs([None, opt])
- self.assertEqual(getattr(program, attr), not_none)
+ self.assertIs(getattr(program, attr), True)
+
+ setattr(program, attr, False)
+ with support.captured_stderr() as stderr, \
+ self.assertRaises(SystemExit) as cm:
+ program.parseArgs([None, opt])
+ self.assertEqual(cm.exception.args, (2,))
+
+ setattr(program, attr, True)
+ with support.captured_stderr() as stderr, \
+ self.assertRaises(SystemExit) as cm:
+ program.parseArgs([None, opt])
+ self.assertEqual(cm.exception.args, (2,))
def testWarning(self):
"""Test the warnings argument"""
diff --git a/Lib/unittest/test/test_result.py b/Lib/unittest/test/test_result.py
index 7d40725cf2..489fe17754 100644
--- a/Lib/unittest/test/test_result.py
+++ b/Lib/unittest/test/test_result.py
@@ -227,6 +227,40 @@ class Test_TestResult(unittest.TestCase):
self.assertIs(test_case, test)
self.assertIsInstance(formatted_exc, str)
+ def test_addSubTest(self):
+ class Foo(unittest.TestCase):
+ def test_1(self):
+ nonlocal subtest
+ with self.subTest(foo=1):
+ subtest = self._subtest
+ try:
+ 1/0
+ except ZeroDivisionError:
+ exc_info_tuple = sys.exc_info()
+ # Register an error by hand (to check the API)
+ result.addSubTest(test, subtest, exc_info_tuple)
+ # Now trigger a failure
+ self.fail("some recognizable failure")
+
+ subtest = None
+ test = Foo('test_1')
+ result = unittest.TestResult()
+
+ test.run(result)
+
+ self.assertFalse(result.wasSuccessful())
+ self.assertEqual(len(result.errors), 1)
+ self.assertEqual(len(result.failures), 1)
+ self.assertEqual(result.testsRun, 1)
+ self.assertEqual(result.shouldStop, False)
+
+ test_case, formatted_exc = result.errors[0]
+ self.assertIs(test_case, subtest)
+ self.assertIn("ZeroDivisionError", formatted_exc)
+ test_case, formatted_exc = result.failures[0]
+ self.assertIs(test_case, subtest)
+ self.assertIn("some recognizable failure", formatted_exc)
+
def testGetDescriptionWithoutDocstring(self):
result = unittest.TextTestResult(None, True, 1)
self.assertEqual(
@@ -234,6 +268,37 @@ class Test_TestResult(unittest.TestCase):
'testGetDescriptionWithoutDocstring (' + __name__ +
'.Test_TestResult)')
+ def testGetSubTestDescriptionWithoutDocstring(self):
+ with self.subTest(foo=1, bar=2):
+ result = unittest.TextTestResult(None, True, 1)
+ self.assertEqual(
+ result.getDescription(self._subtest),
+ 'testGetSubTestDescriptionWithoutDocstring (' + __name__ +
+ '.Test_TestResult) (bar=2, foo=1)')
+ with self.subTest('some message'):
+ result = unittest.TextTestResult(None, True, 1)
+ self.assertEqual(
+ result.getDescription(self._subtest),
+ 'testGetSubTestDescriptionWithoutDocstring (' + __name__ +
+ '.Test_TestResult) [some message]')
+
+ def testGetSubTestDescriptionWithoutDocstringAndParams(self):
+ with self.subTest():
+ result = unittest.TextTestResult(None, True, 1)
+ self.assertEqual(
+ result.getDescription(self._subtest),
+ 'testGetSubTestDescriptionWithoutDocstringAndParams '
+ '(' + __name__ + '.Test_TestResult) (<subtest>)')
+
+ def testGetNestedSubTestDescriptionWithoutDocstring(self):
+ with self.subTest(foo=1):
+ with self.subTest(bar=2):
+ result = unittest.TextTestResult(None, True, 1)
+ self.assertEqual(
+ result.getDescription(self._subtest),
+ 'testGetNestedSubTestDescriptionWithoutDocstring '
+ '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)')
+
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def testGetDescriptionWithOneLineDocstring(self):
@@ -247,6 +312,18 @@ class Test_TestResult(unittest.TestCase):
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
+ def testGetSubTestDescriptionWithOneLineDocstring(self):
+ """Tests getDescription() for a method with a docstring."""
+ result = unittest.TextTestResult(None, True, 1)
+ with self.subTest(foo=1, bar=2):
+ self.assertEqual(
+ result.getDescription(self._subtest),
+ ('testGetSubTestDescriptionWithOneLineDocstring '
+ '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)\n'
+ 'Tests getDescription() for a method with a docstring.'))
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
def testGetDescriptionWithMultiLineDocstring(self):
"""Tests getDescription() for a method with a longer docstring.
The second line of the docstring.
@@ -259,6 +336,21 @@ class Test_TestResult(unittest.TestCase):
'Tests getDescription() for a method with a longer '
'docstring.'))
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def testGetSubTestDescriptionWithMultiLineDocstring(self):
+ """Tests getDescription() for a method with a longer docstring.
+ The second line of the docstring.
+ """
+ result = unittest.TextTestResult(None, True, 1)
+ with self.subTest(foo=1, bar=2):
+ self.assertEqual(
+ result.getDescription(self._subtest),
+ ('testGetSubTestDescriptionWithMultiLineDocstring '
+ '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)\n'
+ 'Tests getDescription() for a method with a longer '
+ 'docstring.'))
+
def testStackFrameTrimming(self):
class Frame(object):
class tb_frame(object):
diff --git a/Lib/unittest/test/test_runner.py b/Lib/unittest/test/test_runner.py
index e22e6bc279..ef1c1af9f1 100644
--- a/Lib/unittest/test/test_runner.py
+++ b/Lib/unittest/test/test_runner.py
@@ -5,6 +5,7 @@ import pickle
import subprocess
import unittest
+from unittest.case import _Outcome
from .support import LoggingResult, ResultWithNoStartTestRunStopTestRun
@@ -42,12 +43,8 @@ class TestCleanUp(unittest.TestCase):
def testNothing(self):
pass
- class MockOutcome(object):
- success = True
- errors = []
-
test = TestableTest('testNothing')
- test._outcomeForDoCleanups = MockOutcome
+ outcome = test._outcome = _Outcome()
exc1 = Exception('foo')
exc2 = Exception('bar')
@@ -61,9 +58,10 @@ class TestCleanUp(unittest.TestCase):
test.addCleanup(cleanup2)
self.assertFalse(test.doCleanups())
- self.assertFalse(MockOutcome.success)
+ self.assertFalse(outcome.success)
- (Type1, instance1, _), (Type2, instance2, _) = reversed(MockOutcome.errors)
+ ((_, (Type1, instance1, _)),
+ (_, (Type2, instance2, _))) = reversed(outcome.errors)
self.assertEqual((Type1, instance1), (Exception, exc1))
self.assertEqual((Type2, instance2), (Exception, exc2))
@@ -341,3 +339,7 @@ class Test_TextTestRunner(unittest.TestCase):
f = io.StringIO()
runner = unittest.TextTestRunner(f)
self.assertTrue(runner.stream.stream is f)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/unittest/test/test_setups.py b/Lib/unittest/test/test_setups.py
index b8d5aa41e9..392f95efc0 100644
--- a/Lib/unittest/test/test_setups.py
+++ b/Lib/unittest/test/test_setups.py
@@ -494,14 +494,13 @@ class TestSetups(unittest.TestCase):
Test.__module__ = 'Module'
sys.modules['Module'] = Module
- _suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test)
- suite = unittest.TestSuite()
- suite.addTest(_suite)
-
messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something')
for phase, msg in enumerate(messages):
+ _suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test)
+ suite = unittest.TestSuite([_suite])
with self.assertRaisesRegex(Exception, msg):
suite.debug()
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/unittest/test/test_skipping.py b/Lib/unittest/test/test_skipping.py
index 952240eeed..e18caa48ba 100644
--- a/Lib/unittest/test/test_skipping.py
+++ b/Lib/unittest/test/test_skipping.py
@@ -29,6 +29,31 @@ class Test_TestSkipping(unittest.TestCase):
self.assertEqual(result.skipped, [(test, "testing")])
self.assertEqual(result.testsRun, 1)
+ def test_skipping_subtests(self):
+ class Foo(unittest.TestCase):
+ def test_skip_me(self):
+ with self.subTest(a=1):
+ with self.subTest(b=2):
+ self.skipTest("skip 1")
+ self.skipTest("skip 2")
+ self.skipTest("skip 3")
+ events = []
+ result = LoggingResult(events)
+ test = Foo("test_skip_me")
+ test.run(result)
+ self.assertEqual(events, ['startTest', 'addSkip', 'addSkip',
+ 'addSkip', 'stopTest'])
+ self.assertEqual(len(result.skipped), 3)
+ subtest, msg = result.skipped[0]
+ self.assertEqual(msg, "skip 1")
+ self.assertIsInstance(subtest, unittest.TestCase)
+ self.assertIsNot(subtest, test)
+ subtest, msg = result.skipped[1]
+ self.assertEqual(msg, "skip 2")
+ self.assertIsInstance(subtest, unittest.TestCase)
+ self.assertIsNot(subtest, test)
+ self.assertEqual(result.skipped[2], (test, "skip 3"))
+
def test_skipping_decorators(self):
op_table = ((unittest.skipUnless, False, True),
(unittest.skipIf, True, False))
@@ -95,6 +120,31 @@ class Test_TestSkipping(unittest.TestCase):
self.assertEqual(result.expectedFailures[0][0], test)
self.assertTrue(result.wasSuccessful())
+ def test_expected_failure_subtests(self):
+ # A failure in any subtest counts as the expected failure of the
+ # whole test.
+ class Foo(unittest.TestCase):
+ @unittest.expectedFailure
+ def test_die(self):
+ with self.subTest():
+ # This one succeeds
+ pass
+ with self.subTest():
+ self.fail("help me!")
+ with self.subTest():
+ # This one doesn't get executed
+ self.fail("shouldn't come here")
+ events = []
+ result = LoggingResult(events)
+ test = Foo("test_die")
+ test.run(result)
+ self.assertEqual(events,
+ ['startTest', 'addSubTestSuccess',
+ 'addExpectedFailure', 'stopTest'])
+ self.assertEqual(len(result.expectedFailures), 1)
+ self.assertIs(result.expectedFailures[0][0], test)
+ self.assertTrue(result.wasSuccessful())
+
def test_unexpected_success(self):
class Foo(unittest.TestCase):
@unittest.expectedFailure
@@ -110,6 +160,30 @@ class Test_TestSkipping(unittest.TestCase):
self.assertEqual(result.unexpectedSuccesses, [test])
self.assertTrue(result.wasSuccessful())
+ def test_unexpected_success_subtests(self):
+ # Success in all subtests counts as the unexpected success of
+ # the whole test.
+ class Foo(unittest.TestCase):
+ @unittest.expectedFailure
+ def test_die(self):
+ with self.subTest():
+ # This one succeeds
+ pass
+ with self.subTest():
+ # So does this one
+ pass
+ events = []
+ result = LoggingResult(events)
+ test = Foo("test_die")
+ test.run(result)
+ self.assertEqual(events,
+ ['startTest',
+ 'addSubTestSuccess', 'addSubTestSuccess',
+ 'addUnexpectedSuccess', 'stopTest'])
+ self.assertFalse(result.failures)
+ self.assertEqual(result.unexpectedSuccesses, [test])
+ self.assertTrue(result.wasSuccessful())
+
def test_skip_doesnt_run_setup(self):
class Foo(unittest.TestCase):
wasSetUp = False
@@ -147,3 +221,7 @@ class Test_TestSkipping(unittest.TestCase):
suite = unittest.TestSuite([test])
suite.run(result)
self.assertEqual(result.skipped, [(test, "testing")])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/unittest/test/test_suite.py b/Lib/unittest/test/test_suite.py
index 2db978ddb8..54cec6ecba 100644
--- a/Lib/unittest/test/test_suite.py
+++ b/Lib/unittest/test/test_suite.py
@@ -1,6 +1,8 @@
import unittest
+import gc
import sys
+import weakref
from .support import LoggingResult, TestEquality
@@ -300,7 +302,54 @@ class Test_TestSuite(unittest.TestCase, TestEquality):
# when the bug is fixed this line will not crash
suite.run(unittest.TestResult())
+ def test_remove_test_at_index(self):
+ if not unittest.BaseTestSuite._cleanup:
+ raise unittest.SkipTest("Suite cleanup is disabled")
+ suite = unittest.TestSuite()
+
+ suite._tests = [1, 2, 3]
+ suite._removeTestAtIndex(1)
+
+ self.assertEqual([1, None, 3], suite._tests)
+
+ def test_remove_test_at_index_not_indexable(self):
+ if not unittest.BaseTestSuite._cleanup:
+ raise unittest.SkipTest("Suite cleanup is disabled")
+
+ suite = unittest.TestSuite()
+ suite._tests = None
+
+ # if _removeAtIndex raises for noniterables this next line will break
+ suite._removeTestAtIndex(2)
+
+ def assert_garbage_collect_test_after_run(self, TestSuiteClass):
+ if not unittest.BaseTestSuite._cleanup:
+ raise unittest.SkipTest("Suite cleanup is disabled")
+
+ class Foo(unittest.TestCase):
+ def test_nothing(self):
+ pass
+
+ test = Foo('test_nothing')
+ wref = weakref.ref(test)
+
+ suite = TestSuiteClass([wref()])
+ suite.run(unittest.TestResult())
+
+ del test
+
+ # for the benefit of non-reference counting implementations
+ gc.collect()
+
+ self.assertEqual(suite._tests, [None])
+ self.assertIsNone(wref())
+
+ def test_garbage_collect_test_after_run_BaseTestSuite(self):
+ self.assert_garbage_collect_test_after_run(unittest.BaseTestSuite)
+
+ def test_garbage_collect_test_after_run_TestSuite(self):
+ self.assert_garbage_collect_test_after_run(unittest.TestSuite)
def test_basetestsuite(self):
class Test(unittest.TestCase):
@@ -363,6 +412,5 @@ class Test_TestSuite(unittest.TestCase, TestEquality):
self.assertFalse(result._testRunEntered)
-
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/unittest/test/testmock/__main__.py b/Lib/unittest/test/testmock/__main__.py
new file mode 100644
index 0000000000..45c633a4ee
--- /dev/null
+++ b/Lib/unittest/test/testmock/__main__.py
@@ -0,0 +1,18 @@
+import os
+import unittest
+
+
+def load_tests(loader, standard_tests, pattern):
+ # top level directory cached on loader instance
+ this_dir = os.path.dirname(__file__)
+ pattern = pattern or "test*.py"
+ # We are inside unittest.test.testmock, so the top-level is three notches up
+ top_level_dir = os.path.dirname(os.path.dirname(os.path.dirname(this_dir)))
+ package_tests = loader.discover(start_dir=this_dir, pattern=pattern,
+ top_level_dir=top_level_dir)
+ standard_tests.addTests(package_tests)
+ return standard_tests
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/unittest/test/testmock/testcallable.py b/Lib/unittest/test/testmock/testcallable.py
index 7b2dd003ea..5390a4e10f 100644
--- a/Lib/unittest/test/testmock/testcallable.py
+++ b/Lib/unittest/test/testmock/testcallable.py
@@ -145,3 +145,7 @@ class TestCallable(unittest.TestCase):
mock.wibble.assert_called_once_with()
self.assertRaises(TypeError, mock.wibble, 'some', 'args')
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py
index a362a2f784..1dbc0b64ba 100644
--- a/Lib/unittest/test/testmock/testhelpers.py
+++ b/Lib/unittest/test/testmock/testhelpers.py
@@ -337,9 +337,10 @@ class SpecSignatureTest(unittest.TestCase):
def test_basic(self):
- for spec in (SomeClass, SomeClass()):
- mock = create_autospec(spec)
- self._check_someclass_mock(mock)
+ mock = create_autospec(SomeClass)
+ self._check_someclass_mock(mock)
+ mock = create_autospec(SomeClass())
+ self._check_someclass_mock(mock)
def test_create_autospec_return_value(self):
@@ -576,10 +577,10 @@ class SpecSignatureTest(unittest.TestCase):
def test_spec_inheritance_for_classes(self):
class Foo(object):
- def a(self):
+ def a(self, x):
pass
class Bar(object):
- def f(self):
+ def f(self, y):
pass
class_mock = create_autospec(Foo)
@@ -587,26 +588,30 @@ class SpecSignatureTest(unittest.TestCase):
self.assertIsNot(class_mock, class_mock())
for this_mock in class_mock, class_mock():
- this_mock.a()
- this_mock.a.assert_called_with()
- self.assertRaises(TypeError, this_mock.a, 'foo')
+ this_mock.a(x=5)
+ this_mock.a.assert_called_with(x=5)
+ this_mock.a.assert_called_with(5)
+ self.assertRaises(TypeError, this_mock.a, 'foo', 'bar')
self.assertRaises(AttributeError, getattr, this_mock, 'b')
instance_mock = create_autospec(Foo())
- instance_mock.a()
- instance_mock.a.assert_called_with()
- self.assertRaises(TypeError, instance_mock.a, 'foo')
+ instance_mock.a(5)
+ instance_mock.a.assert_called_with(5)
+ instance_mock.a.assert_called_with(x=5)
+ self.assertRaises(TypeError, instance_mock.a, 'foo', 'bar')
self.assertRaises(AttributeError, getattr, instance_mock, 'b')
# The return value isn't isn't callable
self.assertRaises(TypeError, instance_mock)
- instance_mock.Bar.f()
- instance_mock.Bar.f.assert_called_with()
+ instance_mock.Bar.f(6)
+ instance_mock.Bar.f.assert_called_with(6)
+ instance_mock.Bar.f.assert_called_with(y=6)
self.assertRaises(AttributeError, getattr, instance_mock.Bar, 'g')
- instance_mock.Bar().f()
- instance_mock.Bar().f.assert_called_with()
+ instance_mock.Bar().f(6)
+ instance_mock.Bar().f.assert_called_with(6)
+ instance_mock.Bar().f.assert_called_with(y=6)
self.assertRaises(AttributeError, getattr, instance_mock.Bar(), 'g')
@@ -663,12 +668,15 @@ class SpecSignatureTest(unittest.TestCase):
self.assertRaises(TypeError, mock)
mock(1, 2)
mock.assert_called_with(1, 2)
+ mock.assert_called_with(1, b=2)
+ mock.assert_called_with(a=1, b=2)
f.f = f
mock = create_autospec(f)
self.assertRaises(TypeError, mock.f)
mock.f(3, 4)
mock.f.assert_called_with(3, 4)
+ mock.f.assert_called_with(a=3, b=4)
def test_skip_attributeerrors(self):
@@ -704,9 +712,13 @@ class SpecSignatureTest(unittest.TestCase):
self.assertRaises(TypeError, mock)
mock(1)
mock.assert_called_once_with(1)
+ mock.assert_called_once_with(a=1)
+ self.assertRaises(AssertionError, mock.assert_called_once_with, 2)
mock(4, 5)
mock.assert_called_with(4, 5)
+ mock.assert_called_with(a=4, b=5)
+ self.assertRaises(AssertionError, mock.assert_called_with, a=5, b=4)
def test_class_with_no_init(self):
@@ -719,24 +731,27 @@ class SpecSignatureTest(unittest.TestCase):
def test_signature_callable(self):
class Callable(object):
- def __init__(self):
+ def __init__(self, x, y):
pass
def __call__(self, a):
pass
mock = create_autospec(Callable)
- mock()
- mock.assert_called_once_with()
+ mock(1, 2)
+ mock.assert_called_once_with(1, 2)
+ mock.assert_called_once_with(x=1, y=2)
self.assertRaises(TypeError, mock, 'a')
- instance = mock()
+ instance = mock(1, 2)
self.assertRaises(TypeError, instance)
instance(a='a')
+ instance.assert_called_once_with('a')
instance.assert_called_once_with(a='a')
instance('a')
instance.assert_called_with('a')
+ instance.assert_called_with(a='a')
- mock = create_autospec(Callable())
+ mock = create_autospec(Callable(1, 2))
mock(a='a')
mock.assert_called_once_with(a='a')
self.assertRaises(TypeError, mock)
@@ -779,7 +794,11 @@ class SpecSignatureTest(unittest.TestCase):
pass
a = create_autospec(Foo)
+ a.f(10)
+ a.f.assert_called_with(10)
+ a.f.assert_called_with(self=10)
a.f(self=10)
+ a.f.assert_called_with(10)
a.f.assert_called_with(self=10)
diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py
index cef5405fe9..20cc6541e6 100644
--- a/Lib/unittest/test/testmock/testmock.py
+++ b/Lib/unittest/test/testmock/testmock.py
@@ -25,6 +25,18 @@ class Iter(object):
__next__ = next
+class Something(object):
+ def meth(self, a, b, c, d=None):
+ pass
+
+ @classmethod
+ def cmeth(cls, a, b, c, d=None):
+ pass
+
+ @staticmethod
+ def smeth(a, b, c, d=None):
+ pass
+
class MockTest(unittest.TestCase):
@@ -273,6 +285,43 @@ class MockTest(unittest.TestCase):
mock.assert_called_with(1, 2, 3, a='fish', b='nothing')
+ def test_assert_called_with_function_spec(self):
+ def f(a, b, c, d=None):
+ pass
+
+ mock = Mock(spec=f)
+
+ mock(1, b=2, c=3)
+ mock.assert_called_with(1, 2, 3)
+ mock.assert_called_with(a=1, b=2, c=3)
+ self.assertRaises(AssertionError, mock.assert_called_with,
+ 1, b=3, c=2)
+ # Expected call doesn't match the spec's signature
+ with self.assertRaises(AssertionError) as cm:
+ mock.assert_called_with(e=8)
+ self.assertIsInstance(cm.exception.__cause__, TypeError)
+
+
+ def test_assert_called_with_method_spec(self):
+ def _check(mock):
+ mock(1, b=2, c=3)
+ mock.assert_called_with(1, 2, 3)
+ mock.assert_called_with(a=1, b=2, c=3)
+ self.assertRaises(AssertionError, mock.assert_called_with,
+ 1, b=3, c=2)
+
+ mock = Mock(spec=Something().meth)
+ _check(mock)
+ mock = Mock(spec=Something.cmeth)
+ _check(mock)
+ mock = Mock(spec=Something().cmeth)
+ _check(mock)
+ mock = Mock(spec=Something.smeth)
+ _check(mock)
+ mock = Mock(spec=Something().smeth)
+ _check(mock)
+
+
def test_assert_called_once_with(self):
mock = Mock()
mock()
@@ -297,6 +346,29 @@ class MockTest(unittest.TestCase):
)
+ def test_assert_called_once_with_function_spec(self):
+ def f(a, b, c, d=None):
+ pass
+
+ mock = Mock(spec=f)
+
+ mock(1, b=2, c=3)
+ mock.assert_called_once_with(1, 2, 3)
+ mock.assert_called_once_with(a=1, b=2, c=3)
+ self.assertRaises(AssertionError, mock.assert_called_once_with,
+ 1, b=3, c=2)
+ # Expected call doesn't match the spec's signature
+ with self.assertRaises(AssertionError) as cm:
+ mock.assert_called_once_with(e=8)
+ self.assertIsInstance(cm.exception.__cause__, TypeError)
+ # Mock called more than once => always fails
+ mock(4, 5, 6)
+ self.assertRaises(AssertionError, mock.assert_called_once_with,
+ 1, 2, 3)
+ self.assertRaises(AssertionError, mock.assert_called_once_with,
+ 4, 5, 6)
+
+
def test_attribute_access_returns_mocks(self):
mock = Mock()
something = mock.something
@@ -995,6 +1067,39 @@ class MockTest(unittest.TestCase):
)
+ def test_assert_has_calls_with_function_spec(self):
+ def f(a, b, c, d=None):
+ pass
+
+ mock = Mock(spec=f)
+
+ mock(1, b=2, c=3)
+ mock(4, 5, c=6, d=7)
+ mock(10, 11, c=12)
+ calls = [
+ ('', (1, 2, 3), {}),
+ ('', (4, 5, 6), {'d': 7}),
+ ((10, 11, 12), {}),
+ ]
+ mock.assert_has_calls(calls)
+ mock.assert_has_calls(calls, any_order=True)
+ mock.assert_has_calls(calls[1:])
+ mock.assert_has_calls(calls[1:], any_order=True)
+ mock.assert_has_calls(calls[:-1])
+ mock.assert_has_calls(calls[:-1], any_order=True)
+ # Reversed order
+ calls = list(reversed(calls))
+ with self.assertRaises(AssertionError):
+ mock.assert_has_calls(calls)
+ mock.assert_has_calls(calls, any_order=True)
+ with self.assertRaises(AssertionError):
+ mock.assert_has_calls(calls[1:])
+ mock.assert_has_calls(calls[1:], any_order=True)
+ with self.assertRaises(AssertionError):
+ mock.assert_has_calls(calls[:-1])
+ mock.assert_has_calls(calls[:-1], any_order=True)
+
+
def test_assert_any_call(self):
mock = Mock()
mock(1, 2)
@@ -1021,6 +1126,26 @@ class MockTest(unittest.TestCase):
)
+ def test_assert_any_call_with_function_spec(self):
+ def f(a, b, c, d=None):
+ pass
+
+ mock = Mock(spec=f)
+
+ mock(1, b=2, c=3)
+ mock(4, 5, c=6, d=7)
+ mock.assert_any_call(1, 2, 3)
+ mock.assert_any_call(a=1, b=2, c=3)
+ mock.assert_any_call(4, 5, 6, 7)
+ mock.assert_any_call(a=4, b=5, c=6, d=7)
+ self.assertRaises(AssertionError, mock.assert_any_call,
+ 1, b=3, c=2)
+ # Expected call doesn't match the spec's signature
+ with self.assertRaises(AssertionError) as cm:
+ mock.assert_any_call(e=8)
+ self.assertIsInstance(cm.exception.__cause__, TypeError)
+
+
def test_mock_calls_create_autospec(self):
def f(a, b):
pass
@@ -1177,20 +1302,6 @@ class MockTest(unittest.TestCase):
self.assertEqual(m.method_calls, [])
- def test_attribute_deletion(self):
- # this behaviour isn't *useful*, but at least it's now tested...
- for Klass in Mock, MagicMock, NonCallableMagicMock, NonCallableMock:
- m = Klass()
- original = m.foo
- m.foo = 3
- del m.foo
- self.assertEqual(m.foo, original)
-
- new = m.foo = Mock()
- del m.foo
- self.assertEqual(m.foo, new)
-
-
def test_mock_parents(self):
for Klass in Mock, MagicMock:
m = Klass()
@@ -1254,7 +1365,8 @@ class MockTest(unittest.TestCase):
def test_attribute_deletion(self):
- for mock in Mock(), MagicMock():
+ for mock in (Mock(), MagicMock(), NonCallableMagicMock(),
+ NonCallableMock()):
self.assertTrue(hasattr(mock, 'm'))
del mock.m
@@ -1274,6 +1386,5 @@ class MockTest(unittest.TestCase):
mock.foo
-
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/unittest/test/testmock/testpatch.py b/Lib/unittest/test/testmock/testpatch.py
index c1091b4e9b..c1bc34fa8c 100644
--- a/Lib/unittest/test/testmock/testpatch.py
+++ b/Lib/unittest/test/testmock/testpatch.py
@@ -1780,6 +1780,5 @@ class PatchTest(unittest.TestCase):
self.assertIs(os.path, path)
-
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/unittest/test/testmock/testwith.py b/Lib/unittest/test/testmock/testwith.py
index 0a0cfad120..f54e051e94 100644
--- a/Lib/unittest/test/testmock/testwith.py
+++ b/Lib/unittest/test/testmock/testwith.py
@@ -172,5 +172,88 @@ class TestMockOpen(unittest.TestCase):
self.assertEqual(result, 'foo')
+ def test_readline_data(self):
+ # Check that readline will return all the lines from the fake file
+ mock = mock_open(read_data='foo\nbar\nbaz\n')
+ with patch('%s.open' % __name__, mock, create=True):
+ h = open('bar')
+ line1 = h.readline()
+ line2 = h.readline()
+ line3 = h.readline()
+ self.assertEqual(line1, 'foo\n')
+ self.assertEqual(line2, 'bar\n')
+ self.assertEqual(line3, 'baz\n')
+
+ # Check that we properly emulate a file that doesn't end in a newline
+ mock = mock_open(read_data='foo')
+ with patch('%s.open' % __name__, mock, create=True):
+ h = open('bar')
+ result = h.readline()
+ self.assertEqual(result, 'foo')
+
+
+ def test_readlines_data(self):
+ # Test that emulating a file that ends in a newline character works
+ mock = mock_open(read_data='foo\nbar\nbaz\n')
+ with patch('%s.open' % __name__, mock, create=True):
+ h = open('bar')
+ result = h.readlines()
+ self.assertEqual(result, ['foo\n', 'bar\n', 'baz\n'])
+
+ # Test that files without a final newline will also be correctly
+ # emulated
+ mock = mock_open(read_data='foo\nbar\nbaz')
+ with patch('%s.open' % __name__, mock, create=True):
+ h = open('bar')
+ result = h.readlines()
+
+ self.assertEqual(result, ['foo\n', 'bar\n', 'baz'])
+
+
+ def test_mock_open_read_with_argument(self):
+ # At one point calling read with an argument was broken
+ # for mocks returned by mock_open
+ some_data = 'foo\nbar\nbaz'
+ mock = mock_open(read_data=some_data)
+ self.assertEqual(mock().read(10), some_data)
+
+
+ def test_interleaved_reads(self):
+ # Test that calling read, readline, and readlines pulls data
+ # sequentially from the data we preload with
+ mock = mock_open(read_data='foo\nbar\nbaz\n')
+ with patch('%s.open' % __name__, mock, create=True):
+ h = open('bar')
+ line1 = h.readline()
+ rest = h.readlines()
+ self.assertEqual(line1, 'foo\n')
+ self.assertEqual(rest, ['bar\n', 'baz\n'])
+
+ mock = mock_open(read_data='foo\nbar\nbaz\n')
+ with patch('%s.open' % __name__, mock, create=True):
+ h = open('bar')
+ line1 = h.readline()
+ rest = h.read()
+ self.assertEqual(line1, 'foo\n')
+ self.assertEqual(rest, 'bar\nbaz\n')
+
+
+ def test_overriding_return_values(self):
+ mock = mock_open(read_data='foo')
+ handle = mock()
+
+ handle.read.return_value = 'bar'
+ handle.readline.return_value = 'bar'
+ handle.readlines.return_value = ['bar']
+
+ self.assertEqual(handle.read(), 'bar')
+ self.assertEqual(handle.readline(), 'bar')
+ self.assertEqual(handle.readlines(), ['bar'])
+
+ # call repeatedly to check that a StopIteration is not propagated
+ self.assertEqual(handle.readline(), 'bar')
+ self.assertEqual(handle.readline(), 'bar')
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/unittest/util.py b/Lib/unittest/util.py
index ccdf0b81fa..aee498fd0b 100644
--- a/Lib/unittest/util.py
+++ b/Lib/unittest/util.py
@@ -1,10 +1,47 @@
"""Various utility functions."""
from collections import namedtuple, OrderedDict
+from os.path import commonprefix
__unittest = True
_MAX_LENGTH = 80
+_PLACEHOLDER_LEN = 12
+_MIN_BEGIN_LEN = 5
+_MIN_END_LEN = 5
+_MIN_COMMON_LEN = 5
+_MIN_DIFF_LEN = _MAX_LENGTH - \
+ (_MIN_BEGIN_LEN + _PLACEHOLDER_LEN + _MIN_COMMON_LEN +
+ _PLACEHOLDER_LEN + _MIN_END_LEN)
+assert _MIN_DIFF_LEN >= 0
+
+def _shorten(s, prefixlen, suffixlen):
+ skip = len(s) - prefixlen - suffixlen
+ if skip > _PLACEHOLDER_LEN:
+ s = '%s[%d chars]%s' % (s[:prefixlen], skip, s[len(s) - suffixlen:])
+ return s
+
+def _common_shorten_repr(*args):
+ args = tuple(map(safe_repr, args))
+ maxlen = max(map(len, args))
+ if maxlen <= _MAX_LENGTH:
+ return args
+
+ prefix = commonprefix(args)
+ prefixlen = len(prefix)
+
+ common_len = _MAX_LENGTH - \
+ (maxlen - prefixlen + _MIN_BEGIN_LEN + _PLACEHOLDER_LEN)
+ if common_len > _MIN_COMMON_LEN:
+ assert _MIN_BEGIN_LEN + _PLACEHOLDER_LEN + _MIN_COMMON_LEN + \
+ (maxlen - prefixlen) < _MAX_LENGTH
+ prefix = _shorten(prefix, _MIN_BEGIN_LEN, common_len)
+ return tuple(prefix + s[prefixlen:] for s in args)
+
+ prefix = _shorten(prefix, _MIN_BEGIN_LEN, _MIN_COMMON_LEN)
+ return tuple(prefix + _shorten(s[prefixlen:], _MIN_DIFF_LEN, _MIN_END_LEN)
+ for s in args)
+
def safe_repr(obj, short=False):
try:
result = repr(obj)
diff --git a/Lib/urllib/error.py b/Lib/urllib/error.py
index b712ebb5cb..45b7169793 100644
--- a/Lib/urllib/error.py
+++ b/Lib/urllib/error.py
@@ -1,6 +1,6 @@
"""Exception classes raised by urllib.
-The base exception class is URLError, which inherits from IOError. It
+The base exception class is URLError, which inherits from OSError. It
doesn't define any behavior of its own, but is the base class for all
exceptions defined in this package.
@@ -17,11 +17,11 @@ __all__ = ['URLError', 'HTTPError', 'ContentTooShortError']
# do these error classes make sense?
-# make sure all of the IOError stuff is overridden. we just want to be
+# make sure all of the OSError stuff is overridden. we just want to be
# subtypes.
-class URLError(IOError):
- # URLError is a sub-type of IOError, but it doesn't share any of
+class URLError(OSError):
+ # URLError is a sub-type of OSError, but it doesn't share any of
# the implementation. need to override __init__ and __str__.
# It sets self.args for compatibility with other EnvironmentError
# subclasses, but args doesn't have the typical format with errno in
@@ -61,9 +61,13 @@ class HTTPError(URLError, urllib.response.addinfourl):
def reason(self):
return self.msg
- def info(self):
+ @property
+ def headers(self):
return self.hdrs
+ @headers.setter
+ def headers(self, headers):
+ self.hdrs = headers
# exception raised when downloaded size does not match content-length
class ContentTooShortError(URLError):
diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
index 9a2df41faf..5328b9d329 100644
--- a/Lib/urllib/parse.py
+++ b/Lib/urllib/parse.py
@@ -846,7 +846,6 @@ def splittype(url):
"""splittype('type:opaquestring') --> 'type', 'opaquestring'."""
global _typeprog
if _typeprog is None:
- import re
_typeprog = re.compile('^([^/:]+):')
match = _typeprog.match(url)
@@ -860,7 +859,6 @@ def splithost(url):
"""splithost('//host[:port]/path') --> 'host[:port]', '/path'."""
global _hostprog
if _hostprog is None:
- import re
_hostprog = re.compile('^//([^/?]*)(.*)$')
match = _hostprog.match(url)
@@ -877,7 +875,6 @@ def splituser(host):
"""splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
global _userprog
if _userprog is None:
- import re
_userprog = re.compile('^(.*)@(.*)$')
match = _userprog.match(host)
@@ -889,7 +886,6 @@ def splitpasswd(user):
"""splitpasswd('user:passwd') -> 'user', 'passwd'."""
global _passwdprog
if _passwdprog is None:
- import re
_passwdprog = re.compile('^([^:]*):(.*)$',re.S)
match = _passwdprog.match(user)
@@ -902,7 +898,6 @@ def splitport(host):
"""splitport('host:port') --> 'host', 'port'."""
global _portprog
if _portprog is None:
- import re
_portprog = re.compile('^(.*):([0-9]+)$')
match = _portprog.match(host)
@@ -917,7 +912,6 @@ def splitnport(host, defport=-1):
Return None if ':' but not a valid number."""
global _nportprog
if _nportprog is None:
- import re
_nportprog = re.compile('^(.*):(.*)$')
match = _nportprog.match(host)
@@ -936,7 +930,6 @@ def splitquery(url):
"""splitquery('/path?query') --> '/path', 'query'."""
global _queryprog
if _queryprog is None:
- import re
_queryprog = re.compile('^(.*)\?([^?]*)$')
match = _queryprog.match(url)
@@ -948,7 +941,6 @@ def splittag(url):
"""splittag('/path#tag') --> '/path', 'tag'."""
global _tagprog
if _tagprog is None:
- import re
_tagprog = re.compile('^(.*)#([^#]*)$')
match = _tagprog.match(url)
@@ -966,7 +958,6 @@ def splitvalue(attr):
"""splitvalue('attr=value') --> 'attr', 'value'."""
global _valueprog
if _valueprog is None:
- import re
_valueprog = re.compile('^([^=]*)=(.*)$')
match = _valueprog.match(attr)
diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py
index a7445d185d..5995cbe24f 100644
--- a/Lib/urllib/request.py
+++ b/Lib/urllib/request.py
@@ -18,7 +18,7 @@ urlopen(url, data=None) -- Basic usage is the same as original
urllib. pass the url and optionally data to post to an HTTP URL, and
get a file-like object back. One difference is that you can also pass
a Request instance instead of URL. Raises a URLError (subclass of
-IOError); for HTTP errors, raises an HTTPError, which can also be
+OSError); for HTTP errors, raises an HTTPError, which can also be
treated as a valid response.
build_opener -- Function that creates a new OpenerDirector instance.
@@ -103,7 +103,8 @@ from urllib.error import URLError, HTTPError, ContentTooShortError
from urllib.parse import (
urlparse, urlsplit, urljoin, unwrap, quote, unquote,
splittype, splithost, splitport, splituser, splitpasswd,
- splitattr, splitquery, splitvalue, splittag, to_bytes, urlunparse)
+ splitattr, splitquery, splitvalue, splittag, to_bytes,
+ unquote_to_bytes, urlunparse)
from urllib.response import addinfourl, addclosehook
# check for SSL
@@ -121,7 +122,7 @@ __all__ = [
'HTTPPasswordMgr', 'HTTPPasswordMgrWithDefaultRealm',
'AbstractBasicAuthHandler', 'HTTPBasicAuthHandler', 'ProxyBasicAuthHandler',
'AbstractDigestAuthHandler', 'HTTPDigestAuthHandler', 'ProxyDigestAuthHandler',
- 'HTTPHandler', 'FileHandler', 'FTPHandler', 'CacheFTPHandler',
+ 'HTTPHandler', 'FileHandler', 'FTPHandler', 'CacheFTPHandler', 'DataHandler',
'UnknownHandler', 'HTTPErrorProcessor',
# Functions
'urlopen', 'install_opener', 'build_opener',
@@ -140,13 +141,9 @@ def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
if cafile or capath or cadefault:
if not _have_ssl:
raise ValueError('SSL support not available')
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- context.options |= ssl.OP_NO_SSLv2
- context.verify_mode = ssl.CERT_REQUIRED
- if cafile or capath:
- context.load_verify_locations(cafile, capath)
- else:
- context.set_default_verify_paths()
+ context = ssl._create_stdlib_context(cert_reqs=ssl.CERT_REQUIRED,
+ cafile=cafile,
+ capath=capath)
https_handler = HTTPSHandler(context=context, check_hostname=True)
opener = build_opener(https_handler)
elif _opener is None:
@@ -227,7 +224,7 @@ def urlcleanup():
for temp_file in _url_tempfiles:
try:
os.unlink(temp_file)
- except EnvironmentError:
+ except OSError:
pass
del _url_tempfiles[:]
@@ -258,24 +255,60 @@ class Request:
def __init__(self, url, data=None, headers={},
origin_req_host=None, unverifiable=False,
method=None):
- # unwrap('<URL:type://host/path>') --> 'type://host/path'
- self.full_url = unwrap(url)
- self.full_url, self.fragment = splittag(self.full_url)
- self.data = data
+ self.full_url = url
self.headers = {}
+ self.unredirected_hdrs = {}
+ self._data = None
+ self.data = data
self._tunnel_host = None
for key, value in headers.items():
self.add_header(key, value)
- self.unredirected_hdrs = {}
if origin_req_host is None:
origin_req_host = request_host(self)
self.origin_req_host = origin_req_host
self.unverifiable = unverifiable
- self.method = method
+ if method:
+ self.method = method
+
+ @property
+ def full_url(self):
+ if self.fragment:
+ return '{}#{}'.format(self._full_url, self.fragment)
+ return self._full_url
+
+ @full_url.setter
+ def full_url(self, url):
+ # unwrap('<URL:type://host/path>') --> 'type://host/path'
+ self._full_url = unwrap(url)
+ self._full_url, self.fragment = splittag(self._full_url)
self._parse()
+ @full_url.deleter
+ def full_url(self):
+ self._full_url = None
+ self.fragment = None
+ self.selector = ''
+
+ @property
+ def data(self):
+ return self._data
+
+ @data.setter
+ def data(self, data):
+ if data != self._data:
+ self._data = data
+ # issue 16464
+ # if we change data we need to remove content-length header
+ # (cause it's most probably calculated for previous value)
+ if self.has_header("Content-length"):
+ self.remove_header("Content-length")
+
+ @data.deleter
+ def data(self):
+ self.data = None
+
def _parse(self):
- self.type, rest = splittype(self.full_url)
+ self.type, rest = splittype(self._full_url)
if self.type is None:
raise ValueError("unknown url type: %r" % self.full_url)
self.host, self.selector = splithost(rest)
@@ -284,62 +317,11 @@ class Request:
def get_method(self):
"""Return a string indicating the HTTP request method."""
- if self.method is not None:
- return self.method
- elif self.data is not None:
- return "POST"
- else:
- return "GET"
+ default_method = "POST" if self.data is not None else "GET"
+ return getattr(self, 'method', default_method)
def get_full_url(self):
- if self.fragment:
- return '%s#%s' % (self.full_url, self.fragment)
- else:
- return self.full_url
-
- # Begin deprecated methods
-
- def add_data(self, data):
- msg = "Request.add_data method is deprecated."
- warnings.warn(msg, DeprecationWarning, stacklevel=1)
- self.data = data
-
- def has_data(self):
- msg = "Request.has_data method is deprecated."
- warnings.warn(msg, DeprecationWarning, stacklevel=1)
- return self.data is not None
-
- def get_data(self):
- msg = "Request.get_data method is deprecated."
- warnings.warn(msg, DeprecationWarning, stacklevel=1)
- return self.data
-
- def get_type(self):
- msg = "Request.get_type method is deprecated."
- warnings.warn(msg, DeprecationWarning, stacklevel=1)
- return self.type
-
- def get_host(self):
- msg = "Request.get_host method is deprecated."
- warnings.warn(msg, DeprecationWarning, stacklevel=1)
- return self.host
-
- def get_selector(self):
- msg = "Request.get_selector method is deprecated."
- warnings.warn(msg, DeprecationWarning, stacklevel=1)
- return self.selector
-
- def is_unverifiable(self):
- msg = "Request.is_unverifiable method is deprecated."
- warnings.warn(msg, DeprecationWarning, stacklevel=1)
- return self.unverifiable
-
- def get_origin_req_host(self):
- msg = "Request.get_origin_req_host method is deprecated."
- warnings.warn(msg, DeprecationWarning, stacklevel=1)
- return self.origin_req_host
-
- # End deprecated methods
+ return self.full_url
def set_proxy(self, host, type):
if self.type == 'https' and not self._tunnel_host:
@@ -369,6 +351,10 @@ class Request:
header_name,
self.unredirected_hdrs.get(header_name, default))
+ def remove_header(self, header_name):
+ self.headers.pop(header_name, None)
+ self.unredirected_hdrs.pop(header_name, None)
+
def header_items(self):
hdrs = self.unredirected_hdrs.copy()
hdrs.update(self.headers)
@@ -531,7 +517,8 @@ def build_opener(*handlers):
opener = OpenerDirector()
default_classes = [ProxyHandler, UnknownHandler, HTTPHandler,
HTTPDefaultErrorHandler, HTTPRedirectHandler,
- FTPHandler, FileHandler, HTTPErrorProcessor]
+ FTPHandler, FileHandler, HTTPErrorProcessor,
+ DataHandler]
if hasattr(http.client, "HTTPSConnection"):
default_classes.append(HTTPSHandler)
skip = set()
@@ -1246,11 +1233,17 @@ class AbstractHTTPHandler(BaseHandler):
try:
h.request(req.get_method(), req.selector, req.data, headers)
- except socket.error as err: # timeout error
+ except OSError as err: # timeout error
h.close()
raise URLError(err)
else:
r = h.getresponse()
+ # If the server does not send us a 'Connection: close' header,
+ # HTTPConnection assumes the socket should be left open. Manually
+ # mark the socket to be closed when this response object goes away.
+ if h.sock:
+ h.sock.close()
+ h.sock = None
r.url = req.get_full_url()
# This line replaces the .msg attribute of the HTTPResponse
@@ -1445,7 +1438,7 @@ class FTPHandler(BaseHandler):
try:
host = socket.gethostbyname(host)
- except socket.error as msg:
+ except OSError as msg:
raise URLError(msg)
path, attrs = splitattr(req.selector)
dirs = path.split('/')
@@ -1531,6 +1524,36 @@ class CacheFTPHandler(FTPHandler):
self.cache.clear()
self.timeout.clear()
+class DataHandler(BaseHandler):
+ def data_open(self, req):
+ # data URLs as specified in RFC 2397.
+ #
+ # ignores POSTed data
+ #
+ # syntax:
+ # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
+ # mediatype := [ type "/" subtype ] *( ";" parameter )
+ # data := *urlchar
+ # parameter := attribute "=" value
+ url = req.full_url
+
+ scheme, data = url.split(":",1)
+ mediatype, data = data.split(",",1)
+
+ # even base64 encoded data URLs might be quoted so unquote in any case:
+ data = unquote_to_bytes(data)
+ if mediatype.endswith(";base64"):
+ data = base64.decodebytes(data)
+ mediatype = mediatype[:-7]
+
+ if not mediatype:
+ mediatype = "text/plain;charset=US-ASCII"
+
+ headers = email.message_from_string("Content-type: %s\nContent-length: %d\n" %
+ (mediatype, len(data)))
+
+ return addinfourl(io.BytesIO(data), headers, url)
+
# Code move from the old urllib module
@@ -1654,20 +1677,20 @@ class URLopener:
return getattr(self, name)(url)
else:
return getattr(self, name)(url, data)
- except HTTPError:
+ except (HTTPError, URLError):
raise
- except socket.error as msg:
- raise IOError('socket error', msg).with_traceback(sys.exc_info()[2])
+ except OSError as msg:
+ raise OSError('socket error', msg).with_traceback(sys.exc_info()[2])
def open_unknown(self, fullurl, data=None):
"""Overridable interface to open unknown URL type."""
type, url = splittype(fullurl)
- raise IOError('url error', 'unknown url type', type)
+ raise OSError('url error', 'unknown url type', type)
def open_unknown_proxy(self, proxy, fullurl, data=None):
"""Overridable interface to open unknown URL type."""
type, url = splittype(fullurl)
- raise IOError('url error', 'invalid proxy for %s' % type, proxy)
+ raise OSError('url error', 'invalid proxy for %s' % type, proxy)
# External interface
def retrieve(self, url, filename=None, reporthook=None, data=None):
@@ -1683,7 +1706,7 @@ class URLopener:
hdrs = fp.info()
fp.close()
return url2pathname(splithost(url1)[1]), hdrs
- except IOError as msg:
+ except OSError as msg:
pass
fp = self.open(url, data)
try:
@@ -1776,7 +1799,7 @@ class URLopener:
if proxy_bypass(realhost):
host = realhost
- if not host: raise IOError('http error', 'no host given')
+ if not host: raise OSError('http error', 'no host given')
if proxy_passwd:
proxy_passwd = unquote(proxy_passwd)
@@ -1849,7 +1872,7 @@ class URLopener:
return self.http_error_default(url, fp, errcode, errmsg, headers)
def http_error_default(self, url, fp, errcode, errmsg, headers):
- """Default error handler: close the connection and raise IOError."""
+ """Default error handler: close the connection and raise OSError."""
fp.close()
raise HTTPError(url, errcode, errmsg, headers, None)
@@ -1976,7 +1999,7 @@ class URLopener:
try:
[type, data] = url.split(',', 1)
except ValueError:
- raise IOError('data error', 'bad data URL')
+ raise OSError('data error', 'bad data URL')
if not type:
type = 'text/plain;charset=US-ASCII'
semi = type.rfind(';')
@@ -2425,7 +2448,7 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings):
try:
hostIP = socket.gethostbyname(hostonly)
hostIP = ip2num(hostIP)
- except socket.error:
+ except OSError:
continue
base = ip2num(m.group(1))
@@ -2511,7 +2534,7 @@ elif os.name == 'nt':
proxies['https'] = 'https://%s' % proxyServer
proxies['ftp'] = 'ftp://%s' % proxyServer
internetSettings.Close()
- except (WindowsError, ValueError, TypeError):
+ except (OSError, ValueError, TypeError):
# Either registry key not found etc, or the value in an
# unexpected format.
# proxies already set up to be empty so nothing to do
@@ -2541,7 +2564,7 @@ elif os.name == 'nt':
proxyOverride = str(winreg.QueryValueEx(internetSettings,
'ProxyOverride')[0])
# ^^^^ Returned as Unicode but problems if not converted to ASCII
- except WindowsError:
+ except OSError:
return 0
if not proxyEnable or not proxyOverride:
return 0
@@ -2552,13 +2575,13 @@ elif os.name == 'nt':
addr = socket.gethostbyname(rawHost)
if addr != rawHost:
host.append(addr)
- except socket.error:
+ except OSError:
pass
try:
fqdn = socket.getfqdn(rawHost)
if fqdn != rawHost:
host.append(fqdn)
- except socket.error:
+ except OSError:
pass
# make a check value list from the registry entry: replace the
# '<local>' string by the localhost entry and the corresponding
diff --git a/Lib/uuid.py b/Lib/uuid.py
index d5c3160fdf..385fb9b087 100644
--- a/Lib/uuid.py
+++ b/Lib/uuid.py
@@ -147,7 +147,7 @@ class UUID(object):
if len(bytes) != 16:
raise ValueError('bytes is not a 16-char string')
assert isinstance(bytes, bytes_), repr(bytes)
- int = int_(('%02x'*16) % tuple(bytes), 16)
+ int = int_.from_bytes(bytes, byteorder='big')
if fields is not None:
if len(fields) != 6:
raise ValueError('fields is not a 6-tuple')
@@ -337,7 +337,7 @@ def _find_mac(command, args, hw_identifiers, get_index):
# dashes. These should be ignored in favor of a
# real MAC address
pass
- except IOError:
+ except OSError:
continue
return None
@@ -379,7 +379,7 @@ def _ipconfig_getnode():
for dir in dirs:
try:
pipe = os.popen(os.path.join(dir, 'ipconfig') + ' /all')
- except IOError:
+ except OSError:
continue
else:
for line in pipe:
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index 3606370e86..3b5f5fab81 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -24,10 +24,13 @@ optional arguments:
raised.
--upgrade Upgrade the environment directory to use this version
of Python, assuming Python has been upgraded in-place.
+ --without-pip Skips installing or upgrading pip in the virtual
+ environment (pip is bootstrapped by default)
"""
import logging
import os
import shutil
+import subprocess
import sys
import sysconfig
import types
@@ -56,14 +59,17 @@ class EnvBuilder:
:param symlinks: If True, attempt to symlink rather than copy files into
virtual environment.
:param upgrade: If True, upgrade an existing virtual environment.
+ :param with_pip: If True, ensure pip is installed in the virtual
+ environment
"""
def __init__(self, system_site_packages=False, clear=False,
- symlinks=False, upgrade=False):
+ symlinks=False, upgrade=False, with_pip=False):
self.system_site_packages = system_site_packages
self.clear = clear
self.symlinks = symlinks
self.upgrade = upgrade
+ self.with_pip = with_pip
def create(self, env_dir):
"""
@@ -76,10 +82,20 @@ class EnvBuilder:
context = self.ensure_directories(env_dir)
self.create_configuration(context)
self.setup_python(context)
+ if self.with_pip:
+ self._setup_pip(context)
if not self.upgrade:
self.setup_scripts(context)
self.post_setup(context)
+ def clear_directory(self, path):
+ for fn in os.listdir(path):
+ fn = os.path.join(path, fn)
+ if os.path.islink(fn) or os.path.isfile(fn):
+ os.remove(fn)
+ elif os.path.isdir(fn):
+ shutil.rmtree(fn)
+
def ensure_directories(self, env_dir):
"""
Create the directories for the environment.
@@ -91,11 +107,11 @@ class EnvBuilder:
def create_if_needed(d):
if not os.path.exists(d):
os.makedirs(d)
+ elif os.path.islink(d) or os.path.isfile(d):
+ raise ValueError('Unable to create directory %r' % d)
- if os.path.exists(env_dir) and not (self.clear or self.upgrade):
- raise ValueError('Directory exists: %s' % env_dir)
if os.path.exists(env_dir) and self.clear:
- shutil.rmtree(env_dir)
+ self.clear_directory(env_dir)
context = types.SimpleNamespace()
context.env_dir = env_dir
context.env_name = os.path.split(env_dir)[1]
@@ -216,6 +232,15 @@ class EnvBuilder:
shutil.copyfile(src, dst)
break
+ def _setup_pip(self, context):
+ """Installs or upgrades pip in a virtual environment"""
+ # We run ensurepip in isolated mode to avoid side effects from
+ # environment vars, the current directory and anything else
+ # intended for the global Python environment
+ cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade',
+ '--default-pip']
+ subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+
def setup_scripts(self, context):
"""
Set up scripts into the created environment from a directory.
@@ -253,7 +278,8 @@ class EnvBuilder:
being processed.
"""
text = text.replace('__VENV_DIR__', context.env_dir)
- text = text.replace('__VENV_NAME__', context.prompt)
+ text = text.replace('__VENV_NAME__', context.env_name)
+ text = text.replace('__VENV_PROMPT__', context.prompt)
text = text.replace('__VENV_BIN_NAME__', context.bin_name)
text = text.replace('__VENV_PYTHON__', context.env_exe)
return text
@@ -308,7 +334,8 @@ class EnvBuilder:
shutil.copymode(srcfile, dstfile)
-def create(env_dir, system_site_packages=False, clear=False, symlinks=False):
+def create(env_dir, system_site_packages=False, clear=False,
+ symlinks=False, with_pip=False):
"""
Create a virtual environment in a directory.
@@ -324,9 +351,11 @@ def create(env_dir, system_site_packages=False, clear=False, symlinks=False):
raised.
:param symlinks: If True, attempt to symlink rather than copy files into
virtual environment.
+ :param with_pip: If True, ensure pip is installed in the virtual
+ environment
"""
builder = EnvBuilder(system_site_packages=system_site_packages,
- clear=clear, symlinks=symlinks)
+ clear=clear, symlinks=symlinks, with_pip=with_pip)
builder.create(env_dir)
def main(args=None):
@@ -360,28 +389,40 @@ def main(args=None):
use_symlinks = False
else:
use_symlinks = True
- parser.add_argument('--symlinks', default=use_symlinks,
- action='store_true', dest='symlinks',
- help='Try to use symlinks rather than copies, '
- 'when symlinks are not the default for '
- 'the platform.')
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument('--symlinks', default=use_symlinks,
+ action='store_true', dest='symlinks',
+ help='Try to use symlinks rather than copies, '
+ 'when symlinks are not the default for '
+ 'the platform.')
+ group.add_argument('--copies', default=not use_symlinks,
+ action='store_false', dest='symlinks',
+ help='Try to use copies rather than symlinks, '
+ 'even when symlinks are the default for '
+ 'the platform.')
parser.add_argument('--clear', default=False, action='store_true',
- dest='clear', help='Delete the environment '
- 'directory if it already '
- 'exists. If not specified and '
- 'the directory exists, an error'
- ' is raised.')
+ dest='clear', help='Delete the contents of the '
+ 'environment directory if it '
+ 'already exists, before '
+ 'environment creation.')
parser.add_argument('--upgrade', default=False, action='store_true',
dest='upgrade', help='Upgrade the environment '
'directory to use this version '
'of Python, assuming Python '
'has been upgraded in-place.')
+ parser.add_argument('--without-pip', dest='with_pip',
+ default=True, action='store_false',
+ help='Skips installing or upgrading pip in the '
+ 'virtual environment (pip is bootstrapped '
+ 'by default)')
options = parser.parse_args(args)
if options.upgrade and options.clear:
raise ValueError('you cannot supply --upgrade and --clear together.')
builder = EnvBuilder(system_site_packages=options.system_site,
- clear=options.clear, symlinks=options.symlinks,
- upgrade=options.upgrade)
+ clear=options.clear,
+ symlinks=options.symlinks,
+ upgrade=options.upgrade,
+ with_pip=options.with_pip)
for d in options.dirs:
builder.create(d)
diff --git a/Lib/venv/scripts/nt/Activate.ps1 b/Lib/venv/scripts/nt/Activate.ps1
index 1c5ef98726..b15decb9c5 100644
--- a/Lib/venv/scripts/nt/Activate.ps1
+++ b/Lib/venv/scripts/nt/Activate.ps1
@@ -1,25 +1,40 @@
-$env:VIRTUAL_ENV="__VENV_DIR__"
+function global:deactivate ([switch]$NonDestructive) {
+ # Revert to original values
+ if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
+ copy-item function:_OLD_VIRTUAL_PROMPT function:prompt
+ remove-item function:_OLD_VIRTUAL_PROMPT
+ }
-# Revert to original values
-if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
- copy-item function:_OLD_VIRTUAL_PROMPT function:prompt
- remove-item function:_OLD_VIRTUAL_PROMPT
-}
+ if (Test-Path env:_OLD_VIRTUAL_PYTHONHOME) {
+ copy-item env:_OLD_VIRTUAL_PYTHONHOME env:PYTHONHOME
+ remove-item env:_OLD_VIRTUAL_PYTHONHOME
+ }
-if (Test-Path env:_OLD_VIRTUAL_PYTHONHOME) {
- copy-item env:_OLD_VIRTUAL_PYTHONHOME env:PYTHONHOME
- remove-item env:_OLD_VIRTUAL_PYTHONHOME
-}
+ if (Test-Path env:_OLD_VIRTUAL_PATH) {
+ copy-item env:_OLD_VIRTUAL_PATH env:PATH
+ remove-item env:_OLD_VIRTUAL_PATH
+ }
-if (Test-Path env:_OLD_VIRTUAL_PATH) {
- copy-item env:_OLD_VIRTUAL_PATH env:PATH
- remove-item env:_OLD_VIRTUAL_PATH
+ if (Test-Path env:VIRTUAL_ENV) {
+ remove-item env:VIRTUAL_ENV
+ }
+
+ if (!$NonDestructive) {
+ # Self destruct!
+ remove-item function:deactivate
+ }
}
+deactivate -nondestructive
+
+$env:VIRTUAL_ENV="__VENV_DIR__"
+
# Set the prompt to include the env name
+# Make sure _OLD_VIRTUAL_PROMPT is global
+function global:_OLD_VIRTUAL_PROMPT {""}
copy-item function:prompt function:_OLD_VIRTUAL_PROMPT
-function prompt {
- Write-Host -NoNewline -ForegroundColor Green '[__VENV_NAME__]'
+function global:prompt {
+ Write-Host -NoNewline -ForegroundColor Green '__VENV_PROMPT__'
_OLD_VIRTUAL_PROMPT
}
diff --git a/Lib/venv/scripts/nt/Deactivate.ps1 b/Lib/venv/scripts/nt/Deactivate.ps1
deleted file mode 100644
index 3d1e96bc8c..0000000000
--- a/Lib/venv/scripts/nt/Deactivate.ps1
+++ /dev/null
@@ -1,19 +0,0 @@
-# Revert to original values
-if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
- copy-item function:_OLD_VIRTUAL_PROMPT function:prompt
- remove-item function:_OLD_VIRTUAL_PROMPT
-}
-
-if (Test-Path env:_OLD_VIRTUAL_PYTHONHOME) {
- copy-item env:_OLD_VIRTUAL_PYTHONHOME env:PYTHONHOME
- remove-item env:_OLD_VIRTUAL_PYTHONHOME
-}
-
-if (Test-Path env:_OLD_VIRTUAL_PATH) {
- copy-item env:_OLD_VIRTUAL_PATH env:PATH
- remove-item env:_OLD_VIRTUAL_PATH
-}
-
-if (Test-Path env:VIRTUAL_ENV) {
- remove-item env:VIRTUAL_ENV
-}
diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat
index 3cebe26381..9eab147f31 100644
--- a/Lib/venv/scripts/nt/activate.bat
+++ b/Lib/venv/scripts/nt/activate.bat
@@ -14,7 +14,7 @@ if defined _OLD_VIRTUAL_PYTHONHOME (
)
set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
-set "PROMPT=__VENV_NAME__%PROMPT%"
+set "PROMPT=__VENV_PROMPT__%PROMPT%"
if defined PYTHONHOME (
set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
diff --git a/Lib/venv/scripts/posix/activate b/Lib/venv/scripts/posix/activate
index c241450c0b..7bbffd9ba6 100644
--- a/Lib/venv/scripts/posix/activate
+++ b/Lib/venv/scripts/posix/activate
@@ -54,8 +54,8 @@ fi
if [ -z "$VIRTUAL_ENV_DISABLE_PROMPT" ] ; then
_OLD_VIRTUAL_PS1="$PS1"
- if [ "x__VENV_NAME__" != x ] ; then
- PS1="__VENV_NAME__$PS1"
+ if [ "x__VENV_PROMPT__" != x ] ; then
+ PS1="__VENV_PROMPT__$PS1"
else
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
# special case for Aspen magic directories
diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh
new file mode 100644
index 0000000000..99d79e0138
--- /dev/null
+++ b/Lib/venv/scripts/posix/activate.csh
@@ -0,0 +1,37 @@
+# This file must be used with "source bin/activate.csh" *from csh*.
+# You cannot run it directly.
+# Created by Davide Di Blasi <davidedb@gmail.com>.
+# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
+
+alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
+
+# Unset irrelavent variables.
+deactivate nondestructive
+
+setenv VIRTUAL_ENV "__VENV_DIR__"
+
+set _OLD_VIRTUAL_PATH="$PATH"
+setenv PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
+
+
+set _OLD_VIRTUAL_PROMPT="$prompt"
+
+if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
+ if ("__VENV_NAME__" != "") then
+ set env_name = "__VENV_NAME__"
+ else
+ if (`basename "VIRTUAL_ENV"` == "__") then
+ # special case for Aspen magic directories
+ # see http://www.zetadev.com/software/aspen/
+ set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
+ else
+ set env_name = `basename "$VIRTUAL_ENV"`
+ endif
+ endif
+ set prompt = "[$env_name] $prompt"
+ unset env_name
+endif
+
+alias pydoc python -m pydoc
+
+rehash
diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish
new file mode 100644
index 0000000000..45391aa01c
--- /dev/null
+++ b/Lib/venv/scripts/posix/activate.fish
@@ -0,0 +1,74 @@
+# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
+# you cannot run it directly
+
+function deactivate -d "Exit virtualenv and return to normal shell environment"
+ # reset old environment variables
+ if test -n "$_OLD_VIRTUAL_PATH"
+ set -gx PATH $_OLD_VIRTUAL_PATH
+ set -e _OLD_VIRTUAL_PATH
+ end
+ if test -n "$_OLD_VIRTUAL_PYTHONHOME"
+ set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
+ set -e _OLD_VIRTUAL_PYTHONHOME
+ end
+
+ if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
+ functions -e fish_prompt
+ set -e _OLD_FISH_PROMPT_OVERRIDE
+ . ( begin
+ printf "function fish_prompt\n\t#"
+ functions _old_fish_prompt
+ end | psub )
+ functions -e _old_fish_prompt
+ end
+
+ set -e VIRTUAL_ENV
+ if test "$argv[1]" != "nondestructive"
+ # Self destruct!
+ functions -e deactivate
+ end
+end
+
+# unset irrelavent variables
+deactivate nondestructive
+
+set -gx VIRTUAL_ENV "__VENV_DIR__"
+
+set -gx _OLD_VIRTUAL_PATH $PATH
+set -gx PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__" $PATH
+
+# unset PYTHONHOME if set
+if set -q PYTHONHOME
+ set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
+ set -e PYTHONHOME
+end
+
+if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
+ # fish uses a function instead of an env var to generate the prompt.
+
+ # save the current fish_prompt function as the function _old_fish_prompt
+ . ( begin
+ printf "function _old_fish_prompt\n\t#"
+ functions fish_prompt
+ end | psub )
+
+ # with the original prompt function renamed, we can override with our own.
+ function fish_prompt
+ # Prompt override?
+ if test -n "__VENV_PROMPT__"
+ printf "%s%s%s" "__VENV_PROMPT__" (set_color normal) (_old_fish_prompt)
+ return
+ end
+ # ...Otherwise, prepend env
+ set -l _checkbase (basename "$VIRTUAL_ENV")
+ if test $_checkbase = "__"
+ # special case for Aspen magic directories
+ # see http://www.zetadev.com/software/aspen/
+ printf "%s[%s]%s %s" (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal) (_old_fish_prompt)
+ else
+ printf "%s(%s)%s%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal) (_old_fish_prompt)
+ end
+ end
+
+ set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
+end
diff --git a/Lib/warnings.py b/Lib/warnings.py
index edbbb5eda4..a427e3510e 100644
--- a/Lib/warnings.py
+++ b/Lib/warnings.py
@@ -1,9 +1,5 @@
"""Python part of the warnings subsystem."""
-# Note: function level imports should *not* be used
-# in this module as it may cause import lock deadlock.
-# See bug 683658.
-import linecache
import sys
__all__ = ["warn", "showwarning", "formatwarning", "filterwarnings",
@@ -16,11 +12,12 @@ def showwarning(message, category, filename, lineno, file=None, line=None):
file = sys.stderr
try:
file.write(formatwarning(message, category, filename, lineno, line))
- except IOError:
+ except OSError:
pass # the file (probably stderr) is invalid - this warning gets lost.
def formatwarning(message, category, filename, lineno, line=None):
"""Function to format a warning the standard way."""
+ import linecache
s = "%s:%s: %s: %s\n" % (filename, lineno, category.__name__, message)
line = linecache.getline(filename, lineno) if line is None else line
if line:
@@ -233,6 +230,7 @@ def warn_explicit(message, category, filename, lineno,
# Prime the linecache for formatting, in case the
# "file" is actually in a zipfile or something.
+ import linecache
linecache.getlines(filename, module_globals)
if action == "error":
diff --git a/Lib/wave.py b/Lib/wave.py
index 4a223451bf..b56395ead7 100644
--- a/Lib/wave.py
+++ b/Lib/wave.py
@@ -18,7 +18,7 @@ This returns an instance of a class with the following public methods:
getcomptype() -- returns compression type ('NONE' for linear samples)
getcompname() -- returns human-readable version of
compression type ('not compressed' linear samples)
- getparams() -- returns a tuple consisting of all of the
+ getparams() -- returns a namedtuple consisting of all of the
above in the above order
getmarkers() -- returns None (for compatibility with the
aifc module)
@@ -82,15 +82,14 @@ WAVE_FORMAT_PCM = 0x0001
_array_fmts = None, 'b', 'h', None, 'i'
+import audioop
import struct
import sys
from chunk import Chunk
+from collections import namedtuple
-def _byteswap3(data):
- ba = bytearray(data)
- ba[::3] = data[2::3]
- ba[2::3] = data[::3]
- return bytes(ba)
+_wave_params = namedtuple('_wave_params',
+ 'nchannels sampwidth framerate nframes comptype compname')
class Wave_read:
"""Variables used in this class:
@@ -169,6 +168,13 @@ class Wave_read:
def __del__(self):
self.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
#
# User visible methods.
#
@@ -207,9 +213,9 @@ class Wave_read:
return self._compname
def getparams(self):
- return self.getnchannels(), self.getsampwidth(), \
- self.getframerate(), self.getnframes(), \
- self.getcomptype(), self.getcompname()
+ return _wave_params(self.getnchannels(), self.getsampwidth(),
+ self.getframerate(), self.getnframes(),
+ self.getcomptype(), self.getcompname())
def getmarkers(self):
return None
@@ -232,29 +238,9 @@ class Wave_read:
self._data_seek_needed = 0
if nframes == 0:
return b''
- if self._sampwidth in (2, 4) and sys.byteorder == 'big':
- # unfortunately the fromfile() method does not take
- # something that only looks like a file object, so
- # we have to reach into the innards of the chunk object
- import array
- chunk = self._data_chunk
- data = array.array(_array_fmts[self._sampwidth])
- assert data.itemsize == self._sampwidth
- nitems = nframes * self._nchannels
- if nitems * self._sampwidth > chunk.chunksize - chunk.size_read:
- nitems = (chunk.chunksize - chunk.size_read) // self._sampwidth
- data.fromfile(chunk.file.file, nitems)
- # "tell" data chunk how much was read
- chunk.size_read = chunk.size_read + nitems * self._sampwidth
- # do the same for the outermost chunk
- chunk = chunk.file
- chunk.size_read = chunk.size_read + nitems * self._sampwidth
- data.byteswap()
- data = data.tobytes()
- else:
- data = self._data_chunk.read(nframes * self._framesize)
- if self._sampwidth == 3 and sys.byteorder == 'big':
- data = _byteswap3(data)
+ data = self._data_chunk.read(nframes * self._framesize)
+ if self._sampwidth != 1 and sys.byteorder == 'big':
+ data = audioop.byteswap(data, self._sampwidth)
if self._convert and data:
data = self._convert(data)
self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)
@@ -328,6 +314,12 @@ class Wave_write:
def __del__(self):
self.close()
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
#
# User visible methods.
#
@@ -402,8 +394,8 @@ class Wave_write:
def getparams(self):
if not self._nchannels or not self._sampwidth or not self._framerate:
raise Error('not all parameters set')
- return self._nchannels, self._sampwidth, self._framerate, \
- self._nframes, self._comptype, self._compname
+ return _wave_params(self._nchannels, self._sampwidth, self._framerate,
+ self._nframes, self._comptype, self._compname)
def setmark(self, id, pos, name):
raise Error('setmark() not supported')
@@ -418,24 +410,16 @@ class Wave_write:
return self._nframeswritten
def writeframesraw(self, data):
+ if not isinstance(data, (bytes, bytearray)):
+ data = memoryview(data).cast('B')
self._ensure_header_written(len(data))
nframes = len(data) // (self._sampwidth * self._nchannels)
if self._convert:
data = self._convert(data)
- if self._sampwidth in (2, 4) and sys.byteorder == 'big':
- import array
- a = array.array(_array_fmts[self._sampwidth])
- a.frombytes(data)
- data = a
- assert data.itemsize == self._sampwidth
- data.byteswap()
- data.tofile(self._file)
- self._datawritten = self._datawritten + len(data) * self._sampwidth
- else:
- if self._sampwidth == 3 and sys.byteorder == 'big':
- data = _byteswap3(data)
- self._file.write(data)
- self._datawritten = self._datawritten + len(data)
+ if self._sampwidth != 1 and sys.byteorder == 'big':
+ data = audioop.byteswap(data, self._sampwidth)
+ self._file.write(data)
+ self._datawritten += len(data)
self._nframeswritten = self._nframeswritten + nframes
def writeframes(self, data):
@@ -476,14 +460,18 @@ class Wave_write:
if not self._nframes:
self._nframes = initlength // (self._nchannels * self._sampwidth)
self._datalength = self._nframes * self._nchannels * self._sampwidth
- self._form_length_pos = self._file.tell()
+ try:
+ self._form_length_pos = self._file.tell()
+ except (AttributeError, OSError):
+ self._form_length_pos = None
self._file.write(struct.pack('<L4s4sLHHLLHH4s',
36 + self._datalength, b'WAVE', b'fmt ', 16,
WAVE_FORMAT_PCM, self._nchannels, self._framerate,
self._nchannels * self._framerate * self._sampwidth,
self._nchannels * self._sampwidth,
self._sampwidth * 8, b'data'))
- self._data_length_pos = self._file.tell()
+ if self._form_length_pos is not None:
+ self._data_length_pos = self._file.tell()
self._file.write(struct.pack('<L', self._datalength))
self._headerwritten = True
diff --git a/Lib/weakref.py b/Lib/weakref.py
index fcb6b74d1b..f6a40ca4bf 100644
--- a/Lib/weakref.py
+++ b/Lib/weakref.py
@@ -21,13 +21,69 @@ from _weakref import (
from _weakrefset import WeakSet, _IterationGuard
import collections # Import after _weakref to avoid circular import.
+import sys
+import itertools
ProxyTypes = (ProxyType, CallableProxyType)
__all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs",
"WeakKeyDictionary", "ReferenceType", "ProxyType",
"CallableProxyType", "ProxyTypes", "WeakValueDictionary",
- "WeakSet"]
+ "WeakSet", "WeakMethod", "finalize"]
+
+
+class WeakMethod(ref):
+ """
+ A custom `weakref.ref` subclass which simulates a weak reference to
+ a bound method, working around the lifetime problem of bound methods.
+ """
+
+ __slots__ = "_func_ref", "_meth_type", "_alive", "__weakref__"
+
+ def __new__(cls, meth, callback=None):
+ try:
+ obj = meth.__self__
+ func = meth.__func__
+ except AttributeError:
+ raise TypeError("argument should be a bound method, not {}"
+ .format(type(meth))) from None
+ def _cb(arg):
+ # The self-weakref trick is needed to avoid creating a reference
+ # cycle.
+ self = self_wr()
+ if self._alive:
+ self._alive = False
+ if callback is not None:
+ callback(self)
+ self = ref.__new__(cls, obj, _cb)
+ self._func_ref = ref(func, _cb)
+ self._meth_type = type(meth)
+ self._alive = True
+ self_wr = ref(self)
+ return self
+
+ def __call__(self):
+ obj = super().__call__()
+ func = self._func_ref()
+ if obj is None or func is None:
+ return None
+ return self._meth_type(func, obj)
+
+ def __eq__(self, other):
+ if isinstance(other, WeakMethod):
+ if not self._alive or not other._alive:
+ return self is other
+ return ref.__eq__(self, other) and self._func_ref == other._func_ref
+ return False
+
+ def __ne__(self, other):
+ if isinstance(other, WeakMethod):
+ if not self._alive or not other._alive:
+ return self is not other
+ return ref.__ne__(self, other) or self._func_ref != other._func_ref
+ return True
+
+ __hash__ = ref.__hash__
class WeakValueDictionary(collections.MutableMapping):
@@ -153,8 +209,7 @@ class WeakValueDictionary(collections.MutableMapping):
"""
with _IterationGuard(self):
- for wr in self.data.values():
- yield wr
+ yield from self.data.values()
def values(self):
with _IterationGuard(self):
@@ -383,3 +438,140 @@ class WeakKeyDictionary(collections.MutableMapping):
d[ref(key, self._remove)] = value
if len(kwargs):
self.update(kwargs)
+
+
+class finalize:
+ """Class for finalization of weakrefable objects
+
+ finalize(obj, func, *args, **kwargs) returns a callable finalizer
+ object which will be called when obj is garbage collected. The
+ first time the finalizer is called it evaluates func(*arg, **kwargs)
+ and returns the result. After this the finalizer is dead, and
+ calling it just returns None.
+
+ When the program exits any remaining finalizers for which the
+ atexit attribute is true will be run in reverse order of creation.
+ By default atexit is true.
+ """
+
+ # Finalizer objects don't have any state of their own. They are
+ # just used as keys to lookup _Info objects in the registry. This
+ # ensures that they cannot be part of a ref-cycle.
+
+ __slots__ = ()
+ _registry = {}
+ _shutdown = False
+ _index_iter = itertools.count()
+ _dirty = False
+ _registered_with_atexit = False
+
+ class _Info:
+ __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index")
+
+ def __init__(self, obj, func, *args, **kwargs):
+ if not self._registered_with_atexit:
+ # We may register the exit function more than once because
+ # of a thread race, but that is harmless
+ import atexit
+ atexit.register(self._exitfunc)
+ finalize._registered_with_atexit = True
+ info = self._Info()
+ info.weakref = ref(obj, self)
+ info.func = func
+ info.args = args
+ info.kwargs = kwargs or None
+ info.atexit = True
+ info.index = next(self._index_iter)
+ self._registry[self] = info
+ finalize._dirty = True
+
+ def __call__(self, _=None):
+ """If alive then mark as dead and return func(*args, **kwargs);
+ otherwise return None"""
+ info = self._registry.pop(self, None)
+ if info and not self._shutdown:
+ return info.func(*info.args, **(info.kwargs or {}))
+
+ def detach(self):
+ """If alive then mark as dead and return (obj, func, args, kwargs);
+ otherwise return None"""
+ info = self._registry.get(self)
+ obj = info and info.weakref()
+ if obj is not None and self._registry.pop(self, None):
+ return (obj, info.func, info.args, info.kwargs or {})
+
+ def peek(self):
+ """If alive then return (obj, func, args, kwargs);
+ otherwise return None"""
+ info = self._registry.get(self)
+ obj = info and info.weakref()
+ if obj is not None:
+ return (obj, info.func, info.args, info.kwargs or {})
+
+ @property
+ def alive(self):
+ """Whether finalizer is alive"""
+ return self in self._registry
+
+ @property
+ def atexit(self):
+ """Whether finalizer should be called at exit"""
+ info = self._registry.get(self)
+ return bool(info) and info.atexit
+
+ @atexit.setter
+ def atexit(self, value):
+ info = self._registry.get(self)
+ if info:
+ info.atexit = bool(value)
+
+ def __repr__(self):
+ info = self._registry.get(self)
+ obj = info and info.weakref()
+ if obj is None:
+ return '<%s object at %#x; dead>' % (type(self).__name__, id(self))
+ else:
+ return '<%s object at %#x; for %r at %#x>' % \
+ (type(self).__name__, id(self), type(obj).__name__, id(obj))
+
+ @classmethod
+ def _select_for_exit(cls):
+ # Return live finalizers marked for exit, oldest first
+ L = [(f,i) for (f,i) in cls._registry.items() if i.atexit]
+ L.sort(key=lambda item:item[1].index)
+ return [f for (f,i) in L]
+
+ @classmethod
+ def _exitfunc(cls):
+ # At shutdown invoke finalizers for which atexit is true.
+ # This is called once all other non-daemonic threads have been
+ # joined.
+ reenable_gc = False
+ try:
+ if cls._registry:
+ import gc
+ if gc.isenabled():
+ reenable_gc = True
+ gc.disable()
+ pending = None
+ while True:
+ if pending is None or finalize._dirty:
+ pending = cls._select_for_exit()
+ finalize._dirty = False
+ if not pending:
+ break
+ f = pending.pop()
+ try:
+ # gc is disabled, so (assuming no daemonic
+ # threads) the following is the only line in
+ # this function which might trigger creation
+ # of a new finalizer
+ f()
+ except Exception:
+ sys.excepthook(*sys.exc_info())
+ assert f not in cls._registry
+ finally:
+ # prevent any more finalizers from executing during shutdown
+ finalize._shutdown = True
+ if reenable_gc:
+ gc.enable()
diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
index 945eda4a95..2714bd144e 100644
--- a/Lib/webbrowser.py
+++ b/Lib/webbrowser.py
@@ -5,6 +5,7 @@
import io
import os
import shlex
+import shutil
import sys
import stat
import subprocess
@@ -83,7 +84,7 @@ def _synthesize(browser, update_tryorder=1):
"""
cmd = browser.split()[0]
- if not _iscommand(cmd):
+ if not shutil.which(cmd):
return [None, None]
name = os.path.basename(cmd)
try:
@@ -102,38 +103,6 @@ def _synthesize(browser, update_tryorder=1):
return [None, None]
-if sys.platform[:3] == "win":
- def _isexecutable(cmd):
- cmd = cmd.lower()
- if os.path.isfile(cmd) and cmd.endswith((".exe", ".bat")):
- return True
- for ext in ".exe", ".bat":
- if os.path.isfile(cmd + ext):
- return True
- return False
-else:
- def _isexecutable(cmd):
- if os.path.isfile(cmd):
- mode = os.stat(cmd)[stat.ST_MODE]
- if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH:
- return True
- return False
-
-def _iscommand(cmd):
- """Return True if cmd is executable or can be found on the executable
- search path."""
- if _isexecutable(cmd):
- return True
- path = os.environ.get("PATH")
- if not path:
- return False
- for d in path.split(os.pathsep):
- exe = os.path.join(d, cmd)
- if _isexecutable(exe):
- return True
- return False
-
-
# General parent classes
class BaseBrowser(object):
@@ -418,11 +387,11 @@ class Grail(BaseBrowser):
# need to PING each one until we find one that's live
try:
s.connect(fn)
- except socket.error:
+ except OSError:
# no good; attempt to clean it out, but don't fail:
try:
os.unlink(fn)
- except IOError:
+ except OSError:
pass
else:
return s
@@ -453,22 +422,22 @@ class Grail(BaseBrowser):
def register_X_browsers():
# use xdg-open if around
- if _iscommand("xdg-open"):
+ if shutil.which("xdg-open"):
register("xdg-open", None, BackgroundBrowser("xdg-open"))
# The default GNOME3 browser
- if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gvfs-open"):
+ if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
# The default GNOME browser
- if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gnome-open"):
+ if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
register("gnome-open", None, BackgroundBrowser("gnome-open"))
# The default KDE browser
- if "KDE_FULL_SESSION" in os.environ and _iscommand("kfmclient"):
+ if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
register("kfmclient", Konqueror, Konqueror("kfmclient"))
- if _iscommand("x-www-browser"):
+ if shutil.which("x-www-browser"):
register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
# The Mozilla/Netscape browsers
@@ -476,39 +445,39 @@ def register_X_browsers():
"mozilla-firebird", "firebird",
"iceweasel", "iceape",
"seamonkey", "mozilla", "netscape"):
- if _iscommand(browser):
+ if shutil.which(browser):
register(browser, None, Mozilla(browser))
# Konqueror/kfm, the KDE browser.
- if _iscommand("kfm"):
+ if shutil.which("kfm"):
register("kfm", Konqueror, Konqueror("kfm"))
- elif _iscommand("konqueror"):
+ elif shutil.which("konqueror"):
register("konqueror", Konqueror, Konqueror("konqueror"))
# Gnome's Galeon and Epiphany
for browser in ("galeon", "epiphany"):
- if _iscommand(browser):
+ if shutil.which(browser):
register(browser, None, Galeon(browser))
# Skipstone, another Gtk/Mozilla based browser
- if _iscommand("skipstone"):
+ if shutil.which("skipstone"):
register("skipstone", None, BackgroundBrowser("skipstone"))
# Google Chrome/Chromium browsers
for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
- if _iscommand(browser):
+ if shutil.which(browser):
register(browser, None, Chrome(browser))
# Opera, quite popular
- if _iscommand("opera"):
+ if shutil.which("opera"):
register("opera", None, Opera("opera"))
# Next, Mosaic -- old but still in use.
- if _iscommand("mosaic"):
+ if shutil.which("mosaic"):
register("mosaic", None, BackgroundBrowser("mosaic"))
# Grail, the Python browser. Does anybody still use it?
- if _iscommand("grail"):
+ if shutil.which("grail"):
register("grail", Grail, None)
# Prefer X browsers if present
@@ -517,18 +486,18 @@ if os.environ.get("DISPLAY"):
# Also try console browsers
if os.environ.get("TERM"):
- if _iscommand("www-browser"):
+ if shutil.which("www-browser"):
register("www-browser", None, GenericBrowser("www-browser"))
# The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
- if _iscommand("links"):
+ if shutil.which("links"):
register("links", None, GenericBrowser("links"))
- if _iscommand("elinks"):
+ if shutil.which("elinks"):
register("elinks", None, Elinks("elinks"))
# The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
- if _iscommand("lynx"):
+ if shutil.which("lynx"):
register("lynx", None, GenericBrowser("lynx"))
# The w3m browser <http://w3m.sourceforge.net/>
- if _iscommand("w3m"):
+ if shutil.which("w3m"):
register("w3m", None, GenericBrowser("w3m"))
#
@@ -540,7 +509,7 @@ if sys.platform[:3] == "win":
def open(self, url, new=0, autoraise=True):
try:
os.startfile(url)
- except WindowsError:
+ except OSError:
# [Error 22] No application is associated with the specified
# file for this operation: '<URL>'
return False
@@ -558,7 +527,7 @@ if sys.platform[:3] == "win":
"Internet Explorer\\IEXPLORE.EXE")
for browser in ("firefox", "firebird", "seamonkey", "mozilla",
"netscape", "opera", iexplore):
- if _iscommand(browser):
+ if shutil.which(browser):
register(browser, None, BackgroundBrowser(browser))
#
@@ -644,17 +613,6 @@ if sys.platform == 'darwin':
register("MacOSX", None, MacOSXOSAScript('default'), -1)
-#
-# Platform support for OS/2
-#
-
-if sys.platform[:3] == "os2" and _iscommand("netscape"):
- _tryorder = []
- _browsers = {}
- register("os2netscape", None,
- GenericBrowser(["start", "netscape", "%s"]), -1)
-
-
# OK, now that we know what the default preference orders for each
# platform are, allow user to override them with the BROWSER variable.
if "BROWSER" in os.environ:
diff --git a/Lib/xml/dom/expatbuilder.py b/Lib/xml/dom/expatbuilder.py
index f074ab932f..81e2df70d7 100644
--- a/Lib/xml/dom/expatbuilder.py
+++ b/Lib/xml/dom/expatbuilder.py
@@ -905,11 +905,8 @@ def parse(file, namespaces=True):
builder = ExpatBuilder()
if isinstance(file, str):
- fp = open(file, 'rb')
- try:
+ with open(file, 'rb') as fp:
result = builder.parseFile(fp)
- finally:
- fp.close()
else:
result = builder.parseFile(file)
return result
@@ -939,11 +936,8 @@ def parseFragment(file, context, namespaces=True):
builder = FragmentBuilder(context)
if isinstance(file, str):
- fp = open(file, 'rb')
- try:
+ with open(file, 'rb') as fp:
result = builder.parseFile(fp)
- finally:
- fp.close()
else:
result = builder.parseFile(file)
return result
diff --git a/Lib/xml/etree/ElementInclude.py b/Lib/xml/etree/ElementInclude.py
index 6cc1b44e95..73e491e5d9 100644
--- a/Lib/xml/etree/ElementInclude.py
+++ b/Lib/xml/etree/ElementInclude.py
@@ -71,8 +71,8 @@ class FatalIncludeError(SyntaxError):
# @return The expanded resource. If the parse mode is "xml", this
# is an ElementTree instance. If the parse mode is "text", this
# is a Unicode string. If the loader fails, it can return None
-# or raise an IOError exception.
-# @throws IOError If the loader fails to load the resource.
+# or raise an OSError exception.
+# @throws OSError If the loader fails to load the resource.
def default_loader(href, parse, encoding=None):
if parse == "xml":
@@ -95,7 +95,7 @@ def default_loader(href, parse, encoding=None):
# that implements the same interface as <b>default_loader</b>.
# @throws FatalIncludeError If the function fails to include a given
# resource, or if the tree contains malformed XInclude elements.
-# @throws IOError If the function fails to load a given resource.
+# @throws OSError If the function fails to load a given resource.
def include(elem, loader=None):
if loader is None:
diff --git a/Lib/xml/etree/ElementPath.py b/Lib/xml/etree/ElementPath.py
index e7015c7c61..d914ddb672 100644
--- a/Lib/xml/etree/ElementPath.py
+++ b/Lib/xml/etree/ElementPath.py
@@ -105,14 +105,12 @@ def prepare_child(next, token):
def prepare_star(next, token):
def select(context, result):
for elem in result:
- for e in elem:
- yield e
+ yield from elem
return select
def prepare_self(next, token):
def select(context, result):
- for elem in result:
- yield elem
+ yield from result
return select
def prepare_descendant(next, token):
@@ -176,7 +174,7 @@ def prepare_predicate(next, token):
if elem.get(key) == value:
yield elem
return select
- if signature == "-" and not re.match("\d+$", predicate[0]):
+ if signature == "-" and not re.match("\-?\d+$", predicate[0]):
# [tag]
tag = predicate[0]
def select(context, result):
@@ -184,7 +182,7 @@ def prepare_predicate(next, token):
if elem.find(tag) is not None:
yield elem
return select
- if signature == "-='" and not re.match("\d+$", predicate[0]):
+ if signature == "-='" and not re.match("\-?\d+$", predicate[0]):
# [tag='value']
tag = predicate[0]
value = predicate[-1]
@@ -198,7 +196,10 @@ def prepare_predicate(next, token):
if signature == "-" or signature == "-()" or signature == "-()-":
# [index] or [last()] or [last()-index]
if signature == "-":
+ # [index]
index = int(predicate[0]) - 1
+ if index < 0:
+ raise SyntaxError("XPath position >= 1 expected")
else:
if predicate[0] != "last":
raise SyntaxError("unsupported function")
@@ -207,6 +208,8 @@ def prepare_predicate(next, token):
index = int(predicate[2]) - 1
except ValueError:
raise SyntaxError("unsupported expression")
+ if index > -2:
+ raise SyntaxError("XPath offset from last() must be negative")
else:
index = -1
def select(context, result):
diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py
index a8e57295c6..cab415cb3b 100644
--- a/Lib/xml/etree/ElementTree.py
+++ b/Lib/xml/etree/ElementTree.py
@@ -1,28 +1,47 @@
+"""Lightweight XML support for Python.
+
+ XML is an inherently hierarchical data format, and the most natural way to
+ represent it is with a tree. This module has two classes for this purpose:
+
+ 1. ElementTree represents the whole XML document as a tree and
+
+ 2. Element represents a single node in this tree.
+
+ Interactions with the whole document (reading and writing to/from files) are
+ usually done on the ElementTree level. Interactions with a single XML element
+ and its sub-elements are done on the Element level.
+
+ Element is a flexible container object designed to store hierarchical data
+ structures in memory. It can be described as a cross between a list and a
+ dictionary. Each Element has a number of properties associated with it:
+
+ 'tag' - a string containing the element's name.
+
+ 'attributes' - a Python dictionary storing the element's attributes.
+
+ 'text' - a string containing the element's text content.
+
+ 'tail' - an optional string containing text after the element's end tag.
+
+ And a number of child elements stored in a Python sequence.
+
+ To create an element instance, use the Element constructor,
+ or the SubElement factory function.
+
+ You can also use the ElementTree class to wrap an element structure
+ and convert it to and from XML.
+
+"""
+
+#---------------------------------------------------------------------
+# Licensed to PSF under a Contributor Agreement.
+# See http://www.python.org/psf/license for licensing details.
#
# ElementTree
-# $Id: ElementTree.py 3440 2008-07-18 14:45:01Z fredrik $
-#
-# light-weight XML support for Python 2.3 and later.
-#
-# history (since 1.2.6):
-# 2005-11-12 fl added tostringlist/fromstringlist helpers
-# 2006-07-05 fl merged in selected changes from the 1.3 sandbox
-# 2006-07-05 fl removed support for 2.1 and earlier
-# 2007-06-21 fl added deprecation/future warnings
-# 2007-08-25 fl added doctype hook, added parser version attribute etc
-# 2007-08-26 fl added new serializer code (better namespace handling, etc)
-# 2007-08-27 fl warn for broken /tag searches on tree level
-# 2007-09-02 fl added html/text methods to serializer (experimental)
-# 2007-09-05 fl added method argument to tostring/tostringlist
-# 2007-09-06 fl improved error handling
-# 2007-09-13 fl added itertext, iterfind; assorted cleanups
-# 2007-12-15 fl added C14N hooks, copy method (experimental)
-#
# Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved.
#
# fredrik@pythonware.com
# http://www.pythonware.com
-#
# --------------------------------------------------------------------
# The ElementTree toolkit is
#
@@ -51,9 +70,6 @@
# OF THIS SOFTWARE.
# --------------------------------------------------------------------
-# Licensed to PSF under a Contributor Agreement.
-# See http://www.python.org/psf/license for licensing details.
-
__all__ = [
# public symbols
"Comment",
@@ -69,34 +85,12 @@ __all__ = [
"TreeBuilder",
"VERSION",
"XML", "XMLID",
- "XMLParser", "XMLTreeBuilder",
+ "XMLParser",
"register_namespace",
]
VERSION = "1.3.0"
-##
-# The <b>Element</b> type is a flexible container object, designed to
-# store hierarchical data structures in memory. The type can be
-# described as a cross between a list and a dictionary.
-# <p>
-# Each element has a number of properties associated with it:
-# <ul>
-# <li>a <i>tag</i>. This is a string identifying what kind of data
-# this element represents (the element type, in other words).</li>
-# <li>a number of <i>attributes</i>, stored in a Python dictionary.</li>
-# <li>a <i>text</i> string.</li>
-# <li>an optional <i>tail</i> string.</li>
-# <li>a number of <i>child elements</i>, stored in a Python sequence</li>
-# </ul>
-#
-# To create an element instance, use the {@link #Element} constructor
-# or the {@link #SubElement} factory function.
-# <p>
-# The {@link #ElementTree} class can be used to wrap an element
-# structure, and convert it from and to XML.
-##
-
import sys
import re
import warnings
@@ -106,81 +100,68 @@ import contextlib
from . import ElementPath
-##
-# Parser error. This is a subclass of <b>SyntaxError</b>.
-# <p>
-# In addition to the exception value, an exception instance contains a
-# specific exception code in the <b>code</b> attribute, and the line and
-# column of the error in the <b>position</b> attribute.
-
class ParseError(SyntaxError):
+ """An error when parsing an XML document.
+
+ In addition to its exception value, a ParseError contains
+ two extra attributes:
+ 'code' - the specific exception code
+ 'position' - the line and column of the error
+
+ """
pass
# --------------------------------------------------------------------
-##
-# Checks if an object appears to be a valid element object.
-#
-# @param An element instance.
-# @return A true value if this is an element object.
-# @defreturn flag
def iselement(element):
- # FIXME: not sure about this;
- # isinstance(element, Element) or look for tag/attrib/text attributes
+ """Return True if *element* appears to be an Element."""
return hasattr(element, 'tag')
-##
-# Element class. This class defines the Element interface, and
-# provides a reference implementation of this interface.
-# <p>
-# The element name, attribute names, and attribute values can be
-# either ASCII strings (ordinary Python strings containing only 7-bit
-# ASCII characters) or Unicode strings.
-#
-# @param tag The element name.
-# @param attrib An optional dictionary, containing element attributes.
-# @param **extra Additional attributes, given as keyword arguments.
-# @see Element
-# @see SubElement
-# @see Comment
-# @see ProcessingInstruction
class Element:
- # <tag attrib>text<child/>...</tag>tail
+ """An XML element.
- ##
- # (Attribute) Element tag.
+ This class is the reference implementation of the Element interface.
- tag = None
+ An element's length is its number of subelements. That means if you
+ you want to check if an element is truly empty, you should check BOTH
+ its length AND its text attribute.
- ##
- # (Attribute) Element attribute dictionary. Where possible, use
- # {@link #Element.get},
- # {@link #Element.set},
- # {@link #Element.keys}, and
- # {@link #Element.items} to access
- # element attributes.
+ The element tag, attribute names, and attribute values can be either
+ bytes or strings.
- attrib = None
+ *tag* is the element name. *attrib* is an optional dictionary containing
+ element attributes. *extra* are additional element attributes given as
+ keyword arguments.
- ##
- # (Attribute) Text before first subelement. This is either a
- # string or the value None. Note that if there was no text, this
- # attribute may be either None or an empty string, depending on
- # the parser.
+ Example form:
+ <tag attrib>text<child/>...</tag>tail
+
+ """
+
+ tag = None
+ """The element's name."""
+
+ attrib = None
+ """Dictionary of the element's attributes."""
text = None
+ """
+ Text before first subelement. This is either a string or the value None.
+ Note that if there is no text, this attribute may be either
+ None or the empty string, depending on the parser.
- ##
- # (Attribute) Text after this element's end tag, but before the
- # next sibling element's start tag. This is either a string or
- # the value None. Note that if there was no text, this attribute
- # may be either None or an empty string, depending on the parser.
+ """
- tail = None # text after end tag, if any
+ tail = None
+ """
+ Text after this element's end tag, but before the next sibling element's
+ start tag. This is either a string or the value None. Note that if there
+ was no text, this attribute may be either None or an empty string,
+ depending on the parser.
- # constructor
+ """
def __init__(self, tag, attrib={}, **extra):
if not isinstance(attrib, dict):
@@ -195,36 +176,30 @@ class Element:
def __repr__(self):
return "<Element %s at 0x%x>" % (repr(self.tag), id(self))
- ##
- # Creates a new element object of the same type as this element.
- #
- # @param tag Element tag.
- # @param attrib Element attributes, given as a dictionary.
- # @return A new element instance.
-
def makeelement(self, tag, attrib):
- return self.__class__(tag, attrib)
+ """Create a new element with the same type.
+
+ *tag* is a string containing the element name.
+ *attrib* is a dictionary containing the element attributes.
+
+ Do not call this method, use the SubElement factory function instead.
- ##
- # (Experimental) Copies the current element. This creates a
- # shallow copy; subelements will be shared with the original tree.
- #
- # @return A new element instance.
+ """
+ return self.__class__(tag, attrib)
def copy(self):
+ """Return copy of current element.
+
+ This creates a shallow copy. Subelements will be shared with the
+ original tree.
+
+ """
elem = self.makeelement(self.tag, self.attrib)
elem.text = self.text
elem.tail = self.tail
elem[:] = self
return elem
- ##
- # Returns the number of subelements. Note that this only counts
- # full elements; to check if there's any content in an element, you
- # have to check both the length and the <b>text</b> attribute.
- #
- # @return The number of subelements.
-
def __len__(self):
return len(self._children)
@@ -236,23 +211,9 @@ class Element:
)
return len(self._children) != 0 # emulate old behaviour, for now
- ##
- # Returns the given subelement, by index.
- #
- # @param index What subelement to return.
- # @return The given subelement.
- # @exception IndexError If the given element does not exist.
-
def __getitem__(self, index):
return self._children[index]
- ##
- # Replaces the given subelement, by index.
- #
- # @param index What subelement to replace.
- # @param element The new element value.
- # @exception IndexError If the given element does not exist.
-
def __setitem__(self, index, element):
# if isinstance(index, slice):
# for elt in element:
@@ -261,76 +222,62 @@ class Element:
# assert iselement(element)
self._children[index] = element
- ##
- # Deletes the given subelement, by index.
- #
- # @param index What subelement to delete.
- # @exception IndexError If the given element does not exist.
-
def __delitem__(self, index):
del self._children[index]
- ##
- # Adds a subelement to the end of this element. In document order,
- # the new element will appear after the last existing subelement (or
- # directly after the text, if it's the first subelement), but before
- # the end tag for this element.
- #
- # @param element The element to add.
+ def append(self, subelement):
+ """Add *subelement* to the end of this element.
- def append(self, element):
- self._assert_is_element(element)
- self._children.append(element)
+ The new element will appear in document order after the last existing
+ subelement (or directly after the text, if it's the first subelement),
+ but before the end tag for this element.
- ##
- # Appends subelements from a sequence.
- #
- # @param elements A sequence object with zero or more elements.
- # @since 1.3
+ """
+ self._assert_is_element(subelement)
+ self._children.append(subelement)
def extend(self, elements):
+ """Append subelements from a sequence.
+
+ *elements* is a sequence with zero or more elements.
+
+ """
for element in elements:
self._assert_is_element(element)
self._children.extend(elements)
- ##
- # Inserts a subelement at the given position in this element.
- #
- # @param index Where to insert the new subelement.
-
- def insert(self, index, element):
- self._assert_is_element(element)
- self._children.insert(index, element)
+ def insert(self, index, subelement):
+ """Insert *subelement* at position *index*."""
+ self._assert_is_element(subelement)
+ self._children.insert(index, subelement)
def _assert_is_element(self, e):
# Need to refer to the actual Python implementation, not the
# shadowing C implementation.
- if not isinstance(e, _Element):
+ if not isinstance(e, _Element_Py):
raise TypeError('expected an Element, not %s' % type(e).__name__)
- ##
- # Removes a matching subelement. Unlike the <b>find</b> methods,
- # this method compares elements based on identity, not on tag
- # value or contents. To remove subelements by other means, the
- # easiest way is often to use a list comprehension to select what
- # elements to keep, and use slice assignment to update the parent
- # element.
- #
- # @param element What element to remove.
- # @exception ValueError If a matching element could not be found.
-
- def remove(self, element):
- # assert iselement(element)
- self._children.remove(element)
+ def remove(self, subelement):
+ """Remove matching subelement.
+
+ Unlike the find methods, this method compares elements based on
+ identity, NOT ON tag value or contents. To remove subelements by
+ other means, the easiest way is to use a list comprehension to
+ select what elements to keep, and then use slice assignment to update
+ the parent element.
+
+ ValueError is raised if a matching element could not be found.
- ##
- # (Deprecated) Returns all subelements. The elements are returned
- # in document order.
- #
- # @return A list of subelements.
- # @defreturn list of Element instances
+ """
+ # assert iselement(element)
+ self._children.remove(subelement)
def getchildren(self):
+ """(Deprecated) Return all subelements.
+
+ Elements are returned in document order.
+
+ """
warnings.warn(
"This method will be removed in future versions. "
"Use 'list(elem)' or iteration over elem instead.",
@@ -338,131 +285,128 @@ class Element:
)
return self._children
- ##
- # Finds the first matching subelement, by tag name or path.
- #
- # @param path What element to look for.
- # @keyparam namespaces Optional namespace prefix map.
- # @return The first matching element, or None if no element was found.
- # @defreturn Element or None
-
def find(self, path, namespaces=None):
- return ElementPath.find(self, path, namespaces)
+ """Find first matching element by tag name or path.
+
+ *path* is a string having either an element tag or an XPath,
+ *namespaces* is an optional mapping from namespace prefix to full name.
- ##
- # Finds text for the first matching subelement, by tag name or path.
- #
- # @param path What element to look for.
- # @param default What to return if the element was not found.
- # @keyparam namespaces Optional namespace prefix map.
- # @return The text content of the first matching element, or the
- # default value no element was found. Note that if the element
- # is found, but has no text content, this method returns an
- # empty string.
- # @defreturn string
+ Return the first matching element, or None if no element was found.
+
+ """
+ return ElementPath.find(self, path, namespaces)
def findtext(self, path, default=None, namespaces=None):
- return ElementPath.findtext(self, path, default, namespaces)
+ """Find text for first matching element by tag name or path.
+
+ *path* is a string having either an element tag or an XPath,
+ *default* is the value to return if the element was not found,
+ *namespaces* is an optional mapping from namespace prefix to full name.
- ##
- # Finds all matching subelements, by tag name or path.
- #
- # @param path What element to look for.
- # @keyparam namespaces Optional namespace prefix map.
- # @return A list or other sequence containing all matching elements,
- # in document order.
- # @defreturn list of Element instances
+ Return text content of first matching element, or default value if
+ none was found. Note that if an element is found having no text
+ content, the empty string is returned.
+
+ """
+ return ElementPath.findtext(self, path, default, namespaces)
def findall(self, path, namespaces=None):
- return ElementPath.findall(self, path, namespaces)
+ """Find all matching subelements by tag name or path.
+
+ *path* is a string having either an element tag or an XPath,
+ *namespaces* is an optional mapping from namespace prefix to full name.
- ##
- # Finds all matching subelements, by tag name or path.
- #
- # @param path What element to look for.
- # @keyparam namespaces Optional namespace prefix map.
- # @return An iterator or sequence containing all matching elements,
- # in document order.
- # @defreturn a generated sequence of Element instances
+ Returns list containing all matching elements in document order.
+
+ """
+ return ElementPath.findall(self, path, namespaces)
def iterfind(self, path, namespaces=None):
- return ElementPath.iterfind(self, path, namespaces)
+ """Find all matching subelements by tag name or path.
+
+ *path* is a string having either an element tag or an XPath,
+ *namespaces* is an optional mapping from namespace prefix to full name.
- ##
- # Resets an element. This function removes all subelements, clears
- # all attributes, and sets the <b>text</b> and <b>tail</b> attributes
- # to None.
+ Return an iterable yielding all matching elements in document order.
+
+ """
+ return ElementPath.iterfind(self, path, namespaces)
def clear(self):
+ """Reset element.
+
+ This function removes all subelements, clears all attributes, and sets
+ the text and tail attributes to None.
+
+ """
self.attrib.clear()
self._children = []
self.text = self.tail = None
- ##
- # Gets an element attribute. Equivalent to <b>attrib.get</b>, but
- # some implementations may handle this a bit more efficiently.
- #
- # @param key What attribute to look for.
- # @param default What to return if the attribute was not found.
- # @return The attribute value, or the default value, if the
- # attribute was not found.
- # @defreturn string or None
-
def get(self, key, default=None):
- return self.attrib.get(key, default)
+ """Get element attribute.
+
+ Equivalent to attrib.get, but some implementations may handle this a
+ bit more efficiently. *key* is what attribute to look for, and
+ *default* is what to return if the attribute was not found.
- ##
- # Sets an element attribute. Equivalent to <b>attrib[key] = value</b>,
- # but some implementations may handle this a bit more efficiently.
- #
- # @param key What attribute to set.
- # @param value The attribute value.
+ Returns a string containing the attribute value, or the default if
+ attribute was not found.
+
+ """
+ return self.attrib.get(key, default)
def set(self, key, value):
- self.attrib[key] = value
+ """Set element attribute.
+
+ Equivalent to attrib[key] = value, but some implementations may handle
+ this a bit more efficiently. *key* is what attribute to set, and
+ *value* is the attribute value to set it to.
- ##
- # Gets a list of attribute names. The names are returned in an
- # arbitrary order (just like for an ordinary Python dictionary).
- # Equivalent to <b>attrib.keys()</b>.
- #
- # @return A list of element attribute names.
- # @defreturn list of strings
+ """
+ self.attrib[key] = value
def keys(self):
- return self.attrib.keys()
+ """Get list of attribute names.
- ##
- # Gets element attributes, as a sequence. The attributes are
- # returned in an arbitrary order. Equivalent to <b>attrib.items()</b>.
- #
- # @return A list of (name, value) tuples for all attributes.
- # @defreturn list of (string, string) tuples
+ Names are returned in an arbitrary order, just like an ordinary
+ Python dict. Equivalent to attrib.keys()
+
+ """
+ return self.attrib.keys()
def items(self):
- return self.attrib.items()
+ """Get element attributes as a sequence.
+
+ The attributes are returned in arbitrary order. Equivalent to
+ attrib.items().
- ##
- # Creates a tree iterator. The iterator loops over this element
- # and all subelements, in document order, and returns all elements
- # with a matching tag.
- # <p>
- # If the tree structure is modified during iteration, new or removed
- # elements may or may not be included. To get a stable set, use the
- # list() function on the iterator, and loop over the resulting list.
- #
- # @param tag What tags to look for (default is to return all elements).
- # @return An iterator containing all the matching elements.
- # @defreturn iterator
+ Return a list of (name, value) tuples.
+
+ """
+ return self.attrib.items()
def iter(self, tag=None):
+ """Create tree iterator.
+
+ The iterator loops over the element and all subelements in document
+ order, returning all elements with a matching tag.
+
+ If the tree structure is modified during iteration, new or removed
+ elements may or may not be included. To get a stable set, use the
+ list() function on the iterator, and loop over the resulting list.
+
+ *tag* is what tags to look for (default is to return all elements)
+
+ Return an iterator containing all the matching elements.
+
+ """
if tag == "*":
tag = None
if tag is None or self.tag == tag:
yield self
for e in self._children:
- for e in e.iter(tag):
- yield e
+ yield from e.iter(tag)
# compatibility
def getiterator(self, tag=None):
@@ -474,78 +418,67 @@ class Element:
)
return list(self.iter(tag))
- ##
- # Creates a text iterator. The iterator loops over this element
- # and all subelements, in document order, and returns all inner
- # text.
- #
- # @return An iterator containing all inner text.
- # @defreturn iterator
-
def itertext(self):
+ """Create text iterator.
+
+ The iterator loops over the element and all subelements in document
+ order, returning all inner text.
+
+ """
tag = self.tag
if not isinstance(tag, str) and tag is not None:
return
if self.text:
yield self.text
for e in self:
- for s in e.itertext():
- yield s
+ yield from e.itertext()
if e.tail:
yield e.tail
-# compatibility
-_Element = _ElementInterface = Element
-
-##
-# Subelement factory. This function creates an element instance, and
-# appends it to an existing element.
-# <p>
-# The element name, attribute names, and attribute values can be
-# either 8-bit ASCII strings or Unicode strings.
-#
-# @param parent The parent element.
-# @param tag The subelement name.
-# @param attrib An optional dictionary, containing element attributes.
-# @param **extra Additional attributes, given as keyword arguments.
-# @return An element instance.
-# @defreturn Element
def SubElement(parent, tag, attrib={}, **extra):
+ """Subelement factory which creates an element instance, and appends it
+ to an existing parent.
+
+ The element tag, attribute names, and attribute values can be either
+ bytes or Unicode strings.
+
+ *parent* is the parent element, *tag* is the subelements name, *attrib* is
+ an optional directory containing element attributes, *extra* are
+ additional attributes given as keyword arguments.
+
+ """
attrib = attrib.copy()
attrib.update(extra)
element = parent.makeelement(tag, attrib)
parent.append(element)
return element
-##
-# Comment element factory. This factory function creates a special
-# element that will be serialized as an XML comment by the standard
-# serializer.
-# <p>
-# The comment string can be either an 8-bit ASCII string or a Unicode
-# string.
-#
-# @param text A string containing the comment string.
-# @return An element instance, representing a comment.
-# @defreturn Element
def Comment(text=None):
+ """Comment element factory.
+
+ This function creates a special element which the standard serializer
+ serializes as an XML comment.
+
+ *text* is a string containing the comment string.
+
+ """
element = Element(Comment)
element.text = text
return element
-##
-# PI element factory. This factory function creates a special element
-# that will be serialized as an XML processing instruction by the standard
-# serializer.
-#
-# @param target A string containing the PI target.
-# @param text A string containing the PI contents, if any.
-# @return An element instance, representing a PI.
-# @defreturn Element
def ProcessingInstruction(target, text=None):
+ """Processing Instruction element factory.
+
+ This function creates a special element which the standard serializer
+ serializes as an XML comment.
+
+ *target* is a string containing the processing instruction, *text* is a
+ string containing the processing instruction contents, if any.
+
+ """
element = Element(ProcessingInstruction)
element.text = target
if text:
@@ -554,17 +487,21 @@ def ProcessingInstruction(target, text=None):
PI = ProcessingInstruction
-##
-# QName wrapper. This can be used to wrap a QName attribute value, in
-# order to get proper namespace handling on output.
-#
-# @param text A string containing the QName value, in the form {uri}local,
-# or, if the tag argument is given, the URI part of a QName.
-# @param tag Optional tag. If given, the first argument is interpreted as
-# an URI, and this argument is interpreted as a local name.
-# @return An opaque object, representing the QName.
class QName:
+ """Qualified name wrapper.
+
+ This class can be used to wrap a QName attribute value in order to get
+ proper namespace handing on output.
+
+ *text_or_uri* is a string containing the QName value either in the form
+ {uri}local, or if the tag argument is given, the URI part of a QName.
+
+ *tag* is an optional argument which if given, will make the first
+ argument (text_or_uri) be interpreted as a URI, and this argument (tag)
+ be interpreted as a local name.
+
+ """
def __init__(self, text_or_uri, tag=None):
if tag:
text_or_uri = "{%s}%s" % (text_or_uri, tag)
@@ -602,63 +539,65 @@ class QName:
# --------------------------------------------------------------------
-##
-# ElementTree wrapper class. This class represents an entire element
-# hierarchy, and adds some extra support for serialization to and from
-# standard XML.
-#
-# @param element Optional root element.
-# @keyparam file Optional file handle or file name. If given, the
-# tree is initialized with the contents of this XML file.
class ElementTree:
+ """An XML element hierarchy.
+ This class also provides support for serialization to and from
+ standard XML.
+
+ *element* is an optional root element node,
+ *file* is an optional file handle or file name of an XML file whose
+ contents will be used to initialize the tree with.
+
+ """
def __init__(self, element=None, file=None):
# assert element is None or iselement(element)
self._root = element # first node
if file:
self.parse(file)
- ##
- # Gets the root element for this tree.
- #
- # @return An element instance.
- # @defreturn Element
-
def getroot(self):
+ """Return root element of this tree."""
return self._root
- ##
- # Replaces the root element for this tree. This discards the
- # current contents of the tree, and replaces it with the given
- # element. Use with care.
- #
- # @param element An element instance.
-
def _setroot(self, element):
+ """Replace root element of this tree.
+
+ This will discard the current contents of the tree and replace it
+ with the given element. Use with care!
+
+ """
# assert iselement(element)
self._root = element
- ##
- # Loads an external XML document into this element tree.
- #
- # @param source A file name or file object. If a file object is
- # given, it only has to implement a <b>read(n)</b> method.
- # @keyparam parser An optional parser instance. If not given, the
- # standard {@link XMLParser} parser is used.
- # @return The document root element.
- # @defreturn Element
- # @exception ParseError If the parser fails to parse the document.
-
def parse(self, source, parser=None):
+ """Load external XML document into element tree.
+
+ *source* is a file name or file object, *parser* is an optional parser
+ instance that defaults to XMLParser.
+
+ ParseError is raised if the parser fails to parse the document.
+
+ Returns the root element of the given source document.
+
+ """
close_source = False
if not hasattr(source, "read"):
source = open(source, "rb")
close_source = True
try:
- if not parser:
- parser = XMLParser(target=TreeBuilder())
- while 1:
+ if parser is None:
+ # If no parser was specified, create a default XMLParser
+ parser = XMLParser()
+ if hasattr(parser, '_parse_whole'):
+ # The default XMLParser, when it comes from an accelerator,
+ # can define an internal _parse_whole API for efficiency.
+ # It can be used to parse the whole source without feeding
+ # it with chunks.
+ self._root = parser._parse_whole(source)
+ return self._root
+ while True:
data = source.read(65536)
if not data:
break
@@ -669,15 +608,15 @@ class ElementTree:
if close_source:
source.close()
- ##
- # Creates a tree iterator for the root element. The iterator loops
- # over all elements in this tree, in document order.
- #
- # @param tag What tags to look for (default is to return all elements)
- # @return An iterator.
- # @defreturn iterator
-
def iter(self, tag=None):
+ """Create and return tree iterator for the root element.
+
+ The iterator loops over all elements in this tree, in document order.
+
+ *tag* is a string with the tag name to iterate over
+ (default is to return all elements).
+
+ """
# assert self._root is not None
return self._root.iter(tag)
@@ -691,15 +630,17 @@ class ElementTree:
)
return list(self.iter(tag))
- ##
- # Same as getroot().find(path), starting at the root of the tree.
- #
- # @param path What element to look for.
- # @keyparam namespaces Optional namespace prefix map.
- # @return The first matching element, or None if no element was found.
- # @defreturn Element or None
-
def find(self, path, namespaces=None):
+ """Find first matching element by tag name or path.
+
+ Same as getroot().find(path), which is Element.find()
+
+ *path* is a string having either an element tag or an XPath,
+ *namespaces* is an optional mapping from namespace prefix to full name.
+
+ Return the first matching element, or None if no element was found.
+
+ """
# assert self._root is not None
if path[:1] == "/":
path = "." + path
@@ -711,19 +652,17 @@ class ElementTree:
)
return self._root.find(path, namespaces)
- ##
- # Same as getroot().findtext(path), starting at the root of the tree.
- #
- # @param path What element to look for.
- # @param default What to return if the element was not found.
- # @keyparam namespaces Optional namespace prefix map.
- # @return The text content of the first matching element, or the
- # default value no element was found. Note that if the element
- # is found, but has no text content, this method returns an
- # empty string.
- # @defreturn string
-
def findtext(self, path, default=None, namespaces=None):
+ """Find first matching element by tag name or path.
+
+ Same as getroot().findtext(path), which is Element.findtext()
+
+ *path* is a string having either an element tag or an XPath,
+ *namespaces* is an optional mapping from namespace prefix to full name.
+
+ Return the first matching element, or None if no element was found.
+
+ """
# assert self._root is not None
if path[:1] == "/":
path = "." + path
@@ -735,16 +674,17 @@ class ElementTree:
)
return self._root.findtext(path, default, namespaces)
- ##
- # Same as getroot().findall(path), starting at the root of the tree.
- #
- # @param path What element to look for.
- # @keyparam namespaces Optional namespace prefix map.
- # @return A list or iterator containing all matching elements,
- # in document order.
- # @defreturn list of Element instances
-
def findall(self, path, namespaces=None):
+ """Find all matching subelements by tag name or path.
+
+ Same as getroot().findall(path), which is Element.findall().
+
+ *path* is a string having either an element tag or an XPath,
+ *namespaces* is an optional mapping from namespace prefix to full name.
+
+ Return list containing all matching elements in document order.
+
+ """
# assert self._root is not None
if path[:1] == "/":
path = "." + path
@@ -756,17 +696,17 @@ class ElementTree:
)
return self._root.findall(path, namespaces)
- ##
- # Finds all matching subelements, by tag name or path.
- # Same as getroot().iterfind(path).
- #
- # @param path What element to look for.
- # @keyparam namespaces Optional namespace prefix map.
- # @return An iterator or sequence containing all matching elements,
- # in document order.
- # @defreturn a generated sequence of Element instances
-
def iterfind(self, path, namespaces=None):
+ """Find all matching subelements by tag name or path.
+
+ Same as getroot().iterfind(path), which is element.iterfind()
+
+ *path* is a string having either an element tag or an XPath,
+ *namespaces* is an optional mapping from namespace prefix to full name.
+
+ Return an iterable yielding all matching elements in document order.
+
+ """
# assert self._root is not None
if path[:1] == "/":
path = "." + path
@@ -778,26 +718,35 @@ class ElementTree:
)
return self._root.iterfind(path, namespaces)
- ##
- # Writes the element tree to a file, as XML.
- #
- # @def write(file, **options)
- # @param file A file name, or a file object opened for writing.
- # @param **options Options, given as keyword arguments.
- # @keyparam encoding Optional output encoding (default is US-ASCII).
- # Use "unicode" to return a Unicode string.
- # @keyparam xml_declaration Controls if an XML declaration should
- # be added to the file. Use False for never, True for always,
- # None for only if not US-ASCII or UTF-8 or Unicode. None is default.
- # @keyparam default_namespace Sets the default XML namespace (for "xmlns").
- # @keyparam method Optional output method ("xml", "html", "text" or
- # "c14n"; default is "xml").
-
def write(self, file_or_filename,
encoding=None,
xml_declaration=None,
default_namespace=None,
- method=None):
+ method=None, *,
+ short_empty_elements=True):
+ """Write element tree to a file as XML.
+
+ Arguments:
+ *file_or_filename* -- file name or a file object opened for writing
+
+ *encoding* -- the output encoding (default: US-ASCII)
+
+ *xml_declaration* -- bool indicating if an XML declaration should be
+ added to the output. If None, an XML declaration
+ is added if encoding IS NOT either of:
+ US-ASCII, UTF-8, or Unicode
+
+ *default_namespace* -- sets the default XML namespace (for "xmlns")
+
+ *method* -- either "xml" (default), "html, "text", or "c14n"
+
+ *short_empty_elements* -- controls the formatting of elements
+ that contain no content. If True (default)
+ they are emitted as a single self-closed
+ tag, otherwise they are emitted as a pair
+ of start/end tags
+
+ """
if not method:
method = "xml"
elif method not in _serialize:
@@ -825,7 +774,8 @@ class ElementTree:
else:
qnames, namespaces = _namespaces(self._root, default_namespace)
serialize = _serialize[method]
- serialize(write, self._root, qnames, namespaces)
+ serialize(write, self._root, qnames, namespaces,
+ short_empty_elements=short_empty_elements)
def write_c14n(self, file):
# lxml.etree compatibility. use output method instead
@@ -947,7 +897,8 @@ def _namespaces(elem, default_namespace=None):
add_qname(text.text)
return qnames, namespaces
-def _serialize_xml(write, elem, qnames, namespaces):
+def _serialize_xml(write, elem, qnames, namespaces,
+ short_empty_elements, **kwargs):
tag = elem.tag
text = elem.text
if tag is Comment:
@@ -960,7 +911,8 @@ def _serialize_xml(write, elem, qnames, namespaces):
if text:
write(_escape_cdata(text))
for e in elem:
- _serialize_xml(write, e, qnames, None)
+ _serialize_xml(write, e, qnames, None,
+ short_empty_elements=short_empty_elements)
else:
write("<" + tag)
items = list(elem.items())
@@ -982,12 +934,13 @@ def _serialize_xml(write, elem, qnames, namespaces):
else:
v = _escape_attrib(v)
write(" %s=\"%s\"" % (qnames[k], v))
- if text or len(elem):
+ if text or len(elem) or not short_empty_elements:
write(">")
if text:
write(_escape_cdata(text))
for e in elem:
- _serialize_xml(write, e, qnames, None)
+ _serialize_xml(write, e, qnames, None,
+ short_empty_elements=short_empty_elements)
write("</" + tag + ">")
else:
write(" />")
@@ -1002,7 +955,7 @@ try:
except NameError:
pass
-def _serialize_html(write, elem, qnames, namespaces):
+def _serialize_html(write, elem, qnames, namespaces, **kwargs):
tag = elem.tag
text = elem.text
if tag is Comment:
@@ -1066,18 +1019,19 @@ _serialize = {
# "c14n": _serialize_c14n,
}
-##
-# Registers a namespace prefix. The registry is global, and any
-# existing mapping for either the given prefix or the namespace URI
-# will be removed.
-#
-# @param prefix Namespace prefix.
-# @param uri Namespace uri. Tags and attributes in this namespace
-# will be serialized with the given prefix, if at all possible.
-# @exception ValueError If the prefix is reserved, or is otherwise
-# invalid.
def register_namespace(prefix, uri):
+ """Register a namespace prefix.
+
+ The registry is global, and any existing mapping for either the
+ given prefix or the namespace URI will be removed.
+
+ *prefix* is the namespace prefix, *uri* is a namespace uri. Tags and
+ attributes in this namespace will be serialized with prefix if possible.
+
+ ValueError is raised if prefix is reserved or is invalid.
+
+ """
if re.match("ns\d+$", prefix):
raise ValueError("Prefix format reserved for internal use")
for k, v in list(_namespace_map.items()):
@@ -1153,40 +1107,27 @@ def _escape_attrib_html(text):
# --------------------------------------------------------------------
-##
-# Generates a string representation of an XML element, including all
-# subelements. If encoding is "unicode", the return type is a string;
-# otherwise it is a bytes array.
-#
-# @param element An Element instance.
-# @keyparam encoding Optional output encoding (default is US-ASCII).
-# Use "unicode" to return a Unicode string.
-# @keyparam method Optional output method ("xml", "html", "text" or
-# "c14n"; default is "xml").
-# @return An (optionally) encoded string containing the XML data.
-# @defreturn string
-
-def tostring(element, encoding=None, method=None):
+def tostring(element, encoding=None, method=None, *,
+ short_empty_elements=True):
+ """Generate string representation of XML element.
+
+ All subelements are included. If encoding is "unicode", a string
+ is returned. Otherwise a bytestring is returned.
+
+ *element* is an Element instance, *encoding* is an optional output
+ encoding defaulting to US-ASCII, *method* is an optional output which can
+ be one of "xml" (default), "html", "text" or "c14n".
+
+ Returns an (optionally) encoded string containing the XML data.
+
+ """
stream = io.StringIO() if encoding == 'unicode' else io.BytesIO()
- ElementTree(element).write(stream, encoding, method=method)
+ ElementTree(element).write(stream, encoding, method=method,
+ short_empty_elements=short_empty_elements)
return stream.getvalue()
-##
-# Generates a string representation of an XML element, including all
-# subelements.
-#
-# @param element An Element instance.
-# @keyparam encoding Optional output encoding (default is US-ASCII).
-# Use "unicode" to return a Unicode string.
-# @keyparam method Optional output method ("xml", "html", "text" or
-# "c14n"; default is "xml").
-# @return A sequence object containing the XML data.
-# @defreturn sequence
-# @since 1.3
-
class _ListDataStream(io.BufferedIOBase):
- """ An auxiliary stream accumulating into a list reference
- """
+ """An auxiliary stream accumulating into a list reference."""
def __init__(self, lst):
self.lst = lst
@@ -1202,22 +1143,25 @@ class _ListDataStream(io.BufferedIOBase):
def tell(self):
return len(self.lst)
-def tostringlist(element, encoding=None, method=None):
+def tostringlist(element, encoding=None, method=None, *,
+ short_empty_elements=True):
lst = []
stream = _ListDataStream(lst)
- ElementTree(element).write(stream, encoding, method=method)
+ ElementTree(element).write(stream, encoding, method=method,
+ short_empty_elements=short_empty_elements)
return lst
-##
-# Writes an element tree or element structure to sys.stdout. This
-# function should be used for debugging only.
-# <p>
-# The exact output format is implementation dependent. In this
-# version, it's written as an ordinary XML file.
-#
-# @param elem An element tree or an individual element.
def dump(elem):
+ """Write element tree or element structure to sys.stdout.
+
+ This function should be used for debugging only.
+
+ *elem* is either an ElementTree, or a single Element. The exact output
+ format is implementation dependent. In this version, it's written as an
+ ordinary XML file.
+
+ """
# debugging
if not isinstance(elem, ElementTree):
elem = ElementTree(elem)
@@ -1229,144 +1173,169 @@ def dump(elem):
# --------------------------------------------------------------------
# parsing
-##
-# Parses an XML document into an element tree.
-#
-# @param source A filename or file object containing XML data.
-# @param parser An optional parser instance. If not given, the
-# standard {@link XMLParser} parser is used.
-# @return An ElementTree instance
def parse(source, parser=None):
+ """Parse XML document into element tree.
+
+ *source* is a filename or file object containing XML data,
+ *parser* is an optional parser instance defaulting to XMLParser.
+
+ Return an ElementTree instance.
+
+ """
tree = ElementTree()
tree.parse(source, parser)
return tree
-##
-# Parses an XML document into an element tree incrementally, and reports
-# what's going on to the user.
-#
-# @param source A filename or file object containing XML data.
-# @param events A list of events to report back. If omitted, only "end"
-# events are reported.
-# @param parser An optional parser instance. If not given, the
-# standard {@link XMLParser} parser is used.
-# @return A (event, elem) iterator.
def iterparse(source, events=None, parser=None):
+ """Incrementally parse XML document into ElementTree.
+
+ This class also reports what's going on to the user based on the
+ *events* it is initialized with. The supported events are the strings
+ "start", "end", "start-ns" and "end-ns" (the "ns" events are used to get
+ detailed namespace information). If *events* is omitted, only
+ "end" events are reported.
+
+ *source* is a filename or file object containing XML data, *events* is
+ a list of events to report back, *parser* is an optional parser instance.
+
+ Returns an iterator providing (event, elem) pairs.
+
+ """
close_source = False
if not hasattr(source, "read"):
source = open(source, "rb")
close_source = True
- if not parser:
- parser = XMLParser(target=TreeBuilder())
return _IterParseIterator(source, events, parser, close_source)
+
+class XMLPullParser:
+
+ def __init__(self, events=None, *, _parser=None):
+ # The _parser argument is for internal use only and must not be relied
+ # upon in user code. It will be removed in a future release.
+ # See http://bugs.python.org/issue17741 for more details.
+
+ # _elementtree.c expects a list, not a deque
+ self._events_queue = []
+ self._index = 0
+ self._parser = _parser or XMLParser(target=TreeBuilder())
+ # wire up the parser for event reporting
+ if events is None:
+ events = ("end",)
+ self._parser._setevents(self._events_queue, events)
+
+ def feed(self, data):
+ """Feed encoded data to parser."""
+ if self._parser is None:
+ raise ValueError("feed() called after end of stream")
+ if data:
+ try:
+ self._parser.feed(data)
+ except SyntaxError as exc:
+ self._events_queue.append(exc)
+
+ def _close_and_return_root(self):
+ # iterparse needs this to set its root attribute properly :(
+ root = self._parser.close()
+ self._parser = None
+ return root
+
+ def close(self):
+ """Finish feeding data to parser.
+
+ Unlike XMLParser, does not return the root element. Use
+ read_events() to consume elements from XMLPullParser.
+ """
+ self._close_and_return_root()
+
+ def read_events(self):
+ """Iterate over currently available (event, elem) pairs.
+
+ Events are consumed from the internal event queue as they are
+ retrieved from the iterator.
+ """
+ events = self._events_queue
+ while True:
+ index = self._index
+ try:
+ event = events[self._index]
+ # Avoid retaining references to past events
+ events[self._index] = None
+ except IndexError:
+ break
+ index += 1
+ # Compact the list in a O(1) amortized fashion
+ # As noted above, _elementree.c needs a list, not a deque
+ if index * 2 >= len(events):
+ events[:index] = []
+ self._index = 0
+ else:
+ self._index = index
+ if isinstance(event, Exception):
+ raise event
+ else:
+ yield event
+
+
class _IterParseIterator:
def __init__(self, source, events, parser, close_source=False):
+ # Use the internal, undocumented _parser argument for now; When the
+ # parser argument of iterparse is removed, this can be killed.
+ self._parser = XMLPullParser(events=events, _parser=parser)
self._file = source
self._close_file = close_source
- self._events = []
- self._index = 0
- self._error = None
self.root = self._root = None
- self._parser = parser
- # wire up the parser for event reporting
- parser = self._parser._parser
- append = self._events.append
- if events is None:
- events = ["end"]
- for event in events:
- if event == "start":
- try:
- parser.ordered_attributes = 1
- parser.specified_attributes = 1
- def handler(tag, attrib_in, event=event, append=append,
- start=self._parser._start_list):
- append((event, start(tag, attrib_in)))
- parser.StartElementHandler = handler
- except AttributeError:
- def handler(tag, attrib_in, event=event, append=append,
- start=self._parser._start):
- append((event, start(tag, attrib_in)))
- parser.StartElementHandler = handler
- elif event == "end":
- def handler(tag, event=event, append=append,
- end=self._parser._end):
- append((event, end(tag)))
- parser.EndElementHandler = handler
- elif event == "start-ns":
- def handler(prefix, uri, event=event, append=append):
- append((event, (prefix or "", uri or "")))
- parser.StartNamespaceDeclHandler = handler
- elif event == "end-ns":
- def handler(prefix, event=event, append=append):
- append((event, None))
- parser.EndNamespaceDeclHandler = handler
- else:
- raise ValueError("unknown event %r" % event)
def __next__(self):
while 1:
- try:
- item = self._events[self._index]
- self._index += 1
- return item
- except IndexError:
- pass
- if self._error:
- e = self._error
- self._error = None
- raise e
- if self._parser is None:
+ for event in self._parser.read_events():
+ return event
+ if self._parser._parser is None:
self.root = self._root
if self._close_file:
self._file.close()
raise StopIteration
# load event buffer
- del self._events[:]
- self._index = 0
- data = self._file.read(16384)
+ data = self._file.read(16 * 1024)
if data:
- try:
- self._parser.feed(data)
- except SyntaxError as exc:
- self._error = exc
+ self._parser.feed(data)
else:
- self._root = self._parser.close()
- self._parser = None
+ self._root = self._parser._close_and_return_root()
def __iter__(self):
return self
-##
-# Parses an XML document from a string constant. This function can
-# be used to embed "XML literals" in Python code.
-#
-# @param source A string containing XML data.
-# @param parser An optional parser instance. If not given, the
-# standard {@link XMLParser} parser is used.
-# @return An Element instance.
-# @defreturn Element
def XML(text, parser=None):
+ """Parse XML document from string constant.
+
+ This function can be used to embed "XML Literals" in Python code.
+
+ *text* is a string containing XML data, *parser* is an
+ optional parser instance, defaulting to the standard XMLParser.
+
+ Returns an Element instance.
+
+ """
if not parser:
parser = XMLParser(target=TreeBuilder())
parser.feed(text)
return parser.close()
-##
-# Parses an XML document from a string constant, and also returns
-# a dictionary which maps from element id:s to elements.
-#
-# @param source A string containing XML data.
-# @param parser An optional parser instance. If not given, the
-# standard {@link XMLParser} parser is used.
-# @return A tuple containing an Element instance and a dictionary.
-# @defreturn (Element, dictionary)
def XMLID(text, parser=None):
+ """Parse XML document from string constant for its IDs.
+
+ *text* is a string containing XML data, *parser* is an
+ optional parser instance, defaulting to the standard XMLParser.
+
+ Returns an (Element, dict) tuple, in which the
+ dict maps element id:s to elements.
+
+ """
if not parser:
parser = XMLParser(target=TreeBuilder())
parser.feed(text)
@@ -1378,27 +1347,18 @@ def XMLID(text, parser=None):
ids[id] = elem
return tree, ids
-##
-# Parses an XML document from a string constant. Same as {@link #XML}.
-#
-# @def fromstring(text)
-# @param source A string containing XML data.
-# @return An Element instance.
-# @defreturn Element
-
+# Parse XML document from string constant. Alias for XML().
fromstring = XML
-##
-# Parses an XML document from a sequence of string fragments.
-#
-# @param sequence A list or other sequence containing XML data fragments.
-# @param parser An optional parser instance. If not given, the
-# standard {@link XMLParser} parser is used.
-# @return An Element instance.
-# @defreturn Element
-# @since 1.3
-
def fromstringlist(sequence, parser=None):
+ """Parse XML document from sequence of string fragments.
+
+ *sequence* is a list of other sequence, *parser* is an optional parser
+ instance, defaulting to the standard XMLParser.
+
+ Returns an Element instance.
+
+ """
if not parser:
parser = XMLParser(target=TreeBuilder())
for text in sequence:
@@ -1407,19 +1367,20 @@ def fromstringlist(sequence, parser=None):
# --------------------------------------------------------------------
-##
-# Generic element structure builder. This builder converts a sequence
-# of {@link #TreeBuilder.start}, {@link #TreeBuilder.data}, and {@link
-# #TreeBuilder.end} method calls to a well-formed element structure.
-# <p>
-# You can use this class to build an element structure using a custom XML
-# parser, or a parser for some other XML-like format.
-#
-# @param element_factory Optional element factory. This factory
-# is called to create new Element instances, as necessary.
class TreeBuilder:
+ """Generic element structure builder.
+
+ This builder converts a sequence of start, data, and end method
+ calls to a well-formed element structure.
+ You can use this class to build an element structure using a custom XML
+ parser, or a parser for some other XML-like format.
+
+ *element_factory* is an optional element factory which is called
+ to create new Element instances, as necessary.
+
+ """
def __init__(self, element_factory=None):
self._data = [] # data collector
self._elem = [] # element stack
@@ -1429,14 +1390,8 @@ class TreeBuilder:
element_factory = Element
self._factory = element_factory
- ##
- # Flushes the builder buffers, and returns the toplevel document
- # element.
- #
- # @return An Element instance.
- # @defreturn Element
-
def close(self):
+ """Flush builder buffers and return toplevel document Element."""
assert len(self._elem) == 0, "missing end tags"
assert self._last is not None, "missing toplevel element"
return self._last
@@ -1453,24 +1408,17 @@ class TreeBuilder:
self._last.text = text
self._data = []
- ##
- # Adds text to the current element.
- #
- # @param data A string. This should be either an 8-bit string
- # containing ASCII text, or a Unicode string.
-
def data(self, data):
+ """Add text to current element."""
self._data.append(data)
- ##
- # Opens a new element.
- #
- # @param tag The element name.
- # @param attrib A dictionary containing element attributes.
- # @return The opened element.
- # @defreturn Element
-
def start(self, tag, attrs):
+ """Open new element and return it.
+
+ *tag* is the element name, *attrs* is a dict containing element
+ attributes.
+
+ """
self._flush()
self._last = elem = self._factory(tag, attrs)
if self._elem:
@@ -1479,14 +1427,12 @@ class TreeBuilder:
self._tail = 0
return elem
- ##
- # Closes the current element.
- #
- # @param tag The element name.
- # @return The closed element.
- # @defreturn Element
-
def end(self, tag):
+ """Close and return current Element.
+
+ *tag* is the element name.
+
+ """
self._flush()
self._last = self._elem.pop()
assert self._last.tag == tag,\
@@ -1495,20 +1441,18 @@ class TreeBuilder:
self._tail = 1
return self._last
-##
-# Element structure builder for XML source data, based on the
-# <b>expat</b> parser.
-#
-# @keyparam target Target object. If omitted, the builder uses an
-# instance of the standard {@link #TreeBuilder} class.
-# @keyparam html Predefine HTML entities. This flag is not supported
-# by the current implementation.
-# @keyparam encoding Optional encoding. If given, the value overrides
-# the encoding specified in the XML file.
-# @see #ElementTree
-# @see #TreeBuilder
+# also see ElementTree and TreeBuilder
class XMLParser:
+ """Element structure builder for XML source data based on the expat parser.
+
+ *html* are predefined HTML entities (not supported currently),
+ *target* is an optional target object which defaults to an instance of the
+ standard TreeBuilder class, *encoding* is an optional encoding string
+ which if given, overrides the encoding specified in the XML file:
+ http://www.iana.org/assignments/character-sets
+
+ """
def __init__(self, html=0, target=None, encoding=None):
try:
@@ -1541,19 +1485,10 @@ class XMLParser:
parser.CommentHandler = target.comment
if hasattr(target, 'pi'):
parser.ProcessingInstructionHandler = target.pi
- # let expat do the buffering, if supported
- try:
- parser.buffer_text = 1
- except AttributeError:
- pass
- # use new-style attribute handling, if supported
- try:
- parser.ordered_attributes = 1
- parser.specified_attributes = 1
- if hasattr(target, 'start'):
- parser.StartElementHandler = self._start_list
- except AttributeError:
- pass
+ # Configure pyexpat: buffering, new-style attribute handling.
+ parser.buffer_text = 1
+ parser.ordered_attributes = 1
+ parser.specified_attributes = 1
self._doctype = None
self.entity = {}
try:
@@ -1561,6 +1496,39 @@ class XMLParser:
except AttributeError:
pass # unknown
+ def _setevents(self, events_queue, events_to_report):
+ # Internal API for XMLPullParser
+ # events_to_report: a list of events to report during parsing (same as
+ # the *events* of XMLPullParser's constructor.
+ # events_queue: a list of actual parsing events that will be populated
+ # by the underlying parser.
+ #
+ parser = self._parser
+ append = events_queue.append
+ for event_name in events_to_report:
+ if event_name == "start":
+ parser.ordered_attributes = 1
+ parser.specified_attributes = 1
+ def handler(tag, attrib_in, event=event_name, append=append,
+ start=self._start):
+ append((event, start(tag, attrib_in)))
+ parser.StartElementHandler = handler
+ elif event_name == "end":
+ def handler(tag, event=event_name, append=append,
+ end=self._end):
+ append((event, end(tag)))
+ parser.EndElementHandler = handler
+ elif event_name == "start-ns":
+ def handler(prefix, uri, event=event_name, append=append):
+ append((event, (prefix or "", uri or "")))
+ parser.StartNamespaceDeclHandler = handler
+ elif event_name == "end-ns":
+ def handler(prefix, event=event_name, append=append):
+ append((event, None))
+ parser.EndNamespaceDeclHandler = handler
+ else:
+ raise ValueError("unknown event %r" % event_name)
+
def _raiseerror(self, value):
err = ParseError(value)
err.code = value.code
@@ -1578,21 +1546,16 @@ class XMLParser:
self._names[key] = name
return name
- def _start(self, tag, attrib_in):
+ def _start(self, tag, attr_list):
+ # Handler for expat's StartElementHandler. Since ordered_attributes
+ # is set, the attributes are reported as a list of alternating
+ # attribute name,value.
fixname = self._fixname
tag = fixname(tag)
attrib = {}
- for key, value in attrib_in.items():
- attrib[fixname(key)] = value
- return self.target.start(tag, attrib)
-
- def _start_list(self, tag, attrib_in):
- fixname = self._fixname
- tag = fixname(tag)
- attrib = {}
- if attrib_in:
- for i in range(0, len(attrib_in), 2):
- attrib[fixname(attrib_in[i])] = attrib_in[i+1]
+ if attr_list:
+ for i in range(0, len(attr_list), 2):
+ attrib[fixname(attr_list[i])] = attr_list[i+1]
return self.target.start(tag, attrib)
def _end(self, tag):
@@ -1650,15 +1613,13 @@ class XMLParser:
self.doctype(name, pubid, system[1:-1])
self._doctype = None
- ##
- # (Deprecated) Handles a doctype declaration.
- #
- # @param name Doctype name.
- # @param pubid Public identifier.
- # @param system System identifier.
-
def doctype(self, name, pubid, system):
- """This method of XMLParser is deprecated."""
+ """(Deprecated) Handle doctype declaration
+
+ *name* is the Doctype name, *pubid* is the public identifier,
+ and *system* is the system identifier.
+
+ """
warnings.warn(
"This method of XMLParser is deprecated. Define doctype() "
"method on the TreeBuilder target.",
@@ -1668,24 +1629,15 @@ class XMLParser:
# sentinel, if doctype is redefined in a subclass
__doctype = doctype
- ##
- # Feeds data to the parser.
- #
- # @param data Encoded data.
-
def feed(self, data):
+ """Feed encoded data to parser."""
try:
self.parser.Parse(data, 0)
except self._error as v:
self._raiseerror(v)
- ##
- # Finishes feeding data to the parser.
- #
- # @return An element structure.
- # @defreturn Element
-
def close(self):
+ """Finish feeding data to parser and return element structure."""
try:
self.parser.Parse("", 1) # end of data
except self._error as v:
@@ -1704,103 +1656,12 @@ class XMLParser:
# Import the C accelerators
try:
+ # Element is going to be shadowed by the C implementation. We need to keep
+ # the Python version of it accessible for some "creative" by external code
+ # (see tests)
+ _Element_Py = Element
+
# Element, SubElement, ParseError, TreeBuilder, XMLParser
from _elementtree import *
except ImportError:
pass
-else:
- # Overwrite 'ElementTree.parse' and 'iterparse' to use the C XMLParser
-
- class ElementTree(ElementTree):
- def parse(self, source, parser=None):
- close_source = False
- if not hasattr(source, 'read'):
- source = open(source, 'rb')
- close_source = True
- try:
- if parser is not None:
- while True:
- data = source.read(65536)
- if not data:
- break
- parser.feed(data)
- self._root = parser.close()
- else:
- parser = XMLParser()
- self._root = parser._parse(source)
- return self._root
- finally:
- if close_source:
- source.close()
-
- class iterparse:
- """Parses an XML section into an element tree incrementally.
-
- Reports what’s going on to the user. 'source' is a filename or file
- object containing XML data. 'events' is a list of events to report back.
- The supported events are the strings "start", "end", "start-ns" and
- "end-ns" (the "ns" events are used to get detailed namespace
- information). If 'events' is omitted, only "end" events are reported.
- 'parser' is an optional parser instance. If not given, the standard
- XMLParser parser is used. Returns an iterator providing
- (event, elem) pairs.
- """
-
- root = None
- def __init__(self, file, events=None, parser=None):
- self._close_file = False
- if not hasattr(file, 'read'):
- file = open(file, 'rb')
- self._close_file = True
- self._file = file
- self._events = []
- self._index = 0
- self._error = None
- self.root = self._root = None
- if parser is None:
- parser = XMLParser(target=TreeBuilder())
- self._parser = parser
- self._parser._setevents(self._events, events)
-
- def __next__(self):
- while True:
- try:
- item = self._events[self._index]
- self._index += 1
- return item
- except IndexError:
- pass
- if self._error:
- e = self._error
- self._error = None
- raise e
- if self._parser is None:
- self.root = self._root
- if self._close_file:
- self._file.close()
- raise StopIteration
- # load event buffer
- del self._events[:]
- self._index = 0
- data = self._file.read(16384)
- if data:
- try:
- self._parser.feed(data)
- except SyntaxError as exc:
- self._error = exc
- else:
- self._root = self._parser.close()
- self._parser = None
-
- def __iter__(self):
- return self
-
-# compatibility
-XMLTreeBuilder = XMLParser
-
-# workaround circular import.
-try:
- from ElementC14N import _serialize_c14n
- _serialize["c14n"] = _serialize_c14n
-except ImportError:
- pass
diff --git a/Lib/xmlrpc/client.py b/Lib/xmlrpc/client.py
index 0f9522aa3c..9e0f0691bf 100644
--- a/Lib/xmlrpc/client.py
+++ b/Lib/xmlrpc/client.py
@@ -1045,7 +1045,7 @@ def gzip_decode(data):
gzf = gzip.GzipFile(mode="rb", fileobj=f)
try:
decoded = gzf.read()
- except IOError:
+ except OSError:
raise ValueError("invalid data")
f.close()
gzf.close()
@@ -1130,8 +1130,9 @@ class Transport:
for i in (0, 1):
try:
return self.single_request(host, handler, request_body, verbose)
- except socket.error as e:
- if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE):
+ except OSError as e:
+ if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED,
+ errno.EPIPE):
raise
except http.client.BadStatusLine: #close after we sent request
if i:
@@ -1385,7 +1386,7 @@ class ServerProxy:
# get the url
type, uri = urllib.parse.splittype(uri)
if type not in ("http", "https"):
- raise IOError("unsupported XML-RPC protocol")
+ raise OSError("unsupported XML-RPC protocol")
self.__host, self.__handler = urllib.parse.splithost(uri)
if not self.__handler:
self.__handler = "/RPC2"
diff --git a/Lib/xmlrpc/server.py b/Lib/xmlrpc/server.py
index 78ca4e0a4a..d914282917 100644
--- a/Lib/xmlrpc/server.py
+++ b/Lib/xmlrpc/server.py
@@ -584,13 +584,6 @@ class SimpleXMLRPCServer(socketserver.TCPServer,
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
- # [Bug #1222790] If possible, set close-on-exec flag; if a
- # method spawns a subprocess, the subprocess shouldn't have
- # the listening socket open.
- if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
- flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
- flags |= fcntl.FD_CLOEXEC
- fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
class MultiPathXMLRPCServer(SimpleXMLRPCServer):
"""Multipath XML-RPC Server
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index 92c98abe1e..fad52a246d 100644
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -6,7 +6,7 @@ XXX references to utf-8 need further investigation.
import io
import os
import re
-import imp
+import importlib.util
import sys
import time
import stat
@@ -164,7 +164,7 @@ def _check_zipfile(fp):
try:
if _EndRecData(fp):
return True # file has correct magic number
- except IOError:
+ except OSError:
pass
return False
@@ -180,7 +180,7 @@ def is_zipfile(filename):
else:
with open(filename, "rb") as fp:
result = _check_zipfile(fp)
- except IOError:
+ except OSError:
pass
return result
@@ -190,7 +190,7 @@ def _EndRecData64(fpin, offset, endrec):
"""
try:
fpin.seek(offset - sizeEndCentDir64Locator, 2)
- except IOError:
+ except OSError:
# If the seek fails, the file is not large enough to contain a ZIP64
# end-of-archive record, so just return the end record we were given.
return endrec
@@ -211,8 +211,8 @@ def _EndRecData64(fpin, offset, endrec):
if len(data) != sizeEndCentDir64:
return endrec
sig, sz, create_version, read_version, disk_num, disk_dir, \
- dircount, dircount2, dirsize, diroffset = \
- struct.unpack(structEndArchive64, data)
+ dircount, dircount2, dirsize, diroffset = \
+ struct.unpack(structEndArchive64, data)
if sig != stringEndArchive64:
return endrec
@@ -242,7 +242,7 @@ def _EndRecData(fpin):
# file if this is the case).
try:
fpin.seek(-sizeEndCentDir, 2)
- except IOError:
+ except OSError:
return None
data = fpin.read()
if (len(data) == sizeEndCentDir and
@@ -292,26 +292,26 @@ class ZipInfo (object):
"""Class with attributes describing each file in the ZIP archive."""
__slots__ = (
- 'orig_filename',
- 'filename',
- 'date_time',
- 'compress_type',
- 'comment',
- 'extra',
- 'create_system',
- 'create_version',
- 'extract_version',
- 'reserved',
- 'flag_bits',
- 'volume',
- 'internal_attr',
- 'external_attr',
- 'header_offset',
- 'CRC',
- 'compress_size',
- 'file_size',
- '_raw_time',
- )
+ 'orig_filename',
+ 'filename',
+ 'date_time',
+ 'compress_type',
+ 'comment',
+ 'extra',
+ 'create_system',
+ 'create_version',
+ 'extract_version',
+ 'reserved',
+ 'flag_bits',
+ 'volume',
+ 'internal_attr',
+ 'external_attr',
+ 'header_offset',
+ 'CRC',
+ 'compress_size',
+ 'file_size',
+ '_raw_time',
+ )
def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
self.orig_filename = filename # Original file name in archive
@@ -376,7 +376,7 @@ class ZipInfo (object):
if zip64:
fmt = '<HHQQ'
extra = extra + struct.pack(fmt,
- 1, struct.calcsize(fmt)-4, file_size, compress_size)
+ 1, struct.calcsize(fmt)-4, file_size, compress_size)
if file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT:
if not zip64:
raise LargeZipFile("Filesize would require ZIP64 extensions")
@@ -395,10 +395,10 @@ class ZipInfo (object):
self.create_version = max(min_version, self.create_version)
filename, flag_bits = self._encodeFilenameFlags()
header = struct.pack(structFileHeader, stringFileHeader,
- self.extract_version, self.reserved, flag_bits,
- self.compress_type, dostime, dosdate, CRC,
- compress_size, file_size,
- len(filename), len(extra))
+ self.extract_version, self.reserved, flag_bits,
+ self.compress_type, dostime, dosdate, CRC,
+ compress_size, file_size,
+ len(filename), len(extra))
return header + filename + extra
def _encodeFilenameFlags(self):
@@ -511,7 +511,7 @@ class LZMACompressor:
def _init(self):
props = lzma._encode_filter_properties({'id': lzma.FILTER_LZMA1})
self._comp = lzma.LZMACompressor(lzma.FORMAT_RAW, filters=[
- lzma._decode_filter_properties(lzma.FILTER_LZMA1, props)
+ lzma._decode_filter_properties(lzma.FILTER_LZMA1, props)
])
return struct.pack('<BBH', 9, 4, len(props)) + props
@@ -543,8 +543,8 @@ class LZMADecompressor:
return b''
self._decomp = lzma.LZMADecompressor(lzma.FORMAT_RAW, filters=[
- lzma._decode_filter_properties(lzma.FILTER_LZMA1,
- self._unconsumed[4:4 + psize])
+ lzma._decode_filter_properties(lzma.FILTER_LZMA1,
+ self._unconsumed[4:4 + psize])
])
data = self._unconsumed[4 + psize:]
del self._unconsumed
@@ -580,15 +580,15 @@ def _check_compression(compression):
elif compression == ZIP_DEFLATED:
if not zlib:
raise RuntimeError(
- "Compression requires the (missing) zlib module")
+ "Compression requires the (missing) zlib module")
elif compression == ZIP_BZIP2:
if not bz2:
raise RuntimeError(
- "Compression requires the (missing) bz2 module")
+ "Compression requires the (missing) bz2 module")
elif compression == ZIP_LZMA:
if not lzma:
raise RuntimeError(
- "Compression requires the (missing) lzma module")
+ "Compression requires the (missing) lzma module")
else:
raise RuntimeError("That compression method is not supported")
@@ -596,7 +596,7 @@ def _check_compression(compression):
def _get_compressor(compress_type):
if compress_type == ZIP_DEFLATED:
return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
- zlib.DEFLATED, -15)
+ zlib.DEFLATED, -15)
elif compress_type == ZIP_BZIP2:
return bz2.BZ2Compressor()
elif compress_type == ZIP_LZMA:
@@ -836,8 +836,8 @@ class ZipExtFile(io.BufferedIOBase):
n = max(n, self.MIN_READ_SIZE)
data = self._decompressor.decompress(data, n)
self._eof = (self._decompressor.eof or
- self._compress_left <= 0 and
- not self._decompressor.unconsumed_tail)
+ self._compress_left <= 0 and
+ not self._decompressor.unconsumed_tail)
if self._eof:
data += self._decompressor.flush()
else:
@@ -876,7 +876,7 @@ class ZipExtFile(io.BufferedIOBase):
class ZipFile:
""" Class with methods to open, read, write, close, list zip files.
- z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=False)
+ z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=True)
file: Either the path to the file, or a file-like object.
If it is a path, the file will be opened and closed by ZipFile.
@@ -892,7 +892,7 @@ class ZipFile:
fp = None # Set here since __del__ checks it
_windows_illegal_name_trans_table = None
- def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False):
+ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
"""Open the ZIP file with mode read "r", write "w" or append "a"."""
if mode not in ("r", "w", "a"):
raise RuntimeError('ZipFile() requires mode "r", "w", or "a"')
@@ -917,7 +917,7 @@ class ZipFile:
modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
try:
self.fp = io.open(file, modeDict[mode])
- except IOError:
+ except OSError:
if mode == 'a':
mode = key = 'w'
self.fp = io.open(file, modeDict[mode])
@@ -968,7 +968,7 @@ class ZipFile:
fp = self.fp
try:
endrec = _EndRecData(fp)
- except IOError:
+ except OSError:
raise BadZipFile("File is not a zip file")
if not endrec:
raise BadZipFile("File is not a zip file")
@@ -1016,8 +1016,8 @@ class ZipFile:
x.comment = fp.read(centdir[_CD_COMMENT_LENGTH])
x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET]
(x.create_version, x.create_system, x.extract_version, x.reserved,
- x.flag_bits, x.compress_type, t, d,
- x.CRC, x.compress_size, x.file_size) = centdir[1:12]
+ x.flag_bits, x.compress_type, t, d,
+ x.CRC, x.compress_size, x.file_size) = centdir[1:12]
if x.extract_version > MAX_EXTRACT_VERSION:
raise NotImplementedError("zip file version %.1f" %
(x.extract_version / 10))
@@ -1025,7 +1025,7 @@ class ZipFile:
# Convert date/time code to (year, month, day, hour, min, sec)
x._raw_time = t
x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
- t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
+ t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
x._decodeExtra()
x.header_offset = x.header_offset + concat
@@ -1103,7 +1103,7 @@ class ZipFile:
if len(comment) >= ZIP_MAX_COMMENT:
if self.debug:
print('Archive comment is too long; truncating to %d bytes'
- % ZIP_MAX_COMMENT)
+ % ZIP_MAX_COMMENT)
comment = comment[:ZIP_MAX_COMMENT]
self._comment = comment
self._didModify = True
@@ -1117,11 +1117,15 @@ class ZipFile:
"""Return file-like object for 'name'."""
if mode not in ("r", "U", "rU"):
raise RuntimeError('open() requires mode "r", "U", or "rU"')
+ if 'U' in mode:
+ import warnings
+ warnings.warn("'U' mode is deprecated",
+ DeprecationWarning, 2)
if pwd and not isinstance(pwd, bytes):
raise TypeError("pwd: expected bytes, got %s" % type(pwd))
if not self.fp:
raise RuntimeError(
- "Attempt to read ZIP archive that was already closed")
+ "Attempt to read ZIP archive that was already closed")
# Only open a new file for instances where we were not
# given a file object in the constructor
@@ -1294,7 +1298,7 @@ class ZipFile:
raise RuntimeError('write() requires mode "w" or "a"')
if not self.fp:
raise RuntimeError(
- "Attempt to write ZIP archive that was already closed")
+ "Attempt to write ZIP archive that was already closed")
_check_compression(zinfo.compress_type)
if zinfo.file_size > ZIP64_LIMIT:
if not self._allowZip64:
@@ -1302,14 +1306,14 @@ class ZipFile:
if zinfo.header_offset > ZIP64_LIMIT:
if not self._allowZip64:
raise LargeZipFile(
- "Zipfile size would require ZIP64 extensions")
+ "Zipfile size would require ZIP64 extensions")
def write(self, filename, arcname=None, compress_type=None):
"""Put the bytes from filename into the archive under the name
arcname."""
if not self.fp:
raise RuntimeError(
- "Attempt to write to ZIP archive that was already closed")
+ "Attempt to write to ZIP archive that was already closed")
st = os.stat(filename)
isdir = stat.S_ISDIR(st.st_mode)
@@ -1356,7 +1360,7 @@ class ZipFile:
zinfo.compress_size = compress_size = 0
# Compressed size can be larger than uncompressed size
zip64 = self._allowZip64 and \
- zinfo.file_size * 1.05 > ZIP64_LIMIT
+ zinfo.file_size * 1.05 > ZIP64_LIMIT
self.fp.write(zinfo.FileHeader(zip64))
file_size = 0
while 1:
@@ -1410,7 +1414,7 @@ class ZipFile:
if not self.fp:
raise RuntimeError(
- "Attempt to write to ZIP archive that was already closed")
+ "Attempt to write to ZIP archive that was already closed")
zinfo.file_size = len(data) # Uncompressed size
zinfo.header_offset = self.fp.tell() # Start of header data
@@ -1430,7 +1434,7 @@ class ZipFile:
else:
zinfo.compress_size = zinfo.file_size
zip64 = zinfo.file_size > ZIP64_LIMIT or \
- zinfo.compress_size > ZIP64_LIMIT
+ zinfo.compress_size > ZIP64_LIMIT
if zip64 and not self._allowZip64:
raise LargeZipFile("Filesize would require ZIP64 extensions")
self.fp.write(zinfo.FileHeader(zip64))
@@ -1439,7 +1443,7 @@ class ZipFile:
# Write CRC and file sizes after the file data
fmt = '<LQQ' if zip64 else '<LLL'
self.fp.write(struct.pack(fmt, zinfo.CRC, zinfo.compress_size,
- zinfo.file_size))
+ zinfo.file_size))
self.fp.flush()
self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo
@@ -1465,7 +1469,7 @@ class ZipFile:
dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
extra = []
if zinfo.file_size > ZIP64_LIMIT \
- or zinfo.compress_size > ZIP64_LIMIT:
+ or zinfo.compress_size > ZIP64_LIMIT:
extra.append(zinfo.file_size)
extra.append(zinfo.compress_size)
file_size = 0xffffffff
@@ -1485,8 +1489,8 @@ class ZipFile:
if extra:
# Append a ZIP64 field to the extra's
extra_data = struct.pack(
- '<HH' + 'Q'*len(extra),
- 1, 8*len(extra), *extra) + extra_data
+ '<HH' + 'Q'*len(extra),
+ 1, 8*len(extra), *extra) + extra_data
min_version = ZIP64_VERSION
@@ -1500,21 +1504,21 @@ class ZipFile:
try:
filename, flag_bits = zinfo._encodeFilenameFlags()
centdir = struct.pack(structCentralDir,
- stringCentralDir, create_version,
- zinfo.create_system, extract_version, zinfo.reserved,
- flag_bits, zinfo.compress_type, dostime, dosdate,
- zinfo.CRC, compress_size, file_size,
- len(filename), len(extra_data), len(zinfo.comment),
- 0, zinfo.internal_attr, zinfo.external_attr,
- header_offset)
+ stringCentralDir, create_version,
+ zinfo.create_system, extract_version, zinfo.reserved,
+ flag_bits, zinfo.compress_type, dostime, dosdate,
+ zinfo.CRC, compress_size, file_size,
+ len(filename), len(extra_data), len(zinfo.comment),
+ 0, zinfo.internal_attr, zinfo.external_attr,
+ header_offset)
except DeprecationWarning:
print((structCentralDir, stringCentralDir, create_version,
- zinfo.create_system, extract_version, zinfo.reserved,
- zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
- zinfo.CRC, compress_size, file_size,
- len(zinfo.filename), len(extra_data), len(zinfo.comment),
- 0, zinfo.internal_attr, zinfo.external_attr,
- header_offset), file=sys.stderr)
+ zinfo.create_system, extract_version, zinfo.reserved,
+ zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
+ zinfo.CRC, compress_size, file_size,
+ len(zinfo.filename), len(extra_data), len(zinfo.comment),
+ 0, zinfo.internal_attr, zinfo.external_attr,
+ header_offset), file=sys.stderr)
raise
self.fp.write(centdir)
self.fp.write(filename)
@@ -1531,22 +1535,22 @@ class ZipFile:
centDirSize > ZIP64_LIMIT):
# Need to write the ZIP64 end-of-archive records
zip64endrec = struct.pack(
- structEndArchive64, stringEndArchive64,
- 44, 45, 45, 0, 0, centDirCount, centDirCount,
- centDirSize, centDirOffset)
+ structEndArchive64, stringEndArchive64,
+ 44, 45, 45, 0, 0, centDirCount, centDirCount,
+ centDirSize, centDirOffset)
self.fp.write(zip64endrec)
zip64locrec = struct.pack(
- structEndArchive64Locator,
- stringEndArchive64Locator, 0, pos2, 1)
+ structEndArchive64Locator,
+ stringEndArchive64Locator, 0, pos2, 1)
self.fp.write(zip64locrec)
centDirCount = min(centDirCount, 0xFFFF)
centDirSize = min(centDirSize, 0xFFFFFFFF)
centDirOffset = min(centDirOffset, 0xFFFFFFFF)
endrec = struct.pack(structEndArchive, stringEndArchive,
- 0, 0, centDirCount, centDirCount,
- centDirSize, centDirOffset, len(self._comment))
+ 0, 0, centDirCount, centDirCount,
+ centDirSize, centDirOffset, len(self._comment))
self.fp.write(endrec)
self.fp.write(self._comment)
self.fp.flush()
@@ -1561,12 +1565,12 @@ class PyZipFile(ZipFile):
"""Class to create ZIP archives with Python library files and packages."""
def __init__(self, file, mode="r", compression=ZIP_STORED,
- allowZip64=False, optimize=-1):
+ allowZip64=True, optimize=-1):
ZipFile.__init__(self, file, mode=mode, compression=compression,
allowZip64=allowZip64)
self._optimize = optimize
- def writepy(self, pathname, basename=""):
+ def writepy(self, pathname, basename="", filterfunc=None):
"""Add all files from "pathname" to the ZIP archive.
If pathname is a package directory, search the directory and
@@ -1577,7 +1581,14 @@ class PyZipFile(ZipFile):
archive. Added modules are always module.pyo or module.pyc.
This method will compile the module.py into module.pyc if
necessary.
+ If filterfunc(pathname) is given, it is called with every argument.
+ When it is False, the file or directory is skipped.
"""
+ if filterfunc and not filterfunc(pathname):
+ if self.debug:
+ label = 'path' if os.path.isdir(pathname) else 'file'
+ print('%s "%s" skipped by filterfunc' % (label, pathname))
+ return
dir, name = os.path.split(pathname)
if os.path.isdir(pathname):
initname = os.path.join(pathname, "__init__.py")
@@ -1602,10 +1613,15 @@ class PyZipFile(ZipFile):
if os.path.isdir(path):
if os.path.isfile(os.path.join(path, "__init__.py")):
# This is a package directory, add it
- self.writepy(path, basename) # Recursive call
+ self.writepy(path, basename,
+ filterfunc=filterfunc) # Recursive call
elif ext == ".py":
+ if filterfunc and not filterfunc(path):
+ if self.debug:
+ print('file "%s" skipped by filterfunc' % path)
+ continue
fname, arcname = self._get_codename(path[0:-3],
- basename)
+ basename)
if self.debug:
print("Adding", arcname)
self.write(fname, arcname)
@@ -1617,15 +1633,19 @@ class PyZipFile(ZipFile):
path = os.path.join(pathname, filename)
root, ext = os.path.splitext(filename)
if ext == ".py":
+ if filterfunc and not filterfunc(path):
+ if self.debug:
+ print('file "%s" skipped by filterfunc' % path)
+ continue
fname, arcname = self._get_codename(path[0:-3],
- basename)
+ basename)
if self.debug:
print("Adding", arcname)
self.write(fname, arcname)
else:
if pathname[-3:] != ".py":
raise RuntimeError(
- 'Files added with writepy() must end with ".py"')
+ 'Files added with writepy() must end with ".py"')
fname, arcname = self._get_codename(pathname[0:-3], basename)
if self.debug:
print("Adding file", arcname)
@@ -1652,8 +1672,8 @@ class PyZipFile(ZipFile):
file_py = pathname + ".py"
file_pyc = pathname + ".pyc"
file_pyo = pathname + ".pyo"
- pycache_pyc = imp.cache_from_source(file_py, True)
- pycache_pyo = imp.cache_from_source(file_py, False)
+ pycache_pyc = importlib.util.cache_from_source(file_py, True)
+ pycache_pyo = importlib.util.cache_from_source(file_py, False)
if self._optimize == -1:
# legacy mode: use whatever file is present
if (os.path.isfile(file_pyo) and
@@ -1764,10 +1784,10 @@ def main(args = None):
elif os.path.isdir(path):
for nm in os.listdir(path):
addToZip(zf,
- os.path.join(path, nm), os.path.join(zippath, nm))
+ os.path.join(path, nm), os.path.join(zippath, nm))
# else: ignore
- with ZipFile(args[1], 'w', allowZip64=True) as zf:
+ with ZipFile(args[1], 'w') as zf:
for src in args[2:]:
addToZip(zf, src, os.path.basename(src))