From 3a4bbb96387d2f14d52ad4c7e816932b667f0d9d Mon Sep 17 00:00:00 2001 From: Hyunjee Kim Date: Wed, 20 Mar 2019 10:58:57 +0900 Subject: [PATCH] Imported Upstream version 3.6.7 --- .azure-pipelines/ci.yml | 136 + .azure-pipelines/docker-steps.yml | 76 + .azure-pipelines/docs-steps.yml | 46 + .azure-pipelines/macos-steps.yml | 25 + .azure-pipelines/posix-deps.sh | 26 + .azure-pipelines/posix-steps.yml | 63 + .azure-pipelines/pr.yml | 86 + .azure-pipelines/prebuild-checks.yml | 36 + .azure-pipelines/windows-steps.yml | 32 + .vsts/docs-release.yml | 43 - .vsts/docs.yml | 44 - .vsts/linux-buildbot.yml | 64 - .vsts/linux-coverage.yml | 70 - .vsts/linux-deps.yml | 30 - .vsts/linux-pr.yml | 68 - .vsts/macos-buildbot.yml | 37 - .vsts/macos-pr.yml | 37 - .vsts/windows-buildbot.yml | 49 - .vsts/windows-pr.yml | 49 - Doc/README.rst | 2 +- Doc/c-api/marshal.rst | 24 +- Doc/c-api/memory.rst | 2 +- Doc/conf.py | 2 +- Doc/distutils/builtdist.rst | 2 +- Doc/distutils/introduction.rst | 2 +- Doc/faq/design.rst | 48 +- Doc/faq/programming.rst | 4 +- Doc/glossary.rst | 15 +- Doc/howto/functional.rst | 46 +- Doc/includes/run-func.c | 2 +- Doc/library/asyncio-protocol.rst | 8 + Doc/library/asyncio-stream.rst | 4 +- Doc/library/collections.rst | 1 + Doc/library/configparser.rst | 11 +- Doc/library/contextlib.rst | 28 +- Doc/library/ctypes.rst | 20 +- Doc/library/datetime.rst | 18 +- Doc/library/depgraph-output.png | Bin 24719 -> 0 bytes Doc/library/dis.rst | 60 +- Doc/library/email.errors.rst | 4 + Doc/library/email.parser.rst | 2 +- Doc/library/email.rst | 2 +- Doc/library/enum.rst | 8 +- Doc/library/exceptions.rst | 2 +- Doc/library/functions.rst | 25 +- Doc/library/hashlib.rst | 29 +- Doc/library/html.rst | 2 +- Doc/library/importlib.rst | 4 +- Doc/library/io.rst | 2 +- Doc/library/json.rst | 11 +- Doc/library/logging.rst | 50 +- Doc/library/multiprocessing.rst | 4 +- Doc/library/optparse.rst | 2 +- Doc/library/pathlib.rst | 9 +- Doc/library/platform.rst | 2 +- Doc/library/profile.rst | 5 + Doc/library/re.rst | 8 +- Doc/library/select.rst | 11 +- Doc/library/site.rst | 2 +- Doc/library/smtplib.rst | 23 +- Doc/library/socket.rst | 67 +- Doc/library/sqlite3.rst | 54 +- Doc/library/ssl.rst | 65 +- Doc/library/statistics.rst | 4 + Doc/library/stdtypes.rst | 54 +- Doc/library/string.rst | 15 +- Doc/library/subprocess.rst | 5 +- Doc/library/tempfile.rst | 2 +- Doc/library/threading.rst | 3 +- Doc/library/traceback.rst | 36 +- Doc/library/typing.rst | 25 +- Doc/library/unittest.mock.rst | 42 +- Doc/library/unittest.rst | 12 +- Doc/library/urllib.request.rst | 8 +- Doc/library/xml.dom.pulldom.rst | 14 + Doc/library/xml.rst | 6 +- Doc/library/xml.sax.rst | 8 + Doc/library/xmlrpc.client.rst | 2 +- Doc/make.bat | 3 + Doc/reference/compound_stmts.rst | 16 +- Doc/reference/expressions.rst | 20 +- Doc/reference/simple_stmts.rst | 22 +- Doc/reference/toplevel_components.rst | 11 +- Doc/tools/extensions/escape4chm.py | 39 + Doc/tutorial/classes.rst | 4 +- Doc/tutorial/inputoutput.rst | 2 +- Doc/tutorial/introduction.rst | 20 +- Doc/tutorial/modules.rst | 2 +- Doc/using/windows.rst | 8 +- Doc/whatsnew/3.3.rst | 6 +- Doc/whatsnew/3.5.rst | 2 +- Doc/whatsnew/3.6.rst | 37 +- Include/patchlevel.h | 4 +- Include/pyexpat.h | 4 +- Lib/_pyio.py | 1 + Lib/antigravity.py | 2 +- Lib/asyncio/base_events.py | 24 +- Lib/asyncio/proactor_events.py | 12 +- Lib/asyncio/selector_events.py | 10 +- Lib/asyncio/test_utils.py | 17 +- Lib/base64.py | 17 +- Lib/cProfile.py | 4 +- Lib/concurrent/futures/_base.py | 8 +- Lib/configparser.py | 8 +- Lib/ctypes/test/test_as_parameter.py | 2 +- Lib/ctypes/test/test_win32.py | 18 + Lib/datetime.py | 9 + Lib/distutils/_msvccompiler.py | 4 +- Lib/distutils/log.py | 6 +- Lib/distutils/spawn.py | 2 +- Lib/distutils/tests/test_bdist.py | 3 + Lib/distutils/tests/test_bdist_wininst.py | 2 + Lib/distutils/tests/test_log.py | 50 +- Lib/distutils/tests/test_spawn.py | 49 +- Lib/email/_encoded_words.py | 48 +- Lib/email/_header_value_parser.py | 2 +- Lib/email/errors.py | 3 + Lib/enum.py | 10 +- Lib/functools.py | 5 + Lib/hashlib.py | 29 +- Lib/http/client.py | 2 +- Lib/http/server.py | 2 +- Lib/idlelib/NEWS.txt | 67 +- Lib/idlelib/autocomplete.py | 1 - Lib/idlelib/autocomplete_w.py | 11 +- Lib/idlelib/autoexpand.py | 4 +- Lib/idlelib/browser.py | 2 +- Lib/idlelib/{calltips.py => calltip.py} | 10 +- Lib/idlelib/calltip_w.py | 208 +- Lib/idlelib/codecontext.py | 8 +- Lib/idlelib/colorizer.py | 16 +- Lib/idlelib/config-main.def | 3 + Lib/idlelib/config.py | 8 +- Lib/idlelib/config_key.py | 6 +- Lib/idlelib/configdialog.py | 46 +- Lib/idlelib/debugger.py | 14 +- Lib/idlelib/debugger_r.py | 5 + Lib/idlelib/debugobj.py | 5 +- Lib/idlelib/debugobj_r.py | 5 + Lib/idlelib/editor.py | 70 +- Lib/idlelib/filelist.py | 20 +- Lib/idlelib/grep.py | 4 +- Lib/idlelib/help.py | 3 + Lib/idlelib/help_about.py | 6 +- Lib/idlelib/hyperparser.py | 4 +- Lib/idlelib/idle_test/README.txt | 60 +- Lib/idlelib/idle_test/htest.py | 16 +- Lib/idlelib/idle_test/template.py | 30 + Lib/idlelib/idle_test/test_autocomplete.py | 7 +- Lib/idlelib/idle_test/test_autocomplete_w.py | 32 + Lib/idlelib/idle_test/test_autoexpand.py | 30 +- Lib/idlelib/idle_test/test_browser.py | 16 +- .../{test_calltips.py => test_calltip.py} | 51 +- Lib/idlelib/idle_test/test_calltip_w.py | 29 + Lib/idlelib/idle_test/test_codecontext.py | 12 +- Lib/idlelib/idle_test/test_colorizer.py | 9 +- Lib/idlelib/idle_test/test_config.py | 19 +- Lib/idlelib/idle_test/test_config_key.py | 4 +- Lib/idlelib/idle_test/test_configdialog.py | 7 +- Lib/idlelib/idle_test/test_debugger.py | 8 +- Lib/idlelib/idle_test/test_debugger_r.py | 29 + Lib/idlelib/idle_test/test_debugobj.py | 57 + Lib/idlelib/idle_test/test_debugobj_r.py | 22 + Lib/idlelib/idle_test/test_delegator.py | 6 +- Lib/idlelib/idle_test/test_editor.py | 40 +- Lib/idlelib/idle_test/test_filelist.py | 33 + Lib/idlelib/idle_test/test_grep.py | 12 +- Lib/idlelib/idle_test/test_help.py | 8 +- Lib/idlelib/idle_test/test_help_about.py | 17 +- Lib/idlelib/idle_test/test_history.py | 6 +- Lib/idlelib/idle_test/test_hyperparser.py | 6 +- Lib/idlelib/idle_test/test_iomenu.py | 261 +- Lib/idlelib/idle_test/test_macosx.py | 6 +- Lib/idlelib/idle_test/test_mainmenu.py | 21 + Lib/idlelib/idle_test/test_multicall.py | 40 + Lib/idlelib/idle_test/test_outwin.py | 7 +- Lib/idlelib/idle_test/test_paragraph.py | 47 +- Lib/idlelib/idle_test/test_parenmatch.py | 4 +- Lib/idlelib/idle_test/test_pathbrowser.py | 12 +- Lib/idlelib/idle_test/test_percolator.py | 8 +- Lib/idlelib/idle_test/test_pyparse.py | 9 +- Lib/idlelib/idle_test/test_pyshell.py | 42 + Lib/idlelib/idle_test/test_query.py | 12 +- Lib/idlelib/idle_test/test_redirector.py | 10 +- Lib/idlelib/idle_test/test_replace.py | 9 +- Lib/idlelib/idle_test/test_rpc.py | 30 + Lib/idlelib/idle_test/test_rstrip.py | 20 +- Lib/idlelib/idle_test/test_run.py | 237 +- Lib/idlelib/idle_test/test_runscript.py | 33 + Lib/idlelib/idle_test/test_scrolledlist.py | 10 +- Lib/idlelib/idle_test/test_search.py | 26 +- Lib/idlelib/idle_test/test_searchbase.py | 8 +- Lib/idlelib/idle_test/test_searchengine.py | 19 +- Lib/idlelib/idle_test/test_squeezer.py | 509 ++++ Lib/idlelib/idle_test/test_stackviewer.py | 47 + Lib/idlelib/idle_test/test_statusbar.py | 41 + Lib/idlelib/idle_test/test_textview.py | 17 +- Lib/idlelib/idle_test/test_tooltip.py | 146 + Lib/idlelib/idle_test/test_tree.py | 6 +- Lib/idlelib/idle_test/test_undo.py | 10 +- Lib/idlelib/idle_test/test_warning.py | 12 +- Lib/idlelib/idle_test/test_window.py | 45 + Lib/idlelib/idle_test/test_zoomheight.py | 39 + Lib/idlelib/iomenu.py | 8 +- Lib/idlelib/macosx.py | 8 +- Lib/idlelib/mainmenu.py | 19 +- Lib/idlelib/multicall.py | 3 + Lib/idlelib/outwin.py | 4 +- Lib/idlelib/paragraph.py | 5 +- Lib/idlelib/parenmatch.py | 4 +- Lib/idlelib/percolator.py | 5 +- Lib/idlelib/pyparse.py | 6 +- Lib/idlelib/pyshell.py | 8 +- Lib/idlelib/query.py | 8 +- Lib/idlelib/redirector.py | 5 +- Lib/idlelib/replace.py | 5 +- Lib/idlelib/rpc.py | 9 + Lib/idlelib/rstrip.py | 6 +- Lib/idlelib/run.py | 4 +- Lib/idlelib/runscript.py | 5 + Lib/idlelib/scrolledlist.py | 4 +- Lib/idlelib/search.py | 5 +- Lib/idlelib/searchbase.py | 5 +- Lib/idlelib/searchengine.py | 5 +- Lib/idlelib/squeezer.py | 355 +++ Lib/idlelib/stackviewer.py | 5 +- Lib/idlelib/statusbar.py | 3 + Lib/idlelib/textview.py | 87 +- Lib/idlelib/tooltip.py | 204 +- Lib/idlelib/tree.py | 4 +- Lib/idlelib/undo.py | 4 +- Lib/idlelib/{windows.py => window.py} | 7 +- Lib/idlelib/zoomheight.py | 9 +- Lib/imaplib.py | 7 +- Lib/inspect.py | 9 +- Lib/logging/__init__.py | 3 +- Lib/logging/config.py | 15 +- Lib/multiprocessing/connection.py | 8 +- Lib/multiprocessing/managers.py | 11 +- Lib/multiprocessing/pool.py | 74 +- Lib/multiprocessing/queues.py | 4 +- Lib/multiprocessing/reduction.py | 2 +- Lib/multiprocessing/synchronize.py | 7 +- Lib/ntpath.py | 42 +- Lib/pathlib.py | 6 +- Lib/pickletools.py | 8 +- Lib/platform.py | 53 +- Lib/pydoc.py | 5 +- Lib/pydoc_data/topics.py | 137 +- Lib/random.py | 4 +- Lib/site.py | 11 +- Lib/smtplib.py | 14 +- Lib/ssl.py | 11 +- Lib/tarfile.py | 20 +- Lib/test/_test_multiprocessing.py | 58 +- Lib/test/allsans.pem | 106 +- Lib/test/capath/b1930218.0 | 41 +- Lib/test/capath/ceff1710.0 | 41 +- Lib/test/datetimetester.py | 6 +- Lib/test/dh1024.pem | 7 - Lib/test/eintrdata/eintr_tester.py | 3 +- Lib/test/ffdh3072.pem | 41 + Lib/test/inspect_fodder.py | 6 + Lib/test/keycert.passwd.pem | 89 +- Lib/test/keycert.pem | 89 +- Lib/test/keycert2.pem | 89 +- Lib/test/keycert3.pem | 211 +- Lib/test/keycert4.pem | 211 +- Lib/test/libregrtest/cmdline.py | 4 + Lib/test/libregrtest/main.py | 63 +- Lib/test/libregrtest/runtest.py | 33 +- Lib/test/libregrtest/runtest_mp.py | 9 +- Lib/test/libregrtest/utils.py | 27 +- Lib/test/lock_tests.py | 5 +- Lib/test/make_ssl_certs.py | 92 +- Lib/test/pycacert.pem | 139 +- Lib/test/pycakey.pem | 64 +- Lib/test/pythoninfo.py | 2 +- Lib/test/revocation.crl | 19 +- Lib/test/ssl_cert.pem | 37 +- Lib/test/ssl_key.passwd.pem | 52 +- Lib/test/ssl_key.pem | 52 +- Lib/test/support/__init__.py | 21 +- Lib/test/support/testresult.py | 201 ++ Lib/test/test_argparse.py | 18 +- Lib/test/test_asyncio/test_base_events.py | 92 +- Lib/test/test_asyncio/test_events.py | 34 +- Lib/test/test_asyncio/test_proactor_events.py | 5 + Lib/test/test_asyncio/test_selector_events.py | 12 +- Lib/test/test_asyncio/test_tasks.py | 36 + Lib/test/test_base64.py | 19 +- Lib/test/test_builtin.py | 9 + Lib/test/test_call.py | 18 + Lib/test/test_capi.py | 261 +- Lib/test/test_complex.py | 3 + Lib/test/test_concurrent_futures.py | 6 +- Lib/test/test_dbm_gnu.py | 11 +- Lib/test/test_deque.py | 15 + Lib/test/test_dict.py | 33 +- Lib/test/test_email/test__encoded_words.py | 6 + .../test_email/test__header_value_parser.py | 34 + Lib/test/test_email/test_defect_handling.py | 17 + Lib/test/test_enum.py | 17 + Lib/test/test_epoll.py | 27 +- Lib/test/test_file.py | 3 +- Lib/test/test_float.py | 3 + Lib/test/test_flufl.py | 23 +- Lib/test/test_ftplib.py | 5 + Lib/test/test_functools.py | 7 + Lib/test/test_gc.py | 17 +- Lib/test/test_gdb.py | 42 +- Lib/test/test_genericpath.py | 81 +- Lib/test/test_getargs2.py | 180 +- Lib/test/test_gzip.py | 99 +- Lib/test/test_hashlib.py | 20 +- Lib/test/test_httplib.py | 15 + Lib/test/test_imaplib.py | 14 + Lib/test/test_import/__init__.py | 7 +- Lib/test/test_importlib/test_util.py | 2 +- Lib/test/test_inspect.py | 14 +- Lib/test/test_io.py | 21 + Lib/test/test_logging.py | 81 +- Lib/test/test_long.py | 7 + Lib/test/test_marshal.py | 32 +- .../test_multiprocessing_main_handling.py | 16 +- Lib/test/test_ntpath.py | 4 + Lib/test/test_opcodes.py | 13 +- Lib/test/test_os.py | 50 +- Lib/test/test_pkg.py | 19 +- Lib/test/test_platform.py | 80 +- Lib/test/test_posix.py | 41 +- Lib/test/test_posixpath.py | 27 +- Lib/test/test_pulldom.py | 7 + Lib/test/test_random.py | 8 + Lib/test/test_regrtest.py | 25 + Lib/test/test_resource.py | 3 - Lib/test/test_sax.py | 60 +- Lib/test/test_script_helper.py | 47 +- Lib/test/test_shutil.py | 2 + Lib/test/test_signal.py | 1 - Lib/test/test_site.py | 78 +- Lib/test/test_smtplib.py | 71 +- Lib/test/test_socket.py | 49 +- Lib/test/test_spwd.py | 2 - Lib/test/test_ssl.py | 301 +- Lib/test/test_subprocess.py | 30 + Lib/test/test_support.py | 2 +- Lib/test/test_sysconfig.py | 25 +- Lib/test/test_tcl.py | 37 + Lib/test/test_threadsignals.py | 8 + Lib/test/test_time.py | 43 +- Lib/test/test_tokenize.py | 64 +- Lib/test/test_tools/test_sundry.py | 18 +- Lib/test/test_traceback.py | 61 +- Lib/test/test_unicodedata.py | 13 + Lib/test/test_urllib2_localnet.py | 2 +- Lib/test/test_venv.py | 62 +- Lib/test/test_warnings/__init__.py | 12 +- Lib/test/test_webbrowser.py | 16 +- Lib/test/test_xml_etree.py | 13 + Lib/test/test_zipfile.py | 14 + Lib/test/test_zlib.py | 23 +- Lib/test/wrongcert.pem | 32 - Lib/tkinter/__init__.py | 5 +- Lib/tkinter/test/support.py | 14 +- Lib/tkinter/test/test_tkinter/test_widgets.py | 25 +- Lib/tokenize.py | 12 +- Lib/traceback.py | 58 +- Lib/turtledemo/penrose.py | 3 - Lib/turtledemo/{wikipedia.py => rosette.py} | 0 Lib/turtledemo/tree.py | 1 - Lib/unittest/test/test_break.py | 15 +- Lib/webbrowser.py | 7 +- Lib/xml/sax/expatreader.py | 2 +- Lib/zipfile.py | 28 +- Mac/BuildScript/build-installer.py | 34 +- Misc/ACKS | 4 + Misc/HISTORY | 10 +- Misc/NEWS | 529 +++- Misc/SpecialBuilds.txt | 2 +- Misc/valgrind-python.supp | 22 +- Modules/Setup.dist | 8 - Modules/_asynciomodule.c | 15 +- Modules/_blake2/blake2b_impl.c | 67 +- Modules/_blake2/blake2s_impl.c | 67 +- Modules/_blake2/clinic/blake2b_impl.c.h | 59 +- Modules/_blake2/clinic/blake2s_impl.c.h | 59 +- Modules/_bz2module.c | 8 +- Modules/_collectionsmodule.c | 16 +- Modules/_csv.c | 51 +- Modules/_ctypes/_ctypes_test.c | 194 ++ Modules/_ctypes/callproc.c | 4 +- Modules/_ctypes/libffi_msvc/ffi.c | 19 +- Modules/_ctypes/libffi_msvc/ffi.h | 3 + Modules/_ctypes/libffi_msvc/prep_cif.c | 7 +- Modules/_ctypes/libffi_msvc/win32.c | 2 +- Modules/_datetimemodule.c | 17 +- Modules/_elementtree.c | 5 + Modules/_hashopenssl.c | 4 + Modules/_io/iobase.c | 11 +- Modules/_io/stringio.c | 4 +- Modules/_io/textio.c | 26 +- Modules/_io/winconsoleio.c | 6 +- Modules/_json.c | 6 +- Modules/_lsprof.c | 4 +- Modules/_lzmamodule.c | 8 +- Modules/_pickle.c | 79 +- Modules/_posixsubprocess.c | 24 +- Modules/_scproxy.c | 6 + Modules/_sha3/clinic/sha3module.c.h | 87 +- Modules/_sha3/sha3module.c | 89 +- Modules/_sqlite/connection.c | 16 +- Modules/_sqlite/cursor.c | 4 +- Modules/_sqlite/module.c | 16 +- Modules/_sqlite/module.h | 8 +- Modules/_sqlite/statement.c | 8 +- Modules/_ssl.c | 228 +- Modules/_testbuffer.c | 8 +- Modules/_testcapimodule.c | 3 +- Modules/_testmultiphase.c | 4 +- Modules/_threadmodule.c | 4 +- Modules/_winapi.c | 10 +- Modules/clinic/_ssl.c.h | 20 +- Modules/clinic/_winapi.c.h | 8 +- Modules/expat/expat.h | 4 +- Modules/expat/expat_external.h | 58 +- Modules/expat/internal.h | 2 +- Modules/expat/loadlibrary.c | 2 +- Modules/expat/siphash.h | 17 + Modules/expat/xmlparse.c | 2659 ++++++++--------- Modules/expat/xmltok.c | 43 +- Modules/expat/xmltok.h | 5 - Modules/expat/xmltok_impl.c | 82 +- Modules/grpmodule.c | 2 +- Modules/md5module.c | 2 +- Modules/mmapmodule.c | 2 +- Modules/ossaudiodev.c | 4 +- Modules/posixmodule.c | 33 +- Modules/pwdmodule.c | 2 +- Modules/pyexpat.c | 5 + Modules/selectmodule.c | 9 +- Modules/sha1module.c | 2 +- Modules/sha256module.c | 4 +- Modules/sha512module.c | 4 +- Modules/socketmodule.c | 6 +- Modules/timemodule.c | 7 + Modules/unicodedata.c | 10 +- Objects/abstract.c | 2 +- Objects/bytesobject.c | 8 +- Objects/codeobject.c | 29 +- Objects/dict-common.h | 11 +- Objects/dictobject.c | 31 +- Objects/longobject.c | 2 +- Objects/moduleobject.c | 4 +- Objects/namespaceobject.c | 2 +- Objects/object.c | 5 +- Objects/odictobject.c | 4 +- Objects/rangeobject.c | 8 +- Objects/typeobject.c | 33 +- Objects/unicodeobject.c | 5 +- PC/_msi.c | 7 + PC/launcher.c | 2 + PC/pyshellext.cpp | 5 + PCbuild/get_externals.bat | 2 +- PCbuild/python.props | 2 +- Parser/myreadline.c | 8 +- Parser/parsetok.c | 11 +- Python/ast.c | 2 + Python/bltinmodule.c | 5 + Python/ceval.c | 15 +- Python/errors.c | 1 + Python/fileutils.c | 2 +- Python/formatter_unicode.c | 17 +- Python/getargs.c | 4 +- Python/pystrtod.c | 4 +- Python/pythonrun.c | 24 +- Python/symtable.c | 6 +- Python/traceback.c | 30 +- README.rst | 8 +- Tools/gdb/libpython.py | 52 +- Tools/i18n/msgfmt.py | 8 +- Tools/msi/README.txt | 8 + Tools/msi/distutils.command.bdist_wininst.py | 3 + Tools/ssl/multissltests.py | 163 +- aclocal.m4 | 78 +- configure | 118 +- configure.ac | 30 +- pyconfig.h.in | 2 +- 488 files changed, 11274 insertions(+), 5646 deletions(-) create mode 100644 .azure-pipelines/ci.yml create mode 100644 .azure-pipelines/docker-steps.yml create mode 100644 .azure-pipelines/docs-steps.yml create mode 100644 .azure-pipelines/macos-steps.yml create mode 100755 .azure-pipelines/posix-deps.sh create mode 100644 .azure-pipelines/posix-steps.yml create mode 100644 .azure-pipelines/pr.yml create mode 100644 .azure-pipelines/prebuild-checks.yml create mode 100644 .azure-pipelines/windows-steps.yml delete mode 100644 .vsts/docs-release.yml delete mode 100644 .vsts/docs.yml delete mode 100644 .vsts/linux-buildbot.yml delete mode 100644 .vsts/linux-coverage.yml delete mode 100644 .vsts/linux-deps.yml delete mode 100644 .vsts/linux-pr.yml delete mode 100644 .vsts/macos-buildbot.yml delete mode 100644 .vsts/macos-pr.yml delete mode 100644 .vsts/windows-buildbot.yml delete mode 100644 .vsts/windows-pr.yml delete mode 100644 Doc/library/depgraph-output.png create mode 100644 Doc/tools/extensions/escape4chm.py rename Lib/idlelib/{calltips.py => calltip.py} (95%) create mode 100644 Lib/idlelib/idle_test/template.py create mode 100644 Lib/idlelib/idle_test/test_autocomplete_w.py rename Lib/idlelib/idle_test/{test_calltips.py => test_calltip.py} (82%) create mode 100644 Lib/idlelib/idle_test/test_calltip_w.py create mode 100644 Lib/idlelib/idle_test/test_debugger_r.py create mode 100644 Lib/idlelib/idle_test/test_debugobj.py create mode 100644 Lib/idlelib/idle_test/test_debugobj_r.py create mode 100644 Lib/idlelib/idle_test/test_filelist.py create mode 100644 Lib/idlelib/idle_test/test_mainmenu.py create mode 100644 Lib/idlelib/idle_test/test_multicall.py create mode 100644 Lib/idlelib/idle_test/test_pyshell.py create mode 100644 Lib/idlelib/idle_test/test_rpc.py create mode 100644 Lib/idlelib/idle_test/test_runscript.py create mode 100644 Lib/idlelib/idle_test/test_squeezer.py create mode 100644 Lib/idlelib/idle_test/test_stackviewer.py create mode 100644 Lib/idlelib/idle_test/test_statusbar.py create mode 100644 Lib/idlelib/idle_test/test_tooltip.py create mode 100644 Lib/idlelib/idle_test/test_window.py create mode 100644 Lib/idlelib/idle_test/test_zoomheight.py create mode 100644 Lib/idlelib/squeezer.py rename Lib/idlelib/{windows.py => window.py} (93%) delete mode 100644 Lib/test/dh1024.pem create mode 100644 Lib/test/ffdh3072.pem create mode 100644 Lib/test/support/testresult.py delete mode 100644 Lib/test/wrongcert.pem rename Lib/turtledemo/{wikipedia.py => rosette.py} (100%) diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml new file mode 100644 index 00000000..49a7bb62 --- /dev/null +++ b/.azure-pipelines/ci.yml @@ -0,0 +1,136 @@ +variables: + manylinux: false + coverage: false + +jobs: +- job: Prebuild + displayName: Pre-build checks + + pool: + vmImage: ubuntu-16.04 + + steps: + - template: ./prebuild-checks.yml + + +- job: Docs_PR + displayName: Docs PR + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['docs.run'], 'true')) + + pool: + vmImage: ubuntu-16.04 + + steps: + - template: ./docs-steps.yml + parameters: + upload: true + + +- job: macOS_CI_Tests + displayName: macOS CI Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + variables: + testRunTitle: '$(build.sourceBranchName)-macos' + testRunPlatform: macos + + pool: + vmImage: xcode9-macos10.13 + + steps: + - template: ./macos-steps.yml + + +- job: Ubuntu_CI_Tests + displayName: Ubuntu CI Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + pool: + vmImage: ubuntu-16.04 + + variables: + testRunTitle: '$(build.sourceBranchName)-linux' + testRunPlatform: linux + openssl_version: 1.1.0g + + steps: + - template: ./posix-steps.yml + + +- job: ManyLinux1_CI_Tests + displayName: ManyLinux1 CI Tests + dependsOn: Prebuild + condition: | + and( + and( + succeeded(), + eq(variables['manylinux'], 'true') + ), + eq(dependencies.Prebuild.outputs['tests.run'], 'true') + ) + + pool: + vmImage: ubuntu-16.04 + + variables: + testRunTitle: '$(build.sourceBranchName)-manylinux1' + testRunPlatform: manylinux1 + imageName: 'dockcross/manylinux-x64' + + steps: + - template: ./docker-steps.yml + + +- job: Ubuntu_Coverage_CI_Tests + displayName: Ubuntu CI Tests (coverage) + dependsOn: Prebuild + condition: | + and( + and( + succeeded(), + eq(variables['coverage'], 'true') + ), + eq(dependencies.Prebuild.outputs['tests.run'], 'true') + ) + + pool: + vmImage: ubuntu-16.04 + + variables: + testRunTitle: '$(Build.SourceBranchName)-linux-coverage' + testRunPlatform: linux-coverage + openssl_version: 1.1.0g + + steps: + - template: ./posix-steps.yml + parameters: + coverage: true + + +- job: Windows_CI_Tests + displayName: Windows CI Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + pool: + vmImage: vs2017-win2016 + + strategy: + matrix: + win32: + arch: win32 + buildOpt: + testRunTitle: '$(Build.SourceBranchName)-win32' + testRunPlatform: win32 + win64: + arch: amd64 + buildOpt: '-p x64' + testRunTitle: '$(Build.SourceBranchName)-win64' + testRunPlatform: win64 + maxParallel: 2 + + steps: + - template: ./windows-steps.yml diff --git a/.azure-pipelines/docker-steps.yml b/.azure-pipelines/docker-steps.yml new file mode 100644 index 00000000..ba4dfd72 --- /dev/null +++ b/.azure-pipelines/docker-steps.yml @@ -0,0 +1,76 @@ +steps: +- checkout: self + clean: true + fetchDepth: 5 + +- ${{ if ne(parameters.targetBranch, '') }}: + - script: | + git fetch -q origin ${{ parameters.targetbranch }} + if ! git diff --name-only HEAD $(git merge-base HEAD FETCH_HEAD) | grep -qvE '(\.rst$|^Doc|^Misc)' + then + echo "Only docs were updated, stopping build process." + echo "##vso[task.setvariable variable=DocOnly]true" + exit + fi + displayName: Detect doc-only changes + +- task: docker@0 + displayName: 'Configure CPython (debug)' + inputs: + action: 'Run an image' + imageName: $(imageName) + volumes: | + $(build.sourcesDirectory):/src + $(build.binariesDirectory):/build + workDir: '/src' + containerCommand: './configure --with-pydebug' + detached: false + condition: and(succeeded(), ne(variables['DocOnly'], 'true')) + +- task: docker@0 + displayName: 'Build CPython' + inputs: + action: 'Run an image' + imageName: $(imageName) + volumes: | + $(build.sourcesDirectory):/src + $(build.binariesDirectory):/build + workDir: '/src' + containerCommand: 'make -s -j4' + detached: false + condition: and(succeeded(), ne(variables['DocOnly'], 'true')) + +- task: docker@0 + displayName: 'Display build info' + inputs: + action: 'Run an image' + imageName: $(imageName) + volumes: | + $(build.sourcesDirectory):/src + $(build.binariesDirectory):/build + workDir: '/src' + containerCommand: 'make pythoninfo' + detached: false + condition: and(succeeded(), ne(variables['DocOnly'], 'true')) + +- task: docker@0 + displayName: 'Tests' + inputs: + action: 'Run an image' + imageName: $(imageName) + volumes: | + $(build.sourcesDirectory):/src + $(build.binariesDirectory):/build + workDir: '/src' + containerCommand: 'make buildbottest TESTOPTS="-j4 -uall,-cpu --junit-xml=/build/test-results.xml"' + detached: false + condition: and(succeeded(), ne(variables['DocOnly'], 'true')) + +- task: PublishTestResults@2 + displayName: 'Publish Test Results' + inputs: + testResultsFiles: '$(build.binariesDirectory)/test-results.xml' + mergeTestResults: true + testRunTitle: $(testRunTitle) + platform: $(testRunPlatform) + condition: and(succeededOrFailed(), ne(variables['DocOnly'], 'true')) diff --git a/.azure-pipelines/docs-steps.yml b/.azure-pipelines/docs-steps.yml new file mode 100644 index 00000000..c0404aeb --- /dev/null +++ b/.azure-pipelines/docs-steps.yml @@ -0,0 +1,46 @@ +parameters: + latex: false + upload: false + +steps: +- checkout: self + clean: true + fetchDepth: 5 + +- task: UsePythonVersion@0 + displayName: 'Use Python 3.6 or later' + inputs: + versionSpec: '>=3.6' + +- script: python -m pip install sphinx~=1.6.1 blurb python-docs-theme + displayName: 'Install build dependencies' + +- ${{ if ne(parameters.latex, 'true') }}: + - script: make check suspicious html PYTHON=python + workingDirectory: '$(build.sourcesDirectory)/Doc' + displayName: 'Build documentation' + +- ${{ if eq(parameters.latex, 'true') }}: + - script: sudo apt-get update && sudo apt-get install -qy --force-yes texlive-full + displayName: 'Install LaTeX' + + - script: make dist PYTHON=python SPHINXBUILD='python -m sphinx' BLURB='python -m blurb' + workingDirectory: '$(build.sourcesDirectory)/Doc' + displayName: 'Build documentation' + +- ${{ if eq(parameters.upload, 'true') }}: + - task: PublishBuildArtifacts@1 + displayName: 'Publish docs' + + inputs: + PathToPublish: '$(build.sourcesDirectory)/Doc/build' + ArtifactName: docs + publishLocation: Container + + - ${{ if eq(parameters.latex, 'true') }}: + - task: PublishBuildArtifacts@1 + displayName: 'Publish dist' + inputs: + PathToPublish: '$(build.sourcesDirectory)/Doc/dist' + ArtifactName: docs_dist + publishLocation: Container diff --git a/.azure-pipelines/macos-steps.yml b/.azure-pipelines/macos-steps.yml new file mode 100644 index 00000000..64708168 --- /dev/null +++ b/.azure-pipelines/macos-steps.yml @@ -0,0 +1,25 @@ +steps: +- checkout: self + clean: true + fetchDepth: 5 + +- script: ./configure --with-pydebug --with-openssl=/usr/local/opt/openssl --prefix=/opt/python-azdev + displayName: 'Configure CPython (debug)' + +- script: make -s -j4 + displayName: 'Build CPython' + +- script: make pythoninfo + displayName: 'Display build info' + +- script: make buildbottest TESTOPTS="-j4 -uall,-cpu --junit-xml=$(build.binariesDirectory)/test-results.xml" + displayName: 'Tests' + +- task: PublishTestResults@2 + displayName: 'Publish Test Results' + inputs: + testResultsFiles: '$(build.binariesDirectory)/test-results.xml' + mergeTestResults: true + testRunTitle: $(testRunTitle) + platform: $(testRunPlatform) + condition: succeededOrFailed() diff --git a/.azure-pipelines/posix-deps.sh b/.azure-pipelines/posix-deps.sh new file mode 100755 index 00000000..a5721075 --- /dev/null +++ b/.azure-pipelines/posix-deps.sh @@ -0,0 +1,26 @@ +sudo apt-get update + +sudo apt-get -yq install \ + build-essential \ + zlib1g-dev \ + libbz2-dev \ + liblzma-dev \ + libncurses5-dev \ + libreadline6-dev \ + libsqlite3-dev \ + libssl-dev \ + libgdbm-dev \ + tk-dev \ + lzma \ + lzma-dev \ + liblzma-dev \ + libffi-dev \ + uuid-dev \ + xvfb + +if [ ! -z "$1" ] +then + echo ##vso[task.prependpath]$PWD/multissl/openssl/$1 + echo ##vso[task.setvariable variable=OPENSSL_DIR]$PWD/multissl/openssl/$1 + python3 Tools/ssl/multissltests.py --steps=library --base-directory $PWD/multissl --openssl $1 --system Linux +fi diff --git a/.azure-pipelines/posix-steps.yml b/.azure-pipelines/posix-steps.yml new file mode 100644 index 00000000..9fec9be8 --- /dev/null +++ b/.azure-pipelines/posix-steps.yml @@ -0,0 +1,63 @@ +parameters: + coverage: false + +steps: +- checkout: self + clean: true + fetchDepth: 5 + +- script: ./.azure-pipelines/posix-deps.sh $(openssl_version) + displayName: 'Install dependencies' + +- script: ./configure --with-pydebug + displayName: 'Configure CPython (debug)' + +- script: make -s -j4 + displayName: 'Build CPython' + +- ${{ if eq(parameters.coverage, 'true') }}: + - script: ./python -m venv venv && ./venv/bin/python -m pip install -U coverage + displayName: 'Set up virtual environment' + + - script: ./venv/bin/python -m test.pythoninfo + displayName: 'Display build info' + + - script: | + xvfb-run ./venv/bin/python -m coverage run --pylib -m test \ + --fail-env-changed \ + -uall,-cpu \ + --junit-xml=$(build.binariesDirectory)/test-results.xml" \ + -x test_multiprocessing_fork \ + -x test_multiprocessing_forkserver \ + -x test_multiprocessing_spawn \ + -x test_concurrent_futures + displayName: 'Tests with coverage' + + - script: ./venv/bin/python -m coverage xml + displayName: 'Generate coverage.xml' + + - script: source ./venv/bin/activate && bash <(curl -s https://codecov.io/bash) + displayName: 'Publish code coverage results' + + +- ${{ if ne(parameters.coverage, 'true') }}: + - script: make pythoninfo + displayName: 'Display build info' + + - script: xvfb-run make buildbottest TESTOPTS="-j4 -uall,-cpu --junit-xml=$(build.binariesDirectory)/test-results.xml" + displayName: 'Tests' + + +- script: ./python Tools/scripts/patchcheck.py --travis true + displayName: 'Run patchcheck.py' + condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest')) + + +- task: PublishTestResults@2 + displayName: 'Publish Test Results' + inputs: + testResultsFiles: '$(build.binariesDirectory)/test-results.xml' + mergeTestResults: true + testRunTitle: $(testRunTitle) + platform: $(testRunPlatform) + condition: succeededOrFailed() diff --git a/.azure-pipelines/pr.yml b/.azure-pipelines/pr.yml new file mode 100644 index 00000000..2d7fba9c --- /dev/null +++ b/.azure-pipelines/pr.yml @@ -0,0 +1,86 @@ +jobs: +- job: Prebuild + displayName: Pre-build checks + + pool: + vmImage: ubuntu-16.04 + + steps: + - template: ./prebuild-checks.yml + + +- job: Docs_PR + displayName: Docs PR + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['docs.run'], 'true')) + + pool: + vmImage: ubuntu-16.04 + + steps: + - template: ./docs-steps.yml + + +- job: macOS_PR_Tests + displayName: macOS PR Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + variables: + testRunTitle: '$(system.pullRequest.TargetBranch)-macos' + testRunPlatform: macos + + pool: + vmImage: xcode9-macos10.13 + + steps: + - template: ./macos-steps.yml + parameters: + targetBranch: $(System.PullRequest.TargetBranch) + + +- job: Ubuntu_PR_Tests + displayName: Ubuntu PR Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + pool: + vmImage: ubuntu-16.04 + + variables: + testRunTitle: '$(system.pullRequest.TargetBranch)-linux' + testRunPlatform: linux + openssl_version: 1.1.0g + + steps: + - template: ./posix-steps.yml + parameters: + targetBranch: $(System.PullRequest.TargetBranch) + + +- job: Windows_PR_Tests + displayName: Windows PR Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + pool: + vmImage: vs2017-win2016 + + strategy: + matrix: + win32: + arch: win32 + buildOpt: + testRunTitle: '$(System.PullRequest.TargetBranch)-win32' + testRunPlatform: win32 + win64: + arch: amd64 + buildOpt: '-p x64' + testRunTitle: '$(System.PullRequest.TargetBranch)-win64' + testRunPlatform: win64 + maxParallel: 2 + + steps: + - template: ./windows-steps.yml + parameters: + targetBranch: $(System.PullRequest.TargetBranch) diff --git a/.azure-pipelines/prebuild-checks.yml b/.azure-pipelines/prebuild-checks.yml new file mode 100644 index 00000000..30ff642d --- /dev/null +++ b/.azure-pipelines/prebuild-checks.yml @@ -0,0 +1,36 @@ +steps: +- checkout: self + fetchDepth: 5 + +- script: echo "##vso[task.setvariable variable=diffTarget]HEAD~1" + displayName: Set default diff target + +- script: | + git fetch -q origin $(System.PullRequest.TargetBranch) + echo "##vso[task.setvariable variable=diffTarget]HEAD \$(git merge-base HEAD FETCH_HEAD)" + displayName: Fetch comparison tree + condition: and(succeeded(), variables['System.PullRequest.TargetBranch']) + +- script: | + if ! git diff --name-only $(diffTarget) | grep -qE '(\.rst$|^Doc|^Misc)' + then + echo "No docs were updated: docs.run=false" + echo "##vso[task.setvariable variable=run;isOutput=true]false" + else + echo "Docs were updated: docs.run=true" + echo "##vso[task.setvariable variable=run;isOutput=true]true" + fi + displayName: Detect documentation changes + name: docs + +- script: | + if ! git diff --name-only $(diffTarget) | grep -qvE '(\.rst$|^Doc|^Misc)' + then + echo "Only docs were updated: tests.run=false" + echo "##vso[task.setvariable variable=run;isOutput=true]false" + else + echo "Code was updated: tests.run=true" + echo "##vso[task.setvariable variable=run;isOutput=true]true" + fi + displayName: Detect source changes + name: tests diff --git a/.azure-pipelines/windows-steps.yml b/.azure-pipelines/windows-steps.yml new file mode 100644 index 00000000..d8d5f175 --- /dev/null +++ b/.azure-pipelines/windows-steps.yml @@ -0,0 +1,32 @@ +steps: +- checkout: self + clean: true + fetchDepth: 5 + +- powershell: | + # Relocate build outputs outside of source directory to make cleaning faster + Write-Host '##vso[task.setvariable variable=Py_IntDir]$(Build.BinariesDirectory)\obj' + # UNDONE: Do not build to a different directory because of broken tests + Write-Host '##vso[task.setvariable variable=Py_OutDir]$(Build.SourcesDirectory)\PCbuild' + Write-Host '##vso[task.setvariable variable=EXTERNAL_DIR]$(Build.BinariesDirectory)\externals' + displayName: Update build locations + +- script: PCbuild\build.bat -e $(buildOpt) + displayName: 'Build CPython' + +- script: python.bat -m test.pythoninfo + displayName: 'Display build info' + +- script: PCbuild\rt.bat -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results.xml" + displayName: 'Tests' + env: + PREFIX: $(Py_OutDir)\$(arch) + +- task: PublishTestResults@2 + displayName: 'Publish Test Results' + inputs: + testResultsFiles: '$(Build.BinariesDirectory)\test-results.xml' + mergeTestResults: true + testRunTitle: $(testRunTitle) + platform: $(testRunPlatform) + condition: succeededOrFailed() diff --git a/.vsts/docs-release.yml b/.vsts/docs-release.yml deleted file mode 100644 index e90428a4..00000000 --- a/.vsts/docs-release.yml +++ /dev/null @@ -1,43 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted Linux Preview - -#variables: - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -- script: sudo apt-get update && sudo apt-get install -qy --force-yes texlive-full - displayName: 'Install LaTeX' - -- task: UsePythonVersion@0 - displayName: 'Use Python 3.6 or later' - inputs: - versionSpec: '>=3.6' - -- script: python -m pip install sphinx blurb python-docs-theme - displayName: 'Install build dependencies' - -- script: make dist PYTHON=python SPHINXBUILD='python -m sphinx' BLURB='python -m blurb' - workingDirectory: '$(build.sourcesDirectory)/Doc' - displayName: 'Build documentation' - -- task: PublishBuildArtifacts@1 - displayName: 'Publish build' - inputs: - PathToPublish: '$(build.sourcesDirectory)/Doc/build' - ArtifactName: build - publishLocation: Container - -- task: PublishBuildArtifacts@1 - displayName: 'Publish dist' - inputs: - PathToPublish: '$(build.sourcesDirectory)/Doc/dist' - ArtifactName: dist - publishLocation: Container diff --git a/.vsts/docs.yml b/.vsts/docs.yml deleted file mode 100644 index 62f6123a..00000000 --- a/.vsts/docs.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted Linux Preview - -trigger: - branches: - include: - - master - - 3.7 - - 3.6 - paths: - include: - - Doc/* - -#variables: - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -- task: UsePythonVersion@0 - displayName: 'Use Python 3.6 or later' - inputs: - versionSpec: '>=3.6' - -- script: python -m pip install sphinx~=1.6.1 blurb python-docs-theme - displayName: 'Install build dependencies' - -- script: make check suspicious html PYTHON=python - workingDirectory: '$(build.sourcesDirectory)/Doc' - displayName: 'Build documentation' - -- task: PublishBuildArtifacts@1 - displayName: 'Publish build' - condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) - inputs: - PathToPublish: '$(build.sourcesDirectory)/Doc/build' - ArtifactName: build - publishLocation: Container diff --git a/.vsts/linux-buildbot.yml b/.vsts/linux-buildbot.yml deleted file mode 100644 index 76222d10..00000000 --- a/.vsts/linux-buildbot.yml +++ /dev/null @@ -1,64 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted Linux Preview - -trigger: - branches: - include: - - master - - 3.7 - - 3.6 - paths: - exclude: - - Doc/* - - Tools/* - -#variables: - - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -#- template: linux-deps.yml - -# See https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted-templates.md -# For now, we copy/paste the steps -- script: echo "deb-src http://archive.ubuntu.com/ubuntu/ xenial main" > /etc/apt/sources.list.d/python.list && sudo apt-get update - displayName: 'Update apt-get lists' - -- script: > - sudo apt-get -yq install - build-essential - zlib1g-dev - libbz2-dev - liblzma-dev - libncurses5-dev - libreadline6-dev - libsqlite3-dev - libssl-dev - libgdbm-dev - tk-dev - lzma - lzma-dev - liblzma-dev - libffi-dev - uuid-dev - displayName: 'Install dependencies' - -- script: ./configure --with-pydebug - displayName: 'Configure CPython (debug)' - -- script: make -s -j4 - displayName: 'Build CPython' - -- script: make pythoninfo - displayName: 'Display build info' - -- script: make buildbottest TESTOPTS="-j4 -uall,-cpu" - displayName: 'Tests' diff --git a/.vsts/linux-coverage.yml b/.vsts/linux-coverage.yml deleted file mode 100644 index d16d9c9d..00000000 --- a/.vsts/linux-coverage.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted Linux Preview - -trigger: - branches: - include: - - master - - 3.7 - - 3.6 - paths: - exclude: - - Doc/* - - Tools/* - -#variables: - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -#- template: linux-deps.yml - -# See https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted-templates.md -# For now, we copy/paste the steps -- script: echo "deb-src http://archive.ubuntu.com/ubuntu/ xenial main" > /etc/apt/sources.list.d/python.list && sudo apt-get update - displayName: 'Update apt-get lists' - -- script: > - sudo apt-get -yq install - build-essential - zlib1g-dev - libbz2-dev - liblzma-dev - libncurses5-dev - libreadline6-dev - libsqlite3-dev - libssl-dev - libgdbm-dev - tk-dev - lzma - lzma-dev - liblzma-dev - libffi-dev - uuid-dev - displayName: 'Install dependencies' - - -- script: ./configure --with-pydebug - displayName: 'Configure CPython (debug)' - -- script: make -s -j4 - displayName: 'Build CPython' - -- script: ./python -m venv venv && ./venv/bin/python -m pip install -U coverage - displayName: 'Set up virtual environment' - -- script: ./venv/bin/python -m test.pythoninfo - displayName: 'Display build info' - -- script: ./venv/bin/python -m coverage run --pylib -m test -uall,-cpu -x test_multiprocessing_fork -x test_multiprocessing_forkserver -x test_multiprocessing_spawn -x test_concurrent_futures - displayName: 'Tests with coverage' - -- script: source ./venv/bin/activate && bash <(curl -s https://codecov.io/bash) - displayName: 'Publish code coverage results' diff --git a/.vsts/linux-deps.yml b/.vsts/linux-deps.yml deleted file mode 100644 index 540b76ec..00000000 --- a/.vsts/linux-deps.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Note: this file is not currently used, but when template support comes to VSTS it -# will be referenced from the other scripts.. - -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -#parameters: - -steps: -- script: echo "deb-src http://archive.ubuntu.com/ubuntu/ xenial main" > /etc/apt/sources.list.d/python.list && sudo apt-get update - displayName: 'Update apt-get lists' - -- script: > - sudo apt-get -yq install - build-essential - zlib1g-dev - libbz2-dev - liblzma-dev - libncurses5-dev - libreadline6-dev - libsqlite3-dev - libssl-dev - libgdbm-dev - tk-dev - lzma - lzma-dev - liblzma-dev - libffi-dev - uuid-dev - displayName: 'Install dependencies' diff --git a/.vsts/linux-pr.yml b/.vsts/linux-pr.yml deleted file mode 100644 index 83df9b43..00000000 --- a/.vsts/linux-pr.yml +++ /dev/null @@ -1,68 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted Linux Preview - -trigger: - branches: - include: - - master - - 3.7 - - 3.6 - paths: - exclude: - - Doc/* - - Tools/* - -#variables: - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -#- template: linux-deps.yml - -# See https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted-templates.md -# For now, we copy/paste the steps -- script: echo "deb-src http://archive.ubuntu.com/ubuntu/ xenial main" > /etc/apt/sources.list.d/python.list && sudo apt-get update - displayName: 'Update apt-get lists' - -- script: > - sudo apt-get -yq install - build-essential - zlib1g-dev - libbz2-dev - liblzma-dev - libncurses5-dev - libreadline6-dev - libsqlite3-dev - libssl-dev - libgdbm-dev - tk-dev - lzma - lzma-dev - liblzma-dev - libffi-dev - uuid-dev - displayName: 'Install dependencies' - - -- script: ./configure --with-pydebug - displayName: 'Configure CPython (debug)' - -- script: make -s -j4 - displayName: 'Build CPython' - -- script: make pythoninfo - displayName: 'Display build info' - -# Run patchcheck and fail if anything is discovered -- script: ./python Tools/scripts/patchcheck.py --travis true - displayName: 'Run patchcheck.py' - -- script: make buildbottest TESTOPTS="-j4 -uall,-cpu" - displayName: 'Tests' diff --git a/.vsts/macos-buildbot.yml b/.vsts/macos-buildbot.yml deleted file mode 100644 index 8a4f6ba8..00000000 --- a/.vsts/macos-buildbot.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted macOS Preview - -trigger: - branches: - include: - - master - - 3.7 - - 3.6 - paths: - exclude: - - Doc/* - - Tools/* - -#variables: - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -- script: ./configure --with-pydebug --with-openssl=/usr/local/opt/openssl - displayName: 'Configure CPython (debug)' - -- script: make -s -j4 - displayName: 'Build CPython' - -- script: make pythoninfo - displayName: 'Display build info' - -- script: make buildbottest TESTOPTS="-j4 -uall,-cpu" - displayName: 'Tests' diff --git a/.vsts/macos-pr.yml b/.vsts/macos-pr.yml deleted file mode 100644 index 8a4f6ba8..00000000 --- a/.vsts/macos-pr.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted macOS Preview - -trigger: - branches: - include: - - master - - 3.7 - - 3.6 - paths: - exclude: - - Doc/* - - Tools/* - -#variables: - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -- script: ./configure --with-pydebug --with-openssl=/usr/local/opt/openssl - displayName: 'Configure CPython (debug)' - -- script: make -s -j4 - displayName: 'Build CPython' - -- script: make pythoninfo - displayName: 'Display build info' - -- script: make buildbottest TESTOPTS="-j4 -uall,-cpu" - displayName: 'Tests' diff --git a/.vsts/windows-buildbot.yml b/.vsts/windows-buildbot.yml deleted file mode 100644 index 5ec45227..00000000 --- a/.vsts/windows-buildbot.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted VS2017 - parallel: 2 - matrix: - amd64: - buildOpt: -p x64 - outDirSuffix: amd64 - win32: - buildOpt: - outDirSuffix: win32 - -trigger: - branches: - include: - - master - - 3.7 - - 3.6 - paths: - exclude: - - Doc/* - - Tools/* - -variables: - # Relocate build outputs outside of source directory to make cleaning faster - Py_IntDir: $(Build.BinariesDirectory)\obj - # UNDONE: Do not build to a different directory because of broken tests - Py_OutDir: $(Build.SourcesDirectory)\PCbuild - EXTERNAL_DIR: $(Build.BinariesDirectory)\externals - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -- script: PCbuild\build.bat -e $(buildOpt) - displayName: 'Build CPython' - -- script: python.bat -m test.pythoninfo - displayName: 'Display build info' - -- script: PCbuild\rt.bat -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 - displayName: 'Tests' - env: - PREFIX: $(Py_OutDir)\$(outDirSuffix) diff --git a/.vsts/windows-pr.yml b/.vsts/windows-pr.yml deleted file mode 100644 index 5ec45227..00000000 --- a/.vsts/windows-pr.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Current docs for the syntax of this file are at: -# https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted.md - -name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr) - -queue: - name: Hosted VS2017 - parallel: 2 - matrix: - amd64: - buildOpt: -p x64 - outDirSuffix: amd64 - win32: - buildOpt: - outDirSuffix: win32 - -trigger: - branches: - include: - - master - - 3.7 - - 3.6 - paths: - exclude: - - Doc/* - - Tools/* - -variables: - # Relocate build outputs outside of source directory to make cleaning faster - Py_IntDir: $(Build.BinariesDirectory)\obj - # UNDONE: Do not build to a different directory because of broken tests - Py_OutDir: $(Build.SourcesDirectory)\PCbuild - EXTERNAL_DIR: $(Build.BinariesDirectory)\externals - -steps: -- checkout: self - clean: true - fetchDepth: 5 - -- script: PCbuild\build.bat -e $(buildOpt) - displayName: 'Build CPython' - -- script: python.bat -m test.pythoninfo - displayName: 'Display build info' - -- script: PCbuild\rt.bat -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 - displayName: 'Tests' - env: - PREFIX: $(Py_OutDir)\$(outDirSuffix) diff --git a/Doc/README.rst b/Doc/README.rst index a29d1f3a..d7bcc5ba 100644 --- a/Doc/README.rst +++ b/Doc/README.rst @@ -33,7 +33,7 @@ To get started on UNIX, you can create a virtual environment with the command :: make venv That will install all the tools necessary to build the documentation. Assuming -the virtual environment was created in the ``env`` directory (the default; +the virtual environment was created in the ``venv`` directory (the default; configurable with the VENVDIR variable), you can run the following command to build the HTML output files:: diff --git a/Doc/c-api/marshal.rst b/Doc/c-api/marshal.rst index c6d1d02a..17ec6216 100644 --- a/Doc/c-api/marshal.rst +++ b/Doc/c-api/marshal.rst @@ -40,12 +40,6 @@ unmarshalling. Version 2 uses a binary format for floating point numbers. The following functions allow marshalled values to be read back in. -XXX What about error detection? It appears that reading past the end of the -file will always result in a negative numeric value (where that's relevant), -but it's not clear that negative values won't be handled properly when there's -no error. What's the right way to tell? Should only non-negative values be -written using these routines? - .. c:function:: long PyMarshal_ReadLongFromFile(FILE *file) @@ -53,7 +47,8 @@ written using these routines? for reading. Only a 32-bit value can be read in using this function, regardless of the native size of :c:type:`long`. - On error, raise an exception and return ``-1``. + On error, sets the appropriate exception (:exc:`EOFError`) and returns + ``-1``. .. c:function:: int PyMarshal_ReadShortFromFile(FILE *file) @@ -62,7 +57,8 @@ written using these routines? for reading. Only a 16-bit value can be read in using this function, regardless of the native size of :c:type:`short`. - On error, raise an exception and return ``-1``. + On error, sets the appropriate exception (:exc:`EOFError`) and returns + ``-1``. .. c:function:: PyObject* PyMarshal_ReadObjectFromFile(FILE *file) @@ -70,8 +66,8 @@ written using these routines? Return a Python object from the data stream in a :c:type:`FILE\*` opened for reading. - On error, sets the appropriate exception (:exc:`EOFError` or - :exc:`TypeError`) and returns *NULL*. + On error, sets the appropriate exception (:exc:`EOFError`, :exc:`ValueError` + or :exc:`TypeError`) and returns *NULL*. .. c:function:: PyObject* PyMarshal_ReadLastObjectFromFile(FILE *file) @@ -84,8 +80,8 @@ written using these routines? file. Only use these variant if you are certain that you won't be reading anything else from the file. - On error, sets the appropriate exception (:exc:`EOFError` or - :exc:`TypeError`) and returns *NULL*. + On error, sets the appropriate exception (:exc:`EOFError`, :exc:`ValueError` + or :exc:`TypeError`) and returns *NULL*. .. c:function:: PyObject* PyMarshal_ReadObjectFromString(const char *data, Py_ssize_t len) @@ -93,6 +89,6 @@ written using these routines? Return a Python object from the data stream in a byte buffer containing *len* bytes pointed to by *data*. - On error, sets the appropriate exception (:exc:`EOFError` or - :exc:`TypeError`) and returns *NULL*. + On error, sets the appropriate exception (:exc:`EOFError`, :exc:`ValueError` + or :exc:`TypeError`) and returns *NULL*. diff --git a/Doc/c-api/memory.rst b/Doc/c-api/memory.rst index 73bec7c9..b502b25f 100644 --- a/Doc/c-api/memory.rst +++ b/Doc/c-api/memory.rst @@ -35,7 +35,7 @@ operate within the bounds of the private heap. It is important to understand that the management of the Python heap is performed by the interpreter itself and that the user has no control over it, -even if she regularly manipulates object pointers to memory blocks inside that +even if they regularly manipulate object pointers to memory blocks inside that heap. The allocation of heap space for Python objects and other internal buffers is performed on demand by the Python memory manager through the Python/C API functions listed in this document. diff --git a/Doc/conf.py b/Doc/conf.py index 43826ec0..e2758bcd 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -13,7 +13,7 @@ sys.path.append(os.path.abspath('tools/extensions')) # --------------------- extensions = ['sphinx.ext.coverage', 'sphinx.ext.doctest', - 'pyspecific', 'c_annotations'] + 'pyspecific', 'c_annotations', 'escape4chm'] # General substitutions. project = 'Python' diff --git a/Doc/distutils/builtdist.rst b/Doc/distutils/builtdist.rst index bbd2a8ce..5e1a0497 100644 --- a/Doc/distutils/builtdist.rst +++ b/Doc/distutils/builtdist.rst @@ -21,7 +21,7 @@ specialty---writing code and creating source distributions---while an intermediary species called *packagers* springs up to turn source distributions into built distributions for as many platforms as there are packagers. -Of course, the module developer could be his own packager; or the packager could +Of course, the module developer could be their own packager; or the packager could be a volunteer "out there" somewhere who has access to a platform which the original developer does not; or it could be software periodically grabbing new source distributions and turning them into built distributions for as many diff --git a/Doc/distutils/introduction.rst b/Doc/distutils/introduction.rst index a3855591..7721484f 100644 --- a/Doc/distutils/introduction.rst +++ b/Doc/distutils/introduction.rst @@ -94,7 +94,7 @@ containing your setup script :file:`setup.py`, and your module :file:`foo.py`. The archive file will be named :file:`foo-1.0.tar.gz` (or :file:`.zip`), and will unpack into a directory :file:`foo-1.0`. -If an end-user wishes to install your :mod:`foo` module, all she has to do is +If an end-user wishes to install your :mod:`foo` module, all they have to do is download :file:`foo-1.0.tar.gz` (or :file:`.zip`), unpack it, and---from the :file:`foo-1.0` directory---run :: diff --git a/Doc/faq/design.rst b/Doc/faq/design.rst index 1bd800b1..234dc9c1 100644 --- a/Doc/faq/design.rst +++ b/Doc/faq/design.rst @@ -2,6 +2,11 @@ Design and History FAQ ====================== +.. only:: html + + .. contents:: + + Why does Python use indentation for grouping of statements? ----------------------------------------------------------- @@ -210,24 +215,25 @@ objects using the ``for`` statement. For example, :term:`file objects Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))? ---------------------------------------------------------------------------------------------------------------- -The major reason is history. Functions were used for those operations that were -generic for a group of types and which were intended to work even for objects -that didn't have methods at all (e.g. tuples). It is also convenient to have a -function that can readily be applied to an amorphous collection of objects when -you use the functional features of Python (``map()``, ``zip()`` et al). +As Guido said: -In fact, implementing ``len()``, ``max()``, ``min()`` as a built-in function is -actually less code than implementing them as methods for each type. One can -quibble about individual cases but it's a part of Python, and it's too late to -make such fundamental changes now. The functions have to remain to avoid massive -code breakage. + (a) For some operations, prefix notation just reads better than + postfix -- prefix (and infix!) operations have a long tradition in + mathematics which likes notations where the visuals help the + mathematician thinking about a problem. Compare the easy with which we + rewrite a formula like x*(a+b) into x*a + x*b to the clumsiness of + doing the same thing using a raw OO notation. -.. XXX talk about protocols? + (b) When I read code that says len(x) I *know* that it is asking for + the length of something. This tells me two things: the result is an + integer, and the argument is some kind of container. To the contrary, + when I read x.len(), I have to already know that x is some kind of + container implementing an interface or inheriting from a class that + has a standard len(). Witness the confusion we occasionally have when + a class that is not implementing a mapping has a get() or keys() + method, or something that isn't a file has a write() method. -.. note:: - - For string operations, Python has moved from external functions (the - ``string`` module) to methods. However, ``len()`` is still a function. + -- https://mail.python.org/pipermail/python-3000/2006-November/004643.html Why is join() a string method instead of a list or tuple method? @@ -465,10 +471,10 @@ you can always change a list's elements. Only immutable elements can be used as dictionary keys, and hence only tuples and not lists can be used as keys. -How are lists implemented? --------------------------- +How are lists implemented in CPython? +------------------------------------- -Python's lists are really variable-length arrays, not Lisp-style linked lists. +CPython's lists are really variable-length arrays, not Lisp-style linked lists. The implementation uses a contiguous array of references to other objects, and keeps a pointer to this array and the array's length in a list head structure. @@ -481,10 +487,10 @@ when the array must be grown, some extra space is allocated so the next few times don't require an actual resize. -How are dictionaries implemented? ---------------------------------- +How are dictionaries implemented in CPython? +-------------------------------------------- -Python's dictionaries are implemented as resizable hash tables. Compared to +CPython's dictionaries are implemented as resizable hash tables. Compared to B-trees, this gives better performance for lookup (the most common operation by far) under most circumstances, and the implementation is simpler. diff --git a/Doc/faq/programming.rst b/Doc/faq/programming.rst index 7476ce11..b717ab8f 100644 --- a/Doc/faq/programming.rst +++ b/Doc/faq/programming.rst @@ -371,8 +371,8 @@ compute, a common technique is to cache the parameters and the resulting value of each call to the function, and return the cached value if the same value is requested again. This is called "memoizing", and can be implemented like this:: - # Callers will never provide a third parameter for this function. - def expensive(arg1, arg2, _cache={}): + # Callers can only provide two parameters and optionally pass _cache by keyword + def expensive(arg1, arg2, *, _cache={}): if (arg1, arg2) in _cache: return _cache[(arg1, arg2)] diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 6dbf0c39..cf2ca671 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -14,8 +14,9 @@ Glossary ``...`` The default Python prompt of the interactive shell when entering code for - an indented code block or within a pair of matching left and right - delimiters (parentheses, square brackets or curly braces). + an indented code block, when within a pair of matching left and right + delimiters (parentheses, square brackets, curly braces or triple quotes), + or after specifying a decorator. 2to3 A tool that tries to convert Python 2.x code to Python 3.x code by @@ -122,10 +123,10 @@ Glossary :meth:`__aiter__` method. Introduced by :pep:`492`. asynchronous iterator - An object that implements :meth:`__aiter__` and :meth:`__anext__` + An object that implements the :meth:`__aiter__` and :meth:`__anext__` methods. ``__anext__`` must return an :term:`awaitable` object. - :keyword:`async for` resolves awaitable returned from asynchronous - iterator's :meth:`__anext__` method until it raises + :keyword:`async for` resolves the awaitables returned by an asynchronous + iterator's :meth:`__anext__` method until it raises a :exc:`StopAsyncIteration` exception. Introduced by :pep:`492`. attribute @@ -636,7 +637,7 @@ Glossary list A built-in Python :term:`sequence`. Despite its name it is more akin to an array in other languages than to a linked list since access to - elements are O(1). + elements is O(1). list comprehension A compact way to process all or part of the elements in a sequence and @@ -1005,7 +1006,7 @@ Glossary struct sequence A tuple with named elements. Struct sequences expose an interface similar - to :term:`named tuple` in that elements can either be accessed either by + to :term:`named tuple` in that elements can be accessed either by index or as an attribute. However, they do not have any of the named tuple methods like :meth:`~collections.somenamedtuple._make` or :meth:`~collections.somenamedtuple._asdict`. Examples of struct sequences diff --git a/Doc/howto/functional.rst b/Doc/howto/functional.rst index 40601812..7e21aa76 100644 --- a/Doc/howto/functional.rst +++ b/Doc/howto/functional.rst @@ -198,7 +198,7 @@ for it. You can experiment with the iteration interface manually: - >>> L = [1,2,3] + >>> L = [1, 2, 3] >>> it = iter(L) >>> it #doctest: +ELLIPSIS <...iterator object at ...> @@ -229,7 +229,7 @@ iterator. These two statements are equivalent:: Iterators can be materialized as lists or tuples by using the :func:`list` or :func:`tuple` constructor functions: - >>> L = [1,2,3] + >>> L = [1, 2, 3] >>> iterator = iter(L) >>> t = tuple(iterator) >>> t @@ -238,10 +238,10 @@ Iterators can be materialized as lists or tuples by using the :func:`list` or Sequence unpacking also supports iterators: if you know an iterator will return N elements, you can unpack them into an N-tuple: - >>> L = [1,2,3] + >>> L = [1, 2, 3] >>> iterator = iter(L) - >>> a,b,c = iterator - >>> a,b,c + >>> a, b, c = iterator + >>> a, b, c (1, 2, 3) Built-in functions such as :func:`max` and :func:`min` can take a single @@ -410,7 +410,7 @@ lengths of all the sequences. If you have two lists of length 3, the output list is 9 elements long: >>> seq1 = 'abc' - >>> seq2 = (1,2,3) + >>> seq2 = (1, 2, 3) >>> [(x, y) for x in seq1 for y in seq2] #doctest: +NORMALIZE_WHITESPACE [('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2), ('b', 3), @@ -478,7 +478,7 @@ Here's a sample usage of the ``generate_ints()`` generator: File "stdin", line 2, in generate_ints StopIteration -You could equally write ``for i in generate_ints(5)``, or ``a,b,c = +You could equally write ``for i in generate_ints(5)``, or ``a, b, c = generate_ints(3)``. Inside a generator function, ``return value`` causes ``StopIteration(value)`` @@ -694,17 +694,17 @@ truth values of an iterable's contents. :func:`any` returns ``True`` if any ele in the iterable is a true value, and :func:`all` returns ``True`` if all of the elements are true values: - >>> any([0,1,0]) + >>> any([0, 1, 0]) True - >>> any([0,0,0]) + >>> any([0, 0, 0]) False - >>> any([1,1,1]) + >>> any([1, 1, 1]) True - >>> all([0,1,0]) + >>> all([0, 1, 0]) False - >>> all([0,0,0]) + >>> all([0, 0, 0]) False - >>> all([1,1,1]) + >>> all([1, 1, 1]) True @@ -763,7 +763,7 @@ which defaults to 0, and the interval between numbers, which defaults to 1:: a provided iterable and returns a new iterator that returns its elements from first to last. The new iterator will repeat these elements infinitely. :: - itertools.cycle([1,2,3,4,5]) => + itertools.cycle([1, 2, 3, 4, 5]) => 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ... :func:`itertools.repeat(elem, [n]) ` returns the provided @@ -874,7 +874,7 @@ iterable's results. :: iterators and returns only those elements of *data* for which the corresponding element of *selectors* is true, stopping whenever either one is exhausted:: - itertools.compress([1,2,3,4,5], [True, True, False, False, True]) => + itertools.compress([1, 2, 3, 4, 5], [True, True, False, False, True]) => 1, 2, 5 @@ -1034,7 +1034,7 @@ first calculation. :: Traceback (most recent call last): ... TypeError: reduce() of empty sequence with no initial value - >>> functools.reduce(operator.mul, [1,2,3], 1) + >>> functools.reduce(operator.mul, [1, 2, 3], 1) 6 >>> functools.reduce(operator.mul, [], 1) 1 @@ -1044,9 +1044,9 @@ elements of the iterable. This case is so common that there's a special built-in called :func:`sum` to compute it: >>> import functools, operator - >>> functools.reduce(operator.add, [1,2,3,4], 0) + >>> functools.reduce(operator.add, [1, 2, 3, 4], 0) 10 - >>> sum([1,2,3,4]) + >>> sum([1, 2, 3, 4]) 10 >>> sum([]) 0 @@ -1056,11 +1056,11 @@ write the obvious :keyword:`for` loop:: import functools # Instead of: - product = functools.reduce(operator.mul, [1,2,3], 1) + product = functools.reduce(operator.mul, [1, 2, 3], 1) # You can write: product = 1 - for i in [1,2,3]: + for i in [1, 2, 3]: product *= i A related function is :func:`itertools.accumulate(iterable, func=operator.add) @@ -1068,10 +1068,10 @@ A related function is :func:`itertools.accumulate(iterable, func=operator.add) returning only the final result, :func:`accumulate` returns an iterator that also yields each partial result:: - itertools.accumulate([1,2,3,4,5]) => + itertools.accumulate([1, 2, 3, 4, 5]) => 1, 3, 6, 10, 15 - itertools.accumulate([1,2,3,4,5], operator.mul) => + itertools.accumulate([1, 2, 3, 4, 5], operator.mul) => 1, 2, 6, 24, 120 @@ -1155,7 +1155,7 @@ But it would be best of all if I had simply used a ``for`` loop:: Or the :func:`sum` built-in and a generator expression:: - total = sum(b for a,b in items) + total = sum(b for a, b in items) Many uses of :func:`functools.reduce` are clearer when written as ``for`` loops. diff --git a/Doc/includes/run-func.c b/Doc/includes/run-func.c index ead7bdd2..9caf1fdb 100644 --- a/Doc/includes/run-func.c +++ b/Doc/includes/run-func.c @@ -3,7 +3,7 @@ int main(int argc, char *argv[]) { - PyObject *pName, *pModule, *pDict, *pFunc; + PyObject *pName, *pModule, *pFunc; PyObject *pArgs, *pValue; int i; diff --git a/Doc/library/asyncio-protocol.rst b/Doc/library/asyncio-protocol.rst index cd84ae76..9605261c 100644 --- a/Doc/library/asyncio-protocol.rst +++ b/Doc/library/asyncio-protocol.rst @@ -124,11 +124,19 @@ ReadTransport the protocol's :meth:`data_received` method until :meth:`resume_reading` is called. + .. versionchanged:: 3.6.7 + The method is idempotent, i.e. it can be called when the + transport is already paused or closed. + .. method:: resume_reading() Resume the receiving end. The protocol's :meth:`data_received` method will be called once again if some data is available for reading. + .. versionchanged:: 3.6.7 + The method is idempotent, i.e. it can be called when the + transport is already reading. + WriteTransport -------------- diff --git a/Doc/library/asyncio-stream.rst b/Doc/library/asyncio-stream.rst index 491afdd6..a510e1e6 100644 --- a/Doc/library/asyncio-stream.rst +++ b/Doc/library/asyncio-stream.rst @@ -88,10 +88,12 @@ Stream functions StreamReader ============ -.. class:: StreamReader(limit=None, loop=None) +.. class:: StreamReader(limit=_DEFAULT_LIMIT, loop=None) This class is :ref:`not thread safe `. + The *limit* argument's default value is set to _DEFAULT_LIMIT which is 2**16 (64 KiB) + .. method:: exception() Get the exception. diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 82ba0573..25687348 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -198,6 +198,7 @@ updates keys found deeper in the chain:: >>> d['lion'] = 'orange' # update an existing key two levels down >>> d['snake'] = 'red' # new keys get added to the topmost dict >>> del d['elephant'] # remove an existing key one level down + >>> d # display result DeepChainMap({'zebra': 'black', 'snake': 'red'}, {}, {'lion': 'orange'}) diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index ba107274..4abc6aa7 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -982,15 +982,16 @@ ConfigParser Objects .. method:: read(filenames, encoding=None) - Attempt to read and parse a list of filenames, returning a list of + Attempt to read and parse an iterable of filenames, returning a list of filenames which were successfully parsed. If *filenames* is a string or :term:`path-like object`, it is treated as a single filename. If a file named in *filenames* cannot be opened, that - file will be ignored. This is designed so that you can specify a list of - potential configuration file locations (for example, the current - directory, the user's home directory, and some system-wide directory), - and all existing configuration files in the list will be read. + file will be ignored. This is designed so that you can specify an + iterable of potential configuration file locations (for example, the + current directory, the user's home directory, and some system-wide + directory), and all existing configuration files in the iterable will be + read. If none of the named files exist, the :class:`ConfigParser` instance will contain an empty dataset. An application which requires diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index dd34c96c..49eaba3d 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -36,22 +36,28 @@ Functions and classes provided: function for :keyword:`with` statement context managers, without needing to create a class or separate :meth:`__enter__` and :meth:`__exit__` methods. - A simple example (this is not recommended as a real way of generating HTML!):: + While many objects natively support use in with statements, sometimes a + resource needs to be managed that isn't a context manager in its own right, + and doesn't implement a ``close()`` method for use with ``contextlib.closing`` + + An abstract example would be the following to ensure correct resource + management:: from contextlib import contextmanager @contextmanager - def tag(name): - print("<%s>" % name) - yield - print("" % name) + def managed_resource(*args, **kwds): + # Code to acquire resource, e.g.: + resource = acquire_resource(*args, **kwds) + try: + yield resource + finally: + # Code to release resource, e.g.: + release_resource(resource) - >>> with tag("h1"): - ... print("foo") - ... -

- foo -

+ >>> with managed_resource(timeout=3600) as resource: + ... # Resource is released at the end of this block, + ... # even if code in the block raises an exception The function being decorated must return a :term:`generator`-iterator when called. This iterator must yield exactly one value, which will be bound to diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index cdcbefa4..a7cc0c84 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -1025,6 +1025,22 @@ As we can easily check, our array is sorted now:: 1 5 7 33 99 >>> +The function factories can be used as decorator factories, so we may as well +write:: + + >>> @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int)) + ... def py_cmp_func(a, b): + ... print("py_cmp_func", a[0], b[0]) + ... return a[0] - b[0] + ... + >>> qsort(ia, len(ia), sizeof(c_int), py_cmp_func) + py_cmp_func 5 1 + py_cmp_func 33 99 + py_cmp_func 7 33 + py_cmp_func 1 7 + py_cmp_func 5 7 + >>> + .. note:: Make sure you keep references to :func:`CFUNCTYPE` objects as long as they @@ -1575,7 +1591,9 @@ Foreign functions can also be created by instantiating function prototypes. Function prototypes are similar to function prototypes in C; they describe a function (return type, argument types, calling convention) without defining an implementation. The factory functions must be called with the desired result -type and the argument types of the function. +type and the argument types of the function, and can be used as decorator +factories, and as such, be applied to functions through the ``@wrapper`` syntax. +See :ref:`ctypes-callback-functions` for examples. .. function:: CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 06141d1c..88bc328e 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -241,7 +241,7 @@ Supported operations: +--------------------------------+-----------------------------------------------+ | ``t1 = t2 - t3`` | Difference of *t2* and *t3*. Afterwards *t1* | | | == *t2* - *t3* and *t2* == *t1* + *t3* are | -| | true. (1) | +| | true. (1)(6) | +--------------------------------+-----------------------------------------------+ | ``t1 = t2 * i or t1 = i * t2`` | Delta multiplied by an integer. | | | Afterwards *t1* // i == *t2* is true, | @@ -316,6 +316,11 @@ Notes: >>> print(_) -1 day, 19:00:00 +(6) + The expression ``t2 - t3`` will always be equal to the expression ``t2 + (-t3)`` except + when t3 is equal to ``timedelta.max``; in that case the former will produce a result + while the latter will overflow. + In addition to the operations listed above :class:`timedelta` objects support certain additions and subtractions with :class:`date` and :class:`.datetime` objects (see below). @@ -497,8 +502,6 @@ Notes: :const:`MINYEAR` or larger than :const:`MAXYEAR`. (2) - This isn't quite equivalent to date1 + (-timedelta), because -timedelta in - isolation can overflow in cases where date1 - timedelta does not. ``timedelta.seconds`` and ``timedelta.microseconds`` are ignored. (3) @@ -507,10 +510,9 @@ Notes: (4) In other words, ``date1 < date2`` if and only if ``date1.toordinal() < - date2.toordinal()``. In order to stop comparison from falling back to the - default scheme of comparing object addresses, date comparison normally raises - :exc:`TypeError` if the other comparand isn't also a :class:`date` object. - However, ``NotImplemented`` is returned instead if the other comparand has a + date2.toordinal()``. Date comparison raises :exc:`TypeError` if + the other comparand isn't also a :class:`date` object. However, + ``NotImplemented`` is returned instead if the other comparand has a :meth:`timetuple` attribute. This hook gives other kinds of date objects a chance at implementing mixed-type comparison. If not, when a :class:`date` object is compared to an object of a different type, :exc:`TypeError` is raised @@ -930,8 +932,6 @@ Supported operations: Computes the datetime2 such that datetime2 + timedelta == datetime1. As for addition, the result has the same :attr:`~.datetime.tzinfo` attribute as the input datetime, and no time zone adjustments are done even if the input is aware. - This isn't quite equivalent to datetime1 + (-timedelta), because -timedelta - in isolation can overflow in cases where datetime1 - timedelta does not. (3) Subtraction of a :class:`.datetime` from a :class:`.datetime` is defined only if diff --git a/Doc/library/depgraph-output.png b/Doc/library/depgraph-output.png deleted file mode 100644 index 960bb1b5639e7ff615ddbd6cbf8a542be31d5fe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24719 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 E4-b|CfdBvi diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 3f615dbd..d5757c2c 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -1043,18 +1043,20 @@ All of the following opcodes use their arguments. .. opcode:: RAISE_VARARGS (argc) - Raises an exception. *argc* indicates the number of parameters to the raise + Raises an exception. *argc* indicates the number of arguments to the raise statement, ranging from 0 to 3. The handler will find the traceback as TOS2, the parameter as TOS1, and the exception as TOS. .. opcode:: CALL_FUNCTION (argc) - Calls a function. *argc* indicates the number of positional arguments. - The positional arguments are on the stack, with the right-most argument - on top. Below the arguments, the function object to call is on the stack. - Pops all function arguments, and the function itself off the stack, and - pushes the return value. + Calls a callable object with positional arguments. + *argc* indicates the number of positional arguments. + The top of the stack contains positional arguments, with the right-most + argument on top. Below the arguments is a callable object to call. + ``CALL_FUNCTION`` pops all arguments and the callable object off the stack, + calls the callable object with those arguments, and pushes the return value + returned by the callable object. .. versionchanged:: 3.6 This opcode is used only for calls with positional arguments. @@ -1062,31 +1064,36 @@ All of the following opcodes use their arguments. .. opcode:: CALL_FUNCTION_KW (argc) - Calls a function. *argc* indicates the number of arguments (positional - and keyword). The top element on the stack contains a tuple of keyword - argument names. Below the tuple, keyword arguments are on the stack, in - the order corresponding to the tuple. Below the keyword arguments, the - positional arguments are on the stack, with the right-most parameter on - top. Below the arguments, the function object to call is on the stack. - Pops all function arguments, and the function itself off the stack, and - pushes the return value. + Calls a callable object with positional (if any) and keyword arguments. + *argc* indicates the total number of positional and keyword arguments. + The top element on the stack contains a tuple of keyword argument names. + Below that are keyword arguments in the order corresponding to the tuple. + Below that are positional arguments, with the right-most parameter on + top. Below the arguments is a callable object to call. + ``CALL_FUNCTION_KW`` pops all arguments and the callable object off the stack, + calls the callable object with those arguments, and pushes the return value + returned by the callable object. .. versionchanged:: 3.6 Keyword arguments are packed in a tuple instead of a dictionary, - *argc* indicates the total number of arguments + *argc* indicates the total number of arguments. .. opcode:: CALL_FUNCTION_EX (flags) - Calls a function. The lowest bit of *flags* indicates whether the - var-keyword argument is placed at the top of the stack. Below the - var-keyword argument, the var-positional argument is on the stack. - Below the arguments, the function object to call is placed. - Pops all function arguments, and the function itself off the stack, and - pushes the return value. Note that this opcode pops at most three items - from the stack. Var-positional and var-keyword arguments are packed - by :opcode:`BUILD_TUPLE_UNPACK_WITH_CALL` and - :opcode:`BUILD_MAP_UNPACK_WITH_CALL`. + Calls a callable object with variable set of positional and keyword + arguments. If the lowest bit of *flags* is set, the top of the stack + contains a mapping object containing additional keyword arguments. + Below that is an iterable object containing positional arguments and + a callable object to call. :opcode:`BUILD_MAP_UNPACK_WITH_CALL` and + :opcode:`BUILD_TUPLE_UNPACK_WITH_CALL` can be used for merging multiple + mapping objects and iterables containing arguments. + Before the callable is called, the mapping object and iterable object + are each "unpacked" and their contents passed in as keyword and + positional arguments respectively. + ``CALL_FUNCTION_EX`` pops all arguments and the callable object off the stack, + calls the callable object with those arguments, and pushes the return value + returned by the callable object. .. versionadded:: 3.6 @@ -1096,7 +1103,8 @@ All of the following opcodes use their arguments. Pushes a new function object on the stack. From bottom to top, the consumed stack must consist of values if the argument carries a specified flag value - * ``0x01`` a tuple of default argument objects in positional order + * ``0x01`` a tuple of default values for positional-only and + positional-or-keyword parameters in positional order * ``0x02`` a dictionary of keyword-only parameters' default values * ``0x04`` an annotation dictionary * ``0x08`` a tuple containing cells for free variables, making a closure @@ -1179,7 +1187,7 @@ instructions: .. data:: hasconst - Sequence of bytecodes that have a constant parameter. + Sequence of bytecodes that access a constant. .. data:: hasfree diff --git a/Doc/library/email.errors.rst b/Doc/library/email.errors.rst index 5838767b..511ad163 100644 --- a/Doc/library/email.errors.rst +++ b/Doc/library/email.errors.rst @@ -108,3 +108,7 @@ All defect classes are subclassed from :class:`email.errors.MessageDefect`. * :class:`InvalidBase64CharactersDefect` -- When decoding a block of base64 encoded bytes, characters outside the base64 alphabet were encountered. The characters are ignored, but the resulting decoded bytes may be invalid. + +* :class:`InvalidBase64LengthDefect` -- When decoding a block of base64 encoded + bytes, the number of non-padding base64 characters was invalid (1 more than + a multiple of 4). The encoded block was kept as-is. diff --git a/Doc/library/email.parser.rst b/Doc/library/email.parser.rst index dea409d2..e0cab6a6 100644 --- a/Doc/library/email.parser.rst +++ b/Doc/library/email.parser.rst @@ -246,7 +246,7 @@ in the top-level :mod:`email` package namespace. Removed the *strict* argument. Added the *policy* keyword. -.. function:: message_from_binary_file(fp, _class=None, *, +.. function:: message_from_binary_file(fp, _class=None, *, \ policy=policy.compat32) Return a message object structure tree from an open binary :term:`file diff --git a/Doc/library/email.rst b/Doc/library/email.rst index c4187dd0..1033d8c1 100644 --- a/Doc/library/email.rst +++ b/Doc/library/email.rst @@ -133,7 +133,7 @@ Legacy API: .. seealso:: Module :mod:`smtplib` - SMTP (Simple Mail Transport Protcol) client + SMTP (Simple Mail Transport Protocol) client Module :mod:`poplib` POP (Post Office Protocol) client diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 5c1b226e..2bf4885d 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -4,10 +4,10 @@ .. module:: enum :synopsis: Implementation of an enumeration class. -.. :moduleauthor:: Ethan Furman -.. :sectionauthor:: Barry Warsaw , -.. :sectionauthor:: Eli Bendersky , -.. :sectionauthor:: Ethan Furman +.. moduleauthor:: Ethan Furman +.. sectionauthor:: Barry Warsaw +.. sectionauthor:: Eli Bendersky +.. sectionauthor:: Ethan Furman .. versionadded:: 3.4 diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index a6b20a5a..cfa1649d 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -524,7 +524,7 @@ The following exceptions are the exceptions that are usually raised. .. exception:: ValueError - Raised when a built-in operation or function receives an argument that has the + Raised when an operation or function receives an argument that has the right type but an inappropriate value, and the situation is not described by a more precise exception such as :exc:`IndexError`. diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 501a3c99..b6e52469 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -98,7 +98,7 @@ are always available. They are listed here in alphabetical order. >>> f'{14:#b}', f'{14:b}' ('0b1110', '1110') - See also :func:`format` for more information. + See also :func:`format` for more information. .. class:: bool([x]) @@ -226,8 +226,8 @@ are always available. They are listed here in alphabetical order. interactive statement (in the latter case, expression statements that evaluate to something other than ``None`` will be printed). - The optional arguments *flags* and *dont_inherit* control which future - statements (see :pep:`236`) affect the compilation of *source*. If neither + The optional arguments *flags* and *dont_inherit* control which :ref:`future + statements ` affect the compilation of *source*. If neither is present (or both are zero) the code is compiled with those future statements that are in effect in the code that is calling :func:`compile`. If the *flags* argument is given and *dont_inherit* is not (or is zero) then the @@ -420,8 +420,10 @@ are always available. They are listed here in alphabetical order. The *expression* argument is parsed and evaluated as a Python expression (technically speaking, a condition list) using the *globals* and *locals* dictionaries as global and local namespace. If the *globals* dictionary is - present and lacks '__builtins__', the current globals are copied into *globals* - before *expression* is parsed. This means that *expression* normally has full + present and does not contain a value for the key ``__builtins__``, a + reference to the dictionary of the built-in module :mod:`builtins` is + inserted under that key before *expression* is parsed. + This means that *expression* normally has full access to the standard :mod:`builtins` module and restricted environments are propagated. If the *locals* dictionary is omitted it defaults to the *globals* dictionary. If both dictionaries are omitted, the expression is executed in the @@ -632,11 +634,11 @@ are always available. They are listed here in alphabetical order. dictionary lookup. Numeric values that compare equal have the same hash value (even if they are of different types, as is the case for 1 and 1.0). - .. note:: + .. note:: - For objects with custom :meth:`__hash__` methods, note that :func:`hash` - truncates the return value based on the bit width of the host machine. - See :meth:`__hash__` for details. + For objects with custom :meth:`__hash__` methods, note that :func:`hash` + truncates the return value based on the bit width of the host machine. + See :meth:`__hash__` for details. .. function:: help([object]) @@ -957,6 +959,11 @@ are always available. They are listed here in alphabetical order. encoding. (For reading and writing raw bytes use binary mode and leave *encoding* unspecified.) The available modes are: + .. _filemodes: + + .. index:: + pair: file; modes + ========= =============================================================== Character Meaning ========= =============================================================== diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst index 9bd39cb4..b4f51763 100644 --- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -101,7 +101,7 @@ More condensed: .. function:: new(name[, data]) - Is a generic constructor that takes the string name of the desired + Is a generic constructor that takes the string *name* of the desired algorithm as its first parameter. It also exists to allow access to the above listed hashes as well as any other algorithms that your OpenSSL library may offer. The named constructors are much faster than :func:`new` @@ -162,10 +162,10 @@ A hash object has the following attributes: A hash object has the following methods: -.. method:: hash.update(arg) +.. method:: hash.update(data) - Update the hash object with the object *arg*, which must be interpretable as - a buffer of bytes. Repeated calls are equivalent to a single call with the + Update the hash object with the :term:`bytes-like object`. + Repeated calls are equivalent to a single call with the concatenation of all the arguments: ``m.update(a); m.update(b)`` is equivalent to ``m.update(a+b)``. @@ -206,7 +206,7 @@ by the SHAKE algorithm. .. method:: shake.digest(length) Return the digest of the data passed to the :meth:`update` method so far. - This is a bytes object of size ``length`` which may contain bytes in + This is a bytes object of size *length* which may contain bytes in the whole range from 0 to 255. @@ -262,9 +262,10 @@ include a `salt `_. The function provides scrypt password-based key derivation function as defined in :rfc:`7914`. - *password* and *salt* must be bytes-like objects. Applications and - libraries should limit *password* to a sensible length (e.g. 1024). *salt* - should be about 16 or more bytes from a proper source, e.g. :func:`os.urandom`. + *password* and *salt* must be :term:`bytes-like objects + `. Applications and libraries should limit *password* + to a sensible length (e.g. 1024). *salt* should be about 16 or more + bytes from a proper source, e.g. :func:`os.urandom`. *n* is the CPU/Memory cost factor, *r* the block size, *p* parallelization factor and *maxmem* limits memory (OpenSSL 1.1.0 defaults to 32 MB). @@ -305,11 +306,11 @@ Creating hash objects New hash objects are created by calling constructor functions: -.. function:: blake2b(data=b'', digest_size=64, key=b'', salt=b'', \ +.. function:: blake2b(data=b'', *, digest_size=64, key=b'', salt=b'', \ person=b'', fanout=1, depth=1, leaf_size=0, node_offset=0, \ node_depth=0, inner_size=0, last_node=False) -.. function:: blake2s(data=b'', digest_size=32, key=b'', salt=b'', \ +.. function:: blake2s(data=b'', *, digest_size=32, key=b'', salt=b'', \ person=b'', fanout=1, depth=1, leaf_size=0, node_offset=0, \ node_depth=0, inner_size=0, last_node=False) @@ -317,8 +318,8 @@ New hash objects are created by calling constructor functions: These functions return the corresponding hash objects for calculating BLAKE2b or BLAKE2s. They optionally take these general parameters: -* *data*: initial chunk of data to hash, which must be interpretable as buffer - of bytes. +* *data*: initial chunk of data to hash, which must be + :term:`bytes-like object`. It can be passed only as positional argument. * *digest_size*: size of output digest in bytes. @@ -427,7 +428,7 @@ object, and, finally, get the digest out of the object by calling As a shortcut, you can pass the first chunk of data to update directly to the -constructor as the first argument (or as *data* keyword argument): +constructor as the positional argument: >>> from hashlib import blake2b >>> blake2b(b'Hello world').hexdigest() @@ -546,7 +547,7 @@ on the hash function used in digital signatures. preparer, generates all or part of a message to be signed by a second party, the message signer. If the message preparer is able to find cryptographic hash function collisions (i.e., two messages producing the - same hash value), then she might prepare meaningful versions of the message + same hash value), then they might prepare meaningful versions of the message that would produce the same hash value and digital signature, but with different results (e.g., transferring $1,000,000 to an account, rather than $10). Cryptographic hash functions have been designed with collision diff --git a/Doc/library/html.rst b/Doc/library/html.rst index d0706bcb..c2b01e14 100644 --- a/Doc/library/html.rst +++ b/Doc/library/html.rst @@ -24,7 +24,7 @@ This module defines utilities to manipulate HTML. .. function:: unescape(s) Convert all named and numeric character references (e.g. ``>``, - ``>``, ``&x3e;``) in the string *s* to the corresponding unicode + ``>``, ``>``) 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 :data:`list of HTML 5 named character references `. diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index c1cd9485..356d1608 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -243,7 +243,7 @@ ABC hierarchy:: .. abstractmethod:: find_module(fullname, path=None) - An abstact method for finding a :term:`loader` for the specified + An abstract method for finding a :term:`loader` for the specified module. Originally specified in :pep:`302`, this method was meant for use in :data:`sys.meta_path` and in the path-based import subsystem. @@ -681,7 +681,7 @@ ABC hierarchy:: Concrete implementation of :meth:`Loader.exec_module`. - .. versionadded:: 3.4 + .. versionadded:: 3.4 .. method:: load_module(fullname) diff --git a/Doc/library/io.rst b/Doc/library/io.rst index 73b3efff..28aac923 100644 --- a/Doc/library/io.rst +++ b/Doc/library/io.rst @@ -790,7 +790,7 @@ Text I/O .. versionadded:: 3.1 - .. method:: read(size) + .. method:: read(size=-1) Read and return at most *size* characters from the stream as a single :class:`str`. If *size* is negative or ``None``, reads until EOF. diff --git a/Doc/library/json.rst b/Doc/library/json.rst index d6dcd520..aa9da913 100644 --- a/Doc/library/json.rst +++ b/Doc/library/json.rst @@ -188,6 +188,11 @@ Basic Usage .. versionchanged:: 3.6 All optional parameters are now :ref:`keyword-only `. + .. note:: + + Unlike :mod:`pickle` and :mod:`marshal`, JSON is not a framed protocol, + so trying to serialize multiple objects with repeated calls to + :func:`dump` using the same *fp* will result in an invalid JSON file. .. function:: dumps(obj, *, skipkeys=False, ensure_ascii=True, \ check_circular=True, allow_nan=True, cls=None, \ @@ -198,12 +203,6 @@ Basic Usage table `. The arguments have the same meaning as in :func:`dump`. - .. note:: - - Unlike :mod:`pickle` and :mod:`marshal`, JSON is not a framed protocol, - so trying to serialize multiple objects with repeated calls to - :func:`dump` using the same *fp* will result in an invalid JSON file. - .. note:: Keys in key/value pairs of JSON are always of the type :class:`str`. When diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 9ef174eb..590d5568 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -1129,52 +1129,54 @@ functions. +--------------+---------------------------------------------+ | Format | Description | +==============+=============================================+ - | ``filename`` | Specifies that a FileHandler be created, | + | *filename* | Specifies that a FileHandler be created, | | | using the specified filename, rather than a | | | StreamHandler. | +--------------+---------------------------------------------+ - | ``filemode`` | Specifies the mode to open the file, if | - | | filename is specified (if filemode is | - | | unspecified, it defaults to 'a'). | + | *filemode* | If *filename* is specified, open the file | + | | in this :ref:`mode `. Defaults | + | | to ``'a'``. | +--------------+---------------------------------------------+ - | ``format`` | Use the specified format string for the | + | *format* | Use the specified format string for the | | | handler. | +--------------+---------------------------------------------+ - | ``datefmt`` | Use the specified date/time format. | + | *datefmt* | Use the specified date/time format, as | + | | accepted by :func:`time.strftime`. | +--------------+---------------------------------------------+ - | ``style`` | If ``format`` is specified, use this style | - | | for the format string. One of '%', '{' or | - | | '$' for %-formatting, :meth:`str.format` or | - | | :class:`string.Template` respectively, and | - | | defaulting to '%' if not specified. | + | *style* | If *format* is specified, use this style | + | | for the format string. One of ``'%'``, | + | | ``'{'`` or ``'$'`` for :ref:`printf-style | + | | `, | + | | :meth:`str.format` or | + | | :class:`string.Template` respectively. | + | | Defaults to ``'%'``. | +--------------+---------------------------------------------+ - | ``level`` | Set the root logger level to the specified | - | | level. | + | *level* | Set the root logger level to the specified | + | | :ref:`level `. | +--------------+---------------------------------------------+ - | ``stream`` | Use the specified stream to initialize the | + | *stream* | Use the specified stream to initialize the | | | StreamHandler. Note that this argument is | - | | incompatible with 'filename' - if both are | - | | present, a ``ValueError`` is raised. | + | | incompatible with *filename* - if both | + | | are present, a ``ValueError`` is raised. | +--------------+---------------------------------------------+ - | ``handlers`` | If specified, this should be an iterable of | + | *handlers* | If specified, this should be an iterable of | | | already created handlers to add to the root | | | logger. Any handlers which don't already | | | have a formatter set will be assigned the | | | default formatter created in this function. | | | Note that this argument is incompatible | - | | with 'filename' or 'stream' - if both are | - | | present, a ``ValueError`` is raised. | + | | with *filename* or *stream* - if both | + | | are present, a ``ValueError`` is raised. | +--------------+---------------------------------------------+ .. versionchanged:: 3.2 - The ``style`` argument was added. + The *style* argument was added. .. versionchanged:: 3.3 - The ``handlers`` argument was added. Additional checks were added to + The *handlers* argument was added. Additional checks were added to catch situations where incompatible arguments are specified (e.g. - ``handlers`` together with ``stream`` or ``filename``, or ``stream`` - together with ``filename``). - + *handlers* together with *stream* or *filename*, or *stream* + together with *filename*). .. function:: shutdown() diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index 20d7974e..a3cdfd74 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -135,7 +135,7 @@ start a *semaphore tracker* process which tracks the unlinked named semaphores created by processes of the program. When all processes have exited the semaphore tracker unlinks any remaining semaphores. Usually there should be none, but if a process was killed by a signal -there may some "leaked" semaphores. (Unlinking the named semaphores +there may be some "leaked" semaphores. (Unlinking the named semaphores is a serious matter since the system allows only a limited number, and they will not be automatically unlinked until the next reboot.) @@ -179,7 +179,7 @@ program. :: Note that objects related to one context may not be compatible with processes for a different context. In particular, locks created using -the *fork* context cannot be passed to a processes started using the +the *fork* context cannot be passed to processes started using the *spawn* or *forkserver* start methods. A library which wants to use a particular start method should probably diff --git a/Doc/library/optparse.rst b/Doc/library/optparse.rst index 337c7c29..e9b82ee2 100644 --- a/Doc/library/optparse.rst +++ b/Doc/library/optparse.rst @@ -1677,7 +1677,7 @@ The callback function should raise :exc:`OptionValueError` if there are any problems with the option or its argument(s). :mod:`optparse` catches this and terminates the program, printing the error message you supply to stderr. Your message should be clear, concise, accurate, and mention the option at fault. -Otherwise, the user will have a hard time figuring out what he did wrong. +Otherwise, the user will have a hard time figuring out what they did wrong. .. _optparse-callback-example-1: diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 34ab3b8e..0377027d 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -555,7 +555,8 @@ Pure paths provide the following methods and properties: .. method:: PurePath.with_suffix(suffix) Return a new path with the :attr:`suffix` changed. If the original path - doesn't have a suffix, the new *suffix* is appended instead:: + doesn't have a suffix, the new *suffix* is appended instead. If the + *suffix* is an empty string, the original suffix is removed:: >>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz') >>> p.with_suffix('.bz2') @@ -563,6 +564,9 @@ Pure paths provide the following methods and properties: >>> p = PureWindowsPath('README') >>> p.with_suffix('.txt') PureWindowsPath('README.txt') + >>> p = PureWindowsPath('README.txt') + >>> p.with_suffix('') + PureWindowsPath('README') .. _concrete-paths: @@ -893,7 +897,8 @@ call fails (for example because the path doesn't exist): >>> p.read_text() 'Text file contents' - The optional parameters have the same meaning as in :func:`open`. + The file is opened and then closed. The optional parameters have the same + meaning as in :func:`open`. .. versionadded:: 3.5 diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index f5cb52cb..7d29dc18 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -270,7 +270,7 @@ Unix Platforms .. deprecated-removed:: 3.5 3.8 See alternative like the `distro `_ package. -.. function:: libc_ver(executable=sys.executable, lib='', version='', chunksize=2048) +.. function:: libc_ver(executable=sys.executable, lib='', version='', chunksize=16384) Tries to determine the libc version against which the file executable (defaults to the Python interpreter) is linked. Returns a tuple of strings ``(lib, diff --git a/Doc/library/profile.rst b/Doc/library/profile.rst index 32d2116e..845065e3 100644 --- a/Doc/library/profile.rst +++ b/Doc/library/profile.rst @@ -290,6 +290,11 @@ functions: Profile ``func(*args, **kwargs)`` +Note that profiling will only work if the called command/function actually +returns. If the interpreter is terminated (e.g. via a :func:`sys.exit` call +during the called command/function execution) no profiling results will be +printed. + .. _profile-stats: The :class:`Stats` Class diff --git a/Doc/library/re.rst b/Doc/library/re.rst index 14360edc..82632b92 100644 --- a/Doc/library/re.rst +++ b/Doc/library/re.rst @@ -1426,8 +1426,8 @@ Finding all Adverbs ^^^^^^^^^^^^^^^^^^^ :func:`findall` matches *all* occurrences of a pattern, not just the first -one as :func:`search` does. For example, if one was a writer and wanted to -find all of the adverbs in some text, he or she might use :func:`findall` in +one as :func:`search` does. For example, if a writer wanted to +find all of the adverbs in some text, they might use :func:`findall` in the following manner:: >>> text = "He was carefully disguised but captured quickly by police." @@ -1441,8 +1441,8 @@ Finding all Adverbs and their Positions If one wants more information about all matches of a pattern than the matched text, :func:`finditer` is useful as it provides :ref:`match objects ` instead of strings. Continuing with the previous example, if -one was a writer who wanted to find all of the adverbs *and their positions* in -some text, he or she would use :func:`finditer` in the following manner:: +a writer wanted to find all of the adverbs *and their positions* in +some text, they would use :func:`finditer` in the following manner:: >>> text = "He was carefully disguised but captured quickly by police." >>> for m in re.finditer(r"\w+ly", text): diff --git a/Doc/library/select.rst b/Doc/library/select.rst index bd5442c6..413ec357 100644 --- a/Doc/library/select.rst +++ b/Doc/library/select.rst @@ -57,7 +57,16 @@ The module defines the following: (Only supported on Linux 2.5.44 and newer.) Return an edge polling object, which can be used as Edge or Level Triggered interface for I/O - events. *sizehint* and *flags* are deprecated and completely ignored. + events. + + *sizehint* informs epoll about the expected number of events to be + registered. It must be positive, or `-1` to use the default. It is only + used on older systems where :c:func:`epoll_create1` is not available; + otherwise it has no effect (though its value is still checked). + + *flags* is deprecated and completely ignored. However, when supplied, its + value must be ``0`` or ``select.EPOLL_CLOEXEC``, otherwise ``OSError`` is + raised. See the :ref:`epoll-objects` section below for the methods supported by epolling objects. diff --git a/Doc/library/site.rst b/Doc/library/site.rst index 71ba999f..dabb4fee 100644 --- a/Doc/library/site.rst +++ b/Doc/library/site.rst @@ -243,7 +243,7 @@ If it is called without arguments, it will print the contents of If both options are given, user base and user site will be printed (always in this order), separated by :data:`os.pathsep`. -If any option is given, the script will exit with one of these values: ``O`` if +If any option is given, the script will exit with one of these values: ``0`` if the user site-packages directory is enabled, ``1`` if it was disabled by the user, ``2`` if it is disabled for security reasons or by an administrator, and a value greater than 2 if there is an error. diff --git a/Doc/library/smtplib.rst b/Doc/library/smtplib.rst index 86e769e6..6fb09342 100644 --- a/Doc/library/smtplib.rst +++ b/Doc/library/smtplib.rst @@ -271,7 +271,7 @@ An :class:`SMTP` instance has the following methods: .. method:: SMTP.ehlo_or_helo_if_needed() - This method call :meth:`ehlo` and or :meth:`helo` if there has been no + This method calls :meth:`ehlo` and/or :meth:`helo` if there has been no previous ``EHLO`` or ``HELO`` command this session. It tries ESMTP ``EHLO`` first. @@ -346,7 +346,7 @@ An :class:`SMTP` instance has the following methods: If optional keyword argument *initial_response_ok* is true, ``authobject()`` will be called first with no argument. It can return the - :rfc:`4954` "initial response" bytes which will be encoded and sent with + :rfc:`4954` "initial response" ASCII ``str`` which will be encoded and sent with the ``AUTH`` command as below. If the ``authobject()`` does not support an initial response (e.g. because it requires a challenge), it should return ``None`` when called with ``challenge=None``. If *initial_response_ok* is @@ -355,7 +355,7 @@ An :class:`SMTP` instance has the following methods: If the initial response check returns ``None``, or if *initial_response_ok* is false, ``authobject()`` will be called to process the server's challenge response; the *challenge* argument it is passed will be a ``bytes``. It - should return ``bytes`` *data* that will be base64 encoded and sent to the + should return ASCII ``str`` *data* that will be base64 encoded and sent to the server. The ``SMTP`` class provides ``authobjects`` for the ``CRAM-MD5``, ``PLAIN``, @@ -379,16 +379,23 @@ An :class:`SMTP` instance has the following methods: commands that follow will be encrypted. You should then call :meth:`ehlo` again. - If *keyfile* and *certfile* are provided, these are passed to the :mod:`socket` - module's :func:`ssl` function. + If *keyfile* and *certfile* are provided, they are used to create an + :class:`ssl.SSLContext`. - Optional *context* parameter is a :class:`ssl.SSLContext` object; This is + Optional *context* parameter is an :class:`ssl.SSLContext` object; This is an alternative to using a keyfile and a certfile and if specified both *keyfile* and *certfile* should be ``None``. If there has been no previous ``EHLO`` or ``HELO`` command this session, this method tries ESMTP ``EHLO`` first. + .. deprecated:: 3.6 + + *keyfile* and *certfile* are deprecated in favor of *context*. + Please use :meth:`ssl.SSLContext.load_cert_chain` instead, or let + :func:`ssl.create_default_context` select the system's trusted CA + certificates for you. + :exc:`SMTPHeloError` The server didn't reply properly to the ``HELO`` greeting. @@ -412,7 +419,7 @@ An :class:`SMTP` instance has the following methods: :exc:`SMTPException`. -.. method:: SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[]) +.. method:: SMTP.sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=()) Send mail. The required arguments are an :rfc:`822` from-address string, a list of :rfc:`822` to-address strings (a bare string will be treated as a list with 1 @@ -484,7 +491,7 @@ An :class:`SMTP` instance has the following methods: .. method:: SMTP.send_message(msg, from_addr=None, to_addrs=None, \ - mail_options=[], rcpt_options=[]) + mail_options=(), rcpt_options=()) This is a convenience method for calling :meth:`sendmail` with the message represented by an :class:`email.message.Message` object. The arguments have diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 512c38e7..f4e5af9d 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -70,6 +70,13 @@ created. Socket addresses are represented as follows: notation like ``'daring.cwi.nl'`` or an IPv4 address like ``'100.50.200.5'``, and *port* is an integer. + - For IPv4 addresses, two special forms are accepted instead of a host + address: ``''`` represents :const:`INADDR_ANY`, which is used to bind to all + interfaces, and the string ``''`` represents + :const:`INADDR_BROADCAST`. This behavior is not compatible with IPv6, + therefore, you may want to avoid these if you intend to support IPv6 with your + Python programs. + - For :const:`AF_INET6` address family, a four-tuple ``(host, port, flowinfo, scopeid)`` is used, where *flowinfo* and *scopeid* represent the ``sin6_flowinfo`` and ``sin6_scope_id`` members in :const:`struct sockaddr_in6` in C. For @@ -149,16 +156,25 @@ created. Socket addresses are represented as follows: .. versionadded:: 3.6 -- Certain other address families (:const:`AF_PACKET`, :const:`AF_CAN`) - support specific representations. - - .. XXX document them! - -For IPv4 addresses, two special forms are accepted instead of a host address: -the empty string represents :const:`INADDR_ANY`, and the string -``''`` represents :const:`INADDR_BROADCAST`. This behavior is not -compatible with IPv6, therefore, you may want to avoid these if you intend -to support IPv6 with your Python programs. +- :const:`AF_PACKET` is a low-level interface directly to network devices. + The packets are represented by the tuple + ``(ifname, proto[, pkttype[, hatype[, addr]]])`` where: + + - *ifname* - String specifying the device name. + - *proto* - An in network-byte-order integer specifying the Ethernet + protocol number. + - *pkttype* - Optional integer specifying the packet type: + + - ``PACKET_HOST`` (the default) - Packet addressed to the local host. + - ``PACKET_BROADCAST`` - Physical-layer broadcast packet. + - ``PACKET_MULTIHOST`` - Packet sent to a physical-layer multicast address. + - ``PACKET_OTHERHOST`` - Packet to some other host that has been caught by + a device driver in promiscuous mode. + - ``PACKET_OUTGOING`` - Packet originating from the local host that is + looped back to a packet socket. + - *hatype* - Optional integer specifying the ARP hardware address type. + - *addr* - Optional bytes-like object specifying the hardware physical + address, whose interpretation depends on the device. If you use a hostname in the *host* portion of IPv4/v6 socket address, the program may show a nondeterministic behavior, as Python uses the first address @@ -342,6 +358,17 @@ Constants .. versionadded:: 3.5 + +.. data:: AF_PACKET + PF_PACKET + PACKET_* + + Many constants of these forms, documented in the Linux documentation, are + also defined in the socket module. + + Availability: Linux >= 2.2. + + .. data:: AF_RDS PF_RDS SOL_RDS @@ -423,16 +450,16 @@ The following functions all create :ref:`socket objects `. Create a new socket using the given address family, socket type and protocol number. The address family should be :const:`AF_INET` (the default), - :const:`AF_INET6`, :const:`AF_UNIX`, :const:`AF_CAN` or :const:`AF_RDS`. The - socket type should be :const:`SOCK_STREAM` (the default), - :const:`SOCK_DGRAM`, :const:`SOCK_RAW` or perhaps one of the other ``SOCK_`` - constants. The protocol number is usually zero and may be omitted or in the - case where the address family is :const:`AF_CAN` the protocol should be one - of :const:`CAN_RAW` or :const:`CAN_BCM`. If *fileno* is specified, the other - arguments are ignored, causing the socket with the specified file descriptor - to return. Unlike :func:`socket.fromfd`, *fileno* will return the same - socket and not a duplicate. This may help close a detached socket using - :meth:`socket.close()`. + :const:`AF_INET6`, :const:`AF_UNIX`, :const:`AF_CAN`, :const:`AF_PACKET`, or + :const:`AF_RDS`. The socket type should be :const:`SOCK_STREAM` (the + default), :const:`SOCK_DGRAM`, :const:`SOCK_RAW` or perhaps one of the other + ``SOCK_`` constants. The protocol number is usually zero and may be omitted + or in the case where the address family is :const:`AF_CAN` the protocol + should be one of :const:`CAN_RAW` or :const:`CAN_BCM`. If *fileno* is + specified, the other arguments are ignored, causing the socket with the + specified file descriptor to return. Unlike :func:`socket.fromfd`, *fileno* + will return the same socket and not a duplicate. This may help close a + detached socket using :meth:`socket.close()`. The newly created socket is :ref:`non-inheritable `. diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index ef0c0bf6..a9048046 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -229,8 +229,8 @@ Module functions and constants Registers a callable to convert a bytestring from the database into a custom Python type. The callable will be invoked for all database values that are of the type *typename*. Confer the parameter *detect_types* of the :func:`connect` - function for how the type detection works. Note that the case of *typename* and - the name of the type in your query must match! + function for how the type detection works. Note that *typename* and the name of + the type in your query are matched in case-insensitive manner. .. function:: register_adapter(type, callable) @@ -274,7 +274,7 @@ Connection Objects .. attribute:: isolation_level - Get or set the current isolation level. :const:`None` for autocommit mode or + Get or set the current default isolation level. :const:`None` for autocommit mode or one of "DEFERRED", "IMMEDIATE" or "EXCLUSIVE". See section :ref:`sqlite3-controlling-transactions` for a more detailed explanation. @@ -764,6 +764,20 @@ Exceptions exists, syntax error in the SQL statement, wrong number of parameters specified, etc. It is a subclass of :exc:`DatabaseError`. +.. exception:: OperationalError + + Exception raised for errors that are related to the database's operation + and not necessarily under the control of the programmer, e.g. an unexpected + disconnect occurs, the data source name is not found, a transaction could + not be processed, etc. It is a subclass of :exc:`DatabaseError`. + +.. exception:: NotSupportedError + + Exception raised in case a method or database API was used which is not + supported by the database, e.g. calling the :meth:`~Connection.rollback` + method on a connection that does not support transaction or has + transactions turned off. It is a subclass of :exc:`DatabaseError`. + .. _sqlite3-types: @@ -932,22 +946,30 @@ timestamp converter. Controlling Transactions ------------------------ -By default, the :mod:`sqlite3` module opens transactions implicitly before a -Data Modification Language (DML) statement (i.e. -``INSERT``/``UPDATE``/``DELETE``/``REPLACE``). +The underlying ``sqlite3`` library operates in ``autocommit`` mode by default, +but the Python :mod:`sqlite3` module by default does not. -You can control which kind of ``BEGIN`` statements sqlite3 implicitly executes -(or none at all) via the *isolation_level* parameter to the :func:`connect` -call, or via the :attr:`isolation_level` property of connections. - -If you want **autocommit mode**, then set :attr:`isolation_level` to ``None``. +``autocommit`` mode means that statements that modify the database take effect +immediately. A ``BEGIN`` or ``SAVEPOINT`` statement disables ``autocommit`` +mode, and a ``COMMIT``, a ``ROLLBACK``, or a ``RELEASE`` that ends the +outermost transaction, turns ``autocommit`` mode back on. -Otherwise leave it at its default, which will result in a plain "BEGIN" -statement, or set it to one of SQLite's supported isolation levels: "DEFERRED", -"IMMEDIATE" or "EXCLUSIVE". +The Python :mod:`sqlite3` module by default issues a ``BEGIN`` statement +implicitly before a Data Modification Language (DML) statement (i.e. +``INSERT``/``UPDATE``/``DELETE``/``REPLACE``). -The current transaction state is exposed through the -:attr:`Connection.in_transaction` attribute of the connection object. +You can control which kind of ``BEGIN`` statements :mod:`sqlite3` implicitly +executes via the *isolation_level* parameter to the :func:`connect` +call, or via the :attr:`isolation_level` property of connections. +If you specify no *isolation_level*, a plain ``BEGIN`` is used, which is +equivalent to specifying ``DEFERRED``. Other possible values are ``IMMEDIATE`` +and ``EXCLUSIVE``. + +You can disable the :mod:`sqlite3` module's implicit transaction management by +setting :attr:`isolation_level` to ``None``. This will leave the underlying +``sqlite3`` library operating in ``autocommit`` mode. You can then completely +control the transaction state by explicitly issuing ``BEGIN``, ``ROLLBACK``, +``SAVEPOINT``, and ``RELEASE`` statements in your code. .. versionchanged:: 3.6 :mod:`sqlite3` used to implicitly commit an open transaction before DDL diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 45418c7d..a85be1a7 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -221,7 +221,7 @@ instead. The *ciphers* parameter sets the available ciphers for this SSL object. It should be a string in the `OpenSSL cipher list format - `_. + `_. The parameter ``do_handshake_on_connect`` specifies whether to do the SSL handshake automatically after doing a :meth:`socket.connect`, or whether the @@ -300,11 +300,6 @@ purposes. 3DES was dropped from the default cipher string. - .. versionchanged:: 3.6.3 - - TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, - and TLS_CHACHA20_POLY1305_SHA256 were added to the default cipher string. - Random generation ^^^^^^^^^^^^^^^^^ @@ -821,6 +816,15 @@ Constants .. versionadded:: 3.3 +.. data:: OP_ENABLE_MIDDLEBOX_COMPAT + + Send dummy Change Cipher Spec (CCS) messages in TLS 1.3 handshake to make + a TLS 1.3 connection look more like a TLS 1.2 connection. + + This option is only available with OpenSSL 1.1.1 and later. + + .. versionadded:: 3.6.7 + .. data:: OP_NO_COMPRESSION Disable compression on the SSL channel. This is useful if the application @@ -1206,6 +1210,26 @@ SSL sockets also have the following additional methods and attributes: returned socket should always be used for further communication with the other side of the connection, rather than the original socket. +.. method:: SSLSocket.verify_client_post_handshake() + + Requests post-handshake authentication (PHA) from a TLS 1.3 client. PHA + can only be initiated for a TLS 1.3 connection from a server-side socket, + after the initial TLS handshake and with PHA enabled on both sides, see + :attr:`SSLContext.post_handshake_auth`. + + The method does not perform a cert exchange immediately. The server-side + sends a CertificateRequest during the next write event and expects the + client to respond with a certificate on the next read event. + + If any precondition isn't met (e.g. not TLS 1.3, PHA not enabled), an + :exc:`SSLError` is raised. + + .. versionadded:: 3.6.7 + + .. note:: + Only available with OpenSSL 1.1.1 and TLS 1.3 enabled. Without TLS 1.3 + support, the method raises :exc:`NotImplementedError`. + .. method:: SSLSocket.version() Return the actual SSL protocol version negotiated by the connection @@ -1465,7 +1489,7 @@ to speed up repeated connections from the same clients. Set the available ciphers for sockets created with this context. It should be a string in the `OpenSSL cipher list format - `_. + `_. If no cipher can be selected (because compile-time options or other configuration forbids use of all the specified ciphers), an :class:`SSLError` will be raised. @@ -1474,6 +1498,9 @@ to speed up repeated connections from the same clients. when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will give the currently selected cipher. + OpenSSL 1.1.1 has TLS 1.3 cipher suites enabled by default. The suites + cannot be disabled with :meth:`~SSLContext.set_ciphers`. + .. method:: SSLContext.set_alpn_protocols(protocols) Specify which protocols the socket should advertise during the SSL/TLS @@ -1686,6 +1713,28 @@ to speed up repeated connections from the same clients. >>> ssl.create_default_context().options +.. attribute:: SSLContext.post_handshake_auth + + Enable TLS 1.3 post-handshake client authentication. Post-handshake auth + is disabled by default and a server can only request a TLS client + certificate during the initial handshake. When enabled, a server may + request a TLS client certificate at any time after the handshake. + + When enabled on client-side sockets, the client signals the server that + it supports post-handshake authentication. + + When enabled on server-side sockets, :attr:`SSLContext.verify_mode` must + be set to :data:`CERT_OPTIONAL` or :data:`CERT_REQUIRED`, too. The + actual client cert exchange is delayed until + :meth:`SSLSocket.verify_client_post_handshake` is called and some I/O is + performed. + + .. versionadded:: 3.6.7 + + .. note:: + Only available with OpenSSL 1.1.1 and TLS 1.3 enabled. Without TLS 1.3 + support, the property value is None and can't be modified + .. attribute:: SSLContext.protocol The protocol version chosen when constructing the context. This attribute @@ -1737,7 +1786,7 @@ message with one of the parts, you can decrypt it with the other part, and A certificate contains information about two principals. It contains the name of a *subject*, and the subject's public key. It also contains a statement by a -second principal, the *issuer*, that the subject is who he claims to be, and +second principal, the *issuer*, that the subject is who they claim to be, and that this is indeed the subject's public key. The issuer's statement is signed with the issuer's private key, which only the issuer knows. However, anyone can verify the issuer's statement by finding the issuer's public key, decrypting the diff --git a/Doc/library/statistics.rst b/Doc/library/statistics.rst index 2aa778c4..652e7513 100644 --- a/Doc/library/statistics.rst +++ b/Doc/library/statistics.rst @@ -169,6 +169,10 @@ However, for reading convenience, most of the examples show sorted sequences. This is suited for when your data is discrete, and you don't mind that the median may not be an actual data point. + If your data is ordinal (supports order operations) but not numeric (doesn't + support addition), you should use :func:`median_low` or :func:`median_high` + instead. + .. seealso:: :func:`median_low`, :func:`median_high`, :func:`median_grouped` diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index d8a1647e..6de7e1a2 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -197,8 +197,8 @@ exception. operator: not in Two more operations with the same syntactic priority, :keyword:`in` and -:keyword:`not in`, are supported only by sequence types (below). - +:keyword:`not in`, are supported by types that are :term:`iterable` or +implement the :meth:`__contains__` method. .. _typesnumeric: @@ -382,7 +382,7 @@ modules. .. _bitstring-ops: Bitwise Operations on Integer Types --------------------------------------- +----------------------------------- .. index:: triple: operations on; integer; types @@ -396,9 +396,9 @@ Bitwise Operations on Integer Types operator: >> operator: ~ -Bitwise operations only make sense for integers. Negative numbers are treated -as their 2's complement value (this assumes that there are enough bits so that -no overflow occurs during the operation). +Bitwise operations only make sense for integers. The result of bitwise +operations is calculated as though carried out in two's complement with an +infinite number of sign bits. The priorities of the binary bitwise operations are all lower than the numeric operations and higher than the comparisons; the unary operation ``~`` has the @@ -409,13 +409,13 @@ This table lists the bitwise operations sorted in ascending priority: +------------+--------------------------------+----------+ | Operation | Result | Notes | +============+================================+==========+ -| ``x | y`` | bitwise :dfn:`or` of *x* and | | +| ``x | y`` | bitwise :dfn:`or` of *x* and | \(4) | | | *y* | | +------------+--------------------------------+----------+ -| ``x ^ y`` | bitwise :dfn:`exclusive or` of | | +| ``x ^ y`` | bitwise :dfn:`exclusive or` of | \(4) | | | *x* and *y* | | +------------+--------------------------------+----------+ -| ``x & y`` | bitwise :dfn:`and` of *x* and | | +| ``x & y`` | bitwise :dfn:`and` of *x* and | \(4) | | | *y* | | +------------+--------------------------------+----------+ | ``x << n`` | *x* shifted left by *n* bits | (1)(2) | @@ -438,6 +438,12 @@ Notes: A right shift by *n* bits is equivalent to division by ``pow(2, n)`` without overflow check. +(4) + Performing these calculations with at least one extra sign extension bit in + a finite two's complement representation (a working bit-width of + ``1 + max(x.bit_length(), y.bit_length()`` or more) is sufficient to get the + same result as if there were an infinite number of sign bits. + Additional Methods on Integer Types ----------------------------------- @@ -1059,10 +1065,10 @@ accepts integers that meet the value restriction ``0 <= x <= 255``). | | sequence (same as | | | | ``s[len(s):len(s)] = [x]``) | | +------------------------------+--------------------------------+---------------------+ -| ``s.clear()`` | removes all items from ``s`` | \(5) | +| ``s.clear()`` | removes all items from *s* | \(5) | | | (same as ``del s[:]``) | | +------------------------------+--------------------------------+---------------------+ -| ``s.copy()`` | creates a shallow copy of ``s``| \(5) | +| ``s.copy()`` | creates a shallow copy of *s* | \(5) | | | (same as ``s[:]``) | | +------------------------------+--------------------------------+---------------------+ | ``s.extend(t)`` or | extends *s* with the | | @@ -1365,7 +1371,7 @@ objects that compare equal might have different :attr:`~range.start`, .. seealso:: * The `linspace recipe `_ - shows how to implement a lazy version of range that suitable for floating + shows how to implement a lazy version of range suitable for floating point applications. .. index:: @@ -1600,13 +1606,14 @@ expression support in the :mod:`re` module). that can be specified in format strings. .. note:: - When formatting a number (:class:`int`, :class:`float`, :class:`float` - and subclasses) with the ``n`` type (ex: ``'{:n}'.format(1234)``), the - function sets temporarily the ``LC_CTYPE`` locale to the ``LC_NUMERIC`` - locale to decode ``decimal_point`` and ``thousands_sep`` fields of - :c:func:`localeconv` if they are non-ASCII or longer than 1 byte, and the - ``LC_NUMERIC`` locale is different than the ``LC_CTYPE`` locale. This - temporary change affects other threads. + When formatting a number (:class:`int`, :class:`float`, :class:`complex`, + :class:`decimal.Decimal` and subclasses) with the ``n`` type + (ex: ``'{:n}'.format(1234)``), the function temporarily sets the + ``LC_CTYPE`` locale to the ``LC_NUMERIC`` locale to decode + ``decimal_point`` and ``thousands_sep`` fields of :c:func:`localeconv` if + they are non-ASCII or longer than 1 byte, and the ``LC_NUMERIC`` locale is + different than the ``LC_CTYPE`` locale. This temporary change affects + other threads. .. versionchanged:: 3.6.5 When formatting a number with the ``n`` type, the function sets @@ -2042,7 +2049,7 @@ expression support in the :mod:`re` module). .. method:: str.upper() Return a copy of the string with all the cased characters [4]_ converted to - uppercase. Note that ``str.upper().isupper()`` might be ``False`` if ``s`` + uppercase. Note that ``s.upper().isupper()`` might be ``False`` if ``s`` contains uncased characters or if the Unicode category of the resulting character(s) is not "Lu" (Letter, uppercase), but e.g. "Lt" (Letter, titlecase). @@ -2305,7 +2312,7 @@ data and are closely related to string objects in a variety of other ways. While bytes literals and representations are based on ASCII text, bytes objects actually behave like immutable sequences of integers, with each value in the sequence restricted such that ``0 <= x < 256`` (attempts to - violate this restriction will trigger :exc:`ValueError`. This is done + violate this restriction will trigger :exc:`ValueError`). This is done deliberately to emphasise that while many binary formats include ASCII based elements and can be usefully manipulated with some text-oriented algorithms, this is not generally the case for arbitrary binary data (blindly applying @@ -3360,7 +3367,10 @@ Notes: The bytearray version of this method does *not* operate in place - it always produces a new object, even if no changes were made. -.. seealso:: :pep:`461`. +.. seealso:: + + :pep:`461` - Adding % formatting to bytes and bytearray + .. versionadded:: 3.5 .. _typememoryview: diff --git a/Doc/library/string.rst b/Doc/library/string.rst index 706d5e1f..a87d285e 100644 --- a/Doc/library/string.rst +++ b/Doc/library/string.rst @@ -231,8 +231,11 @@ attribute using :func:`getattr`, while an expression of the form ``'[index]'`` does an index lookup using :func:`__getitem__`. .. versionchanged:: 3.1 - The positional argument specifiers can be omitted, so ``'{} {}'`` is - equivalent to ``'{0} {1}'``. + The positional argument specifiers can be omitted for :meth:`str.format`, + so ``'{} {}'.format(a, b)`` is equivalent to ``'{0} {1}'.format(a, b)``. + +.. versionchanged:: 3.4 + The positional argument specifiers can be omitted for :class:`Formatter`. Some simple format string examples:: @@ -461,11 +464,11 @@ The available presentation types for floating point and decimal values are: | ``'E'`` | Exponent notation. Same as ``'e'`` except it uses an | | | upper case 'E' as the separator character. | +---------+----------------------------------------------------------+ - | ``'f'`` | Fixed point. Displays the number as a fixed-point | - | | number. The default precision is ``6``. | + | ``'f'`` | Fixed-point notation. Displays the number as a | + | | fixed-point number. The default precision is ``6``. | +---------+----------------------------------------------------------+ - | ``'F'`` | Fixed point. Same as ``'f'``, but converts ``nan`` to | - | | ``NAN`` and ``inf`` to ``INF``. | + | ``'F'`` | Fixed-point notation. Same as ``'f'``, but converts | + | | ``nan`` to ``NAN`` and ``inf`` to ``INF``. | +---------+----------------------------------------------------------+ | ``'g'`` | General format. For a given precision ``p >= 1``, | | | this rounds the number to ``p`` significant digits and | diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index f624adbd..dfb183ab 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -1199,14 +1199,15 @@ handling consistency are valid for these functions. Windows support was added. The function now returns (exitcode, output) instead of (status, output) - as it did in Python 3.3.3 and earlier. See :func:`WEXITSTATUS`. + as it did in Python 3.3.3 and earlier. exitcode has the same value as + :attr:`~Popen.returncode`. .. function:: getoutput(cmd) Return output (stdout and stderr) of executing *cmd* in a shell. - Like :func:`getstatusoutput`, except the exit status is ignored and the return + Like :func:`getstatusoutput`, except the exit code is ignored and the return value is a string containing the command's output. Example:: >>> subprocess.getoutput('ls /bin/ls') diff --git a/Doc/library/tempfile.rst b/Doc/library/tempfile.rst index c59aca1e..0d0da4d6 100644 --- a/Doc/library/tempfile.rst +++ b/Doc/library/tempfile.rst @@ -253,7 +253,7 @@ to specify the directory and this is the recommended approach. default value for the *dir* argument to the functions defined in this module. - If ``tempdir`` is unset or ``None`` at any call to any of the above + If ``tempdir`` is ``None`` (the default) at any call to any of the above functions except :func:`gettempprefix` it is initialized following the algorithm described in :func:`gettempdir`. diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 26e6a35b..063d2c06 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -400,7 +400,8 @@ All methods are executed atomically. The *timeout* parameter is new. .. versionchanged:: 3.2 - Lock acquires can now be interrupted by signals on POSIX. + Lock acquisition can now be interrupted by signals on POSIX if the + underlying threading implementation supports it. .. method:: release() diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index 55d331c9..7ac3cacd 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -88,14 +88,16 @@ The module defines the following functions: .. function:: extract_tb(tb, limit=None) - Return a list of "pre-processed" stack trace entries extracted from the - traceback object *tb*. It is useful for alternate formatting of - stack traces. The optional *limit* argument has the same meaning as for - :func:`print_tb`. A "pre-processed" stack trace entry is a 4-tuple - (*filename*, *line number*, *function name*, *text*) representing the - information that is usually printed for a stack trace. The *text* is a - string with leading and trailing whitespace stripped; if the source is - not available it is ``None``. + Return a :class:`StackSummary` object representing a list of "pre-processed" + stack trace entries extracted from the traceback object *tb*. It is useful + for alternate formatting of stack traces. The optional *limit* argument has + the same meaning as for :func:`print_tb`. A "pre-processed" stack trace + entry is a :class:`FrameSummary` object containing attributes + :attr:`~FrameSummary.filename`, :attr:`~FrameSummary.lineno`, + :attr:`~FrameSummary.name`, and :attr:`~FrameSummary.line` representing the + information that is usually printed for a stack trace. The + :attr:`~FrameSummary.line` is a string with leading and trailing + whitespace stripped; if the source is not available it is ``None``. .. function:: extract_stack(f=None, limit=None) @@ -107,12 +109,12 @@ The module defines the following functions: .. function:: format_list(extracted_list) - Given a list of tuples as returned by :func:`extract_tb` or - :func:`extract_stack`, return a list of strings ready for printing. Each - string in the resulting list corresponds to the item with the same index in - the argument list. Each string ends in a newline; the strings may contain - internal newlines as well, for those items whose source text line is not - ``None``. + Given a list of tuples or :class:`FrameSummary` objects as returned by + :func:`extract_tb` or :func:`extract_stack`, return a list of strings ready + for printing. Each string in the resulting list corresponds to the item with + the same index in the argument list. Each string ends in a newline; the + strings may contain internal newlines as well, for those items whose source + text line is not ``None``. .. function:: format_exception_only(etype, value) @@ -293,9 +295,9 @@ capture data for later printing in a lightweight fashion. .. classmethod:: from_list(a_list) - Construct a :class:`StackSummary` object from a supplied old-style list - of tuples. Each tuple should be a 4-tuple with filename, lineno, name, - line as the elements. + Construct a :class:`StackSummary` object from a supplied list of + :class:`FrameSummary` objects or old-style list of tuples. Each tuple + should be a 4-tuple with filename, lineno, name, line as the elements. .. method:: format() diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index bd6f3a95..e80cd3f2 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -169,6 +169,8 @@ It is possible to declare the return type of a callable without specifying the call signature by substituting a literal ellipsis for the list of arguments in the type hint: ``Callable[..., ReturnType]``. +.. _generics: + Generics -------- @@ -183,7 +185,7 @@ subscription to denote expected types for container elements. def notify_by_email(employees: Sequence[Employee], overrides: Mapping[str, str]) -> None: ... -Generics can be parametrized by using a new factory available in typing +Generics can be parameterized by using a new factory available in typing called :class:`TypeVar`. :: @@ -488,8 +490,9 @@ The module defines the following classes, functions and decorators: required to handle this particular case may change in future revisions of :pep:`484`. - The only legal parameters for :class:`Type` are classes, unions of classes, and - :data:`Any`. For example:: + The only legal parameters for :class:`Type` are classes, :data:`Any`, + :ref:`type variables `, and unions of any of these types. + For example:: def new_non_team_user(user_class: Type[Union[BaseUser, ProUser]]): ... @@ -992,10 +995,18 @@ The module defines the following classes, functions and decorators: Note that this is not the same concept as an optional argument, which is one that has a default. An optional argument with a - default needn't use the ``Optional`` qualifier on its type - annotation (although it is inferred if the default is ``None``). - A mandatory argument may still have an ``Optional`` type if an - explicit value of ``None`` is allowed. + default does not require the ``Optional`` qualifier on its type + annotation just because it is optional. For example:: + + def foo(arg: int = 0) -> None: + ... + + On the other hand, if an explicit value of ``None`` is allowed, the + use of ``Optional`` is appropriate, whether the argument is optional + or not. For example:: + + def foo(arg: Optional[int] = None) -> None: + ... .. data:: Tuple diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 9d4d36fe..6841ef8e 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -1825,12 +1825,12 @@ sentinel .. data:: sentinel - The ``sentinel`` object provides a convenient way of providing unique - objects for your tests. + The ``sentinel`` object provides a convenient way of providing unique + objects for your tests. - Attributes are created on demand when you access them by name. Accessing - the same attribute will always return the same object. The objects - returned have a sensible repr so that test failure messages are readable. + Attributes are created on demand when you access them by name. Accessing + the same attribute will always return the same object. The objects + returned have a sensible repr so that test failure messages are readable. The ``sentinel`` attributes don't preserve their identity when they are :mod:`copied ` or :mod:`pickled `. @@ -2069,22 +2069,22 @@ mock_open .. function:: mock_open(mock=None, read_data=None) - A helper function to create a mock to replace the use of :func:`open`. It works - for :func:`open` called directly or used as a context manager. - - The *mock* argument is the mock object to configure. If ``None`` (the - default) then a :class:`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 :meth:`~io.IOBase.read`, - :meth:`~io.IOBase.readline`, and :meth:`~io.IOBase.readlines` methods - of the file handle to return. Calls to those methods will take data from - *read_data* until it is depleted. The mock of these methods is pretty - simplistic: every time the *mock* is called, the *read_data* is rewound to - the start. If you need more control over the data that you are feeding to - the tested code you will need to customize this mock for yourself. When that - is insufficient, one of the in-memory filesystem packages on `PyPI - `_ can offer a realistic filesystem for testing. + A helper function to create a mock to replace the use of :func:`open`. It works + for :func:`open` called directly or used as a context manager. + + The *mock* argument is the mock object to configure. If ``None`` (the + default) then a :class:`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 :meth:`~io.IOBase.read`, + :meth:`~io.IOBase.readline`, and :meth:`~io.IOBase.readlines` methods + of the file handle to return. Calls to those methods will take data from + *read_data* until it is depleted. The mock of these methods is pretty + simplistic: every time the *mock* is called, the *read_data* is rewound to + the start. If you need more control over the data that you are feeding to + the tested code you will need to customize this mock for yourself. When that + is insufficient, one of the in-memory filesystem packages on `PyPI + `_ can offer a realistic filesystem for testing. .. versionchanged:: 3.4 Added :meth:`~io.IOBase.readline` and :meth:`~io.IOBase.readlines` support. diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index f0de85f7..dd85e9e3 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -393,7 +393,7 @@ run whether the test method succeeded or not. Such a working environment for the testing code is called a :dfn:`test fixture`. A new TestCase instance is created as a unique test fixture used to execute each individual test method. Thus -`~TestCase.setUp`, `~TestCase.tearDown`, and `~TestCase.__init__` +:meth:`~TestCase.setUp`, :meth:`~TestCase.tearDown`, and :meth:`~TestCase.__init__` will be called once per test. It is recommended that you use TestCase implementations to group tests together @@ -705,7 +705,7 @@ Test cases .. method:: setUpClass() - A class method called before tests in an individual class run. + A class method called before tests in an individual class are run. ``setUpClass`` is called with the class as the only argument and must be decorated as a :func:`classmethod`:: @@ -925,7 +925,7 @@ Test cases +---------------------------------------------------------+--------------------------------------+------------+ .. method:: assertRaises(exception, callable, *args, **kwds) - assertRaises(exception, msg=None) + assertRaises(exception, *, msg=None) Test that an exception is raised when *callable* is called with any positional or keyword arguments that are also passed to @@ -965,7 +965,7 @@ Test cases .. method:: assertRaisesRegex(exception, regex, callable, *args, **kwds) - assertRaisesRegex(exception, regex, msg=None) + assertRaisesRegex(exception, regex, *, msg=None) Like :meth:`assertRaises` but also tests that *regex* matches on the string representation of the raised exception. *regex* may be @@ -991,7 +991,7 @@ Test cases .. method:: assertWarns(warning, callable, *args, **kwds) - assertWarns(warning, msg=None) + assertWarns(warning, *, msg=None) Test that a warning is triggered when *callable* is called with any positional or keyword arguments that are also passed to @@ -1032,7 +1032,7 @@ Test cases .. method:: assertWarnsRegex(warning, regex, callable, *args, **kwds) - assertWarnsRegex(warning, regex, msg=None) + assertWarnsRegex(warning, regex, *, msg=None) Like :meth:`assertWarns` but also tests that *regex* matches on the message of the triggered warning. *regex* may be a regular expression diff --git a/Doc/library/urllib.request.rst b/Doc/library/urllib.request.rst index 705517a3..0890f74c 100644 --- a/Doc/library/urllib.request.rst +++ b/Doc/library/urllib.request.rst @@ -1125,7 +1125,7 @@ UnknownHandler Objects HTTPErrorProcessor Objects -------------------------- -.. method:: HTTPErrorProcessor.http_response() +.. method:: HTTPErrorProcessor.http_response(request, response) Process HTTP error responses. @@ -1137,7 +1137,7 @@ HTTPErrorProcessor Objects :exc:`~urllib.error.HTTPError` if no other handler handles the error. -.. method:: HTTPErrorProcessor.https_response() +.. method:: HTTPErrorProcessor.https_response(request, response) Process HTTPS error responses. @@ -1339,9 +1339,9 @@ some point in the future. The second argument, if present, specifies the file location to copy to (if absent, the location will be a tempfile with a generated name). The third - argument, if present, is a hook function that will be called once on + argument, if present, is a callable that will be called once on establishment of the network connection and once after each block read - thereafter. The hook will be passed three arguments; a count of blocks + thereafter. The callable will be passed three arguments; a count of blocks transferred so far, a block size in bytes, and the total size of the file. The third argument may be ``-1`` on older FTP servers which do not return a file size in response to a retrieval request. diff --git a/Doc/library/xml.dom.pulldom.rst b/Doc/library/xml.dom.pulldom.rst index 5c0f469a..2504409e 100644 --- a/Doc/library/xml.dom.pulldom.rst +++ b/Doc/library/xml.dom.pulldom.rst @@ -25,6 +25,20 @@ events until either processing is finished or an error condition occurs. maliciously constructed data. If you need to parse untrusted or unauthenticated data see :ref:`xml-vulnerabilities`. +.. versionchanged:: 3.6.7 + + The SAX parser no longer processes general external entities by default to + increase security by default. To enable processing of external entities, + pass a custom parser instance in:: + + from xml.dom.pulldom import parse + from xml.sax import make_parser + from xml.sax.handler import feature_external_ges + + parser = make_parser() + parser.setFeature(feature_external_ges, True) + parse(filename, parser=parser) + Example:: diff --git a/Doc/library/xml.rst b/Doc/library/xml.rst index 63c24f80..9b8ba6b1 100644 --- a/Doc/library/xml.rst +++ b/Doc/library/xml.rst @@ -65,8 +65,8 @@ kind sax etree minidom p ========================= ============== =============== ============== ============== ============== billion laughs **Vulnerable** **Vulnerable** **Vulnerable** **Vulnerable** **Vulnerable** quadratic blowup **Vulnerable** **Vulnerable** **Vulnerable** **Vulnerable** **Vulnerable** -external entity expansion **Vulnerable** Safe (1) Safe (2) **Vulnerable** Safe (3) -`DTD`_ retrieval **Vulnerable** Safe Safe **Vulnerable** Safe +external entity expansion Safe (4) Safe (1) Safe (2) Safe (4) Safe (3) +`DTD`_ retrieval Safe (4) Safe Safe Safe (4) Safe decompression bomb Safe Safe Safe Safe **Vulnerable** ========================= ============== =============== ============== ============== ============== @@ -75,6 +75,8 @@ decompression bomb Safe Safe Safe S 2. :mod:`xml.dom.minidom` doesn't expand external entities and simply returns the unexpanded entity verbatim. 3. :mod:`xmlrpclib` doesn't expand external entities and omits them. +4. Since Python 3.8.0, external general entities are no longer processed by + default since Python. billion laughs / exponential entity expansion diff --git a/Doc/library/xml.sax.rst b/Doc/library/xml.sax.rst index 78d6633e..1a8f183a 100644 --- a/Doc/library/xml.sax.rst +++ b/Doc/library/xml.sax.rst @@ -24,6 +24,14 @@ the SAX API. constructed data. If you need to parse untrusted or unauthenticated data see :ref:`xml-vulnerabilities`. +.. versionchanged:: 3.6.7 + + The SAX parser no longer processes general external entities by default + to increase security. Before, the parser created network connections + to fetch remote files or loaded local files from the file + system for DTD and entities. The feature can be enabled again with method + :meth:`~xml.sax.xmlreader.XMLReader.setFeature` on the parser object + and argument :data:`~xml.sax.handler.feature_external_ges`. The convenience functions are: diff --git a/Doc/library/xmlrpc.client.rst b/Doc/library/xmlrpc.client.rst index ed2ccaea..27d92e32 100644 --- a/Doc/library/xmlrpc.client.rst +++ b/Doc/library/xmlrpc.client.rst @@ -145,7 +145,7 @@ between conformable Python objects and XML on the wire. .. versionchanged:: 3.6 Added support of type tags with prefixes (e.g. ``ex:nil``). - Added support of unmarsalling additional types used by Apache XML-RPC + Added support of unmarshalling additional types used by Apache XML-RPC implementation for numerics: ``i1``, ``i2``, ``i8``, ``biginteger``, ``float`` and ``bigdecimal``. See http://ws.apache.org/xmlrpc/types.html for a description. diff --git a/Doc/make.bat b/Doc/make.bat index 8c6cb9a3..2ddb3e23 100644 --- a/Doc/make.bat +++ b/Doc/make.bat @@ -131,6 +131,9 @@ if exist ..\Misc\NEWS ( if NOT "%PAPER%" == "" ( set SPHINXOPTS=-D latex_elements.papersize=%PAPER% %SPHINXOPTS% ) +if "%1" EQU "htmlhelp" ( + set SPHINXOPTS=-D html_theme_options.body_max_width=none %SPHINXOPTS% +) cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% -b%1 -dbuild\doctrees . "%BUILDDIR%\%1" %2 %3 %4 %5 %6 %7 %8 %9" if "%1" EQU "htmlhelp" ( diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index dca93624..1f753308 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -91,7 +91,7 @@ The :keyword:`if` statement is used for conditional execution: .. productionlist:: if_stmt: "if" `expression` ":" `suite` - : ( "elif" `expression` ":" `suite` )* + : ("elif" `expression` ":" `suite`)* : ["else" ":" `suite`] It selects exactly one of the suites by evaluating the expressions one by one @@ -203,7 +203,7 @@ returns the list ``[0, 1, 2]``. single: mutable sequence; loop over There is a subtlety when the sequence is being modified by the loop (this can - only occur for mutable sequences, i.e. lists). An internal counter is used + only occur for mutable sequences, e.g. lists). An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. When this counter has reached the length of the sequence the loop terminates. This means that if the suite deletes the current (or a previous) @@ -235,7 +235,7 @@ The :keyword:`try` statement specifies exception handlers and/or cleanup code for a group of statements: .. productionlist:: - try_stmt: try1_stmt | try2_stmt + try_stmt: `try1_stmt` | `try2_stmt` try1_stmt: "try" ":" `suite` : ("except" [`expression` ["as" `identifier`]] ":" `suite`)+ : ["else" ":" `suite`] @@ -383,7 +383,7 @@ This allows common :keyword:`try`...\ :keyword:`except`...\ :keyword:`finally` usage patterns to be encapsulated for convenient reuse. .. productionlist:: - with_stmt: "with" with_item ("," with_item)* ":" `suite` + with_stmt: "with" `with_item` ("," `with_item`)* ":" `suite` with_item: `expression` ["as" `target`] The execution of the :keyword:`with` statement with one "item" proceeds as follows: @@ -467,14 +467,15 @@ A function definition defines a user-defined function object (see section :ref:`types`): .. productionlist:: - funcdef: [`decorators`] "def" `funcname` "(" [`parameter_list`] ")" ["->" `expression`] ":" `suite` + funcdef: [`decorators`] "def" `funcname` "(" [`parameter_list`] ")" + : ["->" `expression`] ":" `suite` decorators: `decorator`+ decorator: "@" `dotted_name` ["(" [`argument_list` [","]] ")"] NEWLINE dotted_name: `identifier` ("." `identifier`)* parameter_list: `defparameter` ("," `defparameter`)* ["," [`parameter_list_starargs`]] : | `parameter_list_starargs` parameter_list_starargs: "*" [`parameter`] ("," `defparameter`)* ["," ["**" `parameter` [","]]] - : | "**" `parameter` [","] + : | "**" `parameter` [","] parameter: `identifier` [":" `expression`] defparameter: `parameter` ["=" `expression`] funcname: `identifier` @@ -682,7 +683,8 @@ Coroutine function definition ----------------------------- .. productionlist:: - async_funcdef: [`decorators`] "async" "def" `funcname` "(" [`parameter_list`] ")" ["->" `expression`] ":" `suite` + async_funcdef: [`decorators`] "async" "def" `funcname` "(" [`parameter_list`] ")" + : ["->" `expression`] ":" `suite` .. index:: keyword: async diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 57ae639a..3f929bf0 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -730,7 +730,7 @@ whose value is one of the keys of the mapping, and the subscription selects the value in the mapping that corresponds to that key. (The expression list is a tuple except if it has exactly one item.) -If the primary is a sequence, the expression (list) must evaluate to an integer +If the primary is a sequence, the expression list must evaluate to an integer or a slice (as discussed in the following section). The formal syntax makes no special provision for negative indices in @@ -1013,7 +1013,7 @@ The power operator binds more tightly than unary operators on its left; it binds less tightly than unary operators on its right. The syntax is: .. productionlist:: - power: ( `await_expr` | `primary` ) ["**" `u_expr`] + power: (`await_expr` | `primary`) ["**" `u_expr`] Thus, in an unparenthesized sequence of power and unary operators, the operators are evaluated from right to left (this does not constrain the evaluation order @@ -1085,7 +1085,7 @@ operators and one for additive operators: .. productionlist:: m_expr: `u_expr` | `m_expr` "*" `u_expr` | `m_expr` "@" `m_expr` | - : `m_expr` "//" `u_expr`| `m_expr` "/" `u_expr` | + : `m_expr` "//" `u_expr` | `m_expr` "/" `u_expr` | : `m_expr` "%" `u_expr` a_expr: `m_expr` | `a_expr` "+" `m_expr` | `a_expr` "-" `m_expr` @@ -1097,7 +1097,9 @@ the other must be a sequence. In the former case, the numbers are converted to a common type and then multiplied together. In the latter case, sequence repetition is performed; a negative repetition factor yields an empty sequence. -.. index:: single: matrix multiplication +.. index:: + single: matrix multiplication + operator: @ The ``@`` (at) operator is intended to be used for matrix multiplication. No builtin Python types implement this operator. @@ -1163,7 +1165,7 @@ Shifting operations The shifting operations have lower priority than the arithmetic operations: .. productionlist:: - shift_expr: `a_expr` | `shift_expr` ( "<<" | ">>" ) `a_expr` + shift_expr: `a_expr` | `shift_expr` ("<<" | ">>") `a_expr` These operators accept integers as arguments. They shift the first argument to the left or right by the number of bits given by the second argument. @@ -1228,7 +1230,7 @@ C, expressions like ``a < b < c`` have the interpretation that is conventional in mathematics: .. productionlist:: - comparison: `or_expr` ( `comp_operator` `or_expr` )* + comparison: `or_expr` (`comp_operator` `or_expr`)* comp_operator: "<" | ">" | "==" | ">=" | "<=" | "!=" : | "is" ["not"] | ["not"] "in" @@ -1592,9 +1594,9 @@ Expression lists .. index:: pair: expression; list .. productionlist:: - expression_list: `expression` ( "," `expression` )* [","] - starred_list: `starred_item` ( "," `starred_item` )* [","] - starred_expression: `expression` | ( `starred_item` "," )* [`starred_item`] + expression_list: `expression` ("," `expression`)* [","] + starred_list: `starred_item` ("," `starred_item`)* [","] + starred_expression: `expression` | (`starred_item` ",")* [`starred_item`] starred_item: `expression` | "*" `or_expr` .. index:: object: tuple diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 8d173838..76630dfc 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -708,15 +708,14 @@ The :keyword:`import` statement keyword: from .. productionlist:: - import_stmt: "import" `module` ["as" `name`] ( "," `module` ["as" `name`] )* - : | "from" `relative_module` "import" `identifier` ["as" `name`] - : ( "," `identifier` ["as" `name`] )* - : | "from" `relative_module` "import" "(" `identifier` ["as" `name`] - : ( "," `identifier` ["as" `name`] )* [","] ")" + import_stmt: "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])* + : | "from" `relative_module` "import" `identifier` ["as" `identifier`] + : ("," `identifier` ["as" `identifier`])* + : | "from" `relative_module` "import" "(" `identifier` ["as" `identifier`] + : ("," `identifier` ["as" `identifier`])* [","] ")" : | "from" `module` "import" "*" module: (`identifier` ".")* `identifier` relative_module: "."* `module` | "."+ - name: `identifier` The basic import statement (no :keyword:`from` clause) is executed in two steps: @@ -838,12 +837,11 @@ features on a per-module basis before the release in which the feature becomes standard. .. productionlist:: * - future_statement: "from" "__future__" "import" feature ["as" name] - : ("," feature ["as" name])* - : | "from" "__future__" "import" "(" feature ["as" name] - : ("," feature ["as" name])* [","] ")" - feature: identifier - name: identifier + future_stmt: "from" "__future__" "import" `feature` ["as" `identifier`] + : ("," `feature` ["as" `identifier`])* + : | "from" "__future__" "import" "(" `feature` ["as" `identifier`] + : ("," `feature` ["as" `identifier`])* [","] ")" + feature: `identifier` A future statement must appear near the top of the module. The only lines that can appear before a future statement are: diff --git a/Doc/reference/toplevel_components.rst b/Doc/reference/toplevel_components.rst index e1687ff0..d5ffb37b 100644 --- a/Doc/reference/toplevel_components.rst +++ b/Doc/reference/toplevel_components.rst @@ -48,14 +48,15 @@ a complete program; each statement is executed in the namespace of .. index:: single: UNIX + single: Windows single: command line single: standard input -Under Unix, a complete program can be passed to the interpreter in three forms: -with the :option:`-c` *string* command line option, as a file passed as the -first command line argument, or as standard input. If the file or standard -input is a tty device, the interpreter enters interactive mode; otherwise, it -executes the file as a complete program. +A complete program can be passed to the interpreter +in three forms: with the :option:`-c` *string* command line option, as a file +passed as the first command line argument, or as standard input. If the file +or standard input is a tty device, the interpreter enters interactive mode; +otherwise, it executes the file as a complete program. .. _file-input: diff --git a/Doc/tools/extensions/escape4chm.py b/Doc/tools/extensions/escape4chm.py new file mode 100644 index 00000000..6f2e3572 --- /dev/null +++ b/Doc/tools/extensions/escape4chm.py @@ -0,0 +1,39 @@ +""" +Escape the `body` part of .chm source file to 7-bit ASCII, to fix visual +effect on some MBCS Windows systems. + +https://bugs.python.org/issue32174 +""" + +import re +from html.entities import codepoint2name + +# escape the characters which codepoint > 0x7F +def _process(string): + def escape(matchobj): + codepoint = ord(matchobj.group(0)) + + name = codepoint2name.get(codepoint) + if name is None: + return '&#%d;' % codepoint + else: + return '&%s;' % name + + return re.sub(r'[^\x00-\x7F]', escape, string) + +def escape_for_chm(app, pagename, templatename, context, doctree): + # only works for .chm output + if not hasattr(app.builder, 'name') or app.builder.name != 'htmlhelp': + return + + # escape the `body` part to 7-bit ASCII + body = context.get('body') + if body is not None: + context['body'] = _process(body) + +def setup(app): + # `html-page-context` event emitted when the HTML builder has + # created a context dictionary to render a template with. + app.connect('html-page-context', escape_for_chm) + + return {'version': '1.0', 'parallel_read_safe': True} diff --git a/Doc/tutorial/classes.rst b/Doc/tutorial/classes.rst index 4676ef4b..f26838cd 100644 --- a/Doc/tutorial/classes.rst +++ b/Doc/tutorial/classes.rst @@ -387,8 +387,8 @@ the corresponding function with an argument list that is created by inserting the method's instance object before the first argument. If you still don't understand how methods work, a look at the implementation can -perhaps clarify matters. When an instance attribute is referenced that isn't a -data attribute, its class is searched. If the name denotes a valid class +perhaps clarify matters. When a non-data attribute of an instance is +referenced, the instance's class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called diff --git a/Doc/tutorial/inputoutput.rst b/Doc/tutorial/inputoutput.rst index d5531029..9fd939a2 100644 --- a/Doc/tutorial/inputoutput.rst +++ b/Doc/tutorial/inputoutput.rst @@ -289,7 +289,7 @@ automatically fail. :: >>> f.read() Traceback (most recent call last): File "", line 1, in - ValueError: I/O operation on closed file + ValueError: I/O operation on closed file. .. _tut-filemethods: diff --git a/Doc/tutorial/introduction.rst b/Doc/tutorial/introduction.rst index 6415ae66..2454bf0b 100644 --- a/Doc/tutorial/introduction.rst +++ b/Doc/tutorial/introduction.rst @@ -143,12 +143,12 @@ to escape quotes:: "doesn't" >>> "doesn't" # ...or use double quotes instead "doesn't" - >>> '"Yes," he said.' - '"Yes," he said.' - >>> "\"Yes,\" he said." - '"Yes," he said.' - >>> '"Isn\'t," she said.' - '"Isn\'t," she said.' + >>> '"Yes," they said.' + '"Yes," they said.' + >>> "\"Yes,\" they said." + '"Yes," they said.' + >>> '"Isn\'t," they said.' + '"Isn\'t," they said.' In the interactive interpreter, the output string is enclosed in quotes and special characters are escaped with backslashes. While this might sometimes @@ -159,10 +159,10 @@ enclosed in single quotes. The :func:`print` function produces a more readable output, by omitting the enclosing quotes and by printing escaped and special characters:: - >>> '"Isn\'t," she said.' - '"Isn\'t," she said.' - >>> print('"Isn\'t," she said.') - "Isn't," she said. + >>> '"Isn\'t," they said.' + '"Isn\'t," they said.' + >>> print('"Isn\'t," they said.') + "Isn't," they said. >>> s = 'First line.\nSecond line.' # \n means newline >>> s # without print(), \n is included in the output 'First line.\nSecond line.' diff --git a/Doc/tutorial/modules.rst b/Doc/tutorial/modules.rst index a4c766ef..04352cf6 100644 --- a/Doc/tutorial/modules.rst +++ b/Doc/tutorial/modules.rst @@ -249,7 +249,7 @@ Some tips for experts: directory. * There is more detail on this process, including a flow chart of the - decisions, in PEP 3147. + decisions, in :pep:`3147`. .. _tut-standardmodules: diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index c6624fe4..a4294686 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -147,7 +147,7 @@ of available options is shown below. | | ``.pyc``. | | +---------------------------+--------------------------------------+--------------------------+ | PrependPath | Add install and Scripts directories | 0 | -| | tho :envvar:`PATH` and ``.PY`` to | | +| | to :envvar:`PATH` and ``.PY`` to | | | | :envvar:`PATHEXT` | | +---------------------------+--------------------------------------+--------------------------+ | Shortcuts | Create shortcuts for the interpreter,| 1 | @@ -210,7 +210,7 @@ The options listed above can also be provided in a file named ``unattend.xml`` alongside the executable. This file specifies a list of options and values. When a value is provided as an attribute, it will be converted to a number if possible. Values provided as element text are always left as strings. This -example file sets the same options and the previous example: +example file sets the same options as the previous example: .. code-block:: xml @@ -610,7 +610,7 @@ Customization via INI files Two .ini files will be searched by the launcher - ``py.ini`` in the current user's "application data" directory (i.e. the directory returned by calling the -Windows function SHGetFolderPath with CSIDL_LOCAL_APPDATA) and ``py.ini`` in the +Windows function ``SHGetFolderPath`` with ``CSIDL_LOCAL_APPDATA``) and ``py.ini`` in the same directory as the launcher. The same .ini files are used for both the 'console' version of the launcher (i.e. py.exe) and for the 'windows' version (i.e. pyw.exe) @@ -816,7 +816,7 @@ following advice will prevent conflicts with other installations: These will ensure that the files in a system-wide installation will not take precedence over the copy of the standard library bundled with your application. Otherwise, your users may experience problems using your application. Note that -the first suggestion is the best, as the other may still be susceptible to +the first suggestion is the best, as the others may still be susceptible to non-standard paths in the registry and user site-packages. .. versionchanged:: diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index b091507a..79b34907 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -303,7 +303,7 @@ The launcher can also be used explicitly from the command line as the ``py`` application. Running ``py`` follows the same version selection rules as implicitly launching scripts, but a more specific version can be selected by passing appropriate arguments (such as ``-3`` to request Python 3 when -Python 2 is also installed, or ``-2.6`` to specifclly request an earlier +Python 2 is also installed, or ``-2.6`` to specifically request an earlier Python version when a more recent version is installed). In addition to the launcher, the Windows installer now includes an @@ -1414,7 +1414,7 @@ http :class:`http.server.BaseHTTPRequestHandler` now buffers the headers and writes them all at once when :meth:`~http.server.BaseHTTPRequestHandler.end_headers` is called. A new method :meth:`~http.server.BaseHTTPRequestHandler.flush_headers` -can be used to directly manage when the accumlated headers are sent. +can be used to directly manage when the accumulated headers are sent. (Contributed by Andrew Schaaf in :issue:`3709`.) :class:`http.server` now produces valid ``HTML 4.01 strict`` output. @@ -2386,7 +2386,7 @@ Porting Python code finder, you will need to remove keys paired with values of ``None`` **and** :class:`imp.NullImporter` to be backwards-compatible. This will lead to extra overhead on older versions of Python that re-insert ``None`` into - :attr:`sys.path_importer_cache` where it repesents the use of implicit + :attr:`sys.path_importer_cache` where it represents the use of implicit finders, but semantically it should not change anything. * :class:`importlib.abc.Finder` no longer specifies a `find_module()` abstract diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 96f86e6c..dc6a3ead 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -2471,7 +2471,7 @@ Changes in the Python API parameter to help control the ``opt-`` tag. Because of this, the *debug_override* parameter of the function is now deprecated. `.pyo` files are also no longer supported as a file argument to the Python interpreter and - thus serve no purpose when distributed on their own (i.e. sourcless code + thus serve no purpose when distributed on their own (i.e. sourceless code distribution). Due to the fact that the magic number for bytecode has changed in Python 3.5, all old `.pyo` files from previous versions of Python are invalid regardless of this PEP. diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst index 2c675a5f..96a8831c 100644 --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -1176,13 +1176,22 @@ Editor code context option revised. Box displays all context lines up to maxlines. Clicking on a context line jumps the editor to that line. Context colors for custom themes is added to Highlights tab of Settings dialog. (Contributed by Cheryl Sabella and Terry Jan Reedy in :issue:`33642`, -:issue:`33768`, and :issue:`33679`) +:issue:`33768`, and :issue:`33679`.) On Windows, a new API call tells Windows that tk scales for DPI. On Windows 8.1+ or 10, with DPI compatibility properties of the Python binary unchanged, and a monitor resolution greater than 96 DPI, this should make text and lines sharper. It should otherwise have no effect. -(Contributed by Terry Jan Reedy in :issue:`33656`). +(Contributed by Terry Jan Reedy in :issue:`33656`.) + +New in 3.6.7: + +Output over N lines (50 by default) is squeezed down to a button. +N can be changed in the PyShell section of the General page of the +Settings dialog. Fewer, but possibly extra long, lines can be squeezed by +right clicking on the output. Squeezed output can be expanded in place +by double-clicking the button or into the clipboard or a separate window +by right-clicking the button. (Contributed by Tal Einat in :issue:`1529353`.) importlib @@ -1462,6 +1471,10 @@ Server and client-side specific TLS protocols for :class:`~ssl.SSLContext` were added. (Contributed by Christian Heimes in :issue:`28085`.) +Added :attr:`SSLContext.post_handshake_auth` to enable and +:meth:`ssl.SSLSocket.verify_client_post_handshake` to initiate TLS 1.3 +post-handshake authentication. +(Contributed by Christian Heimes in :issue:`34670`.) statistics ---------- @@ -1855,7 +1868,7 @@ Build and C API Changes For more information, see :pep:`7` and :issue:`17884`. * Cross-compiling CPython with the Android NDK and the Android API level set to - 21 (Android 5.0 Lollilop) or greater runs successfully. While Android is not + 21 (Android 5.0 Lollipop) or greater runs successfully. While Android is not yet a supported platform, the Python test suite runs on the Android emulator with only about 16 tests failures. See the Android meta-issue :issue:`26865`. @@ -2051,6 +2064,15 @@ connected to and thus what Python interpreter will be used by the virtual environment. (Contributed by Brett Cannon in :issue:`25154`.) +xml +--- + +* As mitigation against DTD and external entity retrieval, the + :mod:`xml.dom.minidom` and mod:`xml.sax` modules no longer process + external entities by default. + (Contributed by Christian Heimes in :issue:`17239`.) + + Deprecated functions and types of the C API ------------------------------------------- @@ -2159,7 +2181,7 @@ Changes in the Python API * The functions in the :mod:`compileall` module now return booleans instead of ``1`` or ``0`` to represent success or failure, respectively. Thanks to - booleans being a subclass of integers, this should only be an issue if you + booleans being a subclass of integers, this should only be an issue if you7 were doing identity checks for ``1`` or ``0``. See :issue:`25768`. * Reading the :attr:`~urllib.parse.SplitResult.port` attribute of @@ -2404,3 +2426,10 @@ Notable changes in Python 3.6.5 The :func:`locale.localeconv` function now sets temporarily the ``LC_CTYPE`` locale to the ``LC_NUMERIC`` locale in some cases. (Contributed by Victor Stinner in :issue:`31900`.) + + +Notable changes in Python 3.6.7 +=============================== + +:mod:`xml.dom.minidom` and mod:`xml.sax` modules no longer process +external entities by default. See also :issue:`17239`. diff --git a/Include/patchlevel.h b/Include/patchlevel.h index f20d080f..ee403a82 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -18,12 +18,12 @@ /*--start constants--*/ #define PY_MAJOR_VERSION 3 #define PY_MINOR_VERSION 6 -#define PY_MICRO_VERSION 6 +#define PY_MICRO_VERSION 7 #define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_FINAL #define PY_RELEASE_SERIAL 0 /* Version as a string */ -#define PY_VERSION "3.6.6" +#define PY_VERSION "3.6.7" /*--end constants--*/ /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2. diff --git a/Include/pyexpat.h b/Include/pyexpat.h index 44259bf6..07020b5d 100644 --- a/Include/pyexpat.h +++ b/Include/pyexpat.h @@ -3,7 +3,7 @@ /* note: you must import expat.h before importing this module! */ -#define PyExpat_CAPI_MAGIC "pyexpat.expat_CAPI 1.0" +#define PyExpat_CAPI_MAGIC "pyexpat.expat_CAPI 1.1" #define PyExpat_CAPSULE_NAME "pyexpat.expat_CAPI" struct PyExpat_CAPI @@ -48,6 +48,8 @@ struct PyExpat_CAPI enum XML_Status (*SetEncoding)(XML_Parser parser, const XML_Char *encoding); int (*DefaultUnknownEncodingHandler)( void *encodingHandlerData, const XML_Char *name, XML_Encoding *info); + /* might be none for expat < 2.1.0 */ + int (*SetHashSalt)(XML_Parser parser, unsigned long hash_salt); /* always add new stuff to the end! */ }; diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 2ebfb057..b577dede 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -2064,6 +2064,7 @@ class TextIOWrapper(TextIOBase): self.buffer.write(b) if self._line_buffering and (haslf or "\r" in s): self.flush() + self._set_decoded_chars('') self._snapshot = None if self._decoder: self._decoder.reset() diff --git a/Lib/antigravity.py b/Lib/antigravity.py index 9b143680..c6f174ca 100644 --- a/Lib/antigravity.py +++ b/Lib/antigravity.py @@ -11,7 +11,7 @@ def geohash(latitude, longitude, datedow): 37.857713 -122.544543 ''' - # http://xkcd.com/426/ + # https://xkcd.com/426/ h = hashlib.md5(datedow).hexdigest() p, q = [('%f' % float.fromhex('0.' + x)) for x in (h[:16], h[16:32])] print('%d%s %d%s' % (latitude, p[1:], longitude, q[1:])) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 083f45df..accd6692 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -54,6 +54,11 @@ _MIN_CANCELLED_TIMER_HANDLES_FRACTION = 0.5 _FATAL_ERROR_IGNORE = (BrokenPipeError, ConnectionResetError, ConnectionAbortedError) +_HAS_IPv6 = hasattr(socket, 'AF_INET6') + +# Maximum timeout passed to select to avoid OS limitations +MAXIMUM_SELECT_TIMEOUT = 24 * 3600 + def _format_handle(handle): cb = handle._callback @@ -136,7 +141,7 @@ def _ipaddr_info(host, port, family, type, proto): if family == socket.AF_UNSPEC: afs = [socket.AF_INET] - if hasattr(socket, 'AF_INET6'): + if _HAS_IPv6: afs.append(socket.AF_INET6) else: afs = [family] @@ -152,7 +157,10 @@ def _ipaddr_info(host, port, family, type, proto): try: socket.inet_pton(af, host) # The host has already been resolved. - return af, type, proto, '', (host, port) + if _HAS_IPv6 and af == socket.AF_INET6: + return af, type, proto, '', (host, port, 0, 0) + else: + return af, type, proto, '', (host, port) except OSError: pass @@ -360,10 +368,7 @@ class BaseEventLoop(events.AbstractEventLoop): def _asyncgen_finalizer_hook(self, agen): self._asyncgens.discard(agen) if not self.is_closed(): - self.create_task(agen.aclose()) - # Wake up the loop if the finalizer was called from - # a different thread. - self._write_to_self() + self.call_soon_threadsafe(self.create_task, agen.aclose()) def _asyncgen_firstiter_hook(self, agen): if self._asyncgens_shutdown_called: @@ -997,7 +1002,6 @@ class BaseEventLoop(events.AbstractEventLoop): 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 = [] @@ -1037,7 +1041,9 @@ class BaseEventLoop(events.AbstractEventLoop): # 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'): + if (_HAS_IPv6 and + af == socket.AF_INET6 and + hasattr(socket, 'IPPROTO_IPV6')): sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, True) @@ -1372,7 +1378,7 @@ class BaseEventLoop(events.AbstractEventLoop): elif self._scheduled: # Compute the desired timeout. when = self._scheduled[0]._when - timeout = max(0, when - self.time()) + timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT) if self._debug and timeout != 0: t0 = self.time() diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 967a6969..6f621ef0 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -160,20 +160,16 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport, self._loop.call_soon(self._loop_reading) def pause_reading(self): - if self._closing: - raise RuntimeError('Cannot pause_reading() when closing') - if self._paused: - raise RuntimeError('Already paused') + if self._closing or self._paused: + return self._paused = True if self._loop.get_debug(): logger.debug("%r pauses reading", self) def resume_reading(self): - if not self._paused: - raise RuntimeError('Not paused') - self._paused = False - if self._closing: + if self._closing or not self._paused: return + self._paused = False if self._reschedule_on_resume: self._loop.call_soon(self._loop_reading, self._read_fut) self._reschedule_on_resume = False diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 81dfd765..bc7c740c 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -703,18 +703,16 @@ class _SelectorSocketTransport(_SelectorTransport): waiter, None) def pause_reading(self): - if self._closing: - raise RuntimeError('Cannot pause_reading() when closing') - if self._paused: - raise RuntimeError('Already paused') + if self._closing or self._paused: + return self._paused = True self._loop._remove_reader(self._sock_fd) if self._loop.get_debug(): logger.debug("%r pauses reading", self) def resume_reading(self): - if not self._paused: - raise RuntimeError('Not paused') + if self._closing or not self._paused: + return self._paused = False self._add_reader(self._sock_fd, self._read_ready) if self._loop.get_debug(): diff --git a/Lib/asyncio/test_utils.py b/Lib/asyncio/test_utils.py index 8b8c22a7..f4172042 100644 --- a/Lib/asyncio/test_utils.py +++ b/Lib/asyncio/test_utils.py @@ -335,12 +335,19 @@ class TestLoop(base_events.BaseEventLoop): return False def assert_reader(self, fd, callback, *args): - assert fd in self.readers, 'fd {} is not registered'.format(fd) + if fd not in self.readers: + raise AssertionError(f'fd {fd} is not registered') handle = self.readers[fd] - assert handle._callback == callback, '{!r} != {!r}'.format( - handle._callback, callback) - assert handle._args == args, '{!r} != {!r}'.format( - handle._args, args) + if handle._callback != callback: + raise AssertionError( + f'unexpected callback: {handle._callback} != {callback}') + if handle._args != args: + raise AssertionError( + f'unexpected callback args: {handle._args} != {args}') + + def assert_no_reader(self, fd): + if fd in self.readers: + raise AssertionError(f'fd {fd} is registered') def _add_writer(self, fd, callback, *args): self.writers[fd] = events.Handle(callback, args, self) diff --git a/Lib/base64.py b/Lib/base64.py index eb8f258a..2be9c395 100755 --- a/Lib/base64.py +++ b/Lib/base64.py @@ -231,23 +231,16 @@ def b32decode(s, casefold=False, map01=None): raise binascii.Error('Non-base32 digit found') from None decoded += acc.to_bytes(5, 'big') # Process the last, partial quanta - if padchars: + if l % 8 or padchars not in {0, 1, 3, 4, 6}: + raise binascii.Error('Incorrect padding') + if padchars and decoded: 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') + leftover = (43 - 5 * padchars) // 8 # 1: 4, 3: 3, 4: 2, 6: 1 + decoded[-5:] = last[:leftover] return bytes(decoded) - # RFC 3548, Base 16 Alphabet specifies uppercase, but hexlify() returns # lowercase. The RFC also recommends against accepting input case # insensitively. diff --git a/Lib/cProfile.py b/Lib/cProfile.py index 1184385a..6ae8512d 100755 --- a/Lib/cProfile.py +++ b/Lib/cProfile.py @@ -25,11 +25,11 @@ runctx.__doc__ = _pyprofile.runctx.__doc__ # ____________________________________________________________ class Profile(_lsprof.Profiler): - """Profile(custom_timer=None, time_unit=None, subcalls=True, builtins=True) + """Profile(timer=None, timeunit=None, subcalls=True, builtins=True) Builds a profiler object using the specified timer function. The default timer is a fast built-in one based on real time. - For custom timer functions returning integers, time_unit can + For custom timer functions returning integers, timeunit can be a float specifying a scale (i.e. how long each integer unit is, in seconds). """ diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index 6bace6c7..4c140508 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -212,7 +212,7 @@ def as_completed(fs, timeout=None): before the given timeout. """ if timeout is not None: - end_time = timeout + time.time() + end_time = timeout + time.monotonic() fs = set(fs) total_futures = len(fs) @@ -231,7 +231,7 @@ def as_completed(fs, timeout=None): if timeout is None: wait_timeout = None else: - wait_timeout = end_time - time.time() + wait_timeout = end_time - time.monotonic() if wait_timeout < 0: raise TimeoutError( '%d (of %d) futures unfinished' % ( @@ -570,7 +570,7 @@ class Executor(object): Exception: If fn(*args) raises for any values. """ if timeout is not None: - end_time = timeout + time.time() + end_time = timeout + time.monotonic() fs = [self.submit(fn, *args) for args in zip(*iterables)] @@ -585,7 +585,7 @@ class Executor(object): if timeout is None: yield fs.pop().result() else: - yield fs.pop().result(end_time - time.time()) + yield fs.pop().result(end_time - time.monotonic()) finally: for future in fs: future.cancel() diff --git a/Lib/configparser.py b/Lib/configparser.py index 230ab2b0..0e529e96 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -80,7 +80,7 @@ ConfigParser -- responsible for parsing a list of Return list of configuration options for the named section. read(filenames, encoding=None) - Read and parse the list of named configuration files, given by + Read and parse the iterable of named configuration files, given by name. A single filename is also allowed. Non-existing files are ignored. Return list of successfully read files. @@ -677,13 +677,13 @@ class RawConfigParser(MutableMapping): return list(opts.keys()) def read(self, filenames, encoding=None): - """Read and parse a filename or a list of filenames. + """Read and parse a filename or an iterable of filenames. Files that cannot be opened are silently ignored; this is - designed so that you can specify a list of potential + designed so that you can specify an iterable of potential configuration file locations (e.g. current directory, user's home directory, systemwide directory), and all existing - configuration files in the list will be read. A single + configuration files in the iterable will be read. A single filename may also be given. Return list of successfully read files. diff --git a/Lib/ctypes/test/test_as_parameter.py b/Lib/ctypes/test/test_as_parameter.py index a2640575..f9d27cb8 100644 --- a/Lib/ctypes/test/test_as_parameter.py +++ b/Lib/ctypes/test/test_as_parameter.py @@ -24,7 +24,7 @@ class BasicWrapTestCase(unittest.TestCase): f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] result = f(self.wrap(1), self.wrap("x"), self.wrap(3), self.wrap(4), self.wrap(5.0), self.wrap(6.0)) self.assertEqual(result, 139) - self.assertTrue(type(result), int) + self.assertIs(type(result), int) def test_pointers(self): f = dll._testfunc_p_p diff --git a/Lib/ctypes/test/test_win32.py b/Lib/ctypes/test/test_win32.py index 5d85ad62..a2941f3f 100644 --- a/Lib/ctypes/test/test_win32.py +++ b/Lib/ctypes/test/test_win32.py @@ -54,6 +54,24 @@ class FunctionCallTestCase(unittest.TestCase): windll.user32.GetDesktopWindow() +@unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') +class ReturnStructSizesTestCase(unittest.TestCase): + def test_sizes(self): + dll = CDLL(_ctypes_test.__file__) + for i in range(1, 11): + fields = [ (f"f{f}", c_char) for f in range(1, i + 1)] + class S(Structure): + _fields_ = fields + f = getattr(dll, f"TestSize{i}") + f.restype = S + res = f() + for i, f in enumerate(fields): + value = getattr(res, f[0]) + expected = bytes([ord('a') + i]) + self.assertEqual(value, expected) + + + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') class TestWintypes(unittest.TestCase): def test_HWND(self): diff --git a/Lib/datetime.py b/Lib/datetime.py index b2b1457e..b8782fc8 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -6,6 +6,7 @@ time zone and DST data sources. import time as _time import math as _math +import sys def _cmp(x, y): return 0 if x == y else 1 if x > y else -1 @@ -1444,6 +1445,14 @@ class datetime(date): # 23 hours at 1969-09-30 13:00:00 in Kwajalein. # Let's probe 24 hours in the past to detect a transition: max_fold_seconds = 24 * 3600 + + # On Windows localtime_s throws an OSError for negative values, + # thus we can't perform fold detection for values of time less + # than the max time fold. See comments in _datetimemodule's + # version of this method for more details. + if t < max_fold_seconds and sys.platform.startswith("win"): + return result + y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6] probe1 = cls(y, m, d, hh, mm, ss, us, tz) trans = result - probe1 - timedelta(0, max_fold_seconds) diff --git a/Lib/distutils/_msvccompiler.py b/Lib/distutils/_msvccompiler.py index c9d3c6c6..30b3b473 100644 --- a/Lib/distutils/_msvccompiler.py +++ b/Lib/distutils/_msvccompiler.py @@ -252,11 +252,11 @@ class MSVCCompiler(CCompiler) : for dir in vc_env.get('include', '').split(os.pathsep): if dir: - self.add_include_dir(dir) + self.add_include_dir(dir.rstrip(os.sep)) for dir in vc_env.get('lib', '').split(os.pathsep): if dir: - self.add_library_dir(dir) + self.add_library_dir(dir.rstrip(os.sep)) self.preprocess_options = None # If vcruntime_redist is available, link against it dynamically. Otherwise, diff --git a/Lib/distutils/log.py b/Lib/distutils/log.py index b301a833..8ef6b28e 100644 --- a/Lib/distutils/log.py +++ b/Lib/distutils/log.py @@ -27,11 +27,13 @@ class Log: stream = sys.stderr else: stream = sys.stdout - if stream.errors == 'strict': + try: + stream.write('%s\n' % msg) + except UnicodeEncodeError: # emulate backslashreplace error handler encoding = stream.encoding msg = msg.encode(encoding, "backslashreplace").decode(encoding) - stream.write('%s\n' % msg) + stream.write('%s\n' % msg) stream.flush() def log(self, level, msg, *args): diff --git a/Lib/distutils/spawn.py b/Lib/distutils/spawn.py index 5dd415a2..53876880 100644 --- a/Lib/distutils/spawn.py +++ b/Lib/distutils/spawn.py @@ -173,7 +173,7 @@ def find_executable(executable, path=None): os.environ['PATH']. Returns the complete filename or None if not found. """ if path is None: - path = os.environ['PATH'] + path = os.environ.get('PATH', os.defpath) paths = path.split(os.pathsep) base, ext = os.path.splitext(executable) diff --git a/Lib/distutils/tests/test_bdist.py b/Lib/distutils/tests/test_bdist.py index f762f5d9..c80b3edc 100644 --- a/Lib/distutils/tests/test_bdist.py +++ b/Lib/distutils/tests/test_bdist.py @@ -39,6 +39,9 @@ class BuildTestCase(support.TempdirManager, for name in names: subcmd = cmd.get_finalized_command(name) + if getattr(subcmd, '_unsupported', False): + # command is not supported on this build + continue self.assertTrue(subcmd.skip_build, '%s should take --skip-build from bdist' % name) diff --git a/Lib/distutils/tests/test_bdist_wininst.py b/Lib/distutils/tests/test_bdist_wininst.py index 5d17ab19..4c19bbab 100644 --- a/Lib/distutils/tests/test_bdist_wininst.py +++ b/Lib/distutils/tests/test_bdist_wininst.py @@ -5,6 +5,8 @@ from test.support import run_unittest from distutils.command.bdist_wininst import bdist_wininst from distutils.tests import support +@unittest.skipIf(getattr(bdist_wininst, '_unsupported', False), + 'bdist_wininst is not supported in this install') class BuildWinInstTestCase(support.TempdirManager, support.LoggingSilencer, unittest.TestCase): diff --git a/Lib/distutils/tests/test_log.py b/Lib/distutils/tests/test_log.py index 0c2ad7a4..75cf9006 100644 --- a/Lib/distutils/tests/test_log.py +++ b/Lib/distutils/tests/test_log.py @@ -1,35 +1,43 @@ """Tests for distutils.log""" +import io import sys import unittest -from tempfile import NamedTemporaryFile -from test.support import run_unittest +from test.support import swap_attr, run_unittest from distutils import log class TestLog(unittest.TestCase): def test_non_ascii(self): - # Issue #8663: test that non-ASCII text is escaped with - # backslashreplace error handler (stream use ASCII encoding and strict - # error handler) - old_stdout = sys.stdout - old_stderr = sys.stderr - old_threshold = log.set_threshold(log.DEBUG) - try: - with NamedTemporaryFile(mode="w+", encoding='ascii') as stdout, \ - NamedTemporaryFile(mode="w+", encoding='ascii') as stderr: - sys.stdout = stdout - sys.stderr = stderr - log.debug("debug:\xe9") - log.fatal("fatal:\xe9") + # Issues #8663, #34421: test that non-encodable text is escaped with + # backslashreplace error handler and encodable non-ASCII text is + # output as is. + for errors in ('strict', 'backslashreplace', 'surrogateescape', + 'replace', 'ignore'): + with self.subTest(errors=errors): + stdout = io.TextIOWrapper(io.BytesIO(), + encoding='cp437', errors=errors) + stderr = io.TextIOWrapper(io.BytesIO(), + encoding='cp437', errors=errors) + old_threshold = log.set_threshold(log.DEBUG) + try: + with swap_attr(sys, 'stdout', stdout), \ + swap_attr(sys, 'stderr', stderr): + log.debug('Dεbug\tMėssãge') + log.fatal('Fαtal\tÈrrōr') + finally: + log.set_threshold(old_threshold) + stdout.seek(0) - self.assertEqual(stdout.read().rstrip(), "debug:\\xe9") + self.assertEqual(stdout.read().rstrip(), + 'Dεbug\tM?ss?ge' if errors == 'replace' else + 'Dεbug\tMssge' if errors == 'ignore' else + 'Dεbug\tM\\u0117ss\\xe3ge') stderr.seek(0) - self.assertEqual(stderr.read().rstrip(), "fatal:\\xe9") - finally: - log.set_threshold(old_threshold) - sys.stdout = old_stdout - sys.stderr = old_stderr + self.assertEqual(stderr.read().rstrip(), + 'Fαtal\t?rr?r' if errors == 'replace' else + 'Fαtal\trrr' if errors == 'ignore' else + 'Fαtal\t\\xc8rr\\u014dr') def test_suite(): return unittest.makeSuite(TestLog) diff --git a/Lib/distutils/tests/test_spawn.py b/Lib/distutils/tests/test_spawn.py index 5edc24a3..0d455385 100644 --- a/Lib/distutils/tests/test_spawn.py +++ b/Lib/distutils/tests/test_spawn.py @@ -1,9 +1,13 @@ """Tests for distutils.spawn.""" -import unittest -import sys import os +import stat +import sys +import unittest +from unittest import mock from test.support import run_unittest, unix_shell +from test import support as test_support +from distutils.spawn import find_executable from distutils.spawn import _nt_quote_args from distutils.spawn import spawn from distutils.errors import DistutilsExecError @@ -51,6 +55,47 @@ class SpawnTestCase(support.TempdirManager, os.chmod(exe, 0o777) spawn([exe]) # should work without any error + def test_find_executable(self): + with test_support.temp_dir() as tmp_dir: + # use TESTFN to get a pseudo-unique filename + program_noeext = test_support.TESTFN + # Give the temporary program an ".exe" suffix for all. + # It's needed on Windows and not harmful on other platforms. + program = program_noeext + ".exe" + + filename = os.path.join(tmp_dir, program) + with open(filename, "wb"): + pass + os.chmod(filename, stat.S_IXUSR) + + # test path parameter + rv = find_executable(program, path=tmp_dir) + self.assertEqual(rv, filename) + + if sys.platform == 'win32': + # test without ".exe" extension + rv = find_executable(program_noeext, path=tmp_dir) + self.assertEqual(rv, filename) + + # test find in the current directory + with test_support.change_cwd(tmp_dir): + rv = find_executable(program) + self.assertEqual(rv, program) + + # test non-existent program + dont_exist_program = "dontexist_" + program + rv = find_executable(dont_exist_program , path=tmp_dir) + self.assertIsNone(rv) + + # test os.defpath: missing PATH environment variable + with test_support.EnvironmentVarGuard() as env: + with mock.patch('distutils.spawn.os.defpath', tmp_dir): + env.pop('PATH') + + rv = find_executable(program) + self.assertEqual(rv, filename) + + def test_suite(): return unittest.makeSuite(SpawnTestCase) diff --git a/Lib/email/_encoded_words.py b/Lib/email/_encoded_words.py index 5eaab36e..b43b1800 100644 --- a/Lib/email/_encoded_words.py +++ b/Lib/email/_encoded_words.py @@ -98,30 +98,42 @@ def len_q(bstring): # def decode_b(encoded): - defects = [] + # First try encoding with validate=True, fixing the padding if needed. + # This will succeed only if encoded includes no invalid characters. pad_err = len(encoded) % 4 - if pad_err: - defects.append(errors.InvalidBase64PaddingDefect()) - padded_encoded = encoded + b'==='[:4-pad_err] - else: - padded_encoded = encoded + missing_padding = b'==='[:4-pad_err] if pad_err else b'' try: - return base64.b64decode(padded_encoded, validate=True), defects + return ( + base64.b64decode(encoded + missing_padding, validate=True), + [errors.InvalidBase64PaddingDefect()] if pad_err else [], + ) except binascii.Error: - # Since we had correct padding, this must an invalid char error. - defects = [errors.InvalidBase64CharactersDefect()] + # Since we had correct padding, this is likely an invalid char error. + # # The non-alphabet characters are ignored as far as padding - # goes, but we don't know how many there are. So we'll just - # try various padding lengths until something works. - for i in 0, 1, 2, 3: + # goes, but we don't know how many there are. So try without adding + # padding to see if it works. + try: + return ( + base64.b64decode(encoded, validate=False), + [errors.InvalidBase64CharactersDefect()], + ) + except binascii.Error: + # Add as much padding as could possibly be necessary (extra padding + # is ignored). try: - return base64.b64decode(encoded+b'='*i, validate=False), defects + return ( + base64.b64decode(encoded + b'==', validate=False), + [errors.InvalidBase64CharactersDefect(), + errors.InvalidBase64PaddingDefect()], + ) except binascii.Error: - if i==0: - defects.append(errors.InvalidBase64PaddingDefect()) - else: - # This should never happen. - raise AssertionError("unexpected binascii.Error") + # This only happens when the encoded string's length is 1 more + # than a multiple of 4, which is invalid. + # + # bpo-27397: Just return the encoded string since there's no + # way to decode. + return encoded, [errors.InvalidBase64LengthDefect()] def encode_b(bstring): return base64.b64encode(bstring).decode('ascii') diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 14ffd30c..de7ab5d1 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -1876,7 +1876,7 @@ def get_group(value): if not value: group.defects.append(errors.InvalidHeaderDefect( "end of header in group")) - if value[0] != ';': + elif value[0] != ';': raise errors.HeaderParseError( "expected ';' at end of group but found {}".format(value)) group.append(ValueTerminal(';', 'group-terminator')) diff --git a/Lib/email/errors.py b/Lib/email/errors.py index 791239fa..d28a6800 100644 --- a/Lib/email/errors.py +++ b/Lib/email/errors.py @@ -73,6 +73,9 @@ class InvalidBase64PaddingDefect(MessageDefect): class InvalidBase64CharactersDefect(MessageDefect): """base64 encoded sequence had characters not in base64 alphabet""" +class InvalidBase64LengthDefect(MessageDefect): + """base64 encoded sequence had invalid length (1 mod 4)""" + # These errors are specific to header parsing. class HeaderDefect(MessageDefect): diff --git a/Lib/enum.py b/Lib/enum.py index 112523e9..8405fa96 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -155,9 +155,11 @@ class EnumMeta(type): enum_class._member_map_ = OrderedDict() # name->value map enum_class._member_type_ = member_type - # save attributes from super classes so we know if we can take - # the shortcut of storing members in the class dict - base_attributes = {a for b in enum_class.mro() for a in b.__dict__} + # save DynamicClassAttribute attributes from super classes so we know + # if we can take the shortcut of storing members in the class dict + dynamic_attributes = {k for c in enum_class.mro() + for k, v in c.__dict__.items() + if isinstance(v, DynamicClassAttribute)} # Reverse value->name map for hashable values. enum_class._value2member_map_ = {} @@ -217,7 +219,7 @@ class EnumMeta(type): enum_class._member_names_.append(member_name) # performance boost for any member that would not shadow # a DynamicClassAttribute - if member_name not in base_attributes: + if member_name not in dynamic_attributes: setattr(enum_class, member_name, enum_member) # now add to _member_map_ enum_class._member_map_[member_name] = enum_member diff --git a/Lib/functools.py b/Lib/functools.py index 89f2cf4f..78462882 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -800,8 +800,13 @@ def singledispatch(func): return func def wrapper(*args, **kw): + if not args: + raise TypeError(f'{funcname} requires at least ' + '1 positional argument') + return dispatch(args[0].__class__)(*args, **kw) + funcname = getattr(func, '__name__', 'singledispatch function') registry[object] = func wrapper.register = register wrapper.dispatch = dispatch diff --git a/Lib/hashlib.py b/Lib/hashlib.py index 053a7add..98d2d798 100644 --- a/Lib/hashlib.py +++ b/Lib/hashlib.py @@ -25,18 +25,18 @@ Choose your hash function wisely. Some have known collision weaknesses. sha384 and sha512 will be slow on 32 bit platforms. Hash objects have these methods: - - update(arg): Update the hash object with the bytes in arg. Repeated calls - are equivalent to a single call with the concatenation of all - the arguments. - - digest(): Return the digest of the bytes passed to the update() method - so far. - - hexdigest(): Like digest() except the digest is returned as a unicode - object of double length, containing only hexadecimal digits. - - copy(): Return a copy (clone) of the hash object. This can be used to - efficiently compute the digests of strings that share a common - initial substring. - -For example, to obtain the digest of the string 'Nobody inspects the + - update(data): Update the hash object with the bytes in data. Repeated calls + are equivalent to a single call with the concatenation of all + the arguments. + - digest(): Return the digest of the bytes passed to the update() method + so far as a bytes object. + - hexdigest(): Like digest() except the digest is returned as a string + of double length, containing only hexadecimal digits. + - copy(): Return a copy (clone) of the hash object. This can be used to + efficiently compute the digests of datas that share a common + initial substring. + +For example, to obtain the digest of the byte string 'Nobody inspects the spammish repetition': >>> import hashlib @@ -130,14 +130,15 @@ def __get_openssl_constructor(name): def __py_new(name, data=b'', **kwargs): """new(name, data=b'', **kwargs) - Return a new hashing object using the - named algorithm; optionally initialized with data (which must be bytes). + named algorithm; optionally initialized with data (which must be + a bytes-like object). """ return __get_builtin_constructor(name)(data, **kwargs) def __hash_new(name, data=b'', **kwargs): """new(name, data=b'') - Return a new hashing object using the named algorithm; - optionally initialized with data (which must be bytes). + optionally initialized with data (which must be a bytes-like object). """ if name in {'blake2b', 'blake2s'}: # Prefer our blake2 implementation. diff --git a/Lib/http/client.py b/Lib/http/client.py index 8a82c57e..baabfeb2 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -322,7 +322,7 @@ class HTTPResponse(io.BufferedIOBase): if self.debuglevel > 0: for hdr in self.headers: - print("header:", hdr, end=" ") + print("header:", hdr + ":", self.headers.get(hdr)) # are we using the chunked-style of transfer encoding? tr_enc = self.headers.get("transfer-encoding") diff --git a/Lib/http/server.py b/Lib/http/server.py index e12e45bf..60a4dadf 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -466,7 +466,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): }) body = content.encode('UTF-8', 'replace') self.send_header("Content-Type", self.error_content_type) - self.send_header('Content-Length', int(len(body))) + self.send_header('Content-Length', str(len(body))) self.end_headers() if self.command != 'HEAD' and body: diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index ada8801d..8ecd85a9 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -1,8 +1,65 @@ -What's New in IDLE 3.6.6 -Released on 2018-06-15? +What's New in IDLE 3.6.7 +Released on 2018-09-24? ====================================== +bpo-1529353: Output over N lines (50 by default) is squeezed down to a button. +N can be changed in the PyShell section of the General page of the +Settings dialog. Fewer, but possibly extra long, lines can be squeezed by +right clicking on the output. Squeezed output can be expanded in place +by double-clicking the button or into the clipboard or a separate window +by right-clicking the button. + +bpo-34548: Use configured color theme for read-only text views. + +bpo-33839: Refactor ToolTip and CallTip classes; add documentation +and tests. + +bpo-34047: Fix mouse wheel scrolling direction on macOS. + +bpo-34275: Make calltips always visible on Mac. +Patch by Kevin Walzer. + +bpo-34120: Fix freezing after closing some dialogs on Mac. +This is one of multiple regressions from using newer tcl/tk. + +bpo-33975: Avoid small type when running htests. +Since part of the purpose of human-viewed tests is to determine that +widgets look right, it is important that they look the same for +testing as when running IDLE. + +bpo-33905: Add test for idlelib.stackview.StackBrowser. + +bpo-33924: Change mainmenu.menudefs key 'windows' to 'window'. +Every other menudef key is the lowercase version of the +corresponding main menu entry (in this case, 'Window'). + +bpo-33906: Rename idlelib.windows as window +Match Window on the main menu and remove last plural module name. +Change imports, test, and attribute references to match new name. + +bpo-33917: Fix and document idlelib/idle_test/template.py. +The revised file compiles, runs, and tests OK. idle_test/README.txt +explains how to use it to create new IDLE test files. + +bpo-33904: In rstrip module, rename class RstripExtension as Rstrip. + +bpo-33907: For consistency and clarity, rename calltip objects. +Module calltips and its class CallTips are now calltip and Calltip. +In module calltip_w, class CallTip is now CalltipWindow. + +bpo-33855: Minimally test all IDLE modules. +Standardize the test file format. Add missing test files that import +the tested module and perform at least one test. Check and record the +coverage of each test. + +bpo-33856: Add 'help' to Shell's initial welcome message. + + +What's New in IDLE 3.6.6 +Released on 2018-06-27 +====================================== + bpo-33656: On Windows, add API call saying that tk scales for DPI. On Windows 8.1+ or 10, with DPI compatibility properties of the Python binary unchanged, and a monitor resolution greater than 96 DPI, this @@ -183,7 +240,7 @@ To see the example in action, enable it on options extensions tab. bpo-31421: Document how IDLE runs tkinter programs. IDLE calls tcl/tk update in the background in order to make live -interaction and experimentatin with tkinter applications much easier. +interaction and experimentation with tkinter applications much easier. bpo-31414: Fix tk entry box tests by deleting first. Adding to an int entry is not the same as deleting and inserting @@ -431,7 +488,7 @@ Released on 2016-12-23 -w option but without -jn. Fix warning from test_config. - Issue #27621: Put query response validation error messages in the query - box itself instead of in a separate massagebox. Redo tests to match. + box itself instead of in a separate messagebox. Redo tests to match. Add Mac OSX refinements. Original patch by Mark Roseman. - Issue #27620: Escape key now closes Query box as cancelled. @@ -497,7 +554,7 @@ Released on 2016-12-23 - Issue #27239: idlelib.macosx.isXyzTk functions initialize as needed. -- Issue #27262: move Aqua unbinding code, which enable context menus, to maxosx. +- Issue #27262: move Aqua unbinding code, which enable context menus, to macosx. - Issue #24759: Make clear in idlelib.idle_test.__init__ that the directory is a private implementation of test.test_idle and tool for maintainers. diff --git a/Lib/idlelib/autocomplete.py b/Lib/idlelib/autocomplete.py index edf445f0..9caf50d5 100644 --- a/Lib/idlelib/autocomplete.py +++ b/Lib/idlelib/autocomplete.py @@ -226,7 +226,6 @@ class AutoComplete: AutoComplete.reload() - if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_autocomplete', verbosity=2) diff --git a/Lib/idlelib/autocomplete_w.py b/Lib/idlelib/autocomplete_w.py index 12113f95..9e0d3365 100644 --- a/Lib/idlelib/autocomplete_w.py +++ b/Lib/idlelib/autocomplete_w.py @@ -246,7 +246,7 @@ class AutoCompleteWindow: acw.wm_geometry("+%d+%d" % (new_x, new_y)) if platform.system().startswith('Windows'): - # See issue 15786. When on windows platform, Tk will misbehave + # See issue 15786. When on Windows platform, Tk will misbehave # to call winconfig_event multiple times, we need to prevent this, # otherwise mouse button double click will not be able to used. acw.unbind(WINCONFIG_SEQUENCE, self.winconfigid) @@ -269,7 +269,7 @@ class AutoCompleteWindow: # mouse click on widget / text area. if self.is_active(): if event.type == EventType.FocusOut: - # On windows platform, it will need to delay the check for + # On Windows platform, it will need to delay the check for # acw.focus_get() when click on acw, otherwise it will return # None and close the window self.widget.after(1, self._hide_event_check) @@ -458,3 +458,10 @@ class AutoCompleteWindow: self.listbox = None self.autocompletewindow.destroy() self.autocompletewindow = None + + +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_autocomplete_w', verbosity=2, exit=False) + +# TODO: autocomplete/w htest here diff --git a/Lib/idlelib/autoexpand.py b/Lib/idlelib/autoexpand.py index 42e733a1..92f5c84e 100644 --- a/Lib/idlelib/autoexpand.py +++ b/Lib/idlelib/autoexpand.py @@ -92,5 +92,5 @@ class AutoExpand: if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_autoexpand', verbosity=2) + from unittest import main + main('idlelib.idle_test.test_autoexpand', verbosity=2) diff --git a/Lib/idlelib/browser.py b/Lib/idlelib/browser.py index 571dd8fe..104a92a4 100644 --- a/Lib/idlelib/browser.py +++ b/Lib/idlelib/browser.py @@ -16,7 +16,7 @@ import sys from idlelib.config import idleConf from idlelib import pyshell from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas -from idlelib.windows import ListedToplevel +from idlelib.window import ListedToplevel file_open = None # Method...Item and Class...Item use this. diff --git a/Lib/idlelib/calltips.py b/Lib/idlelib/calltip.py similarity index 95% rename from Lib/idlelib/calltips.py rename to Lib/idlelib/calltip.py index ec8f6169..758569a4 100644 --- a/Lib/idlelib/calltips.py +++ b/Lib/idlelib/calltip.py @@ -15,7 +15,7 @@ from idlelib.hyperparser import HyperParser import __main__ -class CallTips: +class Calltip: def __init__(self, editwin=None): if editwin is None: # subprocess and test @@ -31,7 +31,7 @@ class CallTips: def _make_tk_calltip_window(self): # See __init__ for usage - return calltip_w.CallTip(self.text) + return calltip_w.CalltipWindow(self.text) def _remove_calltip_window(self, event=None): if self.active_calltip: @@ -44,14 +44,14 @@ class CallTips: return "break" def try_open_calltip_event(self, event): - """Happens when it would be nice to open a CallTip, but not really + """Happens when it would be nice to open a calltip, but not really necessary, for example after an opening bracket, so function calls won't be made. """ self.open_calltip(False) def refresh_calltip_event(self, event): - if self.active_calltip and self.active_calltip.is_active(): + if self.active_calltip and self.active_calltip.tipwindow: self.open_calltip(False) def open_calltip(self, evalfuncs): @@ -175,4 +175,4 @@ def get_argspec(ob): if __name__ == '__main__': from unittest import main - main('idlelib.idle_test.test_calltips', verbosity=2) + main('idlelib.idle_test.test_calltip', verbosity=2) diff --git a/Lib/idlelib/calltip_w.py b/Lib/idlelib/calltip_w.py index bf967f40..7553dfef 100644 --- a/Lib/idlelib/calltip_w.py +++ b/Lib/idlelib/calltip_w.py @@ -1,110 +1,118 @@ -"""A CallTip window class for Tkinter/IDLE. +"""A call-tip window class for Tkinter/IDLE. -After tooltip.py, which uses ideas gleaned from PySol -Used by the calltips IDLE extension. +After tooltip.py, which uses ideas gleaned from PySol. +Used by calltip.py. """ -from tkinter import Toplevel, Label, LEFT, SOLID, TclError +from tkinter import Label, LEFT, SOLID, TclError -HIDE_VIRTUAL_EVENT_NAME = "<>" +from idlelib.tooltip import TooltipBase + +HIDE_EVENT = "<>" HIDE_SEQUENCES = ("", "") -CHECKHIDE_VIRTUAL_EVENT_NAME = "<>" +CHECKHIDE_EVENT = "<>" CHECKHIDE_SEQUENCES = ("", "") -CHECKHIDE_TIME = 100 # milliseconds +CHECKHIDE_TIME = 100 # milliseconds MARK_RIGHT = "calltipwindowregion_right" -class CallTip: - def __init__(self, widget): - self.widget = widget - self.tipwindow = self.label = None - self.parenline = self.parencol = None - self.lastline = None +class CalltipWindow(TooltipBase): + """A call-tip widget for tkinter text widgets.""" + + def __init__(self, text_widget): + """Create a call-tip; shown by showtip(). + + text_widget: a Text widget with code for which call-tips are desired + """ + # Note: The Text widget will be accessible as self.anchor_widget + super(CalltipWindow, self).__init__(text_widget) + + self.label = self.text = None + self.parenline = self.parencol = self.lastline = None self.hideid = self.checkhideid = None self.checkhide_after_id = None - def position_window(self): - """Check if needs to reposition the window, and if so - do it.""" - curline = int(self.widget.index("insert").split('.')[0]) - if curline == self.lastline: - return - self.lastline = curline - self.widget.see("insert") + def get_position(self): + """Choose the position of the call-tip.""" + curline = int(self.anchor_widget.index("insert").split('.')[0]) if curline == self.parenline: - box = self.widget.bbox("%d.%d" % (self.parenline, - self.parencol)) + anchor_index = (self.parenline, self.parencol) else: - box = self.widget.bbox("%d.0" % curline) + anchor_index = (curline, 0) + box = self.anchor_widget.bbox("%d.%d" % anchor_index) if not box: - box = list(self.widget.bbox("insert")) + box = list(self.anchor_widget.bbox("insert")) # align to left of window box[0] = 0 box[2] = 0 - x = box[0] + self.widget.winfo_rootx() + 2 - y = box[1] + box[3] + self.widget.winfo_rooty() - self.tipwindow.wm_geometry("+%d+%d" % (x, y)) + return box[0] + 2, box[1] + box[3] + + def position_window(self): + "Reposition the window if needed." + curline = int(self.anchor_widget.index("insert").split('.')[0]) + if curline == self.lastline: + return + self.lastline = curline + self.anchor_widget.see("insert") + super(CalltipWindow, self).position_window() def showtip(self, text, parenleft, parenright): - """Show the calltip, bind events which will close it and reposition it. + """Show the call-tip, bind events which will close it and reposition it. + + text: the text to display in the call-tip + parenleft: index of the opening parenthesis in the text widget + parenright: index of the closing parenthesis in the text widget, + or the end of the line if there is no closing parenthesis """ - # Only called in CallTips, where lines are truncated + # Only called in calltip.Calltip, where lines are truncated self.text = text if self.tipwindow or not self.text: return - self.widget.mark_set(MARK_RIGHT, parenright) + self.anchor_widget.mark_set(MARK_RIGHT, parenright) self.parenline, self.parencol = map( - int, self.widget.index(parenleft).split(".")) + int, self.anchor_widget.index(parenleft).split(".")) - self.tipwindow = tw = Toplevel(self.widget) - self.position_window() - # remove border on calltip window - tw.wm_overrideredirect(1) - try: - # This command is only needed and available on Tk >= 8.4.0 for OSX - # Without it, call tips intrude on the typing process by grabbing - # the focus. - tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, - "help", "noActivates") - except TclError: - pass - self.label = Label(tw, text=self.text, justify=LEFT, + super(CalltipWindow, self).showtip() + + self._bind_events() + + def showcontents(self): + """Create the call-tip widget.""" + self.label = Label(self.tipwindow, text=self.text, justify=LEFT, background="#ffffe0", relief=SOLID, borderwidth=1, - font = self.widget['font']) + font=self.anchor_widget['font']) self.label.pack() - tw.lift() # work around bug in Tk 8.5.18+ (issue #24570) - - self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME, - self.checkhide_event) - for seq in CHECKHIDE_SEQUENCES: - self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq) - self.widget.after(CHECKHIDE_TIME, self.checkhide_event) - self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, - self.hide_event) - for seq in HIDE_SEQUENCES: - self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq) def checkhide_event(self, event=None): + """Handle CHECK_HIDE_EVENT: call hidetip or reschedule.""" if not self.tipwindow: - # If the event was triggered by the same event that unbinded + # If the event was triggered by the same event that unbound # this function, the function will be called nevertheless, # so do nothing in this case. return None - curline, curcol = map(int, self.widget.index("insert").split('.')) + + # Hide the call-tip if the insertion cursor moves outside of the + # parenthesis. + curline, curcol = map(int, self.anchor_widget.index("insert").split('.')) if curline < self.parenline or \ (curline == self.parenline and curcol <= self.parencol) or \ - self.widget.compare("insert", ">", MARK_RIGHT): + self.anchor_widget.compare("insert", ">", MARK_RIGHT): self.hidetip() return "break" - else: - self.position_window() - if self.checkhide_after_id is not None: - self.widget.after_cancel(self.checkhide_after_id) - self.checkhide_after_id = \ - self.widget.after(CHECKHIDE_TIME, self.checkhide_event) - return None + + # Not hiding the call-tip. + + self.position_window() + # Re-schedule this function to be called again in a short while. + if self.checkhide_after_id is not None: + self.anchor_widget.after_cancel(self.checkhide_after_id) + self.checkhide_after_id = \ + self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event) + return None def hide_event(self, event): + """Handle HIDE_EVENT by calling hidetip.""" if not self.tipwindow: # See the explanation in checkhide_event. return None @@ -112,53 +120,81 @@ class CallTip: return "break" def hidetip(self): + """Hide the call-tip.""" if not self.tipwindow: return - for seq in CHECKHIDE_SEQUENCES: - self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq) - self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid) - self.checkhideid = None - for seq in HIDE_SEQUENCES: - self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq) - self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid) - self.hideid = None - - self.label.destroy() + try: + self.label.destroy() + except TclError: + pass self.label = None - self.tipwindow.destroy() - self.tipwindow = None - self.widget.mark_unset(MARK_RIGHT) self.parenline = self.parencol = self.lastline = None + try: + self.anchor_widget.mark_unset(MARK_RIGHT) + except TclError: + pass - def is_active(self): - return bool(self.tipwindow) + try: + self._unbind_events() + except (TclError, ValueError): + # ValueError may be raised by MultiCall + pass + + super(CalltipWindow, self).hidetip() + + def _bind_events(self): + """Bind event handlers.""" + self.checkhideid = self.anchor_widget.bind(CHECKHIDE_EVENT, + self.checkhide_event) + for seq in CHECKHIDE_SEQUENCES: + self.anchor_widget.event_add(CHECKHIDE_EVENT, seq) + self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event) + self.hideid = self.anchor_widget.bind(HIDE_EVENT, + self.hide_event) + for seq in HIDE_SEQUENCES: + self.anchor_widget.event_add(HIDE_EVENT, seq) + + def _unbind_events(self): + """Unbind event handlers.""" + for seq in CHECKHIDE_SEQUENCES: + self.anchor_widget.event_delete(CHECKHIDE_EVENT, seq) + self.anchor_widget.unbind(CHECKHIDE_EVENT, self.checkhideid) + self.checkhideid = None + for seq in HIDE_SEQUENCES: + self.anchor_widget.event_delete(HIDE_EVENT, seq) + self.anchor_widget.unbind(HIDE_EVENT, self.hideid) + self.hideid = None def _calltip_window(parent): # htest # from tkinter import Toplevel, Text, LEFT, BOTH top = Toplevel(parent) - top.title("Test calltips") + top.title("Test call-tips") x, y = map(int, parent.geometry().split('+')[1:]) - top.geometry("200x100+%d+%d" % (x + 250, y + 175)) + top.geometry("250x100+%d+%d" % (x + 175, y + 150)) text = Text(top) text.pack(side=LEFT, fill=BOTH, expand=1) text.insert("insert", "string.split") top.update() - calltip = CallTip(text) + calltip = CalltipWindow(text) def calltip_show(event): - calltip.showtip("(s=Hello world)", "insert", "end") + calltip.showtip("(s='Hello world')", "insert", "end") def calltip_hide(event): calltip.hidetip() text.event_add("<>", "(") text.event_add("<>", ")") text.bind("<>", calltip_show) text.bind("<>", calltip_hide) + text.focus_set() -if __name__=='__main__': +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_calltip_w', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_calltip_window) diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py index 8b378bce..7c88a4d0 100644 --- a/Lib/idlelib/codecontext.py +++ b/Lib/idlelib/codecontext.py @@ -233,6 +233,8 @@ class CodeContext: CodeContext.reload() -if __name__ == "__main__": # pragma: no cover - import unittest - unittest.main('idlelib.idle_test.test_codecontext', verbosity=2, exit=False) +if __name__ == "__main__": + from unittest import main + main('idlelib.idle_test.test_codecontext', verbosity=2, exit=False) + + # Add htest. diff --git a/Lib/idlelib/colorizer.py b/Lib/idlelib/colorizer.py index f450ec2f..f2de9fc4 100644 --- a/Lib/idlelib/colorizer.py +++ b/Lib/idlelib/colorizer.py @@ -31,11 +31,12 @@ def make_pat(): prog = re.compile(make_pat(), re.S) idprog = re.compile(r"\s+(\w+)", re.S) -def color_config(text): # Called from htest, Editor, and Turtle Demo. - '''Set color opitons of Text widget. +def color_config(text): + """Set color options of Text widget. - Should be called whenever ColorDelegator is called. - ''' + If ColorDelegator is used, this should be called first. + """ + # Called from htest, TextFrame, Editor, and Turtledemo. # Not automatic because ColorDelegator does not know 'text'. theme = idleConf.CurrentTheme() normal_colors = idleConf.GetHighlight(theme, 'normal') @@ -50,6 +51,7 @@ def color_config(text): # Called from htest, Editor, and Turtle Demo. inactiveselectbackground=select_colors['background'], # new in 8.5 ) + class ColorDelegator(Delegator): def __init__(self): @@ -285,10 +287,10 @@ def _color_delegator(parent): # htest # d = ColorDelegator() p.insertfilter(d) + if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_colorizer', - verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_colorizer', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_color_delegator) diff --git a/Lib/idlelib/config-main.def b/Lib/idlelib/config-main.def index 16f4b095..06e3c5ad 100644 --- a/Lib/idlelib/config-main.def +++ b/Lib/idlelib/config-main.def @@ -66,6 +66,9 @@ font-size= 10 font-bold= 0 encoding= none +[PyShell] +auto-squeeze-min-lines= 50 + [Indent] use-spaces= 1 num-spaces= 4 diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 94b20832..0eb90fc8 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -925,7 +925,7 @@ def _dump(): # htest # (not really, but ignore in coverage) print('\nlines = ', line, ', crc = ', crc, sep='') if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_config', - verbosity=2, exit=False) - #_dump() + from unittest import main + main('idlelib.idle_test.test_config', verbosity=2, exit=False) + + # Run revised _dump() as htest? diff --git a/Lib/idlelib/config_key.py b/Lib/idlelib/config_key.py index 3a865f86..abab7426 100644 --- a/Lib/idlelib/config_key.py +++ b/Lib/idlelib/config_key.py @@ -235,10 +235,12 @@ class GetKeysDialog(Toplevel): return if (self.advanced or self.KeysOK(keys)) and self.bind_ok(keys): self.result = keys + self.grab_release() self.destroy() def Cancel(self, event=None): self.result='' + self.grab_release() self.destroy() def KeysOK(self, keys): @@ -291,8 +293,8 @@ class GetKeysDialog(Toplevel): if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_config_key', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_config_key', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(GetKeysDialog) diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 75b917d0..229dc898 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -30,10 +30,12 @@ from idlelib.autocomplete import AutoComplete from idlelib.codecontext import CodeContext from idlelib.parenmatch import ParenMatch from idlelib.paragraph import FormatParagraph +from idlelib.squeezer import Squeezer changes = ConfigChanges() # Reload changed options in the following classes. -reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph) +reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph, + Squeezer) class ConfigDialog(Toplevel): @@ -191,6 +193,7 @@ class ConfigDialog(Toplevel): def destroy(self): global font_sample_text font_sample_text = self.fontpage.font_sample.get('1.0', 'end') + self.grab_release() super().destroy() def help(self): @@ -1747,9 +1750,9 @@ class KeysPage(Frame): self.customlist.SetMenu(item_list, item_list[0]) # Revert to default key set. self.keyset_source.set(idleConf.defaultCfg['main'] - .Get('Keys', 'default')) + .Get('Keys', 'default')) self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name') - or idleConf.default_keys()) + or idleConf.default_keys()) # User can't back out of these changes, they must be applied now. changes.save_all() self.cd.save_all_changed_extensions() @@ -1816,6 +1819,10 @@ class GenPage(Frame): frame_context: Frame context_title: Label (*)context_int: Entry - context_lines + frame_shell: LabelFrame + frame_auto_squeeze_min_lines: Frame + auto_squeeze_min_lines_title: Label + (*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines frame_help: LabelFrame frame_helplist: Frame frame_helplist_buttons: Frame @@ -1841,6 +1848,9 @@ class GenPage(Frame): self.paren_bell = tracers.add( BooleanVar(self), ('extensions', 'ParenMatch', 'bell')) + self.auto_squeeze_min_lines = tracers.add( + StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines')) + self.autosave = tracers.add( IntVar(self), ('main', 'General', 'autosave')) self.format_width = tracers.add( @@ -1854,8 +1864,10 @@ class GenPage(Frame): text=' Window Preferences') frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE, text=' Editor Preferences') + frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE, + text=' Shell Preferences') frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE, - text=' Additional Help Sources ') + text=' Additional Help Sources ') # Frame_window. frame_run = Frame(frame_window, borderwidth=0) startup_title = Label(frame_run, text='At Startup') @@ -1917,6 +1929,13 @@ class GenPage(Frame): self.context_int = Entry( frame_context, textvariable=self.context_lines, width=3) + # Frame_shell. + frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0) + auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines, + text='Auto-Squeeze Min. Lines:') + self.auto_squeeze_min_lines_int = Entry( + frame_auto_squeeze_min_lines, width=4, + textvariable=self.auto_squeeze_min_lines) # frame_help. frame_helplist = Frame(frame_help) @@ -1942,6 +1961,7 @@ class GenPage(Frame): # Body. frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) + frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) # frame_run. frame_run.pack(side=TOP, padx=5, pady=0, fill=X) @@ -1982,6 +2002,11 @@ class GenPage(Frame): context_title.pack(side=LEFT, anchor=W, padx=5, pady=5) self.context_int.pack(side=TOP, padx=5, pady=5) + # frame_auto_squeeze_min_lines + frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X) + auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5) + self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5) + # frame_help. frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y) frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) @@ -2017,6 +2042,10 @@ class GenPage(Frame): self.context_lines.set(idleConf.GetOption( 'extensions', 'CodeContext', 'maxlines', type='int')) + # Set variables for shell windows. + self.auto_squeeze_min_lines.set(idleConf.GetOption( + 'main', 'PyShell', 'auto-squeeze-min-lines', type='int')) + # Set additional help sources. self.user_helplist = idleConf.GetAllExtraHelpSourcesList() self.helplist.delete(0, 'end') @@ -2210,6 +2239,9 @@ long to highlight if cursor is not moved (0 means forever). CodeContext: Maxlines is the maximum number of code context lines to display when Code Context is turned on for an editor window. + +Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines +of output to automatically "squeeze". ''' } @@ -2269,8 +2301,8 @@ class VerticalScrolledFrame(Frame): if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_configdialog', - verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(ConfigDialog) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 114d0d12..09f912c9 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -6,13 +6,13 @@ from tkinter.ttk import Scrollbar from idlelib import macosx from idlelib.scrolledlist import ScrolledList -from idlelib.windows import ListedToplevel +from idlelib.window import ListedToplevel class Idb(bdb.Bdb): def __init__(self, gui): - self.gui = gui + self.gui = gui # An instance of Debugger or proxy of remote. bdb.Bdb.__init__(self) def user_line(self, frame): @@ -40,7 +40,7 @@ class Idb(bdb.Bdb): prev_name = prev_frame.f_code.co_filename if 'idlelib' in prev_name and 'debugger' in prev_name: # catch both idlelib/debugger.py and idlelib/debugger_r.py - # on both posix and windows + # on both Posix and Windows return False return self.in_rpc_code(prev_frame) @@ -63,7 +63,7 @@ class Debugger: if idb is None: idb = Idb(self) self.pyshell = pyshell - self.idb = idb + self.idb = idb # If passed, a proxy of remote instance. self.frame = None self.make_gui() self.interacting = 0 @@ -542,3 +542,9 @@ class NamespaceViewer: def close(self): self.frame.destroy() + +if __name__ == "__main__": + from unittest import main + main('idlelib.idle_test.test_debugger', verbosity=2, exit=False) + +# TODO: htest? diff --git a/Lib/idlelib/debugger_r.py b/Lib/idlelib/debugger_r.py index bc971276..01a3bd25 100644 --- a/Lib/idlelib/debugger_r.py +++ b/Lib/idlelib/debugger_r.py @@ -386,3 +386,8 @@ def restart_subprocess_debugger(rpcclt): idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\ (gui_adap_oid,), {}) assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid' + + +if __name__ == "__main__": + from unittest import main + main('idlelib.idle_test.test_debugger', verbosity=2, exit=False) diff --git a/Lib/idlelib/debugobj.py b/Lib/idlelib/debugobj.py index b70b13ce..5a4c9978 100644 --- a/Lib/idlelib/debugobj.py +++ b/Lib/idlelib/debugobj.py @@ -71,7 +71,7 @@ class ClassTreeItem(ObjectTreeItem): class AtomicObjectTreeItem(ObjectTreeItem): def IsExpandable(self): - return 0 + return False class SequenceTreeItem(ObjectTreeItem): def IsExpandable(self): @@ -135,5 +135,8 @@ def _object_browser(parent): # htest # node.update() if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_debugobj', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_object_browser) diff --git a/Lib/idlelib/debugobj_r.py b/Lib/idlelib/debugobj_r.py index 8031aaea..75e75ebe 100644 --- a/Lib/idlelib/debugobj_r.py +++ b/Lib/idlelib/debugobj_r.py @@ -34,3 +34,8 @@ class StubObjectTreeItem: def _GetSubList(self): sub_list = self.sockio.remotecall(self.oid, "_GetSubList", (), {}) return [StubObjectTreeItem(self.sockio, oid) for oid in sub_list] + + +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_debugobj_r', verbosity=2) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 7c3f215e..6689af64 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -2,9 +2,7 @@ import importlib.abc import importlib.util import os import platform -import re import string -import sys import tokenize import traceback import webbrowser @@ -25,7 +23,7 @@ from idlelib import pyparse from idlelib import query from idlelib import replace from idlelib import search -from idlelib import windows +from idlelib import window # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 @@ -50,15 +48,15 @@ class EditorWindow(object): from idlelib.undo import UndoDelegator from idlelib.iomenu import IOBinding, encoding from idlelib import mainmenu - from tkinter import Toplevel, EventType from idlelib.statusbar import MultiStatusBar from idlelib.autocomplete import AutoComplete from idlelib.autoexpand import AutoExpand - from idlelib.calltips import CallTips + from idlelib.calltip import Calltip from idlelib.codecontext import CodeContext from idlelib.paragraph import FormatParagraph from idlelib.parenmatch import ParenMatch - from idlelib.rstrip import RstripExtension + from idlelib.rstrip import Rstrip + from idlelib.squeezer import Squeezer from idlelib.zoomheight import ZoomHeight filesystemencoding = sys.getfilesystemencoding() # for file names @@ -101,7 +99,7 @@ class EditorWindow(object): root = root or flist.root self.root = root self.menubar = Menu(root) - self.top = top = windows.ListedToplevel(root, menu=self.menubar) + self.top = top = window.ListedToplevel(root, menu=self.menubar) if flist: self.tkinter_vars = flist.vars #self.top.instance_dict makes flist.inversedict available to @@ -138,7 +136,7 @@ class EditorWindow(object): self.top.protocol("WM_DELETE_WINDOW", self.close) self.top.bind("<>", self.close_event) if macosx.isAquaTk(): - # Command-W on editorwindows doesn't work without this. + # Command-W on editor windows doesn't work without this. text.bind('<>', self.close_event) # Some OS X systems have only one mouse button, so use # control-click for popup context menus there. For two @@ -267,7 +265,7 @@ class EditorWindow(object): self.saved_change_hook() self.update_recent_files_list() self.load_extensions() - menu = self.menudict.get('windows') + menu = self.menudict.get('window') if menu: end = menu.index("end") if end is None: @@ -276,7 +274,7 @@ class EditorWindow(object): menu.add_separator() end = end + 1 self.wmenu_end = end - windows.register_callback(self.postwindowsmenu) + window.register_callback(self.postwindowsmenu) # Some abstractions so IDLE extensions are cross-IDE self.askyesno = tkMessageBox.askyesno @@ -310,15 +308,18 @@ class EditorWindow(object): scriptbinding = ScriptBinding(self) text.bind("<>", scriptbinding.check_module_event) text.bind("<>", scriptbinding.run_module_event) - text.bind("<>", self.RstripExtension(self).do_rstrip) - calltips = self.CallTips(self) - text.bind("<>", calltips.try_open_calltip_event) - #refresh-calltips must come after paren-closed to work right - text.bind("<>", calltips.refresh_calltip_event) - text.bind("<>", calltips.force_open_calltip_event) + text.bind("<>", self.Rstrip(self).do_rstrip) + ctip = self.Calltip(self) + text.bind("<>", ctip.try_open_calltip_event) + #refresh-calltip must come after paren-closed to work right + text.bind("<>", ctip.refresh_calltip_event) + text.bind("<>", ctip.force_open_calltip_event) text.bind("<>", self.ZoomHeight(self).zoom_height_event) text.bind("<>", self.CodeContext(self).toggle_code_context_event) + squeezer = self.Squeezer(self) + text.bind("<>", + squeezer.squeeze_current_text_event) def _filename_to_unicode(self, filename): """Return filename as BMP unicode so diplayable in Tk.""" @@ -410,7 +411,7 @@ class EditorWindow(object): ("format", "F_ormat"), ("run", "_Run"), ("options", "_Options"), - ("windows", "_Window"), + ("window", "_Window"), ("help", "_Help"), ] @@ -436,14 +437,14 @@ class EditorWindow(object): self.reset_help_menu_entries() def postwindowsmenu(self): - # Only called when Windows menu exists - menu = self.menudict['windows'] + # Only called when Window menu exists + menu = self.menudict['window'] end = menu.index("end") if end is None: end = -1 if end > self.wmenu_end: menu.delete(self.wmenu_end+1, end) - windows.add_windows_to_menu(menu) + window.add_windows_to_menu(menu) def handle_yview(self, event, *args): "Handle scrollbar." @@ -457,12 +458,19 @@ class EditorWindow(object): return 'break' def mousescroll(self, event): - "Handle scroll wheel." - up = {EventType.MouseWheel: event.delta >= 0 == darwin, + """Handle scrollwheel event. + + For wheel up, event.delta = 120*n on Windows, -1*n on darwin, + where n can be > 1 if one scrolls fast. Flicking the wheel + generates up to maybe 20 events with n up to 10 or more 1. + Macs use wheel down (delta = 1*n) to scroll up, so positive + delta means to scroll up on both systems. + + X-11 sends Control-Button-4 event instead. + """ + up = {EventType.MouseWheel: event.delta > 0, EventType.Button: event.num == 4} - lines = 5 - if up[event.type]: - lines = -lines + lines = -5 if up[event.type] else 5 self.text.yview_scroll(lines, 'units') return 'break' @@ -1012,7 +1020,7 @@ class EditorWindow(object): def _close(self): if self.io.filename: self.update_recent_files_list(new_file=self.io.filename) - windows.unregister_callback(self.postwindowsmenu) + window.unregister_callback(self.postwindowsmenu) self.unload_extensions() self.io.close() self.io = None @@ -1701,13 +1709,17 @@ def _editor_window(parent): # htest # filename = None macosx.setupApp(root, None) edit = EditorWindow(root=root, filename=filename) - edit.text.bind("<>", edit.close_event) + text = edit.text + text['height'] = 10 + for i in range(20): + text.insert('insert', ' '*i + str(i) + '\n') + # text.bind("<>", edit.close_event) # Does not stop error, neither does following # edit.text.bind("<>", edit.close_event) if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_editor', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_editor', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_editor_window) diff --git a/Lib/idlelib/filelist.py b/Lib/idlelib/filelist.py index 5e1a3dcd..52628392 100644 --- a/Lib/idlelib/filelist.py +++ b/Lib/idlelib/filelist.py @@ -1,7 +1,7 @@ -import os +"idlelib.filelist" -from tkinter import * -import tkinter.messagebox as tkMessageBox +import os +from tkinter import messagebox as tkMessageBox class FileList: @@ -111,7 +111,8 @@ class FileList: return os.path.normpath(filename) -def _test(): +def _test(): # TODO check and convert to htest + from tkinter import Tk from idlelib.editor import fixwordbreaks from idlelib.run import fix_scaling import sys @@ -120,13 +121,12 @@ def _test(): fixwordbreaks(root) root.withdraw() flist = FileList(root) - if sys.argv[1:]: - for filename in sys.argv[1:]: - flist.open(filename) - else: - flist.new() + flist.new() if flist.inversedict: root.mainloop() if __name__ == '__main__': - _test() + from unittest import main + main('idlelib.idle_test.test_filelist', verbosity=2) + +# _test() diff --git a/Lib/idlelib/grep.py b/Lib/idlelib/grep.py index c55c48cf..8cc293c3 100644 --- a/Lib/idlelib/grep.py +++ b/Lib/idlelib/grep.py @@ -193,8 +193,8 @@ def _grep_dialog(parent): # htest # button.pack() if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_grep', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_grep', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_grep_dialog) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index fa6112a3..21b5ea5a 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -271,5 +271,8 @@ def show_idlehelp(parent): HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version()) if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_help', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(show_idlehelp) diff --git a/Lib/idlelib/help_about.py b/Lib/idlelib/help_about.py index 77b4b189..64b13ac2 100644 --- a/Lib/idlelib/help_about.py +++ b/Lib/idlelib/help_about.py @@ -195,11 +195,13 @@ class AboutDialog(Toplevel): def ok(self, event=None): "Dismiss help_about dialog." + self.grab_release() self.destroy() if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_help_about', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_help_about', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(AboutDialog) diff --git a/Lib/idlelib/hyperparser.py b/Lib/idlelib/hyperparser.py index a42665bb..7581fe27 100644 --- a/Lib/idlelib/hyperparser.py +++ b/Lib/idlelib/hyperparser.py @@ -308,5 +308,5 @@ class HyperParser: if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_hyperparser', verbosity=2) + from unittest import main + main('idlelib.idle_test.test_hyperparser', verbosity=2) diff --git a/Lib/idlelib/idle_test/README.txt b/Lib/idlelib/idle_test/README.txt index 5f3678fc..566bfd17 100644 --- a/Lib/idlelib/idle_test/README.txt +++ b/Lib/idlelib/idle_test/README.txt @@ -15,28 +15,27 @@ python -m idlelib.idle_test.htest 1. Test Files The idle directory, idlelib, has over 60 xyz.py files. The idle_test -subdirectory should contain a test_xyz.py for each, where 'xyz' is -lowercased even if xyz.py is not. Here is a possible template, with the -blanks after '.' and 'as', and before and after '_' to be filled in. +subdirectory contains test_xyz.py for each implementation file xyz.py. +To add a test for abc.py, open idle_test/template.py and immediately +Save As test_abc.py. Insert 'abc' on the first line, and replace +'zzdummy' with 'abc. -import unittest -from test.support import requires -import idlelib. as - -class _Test(unittest.TestCase): +Remove the imports of requires and tkinter if not needed. Otherwise, +add to the tkinter imports as needed. - def test_(self): +Add a prefix to 'Test' for the initial test class. The template class +contains code needed or possibly needed for gui tests. See the next +section if doing gui tests. If not, and not needed for further classes, +this code can be removed. -if __name__ == '__main__': - unittest.main(verbosity=2) - -Add the following at the end of xyy.py, with the appropriate name added -after 'test_'. Some files already have something like this for htest. -If so, insert the import and unittest.main lines before the htest lines. +Add the following at the end of abc.py. If an htest was added first, +insert the import and main lines before the htest lines. if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_abc', verbosity=2, exit=False) + +The ', exit=False' is only needed if an htest follows. @@ -55,12 +54,14 @@ from test.support import requires requires('gui') To guard a test class, put "requires('gui')" in its setUpClass function. +The template.py file does this. -To avoid interfering with other GUI tests, all GUI objects must be destroyed and -deleted by the end of the test. The Tk root created in a setUpX function should -be destroyed in the corresponding tearDownX and the module or class attribute -deleted. Others widgets should descend from the single root and the attributes -deleted BEFORE root is destroyed. See https://bugs.python.org/issue20567. +To avoid interfering with other GUI tests, all GUI objects must be +destroyed and deleted by the end of the test. The Tk root created in a +setUpX function should be destroyed in the corresponding tearDownX and +the module or class attribute deleted. Others widgets should descend +from the single root and the attributes deleted BEFORE root is +destroyed. See https://bugs.python.org/issue20567. @classmethod def setUpClass(cls): @@ -75,12 +76,23 @@ deleted BEFORE root is destroyed. See https://bugs.python.org/issue20567. cls.root.destroy() del cls.root -The update_idletasks call is sometimes needed to prevent the following warning -either when running a test alone or as part of the test suite (#27196). +The update_idletasks call is sometimes needed to prevent the following +warning either when running a test alone or as part of the test suite +(#27196). It should not hurt if not needed. + can't invoke "event" command: application has been destroyed ... "ttk::ThemeChanged" +If a test creates instance 'e' of EditorWindow, call 'e._close()' before +or as the first part of teardown. The effect of omitting this depends +on the later shutdown. Then enable the after_cancel loop in the +template. This prevents messages like the following. + +bgerror failed to handle background error. + Original error: invalid command name "106096696timer_event" + Error in bgerror: can't invoke "tk" command: application has been destroyed + Requires('gui') causes the test(s) it guards to be skipped if any of these conditions are met: diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index 442f55e2..8c1c24d0 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -65,6 +65,7 @@ autocomplete_w.AutoCompleteWindow outwin.OutputWindow (indirectly being tested with grep test) ''' +import idlelib.pyshell # Set Windows DPI awareness before Tk(). from importlib import import_module import tkinter as tk from tkinter.ttk import Scrollbar @@ -79,11 +80,14 @@ AboutDialog_spec = { "are correctly displayed.\n [Close] to exit.", } +# TODO implement ^\; adding '' to function does not work. _calltip_window_spec = { 'file': 'calltip_w', 'kwds': {}, 'msg': "Typing '(' should display a calltip.\n" "Typing ') should hide the calltip.\n" + "So should moving cursor out of argument area.\n" + "Force-open-calltip does not work here.\n" } _module_browser_spec = { @@ -159,7 +163,7 @@ _grep_dialog_spec = { 'msg': "Click the 'Show GrepDialog' button.\n" "Test the various 'Find-in-files' functions.\n" "The results should be displayed in a new '*Output*' window.\n" - "'Right-click'->'Goto file/line' anywhere in the search results " + "'Right-click'->'Go to file/line' anywhere in the search results " "should open that file \nin a new EditorWindow." } @@ -296,16 +300,6 @@ _stack_viewer_spec = { "Check that exc_value, exc_tb, and exc_type are correct.\n" } -_tabbed_pages_spec = { - 'file': 'tabbedpages', - 'kwds': {}, - 'msg': "Toggle between the two tabs 'foo' and 'bar'\n" - "Add a tab by entering a suitable name for it.\n" - "Remove an existing tab by entering its name.\n" - "Remove all existing tabs.\n" - " is an invalid add page and remove page name.\n" - } - _tooltip_spec = { 'file': 'tooltip', 'kwds': {}, diff --git a/Lib/idlelib/idle_test/template.py b/Lib/idlelib/idle_test/template.py new file mode 100644 index 00000000..725a55b9 --- /dev/null +++ b/Lib/idlelib/idle_test/template.py @@ -0,0 +1,30 @@ +"Test , coverage %." + +from idlelib import zzdummy +import unittest +from test.support import requires +from tkinter import Tk + + +class Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.update_idletasks() +## for id in cls.root.tk.call('after', 'info'): +## cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def test_init(self): + self.assertTrue(True) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_autocomplete.py b/Lib/idlelib/idle_test/test_autocomplete.py index f3f2dea4..d7ee00af 100644 --- a/Lib/idlelib/idle_test/test_autocomplete.py +++ b/Lib/idlelib/idle_test/test_autocomplete.py @@ -1,7 +1,5 @@ -''' Test autocomplete and autocomple_w +"Test autocomplete, coverage 57%." -Coverage of autocomple: 56% -''' import unittest from test.support import requires from tkinter import Tk, Text @@ -11,9 +9,6 @@ import idlelib.autocomplete_w as acw from idlelib.idle_test.mock_idle import Func from idlelib.idle_test.mock_tk import Event -class AutoCompleteWindow: - def complete(): - return class DummyEditwin: def __init__(self, root, text): diff --git a/Lib/idlelib/idle_test/test_autocomplete_w.py b/Lib/idlelib/idle_test/test_autocomplete_w.py new file mode 100644 index 00000000..b1bdc6c7 --- /dev/null +++ b/Lib/idlelib/idle_test/test_autocomplete_w.py @@ -0,0 +1,32 @@ +"Test autocomplete_w, coverage 11%." + +import unittest +from test.support import requires +from tkinter import Tk, Text + +import idlelib.autocomplete_w as acw + + +class AutoCompleteWindowTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.text = Text(cls.root) + cls.acw = acw.AutoCompleteWindow(cls.text) + + @classmethod + def tearDownClass(cls): + del cls.text, cls.acw + cls.root.update_idletasks() + cls.root.destroy() + del cls.root + + def test_init(self): + self.assertEqual(self.acw.widget, self.text) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_autoexpand.py b/Lib/idlelib/idle_test/test_autoexpand.py index ae8186cd..e5f44c46 100644 --- a/Lib/idlelib/idle_test/test_autoexpand.py +++ b/Lib/idlelib/idle_test/test_autoexpand.py @@ -1,9 +1,9 @@ -"""Unit tests for idlelib.autoexpand""" +"Test autoexpand, coverage 100%." + +from idlelib.autoexpand import AutoExpand import unittest from test.support import requires from tkinter import Text, Tk -#from idlelib.idle_test.mock_tk import Text -from idlelib.autoexpand import AutoExpand class Dummy_Editwin: @@ -15,15 +15,27 @@ class AutoExpandTest(unittest.TestCase): @classmethod def setUpClass(cls): - if 'tkinter' in str(Text): - requires('gui') - cls.tk = Tk() - cls.text = Text(cls.tk) - else: - cls.text = Text() + requires('gui') + cls.tk = Tk() + cls.text = Text(cls.tk) cls.auto_expand = AutoExpand(Dummy_Editwin(cls.text)) cls.auto_expand.bell = lambda: None +# If mock_tk.Text._decode understood indexes 'insert' with suffixed 'linestart', +# 'wordstart', and 'lineend', used by autoexpand, we could use the following +# to run these test on non-gui machines (but check bell). +## try: +## requires('gui') +## #raise ResourceDenied() # Uncomment to test mock. +## except ResourceDenied: +## from idlelib.idle_test.mock_tk import Text +## cls.text = Text() +## cls.text.bell = lambda: None +## else: +## from tkinter import Tk, Text +## cls.tk = Tk() +## cls.text = Text(cls.tk) + @classmethod def tearDownClass(cls): del cls.text, cls.auto_expand diff --git a/Lib/idlelib/idle_test/test_browser.py b/Lib/idlelib/idle_test/test_browser.py index 05eb4718..35cc3469 100644 --- a/Lib/idlelib/idle_test/test_browser.py +++ b/Lib/idlelib/idle_test/test_browser.py @@ -1,20 +1,16 @@ -""" Test idlelib.browser. +"Test browser, coverage 90%." -Coverage: 88% -(Higher, because should exclude 3 lines that .coveragerc won't exclude.) -""" +from idlelib import browser +from test.support import requires +import unittest +from unittest import mock +from idlelib.idle_test.mock_idle import Func from collections import deque import os.path from idlelib import _pyclbr as pyclbr from tkinter import Tk -from test.support import requires -import unittest -from unittest import mock -from idlelib.idle_test.mock_idle import Func - -from idlelib import browser from idlelib import filelist from idlelib.tree import TreeNode diff --git a/Lib/idlelib/idle_test/test_calltips.py b/Lib/idlelib/idle_test/test_calltip.py similarity index 82% rename from Lib/idlelib/idle_test/test_calltips.py rename to Lib/idlelib/idle_test/test_calltip.py index f6b71306..b1ee0fe4 100644 --- a/Lib/idlelib/idle_test/test_calltips.py +++ b/Lib/idlelib/idle_test/test_calltip.py @@ -1,9 +1,12 @@ +"Test calltip, coverage 60%" + +from idlelib import calltip import unittest -import idlelib.calltips as ct import textwrap import types -default_tip = ct._default_callable_argspec +default_tip = calltip._default_callable_argspec + # Test Class TC is used in multiple get_argspec test methods class TC(): @@ -31,9 +34,11 @@ class TC(): @staticmethod def sm(b): 'doc' + tc = TC() +signature = calltip.get_argspec # 2.7 and 3.x use different functions + -signature = ct.get_argspec # 2.7 and 3.x use different functions class Get_signatureTest(unittest.TestCase): # The signature function must return a string, even if blank. # Test a variety of objects to be sure that none cause it to raise @@ -56,12 +61,13 @@ class Get_signatureTest(unittest.TestCase): if List.__doc__ is not None: gtest(List, List.__doc__) # This and append_doc changed in 3.7. gtest(list.__new__, - '(*args, **kwargs)\nCreate and return a new object.' - ' See help(type) for accurate signature.') + '(*args, **kwargs)\n' + 'Create and return a new object.' + ' See help(type) for accurate signature.') gtest(list.__init__, - '(self, /, *args, **kwargs)' + ct._argument_positional + '\n' + - 'Initialize self. See help(type(self)) for accurate signature.') - + '(self, /, *args, **kwargs)' + + calltip._argument_positional + '\n' + + 'Initialize self. See help(type(self)) for accurate signature.') append_doc = "L.append(object) -> None -- append object to end" gtest(list.append, append_doc) gtest([].append, append_doc) @@ -71,12 +77,17 @@ class Get_signatureTest(unittest.TestCase): gtest(SB(), default_tip) import re p = re.compile('') - gtest(re.sub, '''(pattern, repl, string, count=0, flags=0)\nReturn the string obtained by replacing the leftmost + gtest(re.sub, '''\ +(pattern, repl, string, count=0, flags=0) +Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl. repl can be either a string or a callable; if a string, backslash escapes in it are processed. If it is a callable, it's passed the match object and must return''') - gtest(p.sub, '''(repl, string, count=0)\nReturn the string obtained by replacing the leftmost non-overlapping occurrences o...''') + gtest(p.sub, '''\ +(repl, string, count=0) +Return the string obtained by replacing the leftmost \ +non-overlapping occurrences o...''') def test_signature_wrap(self): if textwrap.TextWrapper.__doc__ is not None: @@ -89,7 +100,7 @@ a callable, it's passed the match object and must return''') def test_docline_truncation(self): def f(): pass f.__doc__ = 'a'*300 - self.assertEqual(signature(f), '()\n' + 'a' * (ct._MAX_COLS-3) + '...') + self.assertEqual(signature(f), '()\n' + 'a' * (calltip._MAX_COLS-3) + '...') def test_multiline_docstring(self): # Test fewer lines than max. @@ -108,7 +119,7 @@ bytes() -> empty bytes object''') # Test more than max lines def f(): pass f.__doc__ = 'a\n' * 15 - self.assertEqual(signature(f), '()' + '\na' * ct._MAX_LINES) + self.assertEqual(signature(f), '()' + '\na' * calltip._MAX_LINES) def test_functions(self): def t1(): 'doc' @@ -136,8 +147,9 @@ bytes() -> empty bytes object''') def test_bound_methods(self): # test that first parameter is correctly removed from argspec doc = '\ndoc' if TC.__doc__ is not None else '' - for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"), (tc.t6, "(self)"), - (tc.__call__, '(ci)'), (tc, '(ci)'), (TC.cm, "(a)"),): + for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"), + (tc.t6, "(self)"), (tc.__call__, '(ci)'), + (tc, '(ci)'), (TC.cm, "(a)"),): self.assertEqual(signature(meth), mtip + doc) def test_starred_parameter(self): @@ -154,7 +166,7 @@ bytes() -> empty bytes object''') class Test: def __call__(*, a): pass - mtip = ct._invalid_method + mtip = calltip._invalid_method self.assertEqual(signature(C().m2), mtip) self.assertEqual(signature(Test()), mtip) @@ -162,7 +174,7 @@ bytes() -> empty bytes object''') # test that re works to delete a first parameter name that # includes non-ascii chars, such as various forms of A. uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)" - assert ct._first_param.sub('', uni) == '(a)' + assert calltip._first_param.sub('', uni) == '(a)' def test_no_docstring(self): def nd(s): @@ -195,9 +207,10 @@ bytes() -> empty bytes object''') class Get_entityTest(unittest.TestCase): def test_bad_entity(self): - self.assertIsNone(ct.get_entity('1/0')) + self.assertIsNone(calltip.get_entity('1/0')) def test_good_entity(self): - self.assertIs(ct.get_entity('int'), int) + self.assertIs(calltip.get_entity('int'), int) + if __name__ == '__main__': - unittest.main(verbosity=2, exit=False) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_calltip_w.py b/Lib/idlelib/idle_test/test_calltip_w.py new file mode 100644 index 00000000..a5ec76e1 --- /dev/null +++ b/Lib/idlelib/idle_test/test_calltip_w.py @@ -0,0 +1,29 @@ +"Test calltip_w, coverage 18%." + +from idlelib import calltip_w +import unittest +from test.support import requires +from tkinter import Tk, Text + + +class CallTipWindowTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.text = Text(cls.root) + cls.calltip = calltip_w.CalltipWindow(cls.text) + + @classmethod + def tearDownClass(cls): + cls.root.update_idletasks() + cls.root.destroy() + del cls.text, cls.root + + def test_init(self): + self.assertEqual(self.calltip.anchor_widget, self.text) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_codecontext.py b/Lib/idlelib/idle_test/test_codecontext.py index 2e59b850..ef8170e3 100644 --- a/Lib/idlelib/idle_test/test_codecontext.py +++ b/Lib/idlelib/idle_test/test_codecontext.py @@ -1,16 +1,12 @@ -"""Test idlelib.codecontext. - -Coverage: 100% -""" - -import re +"Test codecontext, coverage 100%" +from idlelib import codecontext import unittest -from unittest import mock from test.support import requires from tkinter import Tk, Frame, Text, TclError -import idlelib.codecontext as codecontext +from unittest import mock +import re from idlelib import config diff --git a/Lib/idlelib/idle_test/test_colorizer.py b/Lib/idlelib/idle_test/test_colorizer.py index 238bc3e1..1e74bed1 100644 --- a/Lib/idlelib/idle_test/test_colorizer.py +++ b/Lib/idlelib/idle_test/test_colorizer.py @@ -1,10 +1,6 @@ -'''Test idlelib/colorizer.py +"Test colorizer, coverage 25%." -Perform minimal sanity checks that module imports and some things run. - -Coverage 22%. -''' -from idlelib import colorizer # always test import +from idlelib import colorizer from test.support import requires from tkinter import Tk, Text import unittest @@ -36,6 +32,7 @@ class ColorConfigTest(unittest.TestCase): def test_colorizer(self): colorizer.color_config(self.text) + class ColorDelegatorTest(unittest.TestCase): @classmethod diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index abfec799..8c919728 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -1,9 +1,9 @@ -'''Test idlelib.config. - -Coverage: 96% (100% for IdleConfParser, IdleUserConfParser*, ConfigChanges). +"""Test config, coverage 93%. +(100% for IdleConfParser, IdleUserConfParser*, ConfigChanges). * Exception is OSError clause in Save method. Much of IdleConf is also exercised by ConfigDialog and test_configdialog. -''' +""" +from idlelib import config import copy import sys import os @@ -12,7 +12,6 @@ from test.support import captured_stderr, findfile import unittest from unittest import mock import idlelib -from idlelib import config from idlelib.idle_test.mock_idle import Func # Tests should not depend on fortuitous user configurations. @@ -256,9 +255,9 @@ class IdleConfTest(unittest.TestCase): with self.assertRaises(FileNotFoundError): conf.GetUserCfgDir() - @unittest.skipIf(not sys.platform.startswith('win'), 'this is test for windows system') + @unittest.skipIf(not sys.platform.startswith('win'), 'this is test for Windows system') def test_get_user_cfg_dir_windows(self): - "Test to get user config directory under windows" + "Test to get user config directory under Windows" conf = self.new_config(_utest=True) # Check normal way should success @@ -357,11 +356,11 @@ class IdleConfTest(unittest.TestCase): self.assertCountEqual( conf.GetSectionList('default', 'main'), - ['General', 'EditorWindow', 'Indent', 'Theme', + ['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme', 'Keys', 'History', 'HelpFiles']) self.assertCountEqual( conf.GetSectionList('user', 'main'), - ['General', 'EditorWindow', 'Indent', 'Theme', + ['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme', 'Keys', 'History', 'HelpFiles']) with self.assertRaises(config.InvalidConfigSet): @@ -453,7 +452,7 @@ class IdleConfTest(unittest.TestCase): self.assertCountEqual( conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')), - ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch','ZzDummy']) + ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'ZzDummy']) def test_get_extn_name_for_event(self): userextn.read_string(''' diff --git a/Lib/idlelib/idle_test/test_config_key.py b/Lib/idlelib/idle_test/test_config_key.py index 9074e23a..08471666 100644 --- a/Lib/idlelib/idle_test/test_config_key.py +++ b/Lib/idlelib/idle_test/test_config_key.py @@ -1,7 +1,5 @@ -''' Test idlelib.config_key. +"Test config_key, coverage 75%" -Coverage: 56% from creating and closing dialog. -''' from idlelib import config_key from test.support import requires import sys diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py index 26aba32c..dbfcd01c 100644 --- a/Lib/idlelib/idle_test/test_configdialog.py +++ b/Lib/idlelib/idle_test/test_configdialog.py @@ -1,7 +1,6 @@ -"""Test idlelib.configdialog. +"""Test configdialog, coverage 94%. Half the class creates dialog, half works with user customizations. -Coverage: 95%. """ from idlelib import configdialog from test.support import requires @@ -61,6 +60,7 @@ class FontPageTest(unittest.TestCase): page = cls.page = dialog.fontpage dialog.note.select(page) page.set_samples = Func() # Mask instance method. + page.update() @classmethod def tearDownClass(cls): @@ -211,6 +211,7 @@ class IndentTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.page = dialog.fontpage + cls.page.update() def test_load_tab_cfg(self): d = self.page @@ -241,6 +242,7 @@ class HighPageTest(unittest.TestCase): page.paint_theme_sample = Func() page.set_highlight_target = Func() page.set_color_sample = Func() + page.update() @classmethod def tearDownClass(cls): @@ -1086,6 +1088,7 @@ class GenPageTest(unittest.TestCase): dialog.note.select(page) page.set = page.set_add_delete_state = Func() page.upc = page.update_help_changes = Func() + page.update() @classmethod def tearDownClass(cls): diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index bcba9a45..35efb341 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -1,11 +1,9 @@ -''' Test idlelib.debugger. +"Test debugger, coverage 19%" -Coverage: 19% -''' from idlelib import debugger +import unittest from test.support import requires requires('gui') -import unittest from tkinter import Tk @@ -25,5 +23,7 @@ class NameSpaceTest(unittest.TestCase): debugger.NamespaceViewer(self.root, 'Test') +# Other classes are Idb, Debugger, and StackViewer. + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_debugger_r.py b/Lib/idlelib/idle_test/test_debugger_r.py new file mode 100644 index 00000000..199f6344 --- /dev/null +++ b/Lib/idlelib/idle_test/test_debugger_r.py @@ -0,0 +1,29 @@ +"Test debugger_r, coverage 30%." + +from idlelib import debugger_r +import unittest +from test.support import requires +from tkinter import Tk + + +class Test(unittest.TestCase): + +## @classmethod +## def setUpClass(cls): +## requires('gui') +## cls.root = Tk() +## +## @classmethod +## def tearDownClass(cls): +## cls.root.destroy() +## del cls.root + + def test_init(self): + self.assertTrue(True) # Get coverage of import + + +# Classes GUIProxy, IdbAdapter, FrameProxy, CodeProxy, DictProxy, +# GUIAdapter, IdbProxy plus 7 module functions. + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_debugobj.py b/Lib/idlelib/idle_test/test_debugobj.py new file mode 100644 index 00000000..131ce22b --- /dev/null +++ b/Lib/idlelib/idle_test/test_debugobj.py @@ -0,0 +1,57 @@ +"Test debugobj, coverage 40%." + +from idlelib import debugobj +import unittest + + +class ObjectTreeItemTest(unittest.TestCase): + + def test_init(self): + ti = debugobj.ObjectTreeItem('label', 22) + self.assertEqual(ti.labeltext, 'label') + self.assertEqual(ti.object, 22) + self.assertEqual(ti.setfunction, None) + + +class ClassTreeItemTest(unittest.TestCase): + + def test_isexpandable(self): + ti = debugobj.ClassTreeItem('label', 0) + self.assertTrue(ti.IsExpandable()) + + +class AtomicObjectTreeItemTest(unittest.TestCase): + + def test_isexpandable(self): + ti = debugobj.AtomicObjectTreeItem('label', 0) + self.assertFalse(ti.IsExpandable()) + + +class SequenceTreeItemTest(unittest.TestCase): + + def test_isexpandable(self): + ti = debugobj.SequenceTreeItem('label', ()) + self.assertFalse(ti.IsExpandable()) + ti = debugobj.SequenceTreeItem('label', (1,)) + self.assertTrue(ti.IsExpandable()) + + def test_keys(self): + ti = debugobj.SequenceTreeItem('label', 'abc') + self.assertEqual(list(ti.keys()), [0, 1, 2]) + + +class DictTreeItemTest(unittest.TestCase): + + def test_isexpandable(self): + ti = debugobj.DictTreeItem('label', {}) + self.assertFalse(ti.IsExpandable()) + ti = debugobj.DictTreeItem('label', {1:1}) + self.assertTrue(ti.IsExpandable()) + + def test_keys(self): + ti = debugobj.DictTreeItem('label', {1:1, 0:0, 2:2}) + self.assertEqual(ti.keys(), [0, 1, 2]) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_debugobj_r.py b/Lib/idlelib/idle_test/test_debugobj_r.py new file mode 100644 index 00000000..86e51b6c --- /dev/null +++ b/Lib/idlelib/idle_test/test_debugobj_r.py @@ -0,0 +1,22 @@ +"Test debugobj_r, coverage 56%." + +from idlelib import debugobj_r +import unittest + + +class WrappedObjectTreeItemTest(unittest.TestCase): + + def test_getattr(self): + ti = debugobj_r.WrappedObjectTreeItem(list) + self.assertEqual(ti.append, list.append) + +class StubObjectTreeItemTest(unittest.TestCase): + + def test_init(self): + ti = debugobj_r.StubObjectTreeItem('socket', 1111) + self.assertEqual(ti.sockio, 'socket') + self.assertEqual(ti.oid, 1111) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_delegator.py b/Lib/idlelib/idle_test/test_delegator.py index 85624fbc..92241629 100644 --- a/Lib/idlelib/idle_test/test_delegator.py +++ b/Lib/idlelib/idle_test/test_delegator.py @@ -1,5 +1,8 @@ -import unittest +"Test delegator, coverage 100%." + from idlelib.delegator import Delegator +import unittest + class DelegatorTest(unittest.TestCase): @@ -36,5 +39,6 @@ class DelegatorTest(unittest.TestCase): self.assertEqual(mydel._Delegator__cache, set()) self.assertIs(mydel.delegate, float) + if __name__ == '__main__': unittest.main(verbosity=2, exit=2) diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index 64a2a88b..12bc8473 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -1,14 +1,46 @@ +"Test editor, coverage 35%." + +from idlelib import editor import unittest -from idlelib.editor import EditorWindow +from test.support import requires +from tkinter import Tk + +Editor = editor.EditorWindow + + +class EditorWindowTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.update_idletasks() + for id in cls.root.tk.call('after', 'info'): + cls.root.after_cancel(id) + cls.root.destroy() + del cls.root + + def test_init(self): + e = Editor(root=self.root) + self.assertEqual(e.root, self.root) + e._close() + + +class EditorFunctionTest(unittest.TestCase): -class Editor_func_test(unittest.TestCase): def test_filename_to_unicode(self): - func = EditorWindow._filename_to_unicode - class dummy(): filesystemencoding = 'utf-8' + func = Editor._filename_to_unicode + class dummy(): + filesystemencoding = 'utf-8' pairs = (('abc', 'abc'), ('a\U00011111c', 'a\ufffdc'), (b'abc', 'abc'), (b'a\xf0\x91\x84\x91c', 'a\ufffdc')) for inp, out in pairs: self.assertEqual(func(dummy, inp), out) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_filelist.py b/Lib/idlelib/idle_test/test_filelist.py new file mode 100644 index 00000000..731f1975 --- /dev/null +++ b/Lib/idlelib/idle_test/test_filelist.py @@ -0,0 +1,33 @@ +"Test filelist, coverage 19%." + +from idlelib import filelist +import unittest +from test.support import requires +from tkinter import Tk + +class FileListTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.update_idletasks() + for id in cls.root.tk.call('after', 'info'): + cls.root.after_cancel(id) + cls.root.destroy() + del cls.root + + def test_new_empty(self): + flist = filelist.FileList(self.root) + self.assertEqual(flist.root, self.root) + e = flist.new() + self.assertEqual(type(e), flist.EditorWindow) + e._close() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_grep.py b/Lib/idlelib/idle_test/test_grep.py index 6b54c131..ab0d7860 100644 --- a/Lib/idlelib/idle_test/test_grep.py +++ b/Lib/idlelib/idle_test/test_grep.py @@ -3,14 +3,15 @@ Non-gui unit tests for grep.GrepDialog methods. dummy_command calls grep_it calls findfiles. An exception raised in one method will fail callers. Otherwise, tests are mostly independent. -*** Currently only test grep_it. +Currently only test grep_it, coverage 51%. """ +from idlelib.grep import GrepDialog import unittest from test.support import captured_stdout from idlelib.idle_test.mock_tk import Var -from idlelib.grep import GrepDialog import re + class Dummy_searchengine: '''GrepDialog.__init__ calls parent SearchDiabolBase which attaches the passed in SearchEngine instance as attribute 'engine'. Only a few of the @@ -21,6 +22,7 @@ class Dummy_searchengine: searchengine = Dummy_searchengine() + class Dummy_grep: # Methods tested #default_command = GrepDialog.default_command @@ -34,6 +36,7 @@ class Dummy_grep: grep = Dummy_grep() + class FindfilesTest(unittest.TestCase): # findfiles is really a function, not a method, could be iterator # test that filename return filename @@ -41,6 +44,7 @@ class FindfilesTest(unittest.TestCase): # test that recursive flag adds idle_test .py files pass + class Grep_itTest(unittest.TestCase): # Test captured reports with 0 and some hits. # Should test file names, but Windows reports have mixed / and \ separators @@ -71,10 +75,12 @@ class Grep_itTest(unittest.TestCase): self.assertIn('2', lines[3]) # hits found 2 self.assertTrue(lines[4].startswith('(Hint:')) + class Default_commandTest(unittest.TestCase): # To write this, move outwin import to top of GrepDialog # so it can be replaced by captured_stdout in class setup/teardown. pass + if __name__ == '__main__': - unittest.main(verbosity=2, exit=False) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_help.py b/Lib/idlelib/idle_test/test_help.py index 2c68e23b..b5426599 100644 --- a/Lib/idlelib/idle_test/test_help.py +++ b/Lib/idlelib/idle_test/test_help.py @@ -1,13 +1,12 @@ -'''Test idlelib.help. +"Test help, coverage 87%." -Coverage: 87% -''' from idlelib import help +import unittest from test.support import requires requires('gui') from os.path import abspath, dirname, join from tkinter import Tk -import unittest + class HelpFrameTest(unittest.TestCase): @@ -30,5 +29,6 @@ class HelpFrameTest(unittest.TestCase): text = self.frame.text self.assertEqual(text.get('1.0', '1.end'), ' IDLE ') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_help_about.py b/Lib/idlelib/idle_test/test_help_about.py index 9c6a834a..5839b5d0 100644 --- a/Lib/idlelib/idle_test/test_help_about.py +++ b/Lib/idlelib/idle_test/test_help_about.py @@ -1,18 +1,19 @@ -'''Test idlelib.help_about. +"""Test help_about, coverage 100%. +help_about.build_bits branches on sys.platform='darwin'. +'100% combines coverage on Mac and others. +""" -Coverage: 100% -''' +from idlelib import help_about +import unittest from test.support import requires, findfile from tkinter import Tk, TclError -import unittest -from unittest import mock from idlelib.idle_test.mock_idle import Func from idlelib.idle_test.mock_tk import Mbox_func -from idlelib.help_about import AboutDialog as About -from idlelib import help_about from idlelib import textview import os.path -from platform import python_version, architecture +from platform import python_version + +About = help_about.AboutDialog class LiveDialogTest(unittest.TestCase): diff --git a/Lib/idlelib/idle_test/test_history.py b/Lib/idlelib/idle_test/test_history.py index b2780107..67539651 100644 --- a/Lib/idlelib/idle_test/test_history.py +++ b/Lib/idlelib/idle_test/test_history.py @@ -1,15 +1,18 @@ +" Test history, coverage 100%." + +from idlelib.history import History import unittest from test.support import requires import tkinter as tk from tkinter import Text as tkText from idlelib.idle_test.mock_tk import Text as mkText -from idlelib.history import History from idlelib.config import idleConf line1 = 'a = 7' line2 = 'b = a' + class StoreTest(unittest.TestCase): '''Tests History.__init__ and History.store with mock Text''' @@ -61,6 +64,7 @@ class TextWrapper: def bell(self): self._bell = True + class FetchTest(unittest.TestCase): '''Test History.fetch with wrapped tk.Text. ''' diff --git a/Lib/idlelib/idle_test/test_hyperparser.py b/Lib/idlelib/idle_test/test_hyperparser.py index 73c8281e..8dbfc637 100644 --- a/Lib/idlelib/idle_test/test_hyperparser.py +++ b/Lib/idlelib/idle_test/test_hyperparser.py @@ -1,9 +1,10 @@ -"""Unittest for idlelib.hyperparser.py.""" +"Test hyperparser, coverage 98%." + +from idlelib.hyperparser import HyperParser import unittest from test.support import requires from tkinter import Tk, Text from idlelib.editor import EditorWindow -from idlelib.hyperparser import HyperParser class DummyEditwin: def __init__(self, text): @@ -270,5 +271,6 @@ class HyperParserTest(unittest.TestCase): self.assertEqual(eat_id('2' + 'a' * (length - 1), 0, length), 0) self.assertEqual(eat_id('2' + 'é' * (length - 1), 0, length), 0) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_iomenu.py b/Lib/idlelib/idle_test/test_iomenu.py index 65bf5930..743a05b3 100644 --- a/Lib/idlelib/idle_test/test_iomenu.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -1,233 +1,36 @@ -import unittest -import io - -from idlelib.run import PseudoInputFile, PseudoOutputFile - - -class S(str): - def __str__(self): - return '%s:str' % type(self).__name__ - def __unicode__(self): - return '%s:unicode' % type(self).__name__ - def __len__(self): - return 3 - def __iter__(self): - return iter('abc') - def __getitem__(self, *args): - return '%s:item' % type(self).__name__ - def __getslice__(self, *args): - return '%s:slice' % type(self).__name__ - -class MockShell: - def __init__(self): - self.reset() - - def write(self, *args): - self.written.append(args) - - def readline(self): - return self.lines.pop() - - def close(self): - pass - - def reset(self): - self.written = [] - - def push(self, lines): - self.lines = list(lines)[::-1] - - -class PseudeOutputFilesTest(unittest.TestCase): - def test_misc(self): - shell = MockShell() - f = PseudoOutputFile(shell, 'stdout', 'utf-8') - self.assertIsInstance(f, io.TextIOBase) - self.assertEqual(f.encoding, 'utf-8') - self.assertIsNone(f.errors) - self.assertIsNone(f.newlines) - self.assertEqual(f.name, '') - self.assertFalse(f.closed) - self.assertTrue(f.isatty()) - self.assertFalse(f.readable()) - self.assertTrue(f.writable()) - self.assertFalse(f.seekable()) - - def test_unsupported(self): - shell = MockShell() - f = PseudoOutputFile(shell, 'stdout', 'utf-8') - self.assertRaises(OSError, f.fileno) - self.assertRaises(OSError, f.tell) - self.assertRaises(OSError, f.seek, 0) - self.assertRaises(OSError, f.read, 0) - self.assertRaises(OSError, f.readline, 0) - - def test_write(self): - shell = MockShell() - f = PseudoOutputFile(shell, 'stdout', 'utf-8') - f.write('test') - self.assertEqual(shell.written, [('test', 'stdout')]) - shell.reset() - f.write('t\xe8st') - self.assertEqual(shell.written, [('t\xe8st', 'stdout')]) - shell.reset() - - f.write(S('t\xe8st')) - self.assertEqual(shell.written, [('t\xe8st', 'stdout')]) - self.assertEqual(type(shell.written[0][0]), str) - shell.reset() +"Test , coverage 16%." - self.assertRaises(TypeError, f.write) - self.assertEqual(shell.written, []) - self.assertRaises(TypeError, f.write, b'test') - self.assertRaises(TypeError, f.write, 123) - self.assertEqual(shell.written, []) - self.assertRaises(TypeError, f.write, 'test', 'spam') - self.assertEqual(shell.written, []) - - def test_writelines(self): - shell = MockShell() - f = PseudoOutputFile(shell, 'stdout', 'utf-8') - f.writelines([]) - self.assertEqual(shell.written, []) - shell.reset() - f.writelines(['one\n', 'two']) - self.assertEqual(shell.written, - [('one\n', 'stdout'), ('two', 'stdout')]) - shell.reset() - f.writelines(['on\xe8\n', 'tw\xf2']) - self.assertEqual(shell.written, - [('on\xe8\n', 'stdout'), ('tw\xf2', 'stdout')]) - shell.reset() - - f.writelines([S('t\xe8st')]) - self.assertEqual(shell.written, [('t\xe8st', 'stdout')]) - self.assertEqual(type(shell.written[0][0]), str) - shell.reset() - - self.assertRaises(TypeError, f.writelines) - self.assertEqual(shell.written, []) - self.assertRaises(TypeError, f.writelines, 123) - self.assertEqual(shell.written, []) - self.assertRaises(TypeError, f.writelines, [b'test']) - self.assertRaises(TypeError, f.writelines, [123]) - self.assertEqual(shell.written, []) - self.assertRaises(TypeError, f.writelines, [], []) - self.assertEqual(shell.written, []) - - def test_close(self): - shell = MockShell() - f = PseudoOutputFile(shell, 'stdout', 'utf-8') - self.assertFalse(f.closed) - f.write('test') - f.close() - self.assertTrue(f.closed) - self.assertRaises(ValueError, f.write, 'x') - self.assertEqual(shell.written, [('test', 'stdout')]) - f.close() - self.assertRaises(TypeError, f.close, 1) - - -class PseudeInputFilesTest(unittest.TestCase): - def test_misc(self): - shell = MockShell() - f = PseudoInputFile(shell, 'stdin', 'utf-8') - self.assertIsInstance(f, io.TextIOBase) - self.assertEqual(f.encoding, 'utf-8') - self.assertIsNone(f.errors) - self.assertIsNone(f.newlines) - self.assertEqual(f.name, '') - self.assertFalse(f.closed) - self.assertTrue(f.isatty()) - self.assertTrue(f.readable()) - self.assertFalse(f.writable()) - self.assertFalse(f.seekable()) - - def test_unsupported(self): - shell = MockShell() - f = PseudoInputFile(shell, 'stdin', 'utf-8') - self.assertRaises(OSError, f.fileno) - self.assertRaises(OSError, f.tell) - self.assertRaises(OSError, f.seek, 0) - self.assertRaises(OSError, f.write, 'x') - self.assertRaises(OSError, f.writelines, ['x']) - - def test_read(self): - shell = MockShell() - f = PseudoInputFile(shell, 'stdin', 'utf-8') - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.read(), 'one\ntwo\n') - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.read(-1), 'one\ntwo\n') - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.read(None), 'one\ntwo\n') - shell.push(['one\n', 'two\n', 'three\n', '']) - self.assertEqual(f.read(2), 'on') - self.assertEqual(f.read(3), 'e\nt') - self.assertEqual(f.read(10), 'wo\nthree\n') - - shell.push(['one\n', 'two\n']) - self.assertEqual(f.read(0), '') - self.assertRaises(TypeError, f.read, 1.5) - self.assertRaises(TypeError, f.read, '1') - self.assertRaises(TypeError, f.read, 1, 1) - - def test_readline(self): - shell = MockShell() - f = PseudoInputFile(shell, 'stdin', 'utf-8') - shell.push(['one\n', 'two\n', 'three\n', 'four\n']) - self.assertEqual(f.readline(), 'one\n') - self.assertEqual(f.readline(-1), 'two\n') - self.assertEqual(f.readline(None), 'three\n') - shell.push(['one\ntwo\n']) - self.assertEqual(f.readline(), 'one\n') - self.assertEqual(f.readline(), 'two\n') - shell.push(['one', 'two', 'three']) - self.assertEqual(f.readline(), 'one') - self.assertEqual(f.readline(), 'two') - shell.push(['one\n', 'two\n', 'three\n']) - self.assertEqual(f.readline(2), 'on') - self.assertEqual(f.readline(1), 'e') - self.assertEqual(f.readline(1), '\n') - self.assertEqual(f.readline(10), 'two\n') - - shell.push(['one\n', 'two\n']) - self.assertEqual(f.readline(0), '') - self.assertRaises(TypeError, f.readlines, 1.5) - self.assertRaises(TypeError, f.readlines, '1') - self.assertRaises(TypeError, f.readlines, 1, 1) - - def test_readlines(self): - shell = MockShell() - f = PseudoInputFile(shell, 'stdin', 'utf-8') - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.readlines(), ['one\n', 'two\n']) - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.readlines(-1), ['one\n', 'two\n']) - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.readlines(None), ['one\n', 'two\n']) - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.readlines(0), ['one\n', 'two\n']) - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.readlines(3), ['one\n']) - shell.push(['one\n', 'two\n', '']) - self.assertEqual(f.readlines(4), ['one\n', 'two\n']) - - shell.push(['one\n', 'two\n', '']) - self.assertRaises(TypeError, f.readlines, 1.5) - self.assertRaises(TypeError, f.readlines, '1') - self.assertRaises(TypeError, f.readlines, 1, 1) - - def test_close(self): - shell = MockShell() - f = PseudoInputFile(shell, 'stdin', 'utf-8') - shell.push(['one\n', 'two\n', '']) - self.assertFalse(f.closed) - self.assertEqual(f.readline(), 'one\n') - f.close() - self.assertFalse(f.closed) - self.assertEqual(f.readline(), 'two\n') - self.assertRaises(TypeError, f.close, 1) +from idlelib import iomenu +import unittest +from test.support import requires +from tkinter import Tk + +from idlelib.editor import EditorWindow + + +class IOBindigTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.editwin = EditorWindow(root=cls.root) + + @classmethod + def tearDownClass(cls): + cls.editwin._close() + del cls.editwin + cls.root.update_idletasks() + for id in cls.root.tk.call('after', 'info'): + cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def test_init(self): + io = iomenu.IOBinding(self.editwin) + self.assertIs(io.editwin, self.editwin) + io.close if __name__ == '__main__': diff --git a/Lib/idlelib/idle_test/test_macosx.py b/Lib/idlelib/idle_test/test_macosx.py index 3d85f3ca..b6bd922e 100644 --- a/Lib/idlelib/idle_test/test_macosx.py +++ b/Lib/idlelib/idle_test/test_macosx.py @@ -1,11 +1,9 @@ -'''Test idlelib.macosx.py. +"Test macosx, coverage 45% on Windows." -Coverage: 71% on Windows. -''' from idlelib import macosx +import unittest from test.support import requires import tkinter as tk -import unittest import unittest.mock as mock from idlelib.filelist import FileList diff --git a/Lib/idlelib/idle_test/test_mainmenu.py b/Lib/idlelib/idle_test/test_mainmenu.py new file mode 100644 index 00000000..7ec03683 --- /dev/null +++ b/Lib/idlelib/idle_test/test_mainmenu.py @@ -0,0 +1,21 @@ +"Test mainmenu, coverage 100%." +# Reported as 88%; mocking turtledemo absence would have no point. + +from idlelib import mainmenu +import unittest + + +class MainMenuTest(unittest.TestCase): + + def test_menudefs(self): + actual = [item[0] for item in mainmenu.menudefs] + expect = ['file', 'edit', 'format', 'run', 'shell', + 'debug', 'options', 'window', 'help'] + self.assertEqual(actual, expect) + + def test_default_keydefs(self): + self.assertGreaterEqual(len(mainmenu.default_keydefs), 50) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_multicall.py b/Lib/idlelib/idle_test/test_multicall.py new file mode 100644 index 00000000..68156a74 --- /dev/null +++ b/Lib/idlelib/idle_test/test_multicall.py @@ -0,0 +1,40 @@ +"Test multicall, coverage 33%." + +from idlelib import multicall +import unittest +from test.support import requires +from tkinter import Tk, Text + + +class MultiCallTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.mc = multicall.MultiCallCreator(Text) + + @classmethod + def tearDownClass(cls): + del cls.mc + cls.root.update_idletasks() +## for id in cls.root.tk.call('after', 'info'): +## cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def test_creator(self): + mc = self.mc + self.assertIs(multicall._multicall_dict[Text], mc) + self.assertTrue(issubclass(mc, Text)) + mc2 = multicall.MultiCallCreator(Text) + self.assertIs(mc, mc2) + + def test_init(self): + mctext = self.mc(self.root) + self.assertIsInstance(mctext._MultiCall__binders, list) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_outwin.py b/Lib/idlelib/idle_test/test_outwin.py index 231c7bf9..cd099ecd 100644 --- a/Lib/idlelib/idle_test/test_outwin.py +++ b/Lib/idlelib/idle_test/test_outwin.py @@ -1,12 +1,11 @@ -""" Test idlelib.outwin. -""" +"Test outwin, coverage 76%." +from idlelib import outwin import unittest +from test.support import requires from tkinter import Tk, Text from idlelib.idle_test.mock_tk import Mbox_func from idlelib.idle_test.mock_idle import Func -from idlelib import outwin -from test.support import requires from unittest import mock diff --git a/Lib/idlelib/idle_test/test_paragraph.py b/Lib/idlelib/idle_test/test_paragraph.py index ba350c97..0cb966fb 100644 --- a/Lib/idlelib/idle_test/test_paragraph.py +++ b/Lib/idlelib/idle_test/test_paragraph.py @@ -1,9 +1,10 @@ -# Test the functions and main class method of paragraph.py +"Test paragraph, coverage 76%." + +from idlelib import paragraph as pg import unittest -from idlelib import paragraph as fp -from idlelib.editor import EditorWindow -from tkinter import Tk, Text from test.support import requires +from tkinter import Tk, Text +from idlelib.editor import EditorWindow class Is_Get_Test(unittest.TestCase): @@ -15,26 +16,26 @@ class Is_Get_Test(unittest.TestCase): leadingws_nocomment = ' This is not a comment' def test_is_all_white(self): - self.assertTrue(fp.is_all_white('')) - self.assertTrue(fp.is_all_white('\t\n\r\f\v')) - self.assertFalse(fp.is_all_white(self.test_comment)) + self.assertTrue(pg.is_all_white('')) + self.assertTrue(pg.is_all_white('\t\n\r\f\v')) + self.assertFalse(pg.is_all_white(self.test_comment)) def test_get_indent(self): Equal = self.assertEqual - Equal(fp.get_indent(self.test_comment), '') - Equal(fp.get_indent(self.trailingws_comment), '') - Equal(fp.get_indent(self.leadingws_comment), ' ') - Equal(fp.get_indent(self.leadingws_nocomment), ' ') + Equal(pg.get_indent(self.test_comment), '') + Equal(pg.get_indent(self.trailingws_comment), '') + Equal(pg.get_indent(self.leadingws_comment), ' ') + Equal(pg.get_indent(self.leadingws_nocomment), ' ') def test_get_comment_header(self): Equal = self.assertEqual # Test comment strings - Equal(fp.get_comment_header(self.test_comment), '#') - Equal(fp.get_comment_header(self.trailingws_comment), '#') - Equal(fp.get_comment_header(self.leadingws_comment), ' #') + Equal(pg.get_comment_header(self.test_comment), '#') + Equal(pg.get_comment_header(self.trailingws_comment), '#') + Equal(pg.get_comment_header(self.leadingws_comment), ' #') # Test non-comment strings - Equal(fp.get_comment_header(self.leadingws_nocomment), ' ') - Equal(fp.get_comment_header(self.test_nocomment), '') + Equal(pg.get_comment_header(self.leadingws_nocomment), ' ') + Equal(pg.get_comment_header(self.test_nocomment), '') class FindTest(unittest.TestCase): @@ -62,7 +63,7 @@ class FindTest(unittest.TestCase): linelength = int(text.index("%d.end" % line).split('.')[1]) for col in (0, linelength//2, linelength): tempindex = "%d.%d" % (line, col) - self.assertEqual(fp.find_paragraph(text, tempindex), expected) + self.assertEqual(pg.find_paragraph(text, tempindex), expected) text.delete('1.0', 'end') def test_find_comment(self): @@ -161,7 +162,7 @@ class ReformatFunctionTest(unittest.TestCase): def test_reformat_paragraph(self): Equal = self.assertEqual - reform = fp.reformat_paragraph + reform = pg.reformat_paragraph hw = "O hello world" Equal(reform(' ', 1), ' ') Equal(reform("Hello world", 20), "Hello world") @@ -192,7 +193,7 @@ class ReformatCommentTest(unittest.TestCase): test_string = ( " \"\"\"this is a test of a reformat for a triple quoted string" " will it reformat to less than 70 characters for me?\"\"\"") - result = fp.reformat_comment(test_string, 70, " ") + result = pg.reformat_comment(test_string, 70, " ") expected = ( " \"\"\"this is a test of a reformat for a triple quoted string will it\n" " reformat to less than 70 characters for me?\"\"\"") @@ -201,7 +202,7 @@ class ReformatCommentTest(unittest.TestCase): test_comment = ( "# this is a test of a reformat for a triple quoted string will " "it reformat to less than 70 characters for me?") - result = fp.reformat_comment(test_comment, 70, "#") + result = pg.reformat_comment(test_comment, 70, "#") expected = ( "# this is a test of a reformat for a triple quoted string will it\n" "# reformat to less than 70 characters for me?") @@ -210,7 +211,7 @@ class ReformatCommentTest(unittest.TestCase): class FormatClassTest(unittest.TestCase): def test_init_close(self): - instance = fp.FormatParagraph('editor') + instance = pg.FormatParagraph('editor') self.assertEqual(instance.editwin, 'editor') instance.close() self.assertEqual(instance.editwin, None) @@ -269,14 +270,16 @@ class FormatEventTest(unittest.TestCase): def setUpClass(cls): requires('gui') cls.root = Tk() + cls.root.withdraw() editor = Editor(root=cls.root) cls.text = editor.text.text # Test code does not need the wrapper. - cls.formatter = fp.FormatParagraph(editor).format_paragraph_event + cls.formatter = pg.FormatParagraph(editor).format_paragraph_event # Sets the insert mark just after the re-wrapped and inserted text. @classmethod def tearDownClass(cls): del cls.text, cls.formatter + cls.root.update_idletasks() cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_parenmatch.py b/Lib/idlelib/idle_test/test_parenmatch.py index 3caa2754..f58819ab 100644 --- a/Lib/idlelib/idle_test/test_parenmatch.py +++ b/Lib/idlelib/idle_test/test_parenmatch.py @@ -1,8 +1,8 @@ -'''Test idlelib.parenmatch. +"""Test parenmatch, coverage 91%. This must currently be a gui test because ParenMatch methods use several text methods not defined on idlelib.idle_test.mock_tk.Text. -''' +""" from idlelib.parenmatch import ParenMatch from test.support import requires requires('gui') diff --git a/Lib/idlelib/idle_test/test_pathbrowser.py b/Lib/idlelib/idle_test/test_pathbrowser.py index 74b716a3..13d8b9e1 100644 --- a/Lib/idlelib/idle_test/test_pathbrowser.py +++ b/Lib/idlelib/idle_test/test_pathbrowser.py @@ -1,19 +1,17 @@ -""" Test idlelib.pathbrowser. -""" +"Test pathbrowser, coverage 95%." +from idlelib import pathbrowser +import unittest +from test.support import requires +from tkinter import Tk import os.path import pyclbr # for _modules import sys # for sys.path -from tkinter import Tk -from test.support import requires -import unittest from idlelib.idle_test.mock_idle import Func - import idlelib # for __file__ from idlelib import browser -from idlelib import pathbrowser from idlelib.tree import TreeNode diff --git a/Lib/idlelib/idle_test/test_percolator.py b/Lib/idlelib/idle_test/test_percolator.py index 573b9a1e..17668ccd 100644 --- a/Lib/idlelib/idle_test/test_percolator.py +++ b/Lib/idlelib/idle_test/test_percolator.py @@ -1,10 +1,10 @@ -'''Test percolator.py.''' -from test.support import requires -requires('gui') +"Test percolator, coverage 100%." +from idlelib.percolator import Percolator, Delegator import unittest +from test.support import requires +requires('gui') from tkinter import Text, Tk, END -from idlelib.percolator import Percolator, Delegator class MyFilter(Delegator): diff --git a/Lib/idlelib/idle_test/test_pyparse.py b/Lib/idlelib/idle_test/test_pyparse.py index 574b19d9..0534301b 100644 --- a/Lib/idlelib/idle_test/test_pyparse.py +++ b/Lib/idlelib/idle_test/test_pyparse.py @@ -1,11 +1,8 @@ -"""Unittest for idlelib.pyparse.py. +"Test pyparse, coverage 96%." -Coverage: 97% -""" - -from collections import namedtuple -import unittest from idlelib import pyparse +import unittest +from collections import namedtuple class ParseMapTest(unittest.TestCase): diff --git a/Lib/idlelib/idle_test/test_pyshell.py b/Lib/idlelib/idle_test/test_pyshell.py new file mode 100644 index 00000000..581444ca --- /dev/null +++ b/Lib/idlelib/idle_test/test_pyshell.py @@ -0,0 +1,42 @@ +"Test pyshell, coverage 12%." +# Plus coverage of test_warning. Was 20% with test_openshell. + +from idlelib import pyshell +import unittest +from test.support import requires +from tkinter import Tk + + +class PyShellFileListTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + #cls.root.update_idletasks() +## for id in cls.root.tk.call('after', 'info'): +## cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def test_init(self): + psfl = pyshell.PyShellFileList(self.root) + self.assertEqual(psfl.EditorWindow, pyshell.PyShellEditorWindow) + self.assertIsNone(psfl.pyshell) + +# The following sometimes causes 'invalid command name "109734456recolorize"'. +# Uncommenting after_cancel above prevents this, but results in +# TclError: bad window path name ".!listedtoplevel.!frame.text" +# which is normally prevented by after_cancel. +## def test_openshell(self): +## pyshell.use_subprocess = False +## ps = pyshell.PyShellFileList(self.root).open_shell() +## self.assertIsInstance(ps, pyshell.PyShell) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_query.py b/Lib/idlelib/idle_test/test_query.py index 953f24f0..c1c4a25c 100644 --- a/Lib/idlelib/idle_test/test_query.py +++ b/Lib/idlelib/idle_test/test_query.py @@ -1,4 +1,4 @@ -"""Test idlelib.query. +"""Test query, coverage 91%). Non-gui tests for Query, SectionName, ModuleName, and HelpSource use dummy versions that extract the non-gui methods and add other needed @@ -8,17 +8,15 @@ the subclass definition. The appearance of the widgets is checked by the Query and HelpSource htests. These are run by running query.py. - -Coverage: 94% (100% for Query and SectionName). -6 of 8 missing are ModuleName exceptions I don't know how to trigger. """ +from idlelib import query +import unittest from test.support import requires -import sys from tkinter import Tk -import unittest + +import sys from unittest import mock from idlelib.idle_test.mock_tk import Var -from idlelib import query # NON-GUI TESTS diff --git a/Lib/idlelib/idle_test/test_redirector.py b/Lib/idlelib/idle_test/test_redirector.py index b0385fa7..a97b3002 100644 --- a/Lib/idlelib/idle_test/test_redirector.py +++ b/Lib/idlelib/idle_test/test_redirector.py @@ -1,12 +1,10 @@ -'''Test idlelib.redirector. +"Test redirector, coverage 100%." -100% coverage -''' -from test.support import requires +from idlelib.redirector import WidgetRedirector import unittest -from idlelib.idle_test.mock_idle import Func +from test.support import requires from tkinter import Tk, Text, TclError -from idlelib.redirector import WidgetRedirector +from idlelib.idle_test.mock_idle import Func class InitCloseTest(unittest.TestCase): diff --git a/Lib/idlelib/idle_test/test_replace.py b/Lib/idlelib/idle_test/test_replace.py index df76dec3..c3c5d2ee 100644 --- a/Lib/idlelib/idle_test/test_replace.py +++ b/Lib/idlelib/idle_test/test_replace.py @@ -1,13 +1,14 @@ -"""Unittest for idlelib.replace.py""" +"Test replace, coverage 78%." + +from idlelib.replace import ReplaceDialog +import unittest from test.support import requires requires('gui') +from tkinter import Tk, Text -import unittest from unittest.mock import Mock -from tkinter import Tk, Text from idlelib.idle_test.mock_tk import Mbox import idlelib.searchengine as se -from idlelib.replace import ReplaceDialog orig_mbox = se.tkMessageBox showerror = Mbox.showerror diff --git a/Lib/idlelib/idle_test/test_rpc.py b/Lib/idlelib/idle_test/test_rpc.py new file mode 100644 index 00000000..48be65ba --- /dev/null +++ b/Lib/idlelib/idle_test/test_rpc.py @@ -0,0 +1,30 @@ +"Test rpc, coverage 20%." + +from idlelib import rpc +import unittest + +import marshal + + +class CodePicklerTest(unittest.TestCase): + + def test_pickle_unpickle(self): + def f(): return a + b + c + func, (cbytes,) = rpc.pickle_code(f.__code__) + self.assertIs(func, rpc.unpickle_code) + self.assertIn(b'test_rpc.py', cbytes) + code = rpc.unpickle_code(cbytes) + self.assertEqual(code.co_names, ('a', 'b', 'c')) + + def test_code_pickler(self): + self.assertIn(type((lambda:None).__code__), + rpc.CodePickler.dispatch_table) + + def test_dumps(self): + def f(): pass + # The main test here is that pickling code does not raise. + self.assertIn(b'test_rpc.py', rpc.dumps(f.__code__)) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_rstrip.py b/Lib/idlelib/idle_test/test_rstrip.py index 130e6be2..2bc7c6f0 100644 --- a/Lib/idlelib/idle_test/test_rstrip.py +++ b/Lib/idlelib/idle_test/test_rstrip.py @@ -1,5 +1,7 @@ +"Test rstrip, coverage 100%." + +from idlelib import rstrip import unittest -import idlelib.rstrip as rs from idlelib.idle_test.mock_idle import Editor class rstripTest(unittest.TestCase): @@ -7,7 +9,7 @@ class rstripTest(unittest.TestCase): def test_rstrip_line(self): editor = Editor() text = editor.text - do_rstrip = rs.RstripExtension(editor).do_rstrip + do_rstrip = rstrip.Rstrip(editor).do_rstrip do_rstrip() self.assertEqual(text.get('1.0', 'insert'), '') @@ -20,12 +22,12 @@ class rstripTest(unittest.TestCase): def test_rstrip_multiple(self): editor = Editor() - # Uncomment following to verify that test passes with real widgets. -## from idlelib.editor import EditorWindow as Editor -## from tkinter import Tk -## editor = Editor(root=Tk()) + # Comment above, uncomment 3 below to test with real Editor & Text. + #from idlelib.editor import EditorWindow as Editor + #from tkinter import Tk + #editor = Editor(root=Tk()) text = editor.text - do_rstrip = rs.RstripExtension(editor).do_rstrip + do_rstrip = rstrip.Rstrip(editor).do_rstrip original = ( "Line with an ending tab \n" @@ -45,5 +47,7 @@ class rstripTest(unittest.TestCase): do_rstrip() self.assertEqual(text.get('1.0', 'insert'), stripped) + + if __name__ == '__main__': - unittest.main(verbosity=2, exit=False) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py index d7e627d2..46f0235f 100644 --- a/Lib/idlelib/idle_test/test_run.py +++ b/Lib/idlelib/idle_test/test_run.py @@ -1,11 +1,14 @@ +"Test run, coverage 42%." + +from idlelib import run import unittest from unittest import mock - from test.support import captured_stderr -import idlelib.run as idlerun +import io class RunTest(unittest.TestCase): + def test_print_exception_unhashable(self): class UnhashableException(Exception): def __eq__(self, other): @@ -20,10 +23,10 @@ class RunTest(unittest.TestCase): raise ex1 except UnhashableException: with captured_stderr() as output: - with mock.patch.object(idlerun, + with mock.patch.object(run, 'cleanup_traceback') as ct: ct.side_effect = lambda t, e: t - idlerun.print_exception() + run.print_exception() tb = output.getvalue().strip().splitlines() self.assertEqual(11, len(tb)) @@ -31,5 +34,231 @@ class RunTest(unittest.TestCase): self.assertIn('UnhashableException: ex1', tb[10]) +# PseudoFile tests. + +class S(str): + def __str__(self): + return '%s:str' % type(self).__name__ + def __unicode__(self): + return '%s:unicode' % type(self).__name__ + def __len__(self): + return 3 + def __iter__(self): + return iter('abc') + def __getitem__(self, *args): + return '%s:item' % type(self).__name__ + def __getslice__(self, *args): + return '%s:slice' % type(self).__name__ + + +class MockShell: + def __init__(self): + self.reset() + def write(self, *args): + self.written.append(args) + def readline(self): + return self.lines.pop() + def close(self): + pass + def reset(self): + self.written = [] + def push(self, lines): + self.lines = list(lines)[::-1] + + +class PseudeInputFilesTest(unittest.TestCase): + + def test_misc(self): + shell = MockShell() + f = run.PseudoInputFile(shell, 'stdin', 'utf-8') + self.assertIsInstance(f, io.TextIOBase) + self.assertEqual(f.encoding, 'utf-8') + self.assertIsNone(f.errors) + self.assertIsNone(f.newlines) + self.assertEqual(f.name, '') + self.assertFalse(f.closed) + self.assertTrue(f.isatty()) + self.assertTrue(f.readable()) + self.assertFalse(f.writable()) + self.assertFalse(f.seekable()) + + def test_unsupported(self): + shell = MockShell() + f = run.PseudoInputFile(shell, 'stdin', 'utf-8') + self.assertRaises(OSError, f.fileno) + self.assertRaises(OSError, f.tell) + self.assertRaises(OSError, f.seek, 0) + self.assertRaises(OSError, f.write, 'x') + self.assertRaises(OSError, f.writelines, ['x']) + + def test_read(self): + shell = MockShell() + f = run.PseudoInputFile(shell, 'stdin', 'utf-8') + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.read(), 'one\ntwo\n') + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.read(-1), 'one\ntwo\n') + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.read(None), 'one\ntwo\n') + shell.push(['one\n', 'two\n', 'three\n', '']) + self.assertEqual(f.read(2), 'on') + self.assertEqual(f.read(3), 'e\nt') + self.assertEqual(f.read(10), 'wo\nthree\n') + + shell.push(['one\n', 'two\n']) + self.assertEqual(f.read(0), '') + self.assertRaises(TypeError, f.read, 1.5) + self.assertRaises(TypeError, f.read, '1') + self.assertRaises(TypeError, f.read, 1, 1) + + def test_readline(self): + shell = MockShell() + f = run.PseudoInputFile(shell, 'stdin', 'utf-8') + shell.push(['one\n', 'two\n', 'three\n', 'four\n']) + self.assertEqual(f.readline(), 'one\n') + self.assertEqual(f.readline(-1), 'two\n') + self.assertEqual(f.readline(None), 'three\n') + shell.push(['one\ntwo\n']) + self.assertEqual(f.readline(), 'one\n') + self.assertEqual(f.readline(), 'two\n') + shell.push(['one', 'two', 'three']) + self.assertEqual(f.readline(), 'one') + self.assertEqual(f.readline(), 'two') + shell.push(['one\n', 'two\n', 'three\n']) + self.assertEqual(f.readline(2), 'on') + self.assertEqual(f.readline(1), 'e') + self.assertEqual(f.readline(1), '\n') + self.assertEqual(f.readline(10), 'two\n') + + shell.push(['one\n', 'two\n']) + self.assertEqual(f.readline(0), '') + self.assertRaises(TypeError, f.readlines, 1.5) + self.assertRaises(TypeError, f.readlines, '1') + self.assertRaises(TypeError, f.readlines, 1, 1) + + def test_readlines(self): + shell = MockShell() + f = run.PseudoInputFile(shell, 'stdin', 'utf-8') + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.readlines(), ['one\n', 'two\n']) + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.readlines(-1), ['one\n', 'two\n']) + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.readlines(None), ['one\n', 'two\n']) + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.readlines(0), ['one\n', 'two\n']) + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.readlines(3), ['one\n']) + shell.push(['one\n', 'two\n', '']) + self.assertEqual(f.readlines(4), ['one\n', 'two\n']) + + shell.push(['one\n', 'two\n', '']) + self.assertRaises(TypeError, f.readlines, 1.5) + self.assertRaises(TypeError, f.readlines, '1') + self.assertRaises(TypeError, f.readlines, 1, 1) + + def test_close(self): + shell = MockShell() + f = run.PseudoInputFile(shell, 'stdin', 'utf-8') + shell.push(['one\n', 'two\n', '']) + self.assertFalse(f.closed) + self.assertEqual(f.readline(), 'one\n') + f.close() + self.assertFalse(f.closed) + self.assertEqual(f.readline(), 'two\n') + self.assertRaises(TypeError, f.close, 1) + + +class PseudeOutputFilesTest(unittest.TestCase): + + def test_misc(self): + shell = MockShell() + f = run.PseudoOutputFile(shell, 'stdout', 'utf-8') + self.assertIsInstance(f, io.TextIOBase) + self.assertEqual(f.encoding, 'utf-8') + self.assertIsNone(f.errors) + self.assertIsNone(f.newlines) + self.assertEqual(f.name, '') + self.assertFalse(f.closed) + self.assertTrue(f.isatty()) + self.assertFalse(f.readable()) + self.assertTrue(f.writable()) + self.assertFalse(f.seekable()) + + def test_unsupported(self): + shell = MockShell() + f = run.PseudoOutputFile(shell, 'stdout', 'utf-8') + self.assertRaises(OSError, f.fileno) + self.assertRaises(OSError, f.tell) + self.assertRaises(OSError, f.seek, 0) + self.assertRaises(OSError, f.read, 0) + self.assertRaises(OSError, f.readline, 0) + + def test_write(self): + shell = MockShell() + f = run.PseudoOutputFile(shell, 'stdout', 'utf-8') + f.write('test') + self.assertEqual(shell.written, [('test', 'stdout')]) + shell.reset() + f.write('t\xe8st') + self.assertEqual(shell.written, [('t\xe8st', 'stdout')]) + shell.reset() + + f.write(S('t\xe8st')) + self.assertEqual(shell.written, [('t\xe8st', 'stdout')]) + self.assertEqual(type(shell.written[0][0]), str) + shell.reset() + + self.assertRaises(TypeError, f.write) + self.assertEqual(shell.written, []) + self.assertRaises(TypeError, f.write, b'test') + self.assertRaises(TypeError, f.write, 123) + self.assertEqual(shell.written, []) + self.assertRaises(TypeError, f.write, 'test', 'spam') + self.assertEqual(shell.written, []) + + def test_writelines(self): + shell = MockShell() + f = run.PseudoOutputFile(shell, 'stdout', 'utf-8') + f.writelines([]) + self.assertEqual(shell.written, []) + shell.reset() + f.writelines(['one\n', 'two']) + self.assertEqual(shell.written, + [('one\n', 'stdout'), ('two', 'stdout')]) + shell.reset() + f.writelines(['on\xe8\n', 'tw\xf2']) + self.assertEqual(shell.written, + [('on\xe8\n', 'stdout'), ('tw\xf2', 'stdout')]) + shell.reset() + + f.writelines([S('t\xe8st')]) + self.assertEqual(shell.written, [('t\xe8st', 'stdout')]) + self.assertEqual(type(shell.written[0][0]), str) + shell.reset() + + self.assertRaises(TypeError, f.writelines) + self.assertEqual(shell.written, []) + self.assertRaises(TypeError, f.writelines, 123) + self.assertEqual(shell.written, []) + self.assertRaises(TypeError, f.writelines, [b'test']) + self.assertRaises(TypeError, f.writelines, [123]) + self.assertEqual(shell.written, []) + self.assertRaises(TypeError, f.writelines, [], []) + self.assertEqual(shell.written, []) + + def test_close(self): + shell = MockShell() + f = run.PseudoOutputFile(shell, 'stdout', 'utf-8') + self.assertFalse(f.closed) + f.write('test') + f.close() + self.assertTrue(f.closed) + self.assertRaises(ValueError, f.write, 'x') + self.assertEqual(shell.written, [('test', 'stdout')]) + f.close() + self.assertRaises(TypeError, f.close, 1) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_runscript.py b/Lib/idlelib/idle_test/test_runscript.py new file mode 100644 index 00000000..5fc60185 --- /dev/null +++ b/Lib/idlelib/idle_test/test_runscript.py @@ -0,0 +1,33 @@ +"Test runscript, coverage 16%." + +from idlelib import runscript +import unittest +from test.support import requires +from tkinter import Tk +from idlelib.editor import EditorWindow + + +class ScriptBindingTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.update_idletasks() + for id in cls.root.tk.call('after', 'info'): + cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def test_init(self): + ew = EditorWindow(root=self.root) + sb = runscript.ScriptBinding(ew) + ew._close() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_scrolledlist.py b/Lib/idlelib/idle_test/test_scrolledlist.py index 56aabfec..2f819fda 100644 --- a/Lib/idlelib/idle_test/test_scrolledlist.py +++ b/Lib/idlelib/idle_test/test_scrolledlist.py @@ -1,11 +1,9 @@ -''' Test idlelib.scrolledlist. +"Test scrolledlist, coverage 38%." -Coverage: 39% -''' -from idlelib import scrolledlist +from idlelib.scrolledlist import ScrolledList +import unittest from test.support import requires requires('gui') -import unittest from tkinter import Tk @@ -22,7 +20,7 @@ class ScrolledListTest(unittest.TestCase): def test_init(self): - scrolledlist.ScrolledList(self.root) + ScrolledList(self.root) if __name__ == '__main__': diff --git a/Lib/idlelib/idle_test/test_search.py b/Lib/idlelib/idle_test/test_search.py index 3ab72951..de703c19 100644 --- a/Lib/idlelib/idle_test/test_search.py +++ b/Lib/idlelib/idle_test/test_search.py @@ -1,25 +1,23 @@ -"""Test SearchDialog class in idlelib.search.py""" +"Test search, coverage 69%." + +from idlelib import search +import unittest +from test.support import requires +requires('gui') +from tkinter import Tk, Text, BooleanVar +from idlelib import searchengine # Does not currently test the event handler wrappers. # A usage test should simulate clicks and check highlighting. # Tests need to be coordinated with SearchDialogBase tests # to avoid duplication. -from test.support import requires -requires('gui') - -import unittest -import tkinter as tk -from tkinter import BooleanVar -import idlelib.searchengine as se -import idlelib.search as sd - class SearchDialogTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls.root = tk.Tk() + cls.root = Tk() @classmethod def tearDownClass(cls): @@ -27,10 +25,10 @@ class SearchDialogTest(unittest.TestCase): del cls.root def setUp(self): - self.engine = se.SearchEngine(self.root) - self.dialog = sd.SearchDialog(self.root, self.engine) + self.engine = searchengine.SearchEngine(self.root) + self.dialog = search.SearchDialog(self.root, self.engine) self.dialog.bell = lambda: None - self.text = tk.Text(self.root) + self.text = Text(self.root) self.text.insert('1.0', 'Hello World!') def test_find_again(self): diff --git a/Lib/idlelib/idle_test/test_searchbase.py b/Lib/idlelib/idle_test/test_searchbase.py index 27b02fbe..46c3ad11 100644 --- a/Lib/idlelib/idle_test/test_searchbase.py +++ b/Lib/idlelib/idle_test/test_searchbase.py @@ -1,8 +1,7 @@ -'''tests idlelib.searchbase. +"Test searchbase, coverage 98%." +# The only thing not covered is inconsequential -- +# testing skipping of suite when self.needwrapbutton is false. -Coverage: 99%. The only thing not covered is inconsequential -- -testing skipping of suite when self.needwrapbutton is false. -''' import unittest from test.support import requires from tkinter import Tk, Frame ##, BooleanVar, StringVar @@ -22,6 +21,7 @@ from idlelib.idle_test.mock_idle import Func ## se.BooleanVar = BooleanVar ## se.StringVar = StringVar + class SearchDialogBaseTest(unittest.TestCase): @classmethod diff --git a/Lib/idlelib/idle_test/test_searchengine.py b/Lib/idlelib/idle_test/test_searchengine.py index b3aa8eb8..3d26d62a 100644 --- a/Lib/idlelib/idle_test/test_searchengine.py +++ b/Lib/idlelib/idle_test/test_searchengine.py @@ -1,18 +1,19 @@ -'''Test functions and SearchEngine class in idlelib.searchengine.py.''' +"Test searchengine, coverage 99%." -# With mock replacements, the module does not use any gui widgets. -# The use of tk.Text is avoided (for now, until mock Text is improved) -# by patching instances with an index function returning what is needed. -# This works because mock Text.get does not use .index. - -import re +from idlelib import searchengine as se import unittest # from test.support import requires from tkinter import BooleanVar, StringVar, TclError # ,Tk, Text import tkinter.messagebox as tkMessageBox -from idlelib import searchengine as se from idlelib.idle_test.mock_tk import Var, Mbox from idlelib.idle_test.mock_tk import Text as mockText +import re + +# With mock replacements, the module does not use any gui widgets. +# The use of tk.Text is avoided (for now, until mock Text is improved) +# by patching instances with an index function returning what is needed. +# This works because mock Text.get does not use .index. +# The tkinter imports are used to restore searchengine. def setUpModule(): # Replace s-e module tkinter imports other than non-gui TclError. @@ -326,4 +327,4 @@ class ForwardBackwardTest(unittest.TestCase): if __name__ == '__main__': - unittest.main(verbosity=2, exit=2) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py new file mode 100644 index 00000000..ca8b674c --- /dev/null +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -0,0 +1,509 @@ +from collections import namedtuple +from tkinter import Text, Tk +import unittest +from unittest.mock import Mock, NonCallableMagicMock, patch, sentinel, ANY +from test.support import requires + +from idlelib.config import idleConf +from idlelib.squeezer import count_lines_with_wrapping, ExpandingButton, \ + Squeezer +from idlelib import macosx +from idlelib.textview import view_text +from idlelib.tooltip import Hovertip +from idlelib.pyshell import PyShell + + +SENTINEL_VALUE = sentinel.SENTINEL_VALUE + + +def get_test_tk_root(test_instance): + """Helper for tests: Create a root Tk object.""" + requires('gui') + root = Tk() + root.withdraw() + + def cleanup_root(): + root.update_idletasks() + root.destroy() + test_instance.addCleanup(cleanup_root) + + return root + + +class CountLinesTest(unittest.TestCase): + """Tests for the count_lines_with_wrapping function.""" + def check(self, expected, text, linewidth, tabwidth): + return self.assertEqual( + expected, + count_lines_with_wrapping(text, linewidth, tabwidth), + ) + + def test_count_empty(self): + """Test with an empty string.""" + self.assertEqual(count_lines_with_wrapping(""), 0) + + def test_count_begins_with_empty_line(self): + """Test with a string which begins with a newline.""" + self.assertEqual(count_lines_with_wrapping("\ntext"), 2) + + def test_count_ends_with_empty_line(self): + """Test with a string which ends with a newline.""" + self.assertEqual(count_lines_with_wrapping("text\n"), 1) + + def test_count_several_lines(self): + """Test with several lines of text.""" + self.assertEqual(count_lines_with_wrapping("1\n2\n3\n"), 3) + + def test_tab_width(self): + """Test with various tab widths and line widths.""" + self.check(expected=1, text='\t' * 1, linewidth=8, tabwidth=4) + self.check(expected=1, text='\t' * 2, linewidth=8, tabwidth=4) + self.check(expected=2, text='\t' * 3, linewidth=8, tabwidth=4) + self.check(expected=2, text='\t' * 4, linewidth=8, tabwidth=4) + self.check(expected=3, text='\t' * 5, linewidth=8, tabwidth=4) + + # test longer lines and various tab widths + self.check(expected=4, text='\t' * 10, linewidth=12, tabwidth=4) + self.check(expected=10, text='\t' * 10, linewidth=12, tabwidth=8) + self.check(expected=2, text='\t' * 4, linewidth=10, tabwidth=3) + + # test tabwidth=1 + self.check(expected=2, text='\t' * 9, linewidth=5, tabwidth=1) + self.check(expected=2, text='\t' * 10, linewidth=5, tabwidth=1) + self.check(expected=3, text='\t' * 11, linewidth=5, tabwidth=1) + + # test for off-by-one errors + self.check(expected=2, text='\t' * 6, linewidth=12, tabwidth=4) + self.check(expected=3, text='\t' * 6, linewidth=11, tabwidth=4) + self.check(expected=2, text='\t' * 6, linewidth=13, tabwidth=4) + + +class SqueezerTest(unittest.TestCase): + """Tests for the Squeezer class.""" + def make_mock_editor_window(self): + """Create a mock EditorWindow instance.""" + editwin = NonCallableMagicMock() + # isinstance(editwin, PyShell) must be true for Squeezer to enable + # auto-squeezing; in practice this will always be true + editwin.__class__ = PyShell + return editwin + + def make_squeezer_instance(self, editor_window=None): + """Create an actual Squeezer instance with a mock EditorWindow.""" + if editor_window is None: + editor_window = self.make_mock_editor_window() + return Squeezer(editor_window) + + def test_count_lines(self): + """Test Squeezer.count_lines() with various inputs. + + This checks that Squeezer.count_lines() calls the + count_lines_with_wrapping() function with the appropriate parameters. + """ + for tabwidth, linewidth in [(4, 80), (1, 79), (8, 80), (3, 120)]: + self._test_count_lines_helper(linewidth=linewidth, + tabwidth=tabwidth) + + def _prepare_mock_editwin_for_count_lines(self, editwin, + linewidth, tabwidth): + """Prepare a mock EditorWindow object for Squeezer.count_lines.""" + CHAR_WIDTH = 10 + BORDER_WIDTH = 2 + PADDING_WIDTH = 1 + + # Prepare all the required functionality on the mock EditorWindow object + # so that the calculations in Squeezer.count_lines() can run. + editwin.get_tk_tabwidth.return_value = tabwidth + editwin.text.winfo_width.return_value = \ + linewidth * CHAR_WIDTH + 2 * (BORDER_WIDTH + PADDING_WIDTH) + text_opts = { + 'border': BORDER_WIDTH, + 'padx': PADDING_WIDTH, + 'font': None, + } + editwin.text.cget = lambda opt: text_opts[opt] + + # monkey-path tkinter.font.Font with a mock object, so that + # Font.measure('0') returns CHAR_WIDTH + mock_font = Mock() + def measure(char): + if char == '0': + return CHAR_WIDTH + raise ValueError("measure should only be called on '0'!") + mock_font.return_value.measure = measure + patcher = patch('idlelib.squeezer.Font', mock_font) + patcher.start() + self.addCleanup(patcher.stop) + + def _test_count_lines_helper(self, linewidth, tabwidth): + """Helper for test_count_lines.""" + editwin = self.make_mock_editor_window() + self._prepare_mock_editwin_for_count_lines(editwin, linewidth, tabwidth) + squeezer = self.make_squeezer_instance(editwin) + + mock_count_lines = Mock(return_value=SENTINEL_VALUE) + text = 'TEXT' + with patch('idlelib.squeezer.count_lines_with_wrapping', + mock_count_lines): + self.assertIs(squeezer.count_lines(text), SENTINEL_VALUE) + mock_count_lines.assert_called_with(text, linewidth, tabwidth) + + def test_init(self): + """Test the creation of Squeezer instances.""" + editwin = self.make_mock_editor_window() + squeezer = self.make_squeezer_instance(editwin) + self.assertIs(squeezer.editwin, editwin) + self.assertEqual(squeezer.expandingbuttons, []) + + def test_write_no_tags(self): + """Test Squeezer's overriding of the EditorWindow's write() method.""" + editwin = self.make_mock_editor_window() + for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: + editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) + squeezer = self.make_squeezer_instance(editwin) + + self.assertEqual(squeezer.editwin.write(text, ()), SENTINEL_VALUE) + self.assertEqual(orig_write.call_count, 1) + orig_write.assert_called_with(text, ()) + self.assertEqual(len(squeezer.expandingbuttons), 0) + + def test_write_not_stdout(self): + """Test Squeezer's overriding of the EditorWindow's write() method.""" + for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: + editwin = self.make_mock_editor_window() + editwin.write.return_value = SENTINEL_VALUE + orig_write = editwin.write + squeezer = self.make_squeezer_instance(editwin) + + self.assertEqual(squeezer.editwin.write(text, "stderr"), + SENTINEL_VALUE) + self.assertEqual(orig_write.call_count, 1) + orig_write.assert_called_with(text, "stderr") + self.assertEqual(len(squeezer.expandingbuttons), 0) + + def test_write_stdout(self): + """Test Squeezer's overriding of the EditorWindow's write() method.""" + editwin = self.make_mock_editor_window() + self._prepare_mock_editwin_for_count_lines(editwin, + linewidth=80, tabwidth=8) + + for text in ['', 'TEXT']: + editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) + squeezer = self.make_squeezer_instance(editwin) + squeezer.auto_squeeze_min_lines = 50 + + self.assertEqual(squeezer.editwin.write(text, "stdout"), + SENTINEL_VALUE) + self.assertEqual(orig_write.call_count, 1) + orig_write.assert_called_with(text, "stdout") + self.assertEqual(len(squeezer.expandingbuttons), 0) + + for text in ['LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: + editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) + squeezer = self.make_squeezer_instance(editwin) + squeezer.auto_squeeze_min_lines = 50 + + self.assertEqual(squeezer.editwin.write(text, "stdout"), None) + self.assertEqual(orig_write.call_count, 0) + self.assertEqual(len(squeezer.expandingbuttons), 1) + + def test_auto_squeeze(self): + """Test that the auto-squeezing creates an ExpandingButton properly.""" + root = get_test_tk_root(self) + text_widget = Text(root) + text_widget.mark_set("iomark", "1.0") + + editwin = self.make_mock_editor_window() + editwin.text = text_widget + squeezer = self.make_squeezer_instance(editwin) + squeezer.auto_squeeze_min_lines = 5 + squeezer.count_lines = Mock(return_value=6) + + editwin.write('TEXT\n'*6, "stdout") + self.assertEqual(text_widget.get('1.0', 'end'), '\n') + self.assertEqual(len(squeezer.expandingbuttons), 1) + + def test_squeeze_current_text_event(self): + """Test the squeeze_current_text event.""" + root = get_test_tk_root(self) + + # squeezing text should work for both stdout and stderr + for tag_name in ["stdout", "stderr"]: + text_widget = Text(root) + text_widget.mark_set("iomark", "1.0") + + editwin = self.make_mock_editor_window() + editwin.text = editwin.per.bottom = text_widget + squeezer = self.make_squeezer_instance(editwin) + squeezer.count_lines = Mock(return_value=6) + + # prepare some text in the Text widget + text_widget.insert("1.0", "SOME\nTEXT\n", tag_name) + text_widget.mark_set("insert", "1.0") + self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n') + + self.assertEqual(len(squeezer.expandingbuttons), 0) + + # test squeezing the current text + retval = squeezer.squeeze_current_text_event(event=Mock()) + self.assertEqual(retval, "break") + self.assertEqual(text_widget.get('1.0', 'end'), '\n\n') + self.assertEqual(len(squeezer.expandingbuttons), 1) + self.assertEqual(squeezer.expandingbuttons[0].s, 'SOME\nTEXT') + + # test that expanding the squeezed text works and afterwards the + # Text widget contains the original text + squeezer.expandingbuttons[0].expand(event=Mock()) + self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n') + self.assertEqual(len(squeezer.expandingbuttons), 0) + + def test_squeeze_current_text_event_no_allowed_tags(self): + """Test that the event doesn't squeeze text without a relevant tag.""" + root = get_test_tk_root(self) + + text_widget = Text(root) + text_widget.mark_set("iomark", "1.0") + + editwin = self.make_mock_editor_window() + editwin.text = editwin.per.bottom = text_widget + squeezer = self.make_squeezer_instance(editwin) + squeezer.count_lines = Mock(return_value=6) + + # prepare some text in the Text widget + text_widget.insert("1.0", "SOME\nTEXT\n", "TAG") + text_widget.mark_set("insert", "1.0") + self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n') + + self.assertEqual(len(squeezer.expandingbuttons), 0) + + # test squeezing the current text + retval = squeezer.squeeze_current_text_event(event=Mock()) + self.assertEqual(retval, "break") + self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n') + self.assertEqual(len(squeezer.expandingbuttons), 0) + + def test_squeeze_text_before_existing_squeezed_text(self): + """Test squeezing text before existing squeezed text.""" + root = get_test_tk_root(self) + + text_widget = Text(root) + text_widget.mark_set("iomark", "1.0") + + editwin = self.make_mock_editor_window() + editwin.text = editwin.per.bottom = text_widget + squeezer = self.make_squeezer_instance(editwin) + squeezer.count_lines = Mock(return_value=6) + + # prepare some text in the Text widget and squeeze it + text_widget.insert("1.0", "SOME\nTEXT\n", "stdout") + text_widget.mark_set("insert", "1.0") + squeezer.squeeze_current_text_event(event=Mock()) + self.assertEqual(len(squeezer.expandingbuttons), 1) + + # test squeezing the current text + text_widget.insert("1.0", "MORE\nSTUFF\n", "stdout") + text_widget.mark_set("insert", "1.0") + retval = squeezer.squeeze_current_text_event(event=Mock()) + self.assertEqual(retval, "break") + self.assertEqual(text_widget.get('1.0', 'end'), '\n\n\n') + self.assertEqual(len(squeezer.expandingbuttons), 2) + self.assertTrue(text_widget.compare( + squeezer.expandingbuttons[0], + '<', + squeezer.expandingbuttons[1], + )) + + GetOptionSignature = namedtuple('GetOptionSignature', + 'configType section option default type warn_on_default raw') + @classmethod + def _make_sig(cls, configType, section, option, default=sentinel.NOT_GIVEN, + type=sentinel.NOT_GIVEN, + warn_on_default=sentinel.NOT_GIVEN, + raw=sentinel.NOT_GIVEN): + return cls.GetOptionSignature(configType, section, option, default, + type, warn_on_default, raw) + + @classmethod + def get_GetOption_signature(cls, mock_call_obj): + args, kwargs = mock_call_obj[-2:] + return cls._make_sig(*args, **kwargs) + + def test_reload(self): + """Test the reload() class-method.""" + self.assertIsInstance(Squeezer.auto_squeeze_min_lines, int) + idleConf.SetOption('main', 'PyShell', 'auto-squeeze-min-lines', '42') + Squeezer.reload() + self.assertEqual(Squeezer.auto_squeeze_min_lines, 42) + + +class ExpandingButtonTest(unittest.TestCase): + """Tests for the ExpandingButton class.""" + # In these tests the squeezer instance is a mock, but actual tkinter + # Text and Button instances are created. + def make_mock_squeezer(self): + """Helper for tests: Create a mock Squeezer object.""" + root = get_test_tk_root(self) + squeezer = Mock() + squeezer.editwin.text = Text(root) + + # Set default values for the configuration settings + squeezer.auto_squeeze_min_lines = 50 + return squeezer + + @patch('idlelib.squeezer.Hovertip', autospec=Hovertip) + def test_init(self, MockHovertip): + """Test the simplest creation of an ExpandingButton.""" + squeezer = self.make_mock_squeezer() + text_widget = squeezer.editwin.text + + expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) + self.assertEqual(expandingbutton.s, 'TEXT') + + # check that the underlying tkinter.Button is properly configured + self.assertEqual(expandingbutton.master, text_widget) + self.assertTrue('50 lines' in expandingbutton.cget('text')) + + # check that the text widget still contains no text + self.assertEqual(text_widget.get('1.0', 'end'), '\n') + + # check that the mouse events are bound + self.assertIn('', expandingbutton.bind()) + right_button_code = '' % ('2' if macosx.isAquaTk() else '3') + self.assertIn(right_button_code, expandingbutton.bind()) + + # check that ToolTip was called once, with appropriate values + self.assertEqual(MockHovertip.call_count, 1) + MockHovertip.assert_called_with(expandingbutton, ANY, hover_delay=ANY) + + # check that 'right-click' appears in the tooltip text + tooltip_text = MockHovertip.call_args[0][1] + self.assertIn('right-click', tooltip_text.lower()) + + def test_expand(self): + """Test the expand event.""" + squeezer = self.make_mock_squeezer() + expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) + + # insert the button into the text widget + # (this is normally done by the Squeezer class) + text_widget = expandingbutton.text + text_widget.window_create("1.0", window=expandingbutton) + + # set base_text to the text widget, so that changes are actually made + # to it (by ExpandingButton) and we can inspect these changes afterwards + expandingbutton.base_text = expandingbutton.text + + # trigger the expand event + retval = expandingbutton.expand(event=Mock()) + self.assertEqual(retval, None) + + # check that the text was inserted into the text widget + self.assertEqual(text_widget.get('1.0', 'end'), 'TEXT\n') + + # check that the 'TAGS' tag was set on the inserted text + text_end_index = text_widget.index('end-1c') + self.assertEqual(text_widget.get('1.0', text_end_index), 'TEXT') + self.assertEqual(text_widget.tag_nextrange('TAGS', '1.0'), + ('1.0', text_end_index)) + + # check that the button removed itself from squeezer.expandingbuttons + self.assertEqual(squeezer.expandingbuttons.remove.call_count, 1) + squeezer.expandingbuttons.remove.assert_called_with(expandingbutton) + + def test_expand_dangerous_oupput(self): + """Test that expanding very long output asks user for confirmation.""" + squeezer = self.make_mock_squeezer() + text = 'a' * 10**5 + expandingbutton = ExpandingButton(text, 'TAGS', 50, squeezer) + expandingbutton.set_is_dangerous() + self.assertTrue(expandingbutton.is_dangerous) + + # insert the button into the text widget + # (this is normally done by the Squeezer class) + text_widget = expandingbutton.text + text_widget.window_create("1.0", window=expandingbutton) + + # set base_text to the text widget, so that changes are actually made + # to it (by ExpandingButton) and we can inspect these changes afterwards + expandingbutton.base_text = expandingbutton.text + + # patch the message box module to always return False + with patch('idlelib.squeezer.tkMessageBox') as mock_msgbox: + mock_msgbox.askokcancel.return_value = False + mock_msgbox.askyesno.return_value = False + + # trigger the expand event + retval = expandingbutton.expand(event=Mock()) + + # check that the event chain was broken and no text was inserted + self.assertEqual(retval, 'break') + self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), '') + + # patch the message box module to always return True + with patch('idlelib.squeezer.tkMessageBox') as mock_msgbox: + mock_msgbox.askokcancel.return_value = True + mock_msgbox.askyesno.return_value = True + + # trigger the expand event + retval = expandingbutton.expand(event=Mock()) + + # check that the event chain wasn't broken and the text was inserted + self.assertEqual(retval, None) + self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), text) + + def test_copy(self): + """Test the copy event.""" + # testing with the actual clipboard proved problematic, so this test + # replaces the clipboard manipulation functions with mocks and checks + # that they are called appropriately + squeezer = self.make_mock_squeezer() + expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) + expandingbutton.clipboard_clear = Mock() + expandingbutton.clipboard_append = Mock() + + # trigger the copy event + retval = expandingbutton.copy(event=Mock()) + self.assertEqual(retval, None) + + # check that the expanding button called clipboard_clear() and + # clipboard_append('TEXT') once each + self.assertEqual(expandingbutton.clipboard_clear.call_count, 1) + self.assertEqual(expandingbutton.clipboard_append.call_count, 1) + expandingbutton.clipboard_append.assert_called_with('TEXT') + + def test_view(self): + """Test the view event.""" + squeezer = self.make_mock_squeezer() + expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) + expandingbutton.selection_own = Mock() + + with patch('idlelib.squeezer.view_text', autospec=view_text)\ + as mock_view_text: + # trigger the view event + expandingbutton.view(event=Mock()) + + # check that the expanding button called view_text + self.assertEqual(mock_view_text.call_count, 1) + + # check that the proper text was passed + self.assertEqual(mock_view_text.call_args[0][2], 'TEXT') + + def test_rmenu(self): + """Test the context menu.""" + squeezer = self.make_mock_squeezer() + expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) + with patch('tkinter.Menu') as mock_Menu: + mock_menu = Mock() + mock_Menu.return_value = mock_menu + mock_event = Mock() + mock_event.x = 10 + mock_event.y = 10 + expandingbutton.context_menu_event(event=mock_event) + self.assertEqual(mock_menu.add_command.call_count, + len(expandingbutton.rmenu_specs)) + for label, *data in expandingbutton.rmenu_specs: + mock_menu.add_command.assert_any_call(label=label, command=ANY) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_stackviewer.py b/Lib/idlelib/idle_test/test_stackviewer.py new file mode 100644 index 00000000..98f53f95 --- /dev/null +++ b/Lib/idlelib/idle_test/test_stackviewer.py @@ -0,0 +1,47 @@ +"Test stackviewer, coverage 63%." + +from idlelib import stackviewer +import unittest +from test.support import requires +from tkinter import Tk + +from idlelib.tree import TreeNode, ScrolledCanvas +import sys + + +class StackBrowserTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + svs = stackviewer.sys + try: + abc + except NameError: + svs.last_type, svs.last_value, svs.last_traceback = ( + sys.exc_info()) + + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + svs = stackviewer.sys + del svs.last_traceback, svs.last_type, svs.last_value + + cls.root.update_idletasks() +## for id in cls.root.tk.call('after', 'info'): +## cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def test_init(self): + sb = stackviewer.StackBrowser(self.root) + isi = self.assertIsInstance + isi(stackviewer.sc, ScrolledCanvas) + isi(stackviewer.item, stackviewer.StackTreeItem) + isi(stackviewer.node, TreeNode) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_statusbar.py b/Lib/idlelib/idle_test/test_statusbar.py new file mode 100644 index 00000000..203a57db --- /dev/null +++ b/Lib/idlelib/idle_test/test_statusbar.py @@ -0,0 +1,41 @@ +"Test statusbar, coverage 100%." + +from idlelib import statusbar +import unittest +from test.support import requires +from tkinter import Tk + + +class Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.update_idletasks() + cls.root.destroy() + del cls.root + + def test_init(self): + bar = statusbar.MultiStatusBar(self.root) + self.assertEqual(bar.labels, {}) + + def test_set_label(self): + bar = statusbar.MultiStatusBar(self.root) + bar.set_label('left', text='sometext', width=10) + self.assertIn('left', bar.labels) + left = bar.labels['left'] + self.assertEqual(left['text'], 'sometext') + self.assertEqual(left['width'], 10) + bar.set_label('left', text='revised text') + self.assertEqual(left['text'], 'revised text') + bar.set_label('right', text='correct text') + self.assertEqual(bar.labels['right']['text'], 'correct text') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py index dfd4063e..6f0c1930 100644 --- a/Lib/idlelib/idle_test/test_textview.py +++ b/Lib/idlelib/idle_test/test_textview.py @@ -1,17 +1,15 @@ -'''Test idlelib.textview. +"""Test textview, coverage 100%. Since all methods and functions create (or destroy) a ViewWindow, which is a widget containing a widget, etcetera, all tests must be gui tests. Using mock Text would not change this. Other mocks are used to retrieve information about calls. - -Coverage: 100%. -''' +""" from idlelib import textview as tv +import unittest from test.support import requires requires('gui') -import unittest import os from tkinter import Tk from tkinter.ttk import Button @@ -75,7 +73,6 @@ class TextFrameTest(unittest.TestCase): @classmethod def setUpClass(cls): - "By itself, this tests that file parsed without exception." cls.root = root = Tk() root.withdraw() cls.frame = tv.TextFrame(root, 'test text') @@ -109,7 +106,7 @@ class ViewFunctionTest(unittest.TestCase): view = tv.view_text(root, 'Title', 'test text', modal=False) self.assertIsInstance(view, tv.ViewWindow) self.assertIsInstance(view.viewframe, tv.ViewFrame) - view.ok() + view.viewframe.ok() def test_view_file(self): view = tv.view_file(root, 'Title', __file__, 'ascii', modal=False) @@ -128,11 +125,15 @@ class ViewFunctionTest(unittest.TestCase): def test_bad_encoding(self): p = os.path fn = p.abspath(p.join(p.dirname(__file__), '..', 'CREDITS.txt')) - tv.showerror.title = None view = tv.view_file(root, 'Title', fn, 'ascii', modal=False) self.assertIsNone(view) self.assertEqual(tv.showerror.title, 'Unicode Decode Error') + def test_nowrap(self): + view = tv.view_text(root, 'Title', 'test', modal=False, wrap='none') + text_widget = view.viewframe.textframe.text + self.assertEqual(text_widget.cget('wrap'), 'none') + # Call ViewWindow with _utest=True. class ButtonClickTest(unittest.TestCase): diff --git a/Lib/idlelib/idle_test/test_tooltip.py b/Lib/idlelib/idle_test/test_tooltip.py new file mode 100644 index 00000000..44ea1110 --- /dev/null +++ b/Lib/idlelib/idle_test/test_tooltip.py @@ -0,0 +1,146 @@ +from idlelib.tooltip import TooltipBase, Hovertip +from test.support import requires +requires('gui') + +from functools import wraps +import time +from tkinter import Button, Tk, Toplevel +import unittest + + +def setUpModule(): + global root + root = Tk() + +def root_update(): + global root + root.update() + +def tearDownModule(): + global root + root.update_idletasks() + root.destroy() + del root + +def add_call_counting(func): + @wraps(func) + def wrapped_func(*args, **kwargs): + wrapped_func.call_args_list.append((args, kwargs)) + return func(*args, **kwargs) + wrapped_func.call_args_list = [] + return wrapped_func + + +def _make_top_and_button(testobj): + global root + top = Toplevel(root) + testobj.addCleanup(top.destroy) + top.title("Test tooltip") + button = Button(top, text='ToolTip test button') + button.pack() + testobj.addCleanup(button.destroy) + top.lift() + return top, button + + +class ToolTipBaseTest(unittest.TestCase): + def setUp(self): + self.top, self.button = _make_top_and_button(self) + + def test_base_class_is_unusable(self): + global root + top = Toplevel(root) + self.addCleanup(top.destroy) + + button = Button(top, text='ToolTip test button') + button.pack() + self.addCleanup(button.destroy) + + with self.assertRaises(NotImplementedError): + tooltip = TooltipBase(button) + tooltip.showtip() + + +class HovertipTest(unittest.TestCase): + def setUp(self): + self.top, self.button = _make_top_and_button(self) + + def test_showtip(self): + tooltip = Hovertip(self.button, 'ToolTip text') + self.addCleanup(tooltip.hidetip) + self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + tooltip.showtip() + self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + + def test_showtip_twice(self): + tooltip = Hovertip(self.button, 'ToolTip text') + self.addCleanup(tooltip.hidetip) + self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + tooltip.showtip() + self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + orig_tipwindow = tooltip.tipwindow + tooltip.showtip() + self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + self.assertIs(tooltip.tipwindow, orig_tipwindow) + + def test_hidetip(self): + tooltip = Hovertip(self.button, 'ToolTip text') + self.addCleanup(tooltip.hidetip) + tooltip.showtip() + tooltip.hidetip() + self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + + def test_showtip_on_mouse_enter_no_delay(self): + tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None) + self.addCleanup(tooltip.hidetip) + tooltip.showtip = add_call_counting(tooltip.showtip) + root_update() + self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + self.button.event_generate('', x=0, y=0) + root_update() + self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + self.assertGreater(len(tooltip.showtip.call_args_list), 0) + + def test_showtip_on_mouse_enter_hover_delay(self): + tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=50) + self.addCleanup(tooltip.hidetip) + tooltip.showtip = add_call_counting(tooltip.showtip) + root_update() + self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + self.button.event_generate('', x=0, y=0) + root_update() + self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + time.sleep(0.1) + root_update() + self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + self.assertGreater(len(tooltip.showtip.call_args_list), 0) + + def test_hidetip_on_mouse_leave(self): + tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None) + self.addCleanup(tooltip.hidetip) + tooltip.showtip = add_call_counting(tooltip.showtip) + root_update() + self.button.event_generate('', x=0, y=0) + root_update() + self.button.event_generate('', x=0, y=0) + root_update() + self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + self.assertGreater(len(tooltip.showtip.call_args_list), 0) + + def test_dont_show_on_mouse_leave_before_delay(self): + tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=50) + self.addCleanup(tooltip.hidetip) + tooltip.showtip = add_call_counting(tooltip.showtip) + root_update() + self.button.event_generate('', x=0, y=0) + root_update() + self.button.event_generate('', x=0, y=0) + root_update() + time.sleep(0.1) + root_update() + self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) + self.assertEqual(tooltip.showtip.call_args_list, []) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_tree.py b/Lib/idlelib/idle_test/test_tree.py index bb597d87..9be9abee 100644 --- a/Lib/idlelib/idle_test/test_tree.py +++ b/Lib/idlelib/idle_test/test_tree.py @@ -1,11 +1,9 @@ -''' Test idlelib.tree. +"Test tree. coverage 56%." -Coverage: 56% -''' from idlelib import tree +import unittest from test.support import requires requires('gui') -import unittest from tkinter import Tk diff --git a/Lib/idlelib/idle_test/test_undo.py b/Lib/idlelib/idle_test/test_undo.py index e872927a..beb5b582 100644 --- a/Lib/idlelib/idle_test/test_undo.py +++ b/Lib/idlelib/idle_test/test_undo.py @@ -1,14 +1,13 @@ -"""Unittest for UndoDelegator in idlelib.undo.py. +"Test undo, coverage 77%." +# Only test UndoDelegator so far. -Coverage about 80% (retest). -""" +from idlelib.undo import UndoDelegator +import unittest from test.support import requires requires('gui') -import unittest from unittest.mock import Mock from tkinter import Text, Tk -from idlelib.undo import UndoDelegator from idlelib.percolator import Percolator @@ -131,5 +130,6 @@ class UndoDelegatorTest(unittest.TestCase): text.insert('insert', 'foo') self.assertLessEqual(len(self.delegator.undolist), max_undo) + if __name__ == '__main__': unittest.main(verbosity=2, exit=False) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index f3269f19..221068c5 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -5,20 +5,18 @@ This file could be expanded to include traceback overrides Revise if output destination changes (http://bugs.python.org/issue18318). Make sure warnings module is left unaltered (http://bugs.python.org/issue18081). ''' - +from idlelib import run +from idlelib import pyshell as shell import unittest from test.support import captured_stderr - import warnings + # Try to capture default showwarning before Idle modules are imported. showwarning = warnings.showwarning # But if we run this file within idle, we are in the middle of the run.main loop # and default showwarnings has already been replaced. running_in_idle = 'idle' in showwarning.__name__ -from idlelib import run -from idlelib import pyshell as shell - # The following was generated from pyshell.idle_formatwarning # and checked as matching expectation. idlemsg = ''' @@ -29,6 +27,7 @@ UserWarning: Test ''' shellmsg = idlemsg + ">>> " + class RunWarnTest(unittest.TestCase): @unittest.skipIf(running_in_idle, "Does not work when run within Idle.") @@ -46,6 +45,7 @@ class RunWarnTest(unittest.TestCase): # The following uses .splitlines to erase line-ending differences self.assertEqual(idlemsg.splitlines(), f.getvalue().splitlines()) + class ShellWarnTest(unittest.TestCase): @unittest.skipIf(running_in_idle, "Does not work when run within Idle.") @@ -70,4 +70,4 @@ class ShellWarnTest(unittest.TestCase): if __name__ == '__main__': - unittest.main(verbosity=2, exit=False) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_window.py b/Lib/idlelib/idle_test/test_window.py new file mode 100644 index 00000000..5a2645b9 --- /dev/null +++ b/Lib/idlelib/idle_test/test_window.py @@ -0,0 +1,45 @@ +"Test window, coverage 47%." + +from idlelib import window +import unittest +from test.support import requires +from tkinter import Tk + + +class WindowListTest(unittest.TestCase): + + def test_init(self): + wl = window.WindowList() + self.assertEqual(wl.dict, {}) + self.assertEqual(wl.callbacks, []) + + # Further tests need mock Window. + + +class ListedToplevelTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + window.registry = set() + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + window.registry = window.WindowList() + cls.root.update_idletasks() +## for id in cls.root.tk.call('after', 'info'): +## cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def test_init(self): + + win = window.ListedToplevel(self.root) + self.assertIn(win, window.registry) + self.assertEqual(win.focused_widget, win) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_zoomheight.py b/Lib/idlelib/idle_test/test_zoomheight.py new file mode 100644 index 00000000..bac86ac2 --- /dev/null +++ b/Lib/idlelib/idle_test/test_zoomheight.py @@ -0,0 +1,39 @@ +"Test zoomheight, coverage 66%." +# Some code is system dependent. + +from idlelib import zoomheight +import unittest +from test.support import requires +from tkinter import Tk, Text +from idlelib.editor import EditorWindow + + +class Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.editwin = EditorWindow(root=cls.root) + + @classmethod + def tearDownClass(cls): + cls.editwin._close() + cls.root.update_idletasks() + for id in cls.root.tk.call('after', 'info'): + cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def test_init(self): + zoom = zoomheight.ZoomHeight(self.editwin) + self.assertIs(zoom.editwin, self.editwin) + + def test_zoom_height_event(self): + zoom = zoomheight.ZoomHeight(self.editwin) + zoom.zoom_height_event() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py index f9b6907b..fcd8dcc1 100644 --- a/Lib/idlelib/iomenu.py +++ b/Lib/idlelib/iomenu.py @@ -40,7 +40,7 @@ else: # resulting codeset may be unknown to Python. We ignore all # these problems, falling back to ASCII locale_encoding = locale.nl_langinfo(locale.CODESET) - if locale_encoding is None or locale_encoding is '': + if locale_encoding is None or locale_encoding == '': # situation occurs on Mac OS X locale_encoding = 'ascii' codecs.lookup(locale_encoding) @@ -50,7 +50,7 @@ else: # bugs that can cause ValueError. try: locale_encoding = locale.getdefaultlocale()[1] - if locale_encoding is None or locale_encoding is '': + if locale_encoding is None or locale_encoding == '': # situation occurs on Mac OS X locale_encoding = 'ascii' codecs.lookup(locale_encoding) @@ -567,8 +567,8 @@ def _io_binding(parent): # htest # IOBinding(editwin) if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_iomenu', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_iomenu', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_io_binding) diff --git a/Lib/idlelib/macosx.py b/Lib/idlelib/macosx.py index d85278a0..d3ae2241 100644 --- a/Lib/idlelib/macosx.py +++ b/Lib/idlelib/macosx.py @@ -128,7 +128,7 @@ def overrideRootMenu(root, flist): # menu. from tkinter import Menu from idlelib import mainmenu - from idlelib import windows + from idlelib import window closeItem = mainmenu.menudefs[0][1][-2] @@ -148,7 +148,7 @@ def overrideRootMenu(root, flist): root.configure(menu=menubar) menudict = {} - menudict['windows'] = menu = Menu(menubar, name='windows', tearoff=0) + menudict['window'] = menu = Menu(menubar, name='window', tearoff=0) menubar.add_cascade(label='Window', menu=menu, underline=0) def postwindowsmenu(menu=menu): @@ -158,8 +158,8 @@ def overrideRootMenu(root, flist): if end > 0: menu.delete(0, end) - windows.add_windows_to_menu(menu) - windows.register_callback(postwindowsmenu) + window.add_windows_to_menu(menu) + window.register_callback(postwindowsmenu) def about_dialog(event=None): "Handle Help 'About IDLE' event." diff --git a/Lib/idlelib/mainmenu.py b/Lib/idlelib/mainmenu.py index 143570d6..9fe6b522 100644 --- a/Lib/idlelib/mainmenu.py +++ b/Lib/idlelib/mainmenu.py @@ -36,7 +36,8 @@ menudefs = [ None, ('_Close', '<>'), ('E_xit', '<>'), - ]), + ]), + ('edit', [ ('_Undo', '<>'), ('_Redo', '<>'), @@ -56,9 +57,9 @@ menudefs = [ ('E_xpand Word', '<>'), ('Show C_all Tip', '<>'), ('Show Surrounding P_arens', '<>'), + ]), - ]), -('format', [ + ('format', [ ('_Indent Region', '<>'), ('_Dedent Region', '<>'), ('Comment _Out Region', '<>'), @@ -70,30 +71,36 @@ menudefs = [ ('F_ormat Paragraph', '<>'), ('S_trip Trailing Whitespace', '<>'), ]), + ('run', [ ('Python Shell', '<>'), ('C_heck Module', '<>'), ('R_un Module', '<>'), ]), + ('shell', [ ('_View Last Restart', '<>'), ('_Restart Shell', '<>'), None, ('_Interrupt Execution', '<>'), ]), + ('debug', [ ('_Go to File/Line', '<>'), ('!_Debugger', '<>'), ('_Stack Viewer', '<>'), ('!_Auto-open Stack Viewer', '<>'), ]), + ('options', [ ('Configure _IDLE', '<>'), ('_Code Context', '<>'), ]), - ('windows', [ + + ('window', [ ('Zoom Height', '<>'), ]), + ('help', [ ('_About IDLE', '<>'), None, @@ -106,3 +113,7 @@ if find_spec('turtledemo'): menudefs[-1][1].append(('Turtle Demo', '<>')) default_keydefs = idleConf.GetCurrentKeySet() + +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_mainmenu', verbosity=2) diff --git a/Lib/idlelib/multicall.py b/Lib/idlelib/multicall.py index b74fed4c..dc020012 100644 --- a/Lib/idlelib/multicall.py +++ b/Lib/idlelib/multicall.py @@ -441,5 +441,8 @@ def _multi_call(parent): # htest # bindseq("") if __name__ == "__main__": + from unittest import main + main('idlelib.idle_test.test_mainmenu', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_multi_call) diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py index 6c2a792d..4af9f1af 100644 --- a/Lib/idlelib/outwin.py +++ b/Lib/idlelib/outwin.py @@ -184,5 +184,5 @@ class OnDemandOutputWindow: self.write = self.owin.write if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_outwin', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_outwin', verbosity=2, exit=False) diff --git a/Lib/idlelib/paragraph.py b/Lib/idlelib/paragraph.py index 1270115a..81422571 100644 --- a/Lib/idlelib/paragraph.py +++ b/Lib/idlelib/paragraph.py @@ -190,6 +190,5 @@ def get_comment_header(line): if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_paragraph', - verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_paragraph', verbosity=2, exit=False) diff --git a/Lib/idlelib/parenmatch.py b/Lib/idlelib/parenmatch.py index 983ca206..3fd7aadb 100644 --- a/Lib/idlelib/parenmatch.py +++ b/Lib/idlelib/parenmatch.py @@ -179,5 +179,5 @@ ParenMatch.reload() if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_parenmatch', verbosity=2) + from unittest import main + main('idlelib.idle_test.test_parenmatch', verbosity=2) diff --git a/Lib/idlelib/percolator.py b/Lib/idlelib/percolator.py index d18daf05..db70304f 100644 --- a/Lib/idlelib/percolator.py +++ b/Lib/idlelib/percolator.py @@ -96,9 +96,8 @@ def _percolator(parent): # htest # cb2.pack() if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_percolator', verbosity=2, - exit=False) + from unittest import main + main('idlelib.idle_test.test_percolator', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_percolator) diff --git a/Lib/idlelib/pyparse.py b/Lib/idlelib/pyparse.py index 6196c2b7..1eeb9154 100644 --- a/Lib/idlelib/pyparse.py +++ b/Lib/idlelib/pyparse.py @@ -594,6 +594,6 @@ class Parser: return self.stmt_bracketing -if __name__ == '__main__': #pragma: nocover - import unittest - unittest.main('idlelib.idle_test.test_pyparse', verbosity=2) +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_pyparse', verbosity=2) diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index ddfb56ac..5458c59d 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -852,10 +852,14 @@ class PyShell(OutputWindow): ("edit", "_Edit"), ("debug", "_Debug"), ("options", "_Options"), - ("windows", "_Window"), + ("window", "_Window"), ("help", "_Help"), ] + # Extend right-click context menu + rmenu_specs = OutputWindow.rmenu_specs + [ + ("Squeeze", "<>"), + ] # New classes from idlelib.history import History @@ -1033,7 +1037,7 @@ class PyShell(OutputWindow): return self.shell_title COPYRIGHT = \ - 'Type "copyright", "credits" or "license()" for more information.' + 'Type "help", "copyright", "credits" or "license()" for more information.' def begin(self): self.text.mark_set("iomark", "insert") diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py index 59350638..c2628cce 100644 --- a/Lib/idlelib/query.py +++ b/Lib/idlelib/query.py @@ -143,6 +143,10 @@ class Query(Toplevel): self.result = None self.destroy() + def destroy(self): + self.grab_release() + super().destroy() + class SectionName(Query): "Get a name for a config file section name." @@ -301,8 +305,8 @@ class HelpSource(Query): if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_query', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_query', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(Query, HelpSource) diff --git a/Lib/idlelib/redirector.py b/Lib/idlelib/redirector.py index ec681de3..9ab34c5a 100644 --- a/Lib/idlelib/redirector.py +++ b/Lib/idlelib/redirector.py @@ -167,9 +167,8 @@ def _widget_redirector(parent): # htest # original_insert = redir.register("insert", my_insert) if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_redirector', - verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_redirector', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_widget_redirector) diff --git a/Lib/idlelib/replace.py b/Lib/idlelib/replace.py index abd9e59f..83cf9875 100644 --- a/Lib/idlelib/replace.py +++ b/Lib/idlelib/replace.py @@ -235,9 +235,8 @@ def _replace_dialog(parent): # htest # button.pack() if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_replace', - verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_replace', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_replace_dialog) diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py index 8f57edb8..9962477c 100644 --- a/Lib/idlelib/rpc.py +++ b/Lib/idlelib/rpc.py @@ -43,16 +43,20 @@ import traceback import types def unpickle_code(ms): + "Return code object from marshal string ms." co = marshal.loads(ms) assert isinstance(co, types.CodeType) return co def pickle_code(co): + "Return unpickle function and tuple with marshalled co code object." assert isinstance(co, types.CodeType) ms = marshal.dumps(co) return unpickle_code, (ms,) def dumps(obj, protocol=None): + "Return pickled (or marshalled) string for obj." + # IDLE passes 'None' to select pickle.DEFAULT_PROTOCOL. f = io.BytesIO() p = CodePickler(f, protocol) p.dump(obj) @@ -625,3 +629,8 @@ def displayhook(value): sys.stdout.write(text) sys.stdout.write("\n") builtins._ = value + + +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_rpc', verbosity=2,) diff --git a/Lib/idlelib/rstrip.py b/Lib/idlelib/rstrip.py index 18c86f9b..f93b5e8f 100644 --- a/Lib/idlelib/rstrip.py +++ b/Lib/idlelib/rstrip.py @@ -1,6 +1,6 @@ 'Provides "Strip trailing whitespace" under the "Format" menu.' -class RstripExtension: +class Rstrip: def __init__(self, editwin): self.editwin = editwin @@ -25,5 +25,5 @@ class RstripExtension: undo.undo_block_stop() if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_rstrip', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_rstrip', verbosity=2,) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 176fe3db..6fa373f2 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -11,7 +11,7 @@ import warnings import tkinter # Tcl, deletions, messagebox if startup fails from idlelib import autocomplete # AutoComplete, fetch_encodings -from idlelib import calltips # CallTips +from idlelib import calltip # Calltip from idlelib import debugger_r # start_debugger from idlelib import debugobj_r # remote_object_tree_item from idlelib import iomenu # encoding @@ -462,7 +462,7 @@ class Executive(object): def __init__(self, rpchandler): self.rpchandler = rpchandler self.locals = __main__.__dict__ - self.calltip = calltips.CallTips() + self.calltip = calltip.Calltip() self.autocomplete = autocomplete.AutoComplete() def runcode(self, code): diff --git a/Lib/idlelib/runscript.py b/Lib/idlelib/runscript.py index 45bf5634..83433b1c 100644 --- a/Lib/idlelib/runscript.py +++ b/Lib/idlelib/runscript.py @@ -193,3 +193,8 @@ class ScriptBinding: # XXX This should really be a function of EditorWindow... tkMessageBox.showerror(title, message, parent=self.editwin.text) self.editwin.text.focus_set() + + +if __name__ == "__main__": + from unittest import main + main('idlelib.idle_test.test_runscript', verbosity=2,) diff --git a/Lib/idlelib/scrolledlist.py b/Lib/idlelib/scrolledlist.py index cdf65840..10229b63 100644 --- a/Lib/idlelib/scrolledlist.py +++ b/Lib/idlelib/scrolledlist.py @@ -142,6 +142,8 @@ def _scrolled_list(parent): # htest # scrolled_list.append("Item %02d" % i) if __name__ == '__main__': - # At the moment, test_scrolledlist merely creates instance, like htest. + from unittest import main + main('idlelib.idle_test.test_scrolledlist', verbosity=2,) + from idlelib.idle_test.htest import run run(_scrolled_list) diff --git a/Lib/idlelib/search.py b/Lib/idlelib/search.py index 4b906593..6223661c 100644 --- a/Lib/idlelib/search.py +++ b/Lib/idlelib/search.py @@ -94,9 +94,8 @@ def _search_dialog(parent): # htest # button.pack() if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_search', - verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_search', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_search_dialog) diff --git a/Lib/idlelib/searchbase.py b/Lib/idlelib/searchbase.py index 5f81785b..9b03ff64 100644 --- a/Lib/idlelib/searchbase.py +++ b/Lib/idlelib/searchbase.py @@ -192,9 +192,10 @@ class _searchbase(SearchDialogBase): # htest # def default_command(self, dummy): pass + if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_searchbase', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_searchbase', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_searchbase) diff --git a/Lib/idlelib/searchengine.py b/Lib/idlelib/searchengine.py index 253f1b08..911e7d46 100644 --- a/Lib/idlelib/searchengine.py +++ b/Lib/idlelib/searchengine.py @@ -231,6 +231,7 @@ def get_line_col(index): line, col = map(int, index.split(".")) # Fails on invalid index return line, col + if __name__ == "__main__": - import unittest - unittest.main('idlelib.idle_test.test_searchengine', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_searchengine', verbosity=2) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py new file mode 100644 index 00000000..f5aac813 --- /dev/null +++ b/Lib/idlelib/squeezer.py @@ -0,0 +1,355 @@ +"""An IDLE extension to avoid having very long texts printed in the shell. + +A common problem in IDLE's interactive shell is printing of large amounts of +text into the shell. This makes looking at the previous history difficult. +Worse, this can cause IDLE to become very slow, even to the point of being +completely unusable. + +This extension will automatically replace long texts with a small button. +Double-cliking this button will remove it and insert the original text instead. +Middle-clicking will copy the text to the clipboard. Right-clicking will open +the text in a separate viewing window. + +Additionally, any output can be manually "squeezed" by the user. This includes +output written to the standard error stream ("stderr"), such as exception +messages and their tracebacks. +""" +import re + +import tkinter as tk +from tkinter.font import Font +import tkinter.messagebox as tkMessageBox + +from idlelib.config import idleConf +from idlelib.textview import view_text +from idlelib.tooltip import Hovertip +from idlelib import macosx + + +def count_lines_with_wrapping(s, linewidth=80, tabwidth=8): + """Count the number of lines in a given string. + + Lines are counted as if the string was wrapped so that lines are never over + linewidth characters long. + + Tabs are considered tabwidth characters long. + """ + pos = 0 + linecount = 1 + current_column = 0 + + for m in re.finditer(r"[\t\n]", s): + # process the normal chars up to tab or newline + numchars = m.start() - pos + pos += numchars + current_column += numchars + + # deal with tab or newline + if s[pos] == '\n': + linecount += 1 + current_column = 0 + else: + assert s[pos] == '\t' + current_column += tabwidth - (current_column % tabwidth) + + # if a tab passes the end of the line, consider the entire tab as + # being on the next line + if current_column > linewidth: + linecount += 1 + current_column = tabwidth + + pos += 1 # after the tab or newline + + # avoid divmod(-1, linewidth) + if current_column > 0: + # If the length was exactly linewidth, divmod would give (1,0), + # even though a new line hadn't yet been started. The same is true + # if length is any exact multiple of linewidth. Therefore, subtract + # 1 before doing divmod, and later add 1 to the column to + # compensate. + lines, column = divmod(current_column - 1, linewidth) + linecount += lines + current_column = column + 1 + + # process remaining chars (no more tabs or newlines) + current_column += len(s) - pos + # avoid divmod(-1, linewidth) + if current_column > 0: + linecount += (current_column - 1) // linewidth + else: + # the text ended with a newline; don't count an extra line after it + linecount -= 1 + + return linecount + + +class ExpandingButton(tk.Button): + """Class for the "squeezed" text buttons used by Squeezer + + These buttons are displayed inside a Tk Text widget in place of text. A + user can then use the button to replace it with the original text, copy + the original text to the clipboard or view the original text in a separate + window. + + Each button is tied to a Squeezer instance, and it knows to update the + Squeezer instance when it is expanded (and therefore removed). + """ + def __init__(self, s, tags, numoflines, squeezer): + self.s = s + self.tags = tags + self.numoflines = numoflines + self.squeezer = squeezer + self.editwin = editwin = squeezer.editwin + self.text = text = editwin.text + + # the base Text widget of the PyShell object, used to change text + # before the iomark + self.base_text = editwin.per.bottom + + button_text = "Squeezed text (%d lines)." % self.numoflines + tk.Button.__init__(self, text, text=button_text, + background="#FFFFC0", activebackground="#FFFFE0") + + button_tooltip_text = ( + "Double-click to expand, right-click for more options." + ) + Hovertip(self, button_tooltip_text, hover_delay=80) + + self.bind("", self.expand) + if macosx.isAquaTk(): + # AquaTk defines <2> as the right button, not <3>. + self.bind("", self.context_menu_event) + else: + self.bind("", self.context_menu_event) + self.selection_handle( + lambda offset, length: s[int(offset):int(offset) + int(length)]) + + self.is_dangerous = None + self.after_idle(self.set_is_dangerous) + + def set_is_dangerous(self): + dangerous_line_len = 50 * self.text.winfo_width() + self.is_dangerous = ( + self.numoflines > 1000 or + len(self.s) > 50000 or + any( + len(line_match.group(0)) >= dangerous_line_len + for line_match in re.finditer(r'[^\n]+', self.s) + ) + ) + + def expand(self, event=None): + """expand event handler + + This inserts the original text in place of the button in the Text + widget, removes the button and updates the Squeezer instance. + + If the original text is dangerously long, i.e. expanding it could + cause a performance degradation, ask the user for confirmation. + """ + if self.is_dangerous is None: + self.set_is_dangerous() + if self.is_dangerous: + confirm = tkMessageBox.askokcancel( + title="Expand huge output?", + message="\n\n".join([ + "The squeezed output is very long: %d lines, %d chars.", + "Expanding it could make IDLE slow or unresponsive.", + "It is recommended to view or copy the output instead.", + "Really expand?" + ]) % (self.numoflines, len(self.s)), + default=tkMessageBox.CANCEL, + parent=self.text) + if not confirm: + return "break" + + self.base_text.insert(self.text.index(self), self.s, self.tags) + self.base_text.delete(self) + self.squeezer.expandingbuttons.remove(self) + + def copy(self, event=None): + """copy event handler + + Copy the original text to the clipboard. + """ + self.clipboard_clear() + self.clipboard_append(self.s) + + def view(self, event=None): + """view event handler + + View the original text in a separate text viewer window. + """ + view_text(self.text, "Squeezed Output Viewer", self.s, + modal=False, wrap='none') + + rmenu_specs = ( + # item structure: (label, method_name) + ('copy', 'copy'), + ('view', 'view'), + ) + + def context_menu_event(self, event): + self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) + rmenu = tk.Menu(self.text, tearoff=0) + for label, method_name in self.rmenu_specs: + rmenu.add_command(label=label, command=getattr(self, method_name)) + rmenu.tk_popup(event.x_root, event.y_root) + return "break" + + +class Squeezer: + """Replace long outputs in the shell with a simple button. + + This avoids IDLE's shell slowing down considerably, and even becoming + completely unresponsive, when very long outputs are written. + """ + @classmethod + def reload(cls): + """Load class variables from config.""" + cls.auto_squeeze_min_lines = idleConf.GetOption( + "main", "PyShell", "auto-squeeze-min-lines", + type="int", default=50, + ) + + def __init__(self, editwin): + """Initialize settings for Squeezer. + + editwin is the shell's Editor window. + self.text is the editor window text widget. + self.base_test is the actual editor window Tk text widget, rather than + EditorWindow's wrapper. + self.expandingbuttons is the list of all buttons representing + "squeezed" output. + """ + self.editwin = editwin + self.text = text = editwin.text + + # Get the base Text widget of the PyShell object, used to change text + # before the iomark. PyShell deliberately disables changing text before + # the iomark via its 'text' attribute, which is actually a wrapper for + # the actual Text widget. Squeezer, however, needs to make such changes. + self.base_text = editwin.per.bottom + + self.expandingbuttons = [] + from idlelib.pyshell import PyShell # done here to avoid import cycle + if isinstance(editwin, PyShell): + # If we get a PyShell instance, replace its write method with a + # wrapper, which inserts an ExpandingButton instead of a long text. + def mywrite(s, tags=(), write=editwin.write): + # only auto-squeeze text which has just the "stdout" tag + if tags != "stdout": + return write(s, tags) + + # only auto-squeeze text with at least the minimum + # configured number of lines + numoflines = self.count_lines(s) + if numoflines < self.auto_squeeze_min_lines: + return write(s, tags) + + # create an ExpandingButton instance + expandingbutton = ExpandingButton(s, tags, numoflines, + self) + + # insert the ExpandingButton into the Text widget + text.mark_gravity("iomark", tk.RIGHT) + text.window_create("iomark", window=expandingbutton, + padx=3, pady=5) + text.see("iomark") + text.update() + text.mark_gravity("iomark", tk.LEFT) + + # add the ExpandingButton to the Squeezer's list + self.expandingbuttons.append(expandingbutton) + + editwin.write = mywrite + + def count_lines(self, s): + """Count the number of lines in a given text. + + Before calculation, the tab width and line length of the text are + fetched, so that up-to-date values are used. + + Lines are counted as if the string was wrapped so that lines are never + over linewidth characters long. + + Tabs are considered tabwidth characters long. + """ + # Tab width is configurable + tabwidth = self.editwin.get_tk_tabwidth() + + # Get the Text widget's size + linewidth = self.editwin.text.winfo_width() + # Deduct the border and padding + linewidth -= 2*sum([int(self.editwin.text.cget(opt)) + for opt in ('border', 'padx')]) + + # Get the Text widget's font + font = Font(self.editwin.text, name=self.editwin.text.cget('font')) + # Divide the size of the Text widget by the font's width. + # According to Tk8.5 docs, the Text widget's width is set + # according to the width of its font's '0' (zero) character, + # so we will use this as an approximation. + # see: http://www.tcl.tk/man/tcl8.5/TkCmd/text.htm#M-width + linewidth //= font.measure('0') + + return count_lines_with_wrapping(s, linewidth, tabwidth) + + def squeeze_current_text_event(self, event): + """squeeze-current-text event handler + + Squeeze the block of text inside which contains the "insert" cursor. + + If the insert cursor is not in a squeezable block of text, give the + user a small warning and do nothing. + """ + # set tag_name to the first valid tag found on the "insert" cursor + tag_names = self.text.tag_names(tk.INSERT) + for tag_name in ("stdout", "stderr"): + if tag_name in tag_names: + break + else: + # the insert cursor doesn't have a "stdout" or "stderr" tag + self.text.bell() + return "break" + + # find the range to squeeze + start, end = self.text.tag_prevrange(tag_name, tk.INSERT + "+1c") + s = self.text.get(start, end) + + # if the last char is a newline, remove it from the range + if len(s) > 0 and s[-1] == '\n': + end = self.text.index("%s-1c" % end) + s = s[:-1] + + # delete the text + self.base_text.delete(start, end) + + # prepare an ExpandingButton + numoflines = self.count_lines(s) + expandingbutton = ExpandingButton(s, tag_name, numoflines, self) + + # insert the ExpandingButton to the Text + self.text.window_create(start, window=expandingbutton, + padx=3, pady=5) + + # insert the ExpandingButton to the list of ExpandingButtons, while + # keeping the list ordered according to the position of the buttons in + # the Text widget + i = len(self.expandingbuttons) + while i > 0 and self.text.compare(self.expandingbuttons[i-1], + ">", expandingbutton): + i -= 1 + self.expandingbuttons.insert(i, expandingbutton) + + return "break" + + +Squeezer.reload() + + +if __name__ == "__main__": + from unittest import main + main('idlelib.idle_test.test_squeezer', verbosity=2, exit=False) + + # Add htest. diff --git a/Lib/idlelib/stackviewer.py b/Lib/idlelib/stackviewer.py index 400fa632..94ffb4ef 100644 --- a/Lib/idlelib/stackviewer.py +++ b/Lib/idlelib/stackviewer.py @@ -8,6 +8,7 @@ from idlelib.debugobj import ObjectTreeItem, make_objecttreeitem from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas def StackBrowser(root, flist=None, tb=None, top=None): + global sc, item, node # For testing. if top is None: top = tk.Toplevel(root) sc = ScrolledCanvas(top, bg="white", highlightthickness=0) @@ -134,7 +135,6 @@ def _stack_viewer(parent): # htest # intentional_name_error except NameError: exc_type, exc_value, exc_tb = sys.exc_info() - # inject stack trace to sys sys.last_type = exc_type sys.last_value = exc_value @@ -148,5 +148,8 @@ def _stack_viewer(parent): # htest # del sys.last_traceback if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_stackviewer', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_stack_viewer) diff --git a/Lib/idlelib/statusbar.py b/Lib/idlelib/statusbar.py index 8618528d..c071f898 100644 --- a/Lib/idlelib/statusbar.py +++ b/Lib/idlelib/statusbar.py @@ -42,5 +42,8 @@ def _multistatus_bar(parent): # htest # frame.pack() if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_statusbar', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_multistatus_bar) diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index 66201344..4867a80d 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -1,15 +1,37 @@ """Simple text browser for IDLE """ -from tkinter import Toplevel, Text +from tkinter import Toplevel, Text, TclError,\ + HORIZONTAL, VERTICAL, N, S, E, W from tkinter.ttk import Frame, Scrollbar, Button from tkinter.messagebox import showerror +from idlelib.colorizer import color_config + + +class AutoHiddenScrollbar(Scrollbar): + """A scrollbar that is automatically hidden when not needed. + + Only the grid geometry manager is supported. + """ + def set(self, lo, hi): + if float(lo) > 0.0 or float(hi) < 1.0: + self.grid() + else: + self.grid_remove() + super().set(lo, hi) + + def pack(self, **kwargs): + raise TclError(f'{self.__class__.__name__} does not support "pack"') + + def place(self, **kwargs): + raise TclError(f'{self.__class__.__name__} does not support "place"') + class TextFrame(Frame): "Display text with scrollbar." - def __init__(self, parent, rawtext): + def __init__(self, parent, rawtext, wrap='word'): """Create a frame for Textview. parent - parent widget for this frame @@ -18,31 +40,40 @@ class TextFrame(Frame): super().__init__(parent) self['relief'] = 'sunken' self['height'] = 700 - # TODO: get fg/bg from theme. - self.bg = '#ffffff' - self.fg = '#000000' - - self.text = text = Text(self, wrap='word', highlightthickness=0, - fg=self.fg, bg=self.bg) - self.scroll = scroll = Scrollbar(self, orient='vertical', - takefocus=False, command=text.yview) - text['yscrollcommand'] = scroll.set + + self.text = text = Text(self, wrap=wrap, highlightthickness=0) + color_config(text) + text.grid(row=0, column=0, sticky=N+S+E+W) + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) text.insert(0.0, rawtext) text['state'] = 'disabled' text.focus_set() - scroll.pack(side='right', fill='y') - text.pack(side='left', expand=True, fill='both') + # vertical scrollbar + self.yscroll = yscroll = AutoHiddenScrollbar(self, orient=VERTICAL, + takefocus=False, + command=text.yview) + text['yscrollcommand'] = yscroll.set + yscroll.grid(row=0, column=1, sticky=N+S) + + if wrap == 'none': + # horizontal scrollbar + self.xscroll = xscroll = AutoHiddenScrollbar(self, orient=HORIZONTAL, + takefocus=False, + command=text.xview) + text['xscrollcommand'] = xscroll.set + xscroll.grid(row=1, column=0, sticky=E+W) class ViewFrame(Frame): "Display TextFrame and Close button." - def __init__(self, parent, text): + def __init__(self, parent, text, wrap='word'): super().__init__(parent) self.parent = parent self.bind('', self.ok) self.bind('', self.ok) - self.textframe = TextFrame(self, text) + self.textframe = TextFrame(self, text, wrap=wrap) self.button_ok = button_ok = Button( self, text='Close', command=self.ok, takefocus=False) self.textframe.pack(side='top', expand=True, fill='both') @@ -56,7 +87,7 @@ class ViewFrame(Frame): class ViewWindow(Toplevel): "A simple text viewer dialog for IDLE." - def __init__(self, parent, title, text, modal=True, + def __init__(self, parent, title, text, modal=True, wrap='word', *, _htest=False, _utest=False): """Show the given text in a scrollable window with a 'close' button. @@ -66,6 +97,7 @@ class ViewWindow(Toplevel): parent - parent of this dialog title - string which is title of popup dialog text - text to display in dialog + wrap - type of text wrapping to use ('word', 'char' or 'none') _htest - bool; change box location when running htest. _utest - bool; don't wait_window when running unittest. """ @@ -77,13 +109,14 @@ class ViewWindow(Toplevel): self.geometry(f'=750x500+{x}+{y}') self.title(title) - self.viewframe = ViewFrame(self, text) + self.viewframe = ViewFrame(self, text, wrap=wrap) self.protocol("WM_DELETE_WINDOW", self.ok) self.button_ok = button_ok = Button(self, text='Close', command=self.ok, takefocus=False) self.viewframe.pack(side='top', expand=True, fill='both') - if modal: + self.is_modal = modal + if self.is_modal: self.transient(parent) self.grab_set() if not _utest: @@ -91,23 +124,27 @@ class ViewWindow(Toplevel): def ok(self, event=None): """Dismiss text viewer dialog.""" + if self.is_modal: + self.grab_release() self.destroy() -def view_text(parent, title, text, modal=True, _utest=False): +def view_text(parent, title, text, modal=True, wrap='word', _utest=False): """Create text viewer for given text. parent - parent of this dialog title - string which is the title of popup dialog text - text to display in this dialog + wrap - type of text wrapping to use ('word', 'char' or 'none') modal - controls if users can interact with other windows while this dialog is displayed _utest - bool; controls wait_window on unittest """ - return ViewWindow(parent, title, text, modal, _utest=_utest) + return ViewWindow(parent, title, text, modal, wrap=wrap, _utest=_utest) -def view_file(parent, title, filename, encoding, modal=True, _utest=False): +def view_file(parent, title, filename, encoding, modal=True, wrap='word', + _utest=False): """Create text viewer for text in filename. Return error message if file cannot be read. Otherwise calls view_text @@ -125,12 +162,14 @@ def view_file(parent, title, filename, encoding, modal=True, _utest=False): message=str(err), parent=parent) else: - return view_text(parent, title, contents, modal, _utest=_utest) + return view_text(parent, title, contents, modal, wrap=wrap, + _utest=_utest) return None if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_textview', verbosity=2, exit=False) + from unittest import main + main('idlelib.idle_test.test_textview', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(ViewWindow) diff --git a/Lib/idlelib/tooltip.py b/Lib/idlelib/tooltip.py index 843fb4a7..f54ea36f 100644 --- a/Lib/idlelib/tooltip.py +++ b/Lib/idlelib/tooltip.py @@ -1,80 +1,167 @@ -# general purpose 'tooltip' routines - currently unused in idlelib -# (although the 'calltips' extension is partly based on this code) -# may be useful for some purposes in (or almost in ;) the current project scope -# Ideas gleaned from PySol +"""Tools for displaying tool-tips. +This includes: + * an abstract base-class for different kinds of tooltips + * a simple text-only Tooltip class +""" from tkinter import * -class ToolTipBase: - def __init__(self, button): - self.button = button - self.tipwindow = None - self.id = None - self.x = self.y = 0 - self._id1 = self.button.bind("", self.enter) - self._id2 = self.button.bind("", self.leave) - self._id3 = self.button.bind("", self.leave) +class TooltipBase(object): + """abstract base class for tooltips""" - def enter(self, event=None): - self.schedule() + def __init__(self, anchor_widget): + """Create a tooltip. - def leave(self, event=None): - self.unschedule() - self.hidetip() + anchor_widget: the widget next to which the tooltip will be shown - def schedule(self): - self.unschedule() - self.id = self.button.after(1500, self.showtip) + Note that a widget will only be shown when showtip() is called. + """ + self.anchor_widget = anchor_widget + self.tipwindow = None - def unschedule(self): - id = self.id - self.id = None - if id: - self.button.after_cancel(id) + def __del__(self): + self.hidetip() def showtip(self): + """display the tooltip""" if self.tipwindow: return - # The tip window must be completely outside the button; + self.tipwindow = tw = Toplevel(self.anchor_widget) + # show no border on the top level window + tw.wm_overrideredirect(1) + try: + # This command is only needed and available on Tk >= 8.4.0 for OSX. + # Without it, call tips intrude on the typing process by grabbing + # the focus. + tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, + "help", "noActivates") + except TclError: + pass + + self.position_window() + self.showcontents() + self.tipwindow.update_idletasks() # Needed on MacOS -- see #34275. + self.tipwindow.lift() # work around bug in Tk 8.5.18+ (issue #24570) + + def position_window(self): + """(re)-set the tooltip's screen position""" + x, y = self.get_position() + root_x = self.anchor_widget.winfo_rootx() + x + root_y = self.anchor_widget.winfo_rooty() + y + self.tipwindow.wm_geometry("+%d+%d" % (root_x, root_y)) + + def get_position(self): + """choose a screen position for the tooltip""" + # The tip window must be completely outside the anchor widget; # otherwise when the mouse enters the tip window we get # a leave event and it disappears, and then we get an enter # event and it reappears, and so on forever :-( - x = self.button.winfo_rootx() + 20 - y = self.button.winfo_rooty() + self.button.winfo_height() + 1 - self.tipwindow = tw = Toplevel(self.button) - tw.wm_overrideredirect(1) - tw.wm_geometry("+%d+%d" % (x, y)) - self.showcontents() + # + # Note: This is a simplistic implementation; sub-classes will likely + # want to override this. + return 20, self.anchor_widget.winfo_height() + 1 - def showcontents(self, text="Your text here"): - # Override this in derived class - label = Label(self.tipwindow, text=text, justify=LEFT, - background="#ffffe0", relief=SOLID, borderwidth=1) - label.pack() + def showcontents(self): + """content display hook for sub-classes""" + # See ToolTip for an example + raise NotImplementedError def hidetip(self): + """hide the tooltip""" + # Note: This is called by __del__, so careful when overriding/extending tw = self.tipwindow self.tipwindow = None if tw: - tw.destroy() + try: + tw.destroy() + except TclError: + pass + + +class OnHoverTooltipBase(TooltipBase): + """abstract base class for tooltips, with delayed on-hover display""" + + def __init__(self, anchor_widget, hover_delay=1000): + """Create a tooltip with a mouse hover delay. + + anchor_widget: the widget next to which the tooltip will be shown + hover_delay: time to delay before showing the tooltip, in milliseconds -class ToolTip(ToolTipBase): - def __init__(self, button, text): - ToolTipBase.__init__(self, button) + Note that a widget will only be shown when showtip() is called, + e.g. after hovering over the anchor widget with the mouse for enough + time. + """ + super(OnHoverTooltipBase, self).__init__(anchor_widget) + self.hover_delay = hover_delay + + self._after_id = None + self._id1 = self.anchor_widget.bind("", self._show_event) + self._id2 = self.anchor_widget.bind("", self._hide_event) + self._id3 = self.anchor_widget.bind("