From python-checkins at python.org Fri Nov 1 00:55:45 2013 From: python-checkins at python.org (victor.stinner) Date: Fri, 1 Nov 2013 00:55:45 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Close_=2319442=3A_warn=5Fe?= =?utf-8?q?xplicit=28=29_does_nothing_when_called_late_during_Python?= Message-ID: <3d9k1s6yM1z7Lk8@mail.python.org> http://hg.python.org/cpython/rev/13a05ed33cf7 changeset: 86816:13a05ed33cf7 user: Victor Stinner date: Fri Nov 01 00:55:30 2013 +0100 summary: Close #19442: warn_explicit() does nothing when called late during Python shutdown After more tests, I now think that it is the safest option. files: Python/_warnings.c | 18 +++++++++--------- 1 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Python/_warnings.c b/Python/_warnings.c --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -333,6 +333,13 @@ PyObject *action; int rc; + /* module can be None if a warning is emitted late during Python shutdown. + In this case, the Python warnings module was probably unloaded, filters + are no more available to choose as action. It is safer to ignore the + warning and do nothing. */ + if (module == Py_None) + Py_RETURN_NONE; + if (registry && !PyDict_Check(registry) && (registry != Py_None)) { PyErr_SetString(PyExc_TypeError, "'registry' must be a dict"); return NULL; @@ -635,15 +642,8 @@ if (!setup_context(stack_level, &filename, &lineno, &module, ®istry)) return NULL; - if (module != Py_None) { - res = warn_explicit(category, message, filename, lineno, module, registry, - NULL); - } - else { - /* FIXME: emitting warnings at exit does crash Python */ - res = Py_None; - Py_INCREF(res); - } + res = warn_explicit(category, message, filename, lineno, module, registry, + NULL); Py_DECREF(filename); Py_DECREF(registry); Py_DECREF(module); -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 01:24:12 2013 From: python-checkins at python.org (benjamin.peterson) Date: Fri, 1 Nov 2013 01:24:12 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=282=2E7=29=3A_fix_xmlcharref?= =?utf-8?q?replace_tests_on_wide_build_when_tests_are_loaded_from_=2Epy=5B?= =?utf-8?b?Y29d?= Message-ID: <3d9kfh2LRbz7LjT@mail.python.org> http://hg.python.org/cpython/rev/2d02b7a97e0b changeset: 86817:2d02b7a97e0b branch: 2.7 parent: 86775:e4fe8fcaef0d user: Benjamin Peterson date: Thu Oct 31 20:22:41 2013 -0400 summary: fix xmlcharrefreplace tests on wide build when tests are loaded from .py[co] files. files: Lib/test/test_codeccallbacks.py | 4 ++-- Lib/test/test_unicode.py | 4 ++-- Misc/NEWS | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_codeccallbacks.py b/Lib/test/test_codeccallbacks.py --- a/Lib/test/test_codeccallbacks.py +++ b/Lib/test/test_codeccallbacks.py @@ -84,9 +84,9 @@ tests = [(u'\U0001f49d', '💝'), (u'\ud83d', '�'), (u'\udc9d', '�'), - (u'\ud83d\udc9d', '💝' if len(u'\U0001f49d') > 1 else - '��'), ] + if u'\ud83d\udc9d' != u'\U0001f49d': + tests += [(u'\ud83d\udc9d', '��')] for encoding in ['ascii', 'latin1', 'iso-8859-15']: for s, exp in tests: self.assertEqual(s.encode(encoding, 'xmlcharrefreplace'), diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -1663,9 +1663,9 @@ tests = [(u'\U0001f49d', '💝'), (u'\ud83d', '�'), (u'\udc9d', '�'), - (u'\ud83d\udc9d', '💝' if len(u'\U0001f49d') > 1 else - '��'), ] + if u'\ud83d\udc9d' != u'\U0001f49d': + tests += [(u'\ud83d\udc9d', '��')] for s, exp in tests: self.assertEqual( unicode_encodedecimal(u"123" + s, "xmlcharrefreplace"), diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -16,6 +16,12 @@ - Issue #19426: Fixed the opening of Python source file with specified encoding. +Tests +----- + +- Issue #19457: Fixed xmlcharrefreplace tests on wide build when tests are + loaded from .py[co] files. + What's New in Python 2.7.6 release candidate 1? =============================================== -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 01:24:13 2013 From: python-checkins at python.org (benjamin.peterson) Date: Fri, 1 Nov 2013 01:24:13 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAobWVyZ2UgMi43IC0+IDIuNyk6?= =?utf-8?q?_merge_2=2E7=2E6_release_branch?= Message-ID: <3d9kfj4GWdz7LjZ@mail.python.org> http://hg.python.org/cpython/rev/01087a302721 changeset: 86818:01087a302721 branch: 2.7 parent: 86800:8d5df9602a72 parent: 86817:2d02b7a97e0b user: Benjamin Peterson date: Thu Oct 31 20:23:57 2013 -0400 summary: merge 2.7.6 release branch files: Misc/NEWS | 35 +++++++++++++++++++---------------- 1 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -15,26 +15,29 @@ Tests ----- + +Whats' New in Python 2.7.6? +=========================== + +*Release date: 2013-11-02* + +Library +------- + +- Issue #19435: Fix directory traversal attack on CGIHttpRequestHandler. + +IDLE +---- + +- Issue #19426: Fixed the opening of Python source file with specified encoding. + +Tests +----- + - Issue #19457: Fixed xmlcharrefreplace tests on wide build when tests are loaded from .py[co] files. -Whats' New in Python 2.7.6? -=========================== - -*Release date: 2013-11-02* - -Library -------- - -- Issue #19435: Fix directory traversal attack on CGIHttpRequestHandler. - -IDLE ----- - -- Issue #19426: Fixed the opening of Python source file with specified encoding. - - What's New in Python 2.7.6 release candidate 1? =============================================== -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 05:34:47 2013 From: python-checkins at python.org (eric.snow) Date: Fri, 1 Nov 2013 05:34:47 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2319413=3A_Restore_?= =?utf-8?q?pre-3=2E3_reload=28=29_semantics_of_re-finding_modules=2E?= Message-ID: <3d9rCq6wq8z7Ljk@mail.python.org> http://hg.python.org/cpython/rev/88c3a1a3c2ff changeset: 86819:88c3a1a3c2ff parent: 86816:13a05ed33cf7 user: Eric Snow date: Thu Oct 31 22:22:15 2013 -0600 summary: Issue #19413: Restore pre-3.3 reload() semantics of re-finding modules. files: Lib/importlib/__init__.py | 8 +- Lib/importlib/_bootstrap.py | 8 +- Lib/test/test_importlib/test_api.py | 120 + Misc/NEWS | 2 + Python/importlib.h | 1011 +++++++------- 5 files changed, 642 insertions(+), 507 deletions(-) diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py --- a/Lib/importlib/__init__.py +++ b/Lib/importlib/__init__.py @@ -107,7 +107,7 @@ if not module or not isinstance(module, types.ModuleType): raise TypeError("reload() argument must be module") name = module.__name__ - if name not in sys.modules: + if sys.modules.get(name) is not module: msg = "module {} not in sys.modules" raise ImportError(msg.format(name), name=name) if name in _RELOADING: @@ -118,7 +118,11 @@ if parent_name and parent_name not in sys.modules: msg = "parent {!r} not in sys.modules" raise ImportError(msg.format(parent_name), name=parent_name) - module.__loader__.load_module(name) + loader = _bootstrap._find_module(name, None) + if loader is None: + raise ImportError(_bootstrap._ERR_MSG.format(name), name=name) + module.__loader__ = loader + loader.load_module(name) # The module may have replaced itself in sys.modules! return sys.modules[module.__name__] finally: diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -1510,15 +1510,19 @@ """Find a module's loader.""" if not sys.meta_path: _warnings.warn('sys.meta_path is empty', ImportWarning) + is_reload = name in sys.modules for finder in sys.meta_path: with _ImportLockContext(): loader = finder.find_module(name, path) if loader is not None: # The parent import may have already imported this module. - if name not in sys.modules: + if is_reload or name not in sys.modules: return loader else: - return sys.modules[name].__loader__ + try: + return sys.modules[name].__loader__ + except AttributeError: + return loader else: return None diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py --- a/Lib/test/test_importlib/test_api.py +++ b/Lib/test/test_importlib/test_api.py @@ -1,8 +1,10 @@ from . import util frozen_init, source_init = util.import_importlib('importlib') +frozen_util, source_util = util.import_importlib('importlib.util') frozen_machinery, source_machinery = util.import_importlib('importlib.machinery') +import os.path import sys from test import support import types @@ -190,11 +192,129 @@ self.assertEqual(actual.spam, 3) self.assertEqual(reloaded.spam, 3) + def test_reload_missing_loader(self): + with support.CleanImport('types'): + import types + loader = types.__loader__ + del types.__loader__ + reloaded = self.init.reload(types) + + self.assertIs(reloaded, types) + self.assertIs(sys.modules['types'], types) + self.assertEqual(reloaded.__loader__.path, loader.path) + + def test_reload_loader_replaced(self): + with support.CleanImport('types'): + import types + types.__loader__ = None + self.init.invalidate_caches() + reloaded = self.init.reload(types) + + self.assertIsNot(reloaded.__loader__, None) + self.assertIs(reloaded, types) + self.assertIs(sys.modules['types'], types) + + def test_reload_location_changed(self): + name = 'spam' + with support.temp_cwd(None) as cwd: + with util.uncache('spam'): + with support.DirsOnSysPath(cwd): + self.init.invalidate_caches() + path = os.path.join(cwd, name + '.py') + cached = self.util.cache_from_source(path) + expected = {'__name__': name, + '__package__': '', + '__file__': path, + '__cached__': cached, + '__doc__': None, + '__builtins__': __builtins__, + } + support.create_empty_file(path) + module = self.init.import_module(name) + ns = vars(module) + del ns['__initializing__'] + loader = ns.pop('__loader__') + self.assertEqual(loader.path, path) + self.assertEqual(ns, expected) + + self.init.invalidate_caches() + init_path = os.path.join(cwd, name, '__init__.py') + cached = self.util.cache_from_source(init_path) + expected = {'__name__': name, + '__package__': name, + '__file__': init_path, + '__cached__': cached, + '__path__': [os.path.dirname(init_path)], + '__doc__': None, + '__builtins__': __builtins__, + } + os.mkdir(name) + os.rename(path, init_path) + reloaded = self.init.reload(module) + ns = vars(reloaded) + del ns['__initializing__'] + loader = ns.pop('__loader__') + self.assertIs(reloaded, module) + self.assertEqual(loader.path, init_path) + self.assertEqual(ns, expected) + + def test_reload_namespace_changed(self): + self.maxDiff = None + name = 'spam' + with support.temp_cwd(None) as cwd: + with util.uncache('spam'): + with support.DirsOnSysPath(cwd): + self.init.invalidate_caches() + bad_path = os.path.join(cwd, name, '__init.py') + cached = self.util.cache_from_source(bad_path) + expected = {'__name__': name, + '__package__': name, + '__doc__': None, + } + os.mkdir(name) + with open(bad_path, 'w') as init_file: + init_file.write('eggs = None') + module = self.init.import_module(name) + ns = vars(module) + del ns['__initializing__'] + loader = ns.pop('__loader__') + path = ns.pop('__path__') + self.assertEqual(list(path), + [os.path.dirname(bad_path)] * 2) + with self.assertRaises(AttributeError): + # a NamespaceLoader + loader.path + self.assertEqual(ns, expected) + + self.init.invalidate_caches() + init_path = os.path.join(cwd, name, '__init__.py') + cached = self.util.cache_from_source(init_path) + expected = {'__name__': name, + '__package__': name, + '__file__': init_path, + '__cached__': cached, + '__path__': [os.path.dirname(init_path)], + '__doc__': None, + '__builtins__': __builtins__, + 'eggs': None, + } + os.rename(bad_path, init_path) + reloaded = self.init.reload(module) + ns = vars(reloaded) + del ns['__initializing__'] + loader = ns.pop('__loader__') + self.assertIs(reloaded, module) + self.assertEqual(loader.path, init_path) + self.assertEqual(ns, expected) + + class Frozen_ReloadTests(ReloadTests, unittest.TestCase): init = frozen_init + util = frozen_util class Source_ReloadTests(ReloadTests, unittest.TestCase): init = source_init + util = source_util class InvalidateCacheTests: diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -127,6 +127,8 @@ - Issue #8964: fix platform._sys_version to handle IronPython 2.6+. Patch by Martin Matusiak. +- Issue #19413: Restore pre-3.3 reload() semantics of re-finding modules. + - Issue #18958: Improve error message for json.load(s) while passing a string that starts with a UTF-8 BOM. diff --git a/Python/importlib.h b/Python/importlib.h --- a/Python/importlib.h +++ b/Python/importlib.h [stripped] -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 06:49:42 2013 From: python-checkins at python.org (eric.snow) Date: Fri, 1 Nov 2013 06:49:42 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2319413=3A_Disregar?= =?utf-8?q?d_duplicate_namespace_portions_during_reload_tests=2E?= Message-ID: <3d9stG1mV7z7Ljv@mail.python.org> http://hg.python.org/cpython/rev/78d36d54391c changeset: 86820:78d36d54391c user: Eric Snow date: Thu Oct 31 23:44:31 2013 -0600 summary: Issue #19413: Disregard duplicate namespace portions during reload tests. files: Lib/test/test_importlib/test_api.py | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py --- a/Lib/test/test_importlib/test_api.py +++ b/Lib/test/test_importlib/test_api.py @@ -279,8 +279,8 @@ del ns['__initializing__'] loader = ns.pop('__loader__') path = ns.pop('__path__') - self.assertEqual(list(path), - [os.path.dirname(bad_path)] * 2) + self.assertEqual(set(path), + set([os.path.dirname(bad_path)])) with self.assertRaises(AttributeError): # a NamespaceLoader loader.path -- Repository URL: http://hg.python.org/cpython From solipsis at pitrou.net Fri Nov 1 07:34:05 2013 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Fri, 01 Nov 2013 07:34:05 +0100 Subject: [Python-checkins] Daily reference leaks (13a05ed33cf7): sum=0 Message-ID: results for 13a05ed33cf7 on branch "default" -------------------------------------------- test_site leaked [0, -2, 2] references, sum=0 test_site leaked [0, -2, 2] memory blocks, sum=0 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/antoine/cpython/refleaks/reflogFwlbI4', '-x'] From python-checkins at python.org Fri Nov 1 12:05:59 2013 From: python-checkins at python.org (victor.stinner) Date: Fri, 1 Nov 2013 12:05:59 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_PEP_454?= Message-ID: <3dB0vC3SflzSCM@mail.python.org> http://hg.python.org/peps/rev/50539da3ea22 changeset: 5241:50539da3ea22 user: Victor Stinner date: Fri Nov 01 12:05:37 2013 +0100 summary: PEP 454 * rename disable/enable/is_enabled() to stop/start/is_tracing() * add disable/enable/is_enabled() which are temporary, and disable() doesn't clear traces * a traceback now always contains a least 1 frame, use (('', 0),) if the traceback cannot read or if the traceback limit is 0 * write more documentation on filter functions files: pep-0454.txt | 87 ++++++++++++++++++++++++++++++++++----- 1 files changed, 75 insertions(+), 12 deletions(-) diff --git a/pep-0454.txt b/pep-0454.txt --- a/pep-0454.txt +++ b/pep-0454.txt @@ -117,20 +117,23 @@ ``disable()`` function: - Stop tracing Python memory allocations and clear traces of memory - blocks allocated by Python. + Disable temporarily tracing new Python memory allocations, + deallocations are still traced. The change is process-wide, tracing + new Python memory allocations is disabled in all threads. Call + ``enable()`` to reenable tracing new Python memory allocations. - Call ``get_traces()`` or ``take_snapshot()`` function to get traces - before clearing them. + Filters can be used to not trace memory allocations in some files: + use the ``add_filter()`` function. See also ``enable()`` and ``is_enabled()`` functions. ``enable()`` function: - Start tracing Python memory allocations. + Reenable tracing Python memory allocations if was disabled by te + ``disable()`` method. - See also ``disable()`` and ``is_enabled()`` functions. + See also ``is_enabled()`` functions. ``get_traced_memory()`` function: @@ -147,10 +150,50 @@ ``is_enabled()`` function: + ``True`` if the ``tracemalloc`` module is enabled Python memory + allocations, ``False`` if the module is disabled. + + The ``tracemalloc`` module only traces new allocations if + ``is_tracing()`` and ``is_enabled()`` are ``True``. + + See also ``enable()`` and ``disable()`` functions. + + +``is_tracing()`` function: + ``True`` if the ``tracemalloc`` module is tracing Python memory allocations, ``False`` otherwise. - See also ``disable()`` and ``enable()`` functions. + The ``tracemalloc`` module only traces new allocations if + ``is_tracing()`` and ``is_enabled()`` are ``True``. + + See also ``start()`` and ``stop()`` functions. + + +``stop()`` function: + + Stop tracing Python memory allocations and clear traces of memory + blocks allocated by Python. + + The function uninstalls hooks on Python memory allocators, so the + overhead of the module becomes null. + + Call ``get_traces()`` or ``take_snapshot()`` function to get traces + before clearing them. Use ``disable()`` to disable tracing + temporarily. + + See also ``enable()`` and ``is_enabled()`` functions. + + +``start()`` function: + + Start tracing Python memory allocations. + + The function installs hooks on Python memory allocators. These hooks + have important overhead in term of performances and memory usage: + see `Filter functions`_ to limit the overhead. + + See also ``disable()`` and ``is_tracing()`` functions. ``take_snapshot()`` function: @@ -177,8 +220,10 @@ frame, limited to ``get_traceback_limit()`` frames. A frame is a ``(filename: str, lineno: int)`` tuple. -If ``tracemalloc`` failed to get the whole traceback, the traceback may be -empty, truncated or contain ``""`` filename and line number 0. +A traceback contains at least ``1`` frame. If the ``tracemalloc`` module +failed to get a frame, the ``""`` filename and the line number ``0`` +are used. If it failed to get the traceback or if the traceback limit is ``0``, +the traceback is ``(('', 0),)``. Example of a trace: ``(32, (('x.py', 7), ('x.py', 11)))``. The memory block has a size of 32 bytes and was allocated at ``x.py:7``, line called from line @@ -238,6 +283,9 @@ function to measure the overhead and the ``add_filter()`` function to select which memory allocations are traced. + If the limit is set to ``0`` frame, the traceback ``(('', + 0),)`` will be used for all traces. + Use the ``get_traceback_limit()`` function to get the current limit. The ``PYTHONTRACEMALLOC`` environment variable and the ``-X`` @@ -248,6 +296,24 @@ Filter functions ---------------- +Tracing all Python memroy allocations has an important overhead on performances +and on the memory usage. + +To limit the overhead, some files can be excluded or tracing can be restricted +to a set of files using filters. Examples: ``add_filter(Filter(True, +subprocess.__file__))`` only traces memory allocations in the ``subprocess`` +module, and ``add_filter(Filter(False, tracemalloc.__file__))`` do not trace +memory allocations in the ``tracemalloc`` module + +By default, there is one exclusive filter to ignore Python memory blocks +allocated by the ``tracemalloc`` module. + +Tracing can be also be disabled temporarily using the ``disable()`` function. + +Use the ``get_tracemalloc_memory()`` function to measure the memory usage. +See also the ``set_traceback_limit()`` function to configure how many +frames are stored. + ``add_filter(filter)`` function: Add a new filter on Python memory allocations, *filter* is a @@ -274,9 +340,6 @@ Get the filters on Python memory allocations. Return a list of ``Filter`` instances. - By default, there is one exclusive filter to ignore Python memory - blocks allocated by the ``tracemalloc`` module. - See also the ``clear_filters()`` function. -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Fri Nov 1 14:13:36 2013 From: python-checkins at python.org (tim.golden) Date: Fri, 1 Nov 2013 14:13:36 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2319464_Suppress_co?= =?utf-8?q?mpiler_warnings_during_clean=2E_Patch_by_Zachary_Ware=2E?= Message-ID: <3dB3kS5977z7Ljb@mail.python.org> http://hg.python.org/cpython/rev/599b5200ad51 changeset: 86821:599b5200ad51 user: Tim Golden date: Fri Nov 01 13:12:17 2013 +0000 summary: Issue #19464 Suppress compiler warnings during clean. Patch by Zachary Ware. files: PCbuild/ssl.vcxproj | 16 ++++++++-------- 1 files changed, 8 insertions(+), 8 deletions(-) diff --git a/PCbuild/ssl.vcxproj b/PCbuild/ssl.vcxproj --- a/PCbuild/ssl.vcxproj +++ b/PCbuild/ssl.vcxproj @@ -122,7 +122,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -133,7 +133,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -144,7 +144,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -155,7 +155,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -166,7 +166,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -177,7 +177,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -188,7 +188,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -199,7 +199,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 15:25:19 2013 From: python-checkins at python.org (brett.cannon) Date: Fri, 1 Nov 2013 15:25:19 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Silence_a_compiler_warning?= =?utf-8?q?_about_an_unused_function?= Message-ID: <3dB5KC3fbtz7LjP@mail.python.org> http://hg.python.org/cpython/rev/b59ef5f203da changeset: 86822:b59ef5f203da user: Brett Cannon date: Fri Nov 01 10:25:13 2013 -0400 summary: Silence a compiler warning about an unused function files: Modules/_hashopenssl.c | 56 +++++++++++++++--------------- 1 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -63,34 +63,6 @@ DEFINE_CONSTS_FOR_NEW(sha512) #endif -static PyObject * -_setException(PyObject *exc) -{ - unsigned long errcode; - const char *lib, *func, *reason; - - errcode = ERR_peek_last_error(); - if (!errcode) { - PyErr_SetString(exc, "unknown reasons"); - return NULL; - } - ERR_clear_error(); - - lib = ERR_lib_error_string(errcode); - func = ERR_func_error_string(errcode); - reason = ERR_reason_error_string(errcode); - - if (lib && func) { - PyErr_Format(exc, "[%s: %s] %s", lib, func, reason); - } - else if (lib) { - PyErr_Format(exc, "[%s] %s", lib, reason); - } - else { - PyErr_SetString(exc, reason); - } - return NULL; -} static EVPobject * newEVPobject(PyObject *name) @@ -588,6 +560,34 @@ return 1; } +static PyObject * +_setException(PyObject *exc) +{ + unsigned long errcode; + const char *lib, *func, *reason; + + errcode = ERR_peek_last_error(); + if (!errcode) { + PyErr_SetString(exc, "unknown reasons"); + return NULL; + } + ERR_clear_error(); + + lib = ERR_lib_error_string(errcode); + func = ERR_func_error_string(errcode); + reason = ERR_reason_error_string(errcode); + + if (lib && func) { + PyErr_Format(exc, "[%s: %s] %s", lib, func, reason); + } + else if (lib) { + PyErr_Format(exc, "[%s] %s", lib, reason); + } + else { + PyErr_SetString(exc, reason); + } + return NULL; +} PyDoc_STRVAR(pbkdf2_hmac__doc__, "pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None) -> key\n\ -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 15:38:05 2013 From: python-checkins at python.org (brett.cannon) Date: Fri, 1 Nov 2013 15:38:05 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2319410=3A_Put_back?= =?utf-8?q?_in_special-casing_of_=27=27_for?= Message-ID: <3dB5bx5M8sz7LjY@mail.python.org> http://hg.python.org/cpython/rev/17d730d37b2f changeset: 86823:17d730d37b2f user: Brett Cannon date: Fri Nov 01 10:37:57 2013 -0400 summary: Issue #19410: Put back in special-casing of '' for importlib.machinery.FileFinder. While originally moved to stop special-casing '' as PathFinder farther up the typical call chain now uses the cwd in the instance of '', it was deemed an unnecessary risk to breaking subclasses of FileFinder to take the special-casing out. files: Doc/library/importlib.rst | 3 - Doc/whatsnew/3.4.rst | 3 - Lib/importlib/_bootstrap.py | 2 +- Misc/NEWS | 3 + Python/importlib.h | 1586 +++++++++++----------- 5 files changed, 797 insertions(+), 800 deletions(-) diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -753,9 +753,6 @@ .. versionadded:: 3.3 - .. versionchanged:: 3.4 - The empty string is no longer special-cased to be changed into ``'.'``. - .. attribute:: path The path the finder will search in. diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -812,6 +812,3 @@ working directory will also now have an absolute path, including when using ``-m`` with the interpreter (this does not influence when the path to a file is specified on the command-line). - -* :class:`importlib.machinery.FileFinder` no longer special-cases the empty string - to be changed to ``'.'``. diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -1375,7 +1375,7 @@ loaders.extend((suffix, loader) for suffix in suffixes) self._loaders = loaders # Base (directory) path - self.path = path + self.path = path or '.' self._path_mtime = -1 self._path_cache = set() self._relaxed_path_cache = set() diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,9 @@ Library ------- +- Issue #19410: Undo the special-casing removal of '' for + importlib.machinery.FileFinder. + - Issue #19424: Fix the warnings module to accept filename containing surrogate characters. diff --git a/Python/importlib.h b/Python/importlib.h --- a/Python/importlib.h +++ b/Python/importlib.h [stripped] -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 17:56:45 2013 From: python-checkins at python.org (barry.warsaw) Date: Fri, 1 Nov 2013 17:56:45 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_Ethan_Furman=27s_latest_patch?= =?utf-8?q?_for_Issue_19331=2E?= Message-ID: <3dB8gx3FPGz7LkB@mail.python.org> http://hg.python.org/peps/rev/1a40d4eaa00b changeset: 5242:1a40d4eaa00b user: Barry Warsaw date: Fri Nov 01 12:56:37 2013 -0400 summary: Ethan Furman's latest patch for Issue 19331. files: pep-0008.txt | 16 ++++++++++++++-- 1 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pep-0008.txt b/pep-0008.txt --- a/pep-0008.txt +++ b/pep-0008.txt @@ -580,6 +580,12 @@ standards, but where an existing library has a different style, internal consistency is preferred. +Overriding Principle +-------------------- + +Names that are visible to the user as public parts of the API should +follow conventions that reflect usage rather than implementation. + Descriptive: Naming Styles -------------------------- @@ -676,8 +682,14 @@ Class Names ~~~~~~~~~~~ -Almost without exception, class names use the CapWords convention. -Classes for internal use have a leading underscore in addition. +Class names should normally use the CapWords convention. + +The naming convention for functions may be used instead in cases where +the interface is documented and used primarily as a callable. + +Note that there is a separate convention for builtin names: most builtin +names are single words (or two words run together), with the CapWords +convention used only for exception names and builtin constants. Exception Names ~~~~~~~~~~~~~~~ -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Fri Nov 1 19:04:32 2013 From: python-checkins at python.org (brett.cannon) Date: Fri, 1 Nov 2013 19:04:32 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Abstract_out_stat_calls_in?= =?utf-8?q?_importlib_for_easier_experimentation=2E?= Message-ID: <3dBBB85NnZz7Ljn@mail.python.org> http://hg.python.org/cpython/rev/e071977772bf changeset: 86824:e071977772bf user: Brett Cannon date: Fri Nov 01 14:04:24 2013 -0400 summary: Abstract out stat calls in importlib for easier experimentation. files: Lib/importlib/_bootstrap.py | 20 +- Python/importlib.h | 5488 +++++++++++----------- 2 files changed, 2766 insertions(+), 2742 deletions(-) diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -65,10 +65,20 @@ return '', path +def _path_stat(path): + """Stat the path. + + Made a separate function to make it easier to override in experiments + (e.g. cache stat results). + + """ + return _os.stat(path) + + def _path_is_mode_type(path, mode): """Test whether the path is the specified mode type.""" try: - stat_info = _os.stat(path) + stat_info = _path_stat(path) except OSError: return False return (stat_info.st_mode & 0o170000) == mode @@ -458,7 +468,7 @@ def _calc_mode(path): """Calculate the mode permissions for a bytecode file.""" try: - mode = _os.stat(path).st_mode + mode = _path_stat(path).st_mode except OSError: mode = 0o666 # We always ensure write access so we can update cached files @@ -880,7 +890,7 @@ if filepath is None: return None try: - _os.stat(filepath) + _path_stat(filepath) except OSError: return None for loader, suffixes in _get_supported_file_loaders(): @@ -1074,7 +1084,7 @@ def path_stats(self, path): """Return the metadata for the path.""" - st = _os.stat(path) + st = _path_stat(path) return {'mtime': st.st_mtime, 'size': st.st_size} def _cache_bytecode(self, source_path, bytecode_path, data): @@ -1392,7 +1402,7 @@ is_namespace = False tail_module = fullname.rpartition('.')[2] try: - mtime = _os.stat(self.path or _os.getcwd()).st_mtime + mtime = _path_stat(self.path or _os.getcwd()).st_mtime except OSError: mtime = -1 if mtime != self._path_mtime: diff --git a/Python/importlib.h b/Python/importlib.h --- a/Python/importlib.h +++ b/Python/importlib.h [stripped] -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:47 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:47 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Pause_accepting?= =?utf-8?q?_whenever_accept=28=29_returns_certain_errors=2E_Fixes?= Message-ID: <3dBGfM0ZRZz7LjT@mail.python.org> http://hg.python.org/cpython/rev/3ffa457c8d7c changeset: 86825:3ffa457c8d7c user: Guido van Rossum date: Fri Nov 01 14:12:50 2013 -0700 summary: asyncio: Pause accepting whenever accept() returns certain errors. Fixes asyncio issue #78. files: Lib/asyncio/constants.py | 5 ++- Lib/asyncio/selector_events.py | 21 +++++++--- Lib/test/test_asyncio/test_base_events.py | 13 +++++- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Lib/asyncio/constants.py b/Lib/asyncio/constants.py --- a/Lib/asyncio/constants.py +++ b/Lib/asyncio/constants.py @@ -1,4 +1,7 @@ """Constants.""" +# After the connection is lost, log warnings after this many write()s. +LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 -LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 +# Seconds to wait before retrying accept(). +ACCEPT_RETRY_DELAY = 1 diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -5,6 +5,7 @@ """ import collections +import errno import socket try: import ssl @@ -98,15 +99,23 @@ try: conn, addr = sock.accept() conn.setblocking(False) - except (BlockingIOError, InterruptedError): + except (BlockingIOError, InterruptedError, ConnectionAbortedError): pass # False alarm. - except Exception: - # Bad error. Stop serving. - self.remove_reader(sock.fileno()) - sock.close() + except OSError as exc: # There's nowhere to send the error, so just log it. # TODO: Someone will want an error handler for this. - logger.exception('Accept failed') + if exc.errno in (errno.EMFILE, errno.ENFILE, + errno.ENOBUFS, errno.ENOMEM): + # Some platforms (e.g. Linux keep reporting the FD as + # ready, so we remove the read handler temporarily. + # We'll try again in a while. + logger.exception('Accept out of system resource (%s)', exc) + self.remove_reader(sock.fileno()) + self.call_later(constants.ACCEPT_RETRY_DELAY, + self._start_serving, + protocol_factory, sock, ssl, server) + else: + raise # The event loop will catch, log and ignore it. else: if ssl: self._make_ssl_transport( diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1,5 +1,6 @@ """Tests for base_events.py""" +import errno import logging import socket import time @@ -8,6 +9,7 @@ from test.support import find_unused_port, IPV6_ENABLED from asyncio import base_events +from asyncio import constants from asyncio import events from asyncio import futures from asyncio import protocols @@ -585,11 +587,18 @@ def test_accept_connection_exception(self, m_log): sock = unittest.mock.Mock() sock.fileno.return_value = 10 - sock.accept.side_effect = OSError() + sock.accept.side_effect = OSError(errno.EMFILE, 'Too many open files') + self.loop.remove_reader = unittest.mock.Mock() + self.loop.call_later = unittest.mock.Mock() self.loop._accept_connection(MyProto, sock) - self.assertTrue(sock.close.called) self.assertTrue(m_log.exception.called) + self.assertFalse(sock.close.called) + self.loop.remove_reader.assert_called_with(10) + self.loop.call_later.assert_called_with(constants.ACCEPT_RETRY_DELAY, + # self.loop._start_serving + unittest.mock.ANY, + MyProto, sock, None, None) if __name__ == '__main__': -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:48 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:48 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Fold_some_long_?= =?utf-8?q?lines=2E?= Message-ID: <3dBGfN2DWlz7Ljn@mail.python.org> http://hg.python.org/cpython/rev/b89dbb0bf0a8 changeset: 86826:b89dbb0bf0a8 user: Guido van Rossum date: Fri Nov 01 14:13:30 2013 -0700 summary: asyncio: Fold some long lines. files: Lib/asyncio/selector_events.py | 3 ++- Lib/asyncio/tasks.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -416,7 +416,8 @@ tulip_log.exception('pause_writing() failed') def _maybe_resume_protocol(self): - if self._protocol_paused and self.get_write_buffer_size() <= self._low_water: + if (self._protocol_paused and + self.get_write_buffer_size() <= self._low_water): self._protocol_paused = False try: self._protocol.resume_writing() diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -62,8 +62,9 @@ code = func.__code__ filename = code.co_filename lineno = code.co_firstlineno - logger.error('Coroutine %r defined at %s:%s was never yielded from', - func.__name__, filename, lineno) + logger.error( + 'Coroutine %r defined at %s:%s was never yielded from', + func.__name__, filename, lineno) def coroutine(func): -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:49 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:49 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Add_server=5Fho?= =?utf-8?q?stname_as_create=5Fconnection=28=29_argument=2C_with_secure?= Message-ID: <3dBGfP57tFz7Ljp@mail.python.org> http://hg.python.org/cpython/rev/97bbaf675d61 changeset: 86827:97bbaf675d61 user: Guido van Rossum date: Fri Nov 01 14:16:54 2013 -0700 summary: asyncio: Add server_hostname as create_connection() argument, with secure default. files: Lib/asyncio/base_events.py | 23 ++++- Lib/asyncio/events.py | 2 +- Lib/asyncio/selector_events.py | 4 +- Lib/test/test_asyncio/test_base_events.py | 54 +++++++++++ 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -275,8 +275,27 @@ @tasks.coroutine def create_connection(self, protocol_factory, host=None, port=None, *, ssl=None, family=0, proto=0, flags=0, sock=None, - local_addr=None): + local_addr=None, server_hostname=None): """XXX""" + if server_hostname is not None and not ssl: + raise ValueError('server_hostname is only meaningful with ssl') + + if server_hostname is None and ssl: + # Use host as default for server_hostname. It is an error + # if host is empty or not set, e.g. when an + # already-connected socket was passed or when only a port + # is given. To avoid this error, you can pass + # server_hostname='' -- this will bypass the hostname + # check. (This also means that if host is a numeric + # IP/IPv6 address, we will attempt to verify that exact + # address; this will probably fail, but it is possible to + # create a certificate for a specific IP address, so we + # don't judge it here.) + if not host: + raise ValueError('You must set server_hostname ' + 'when using ssl without a host') + server_hostname = host + if host is not None or port is not None: if sock is not None: raise ValueError( @@ -357,7 +376,7 @@ sslcontext = None if isinstance(ssl, bool) else ssl transport = self._make_ssl_transport( sock, protocol, sslcontext, waiter, - server_side=False, server_hostname=host) + server_side=False, server_hostname=server_hostname) else: transport = self._make_socket_transport(sock, protocol, waiter) diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -172,7 +172,7 @@ def create_connection(self, protocol_factory, host=None, port=None, *, ssl=None, family=0, proto=0, flags=0, sock=None, - local_addr=None): + local_addr=None, server_hostname=None): raise NotImplementedError def create_server(self, protocol_factory, host=None, port=None, *, diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -573,7 +573,7 @@ 'server_side': server_side, 'do_handshake_on_connect': False, } - if server_hostname is not None and not server_side and ssl.HAS_SNI: + if server_hostname and not server_side and ssl.HAS_SNI: wrap_kwargs['server_hostname'] = server_hostname sslsock = sslcontext.wrap_socket(rawsock, **wrap_kwargs) @@ -619,7 +619,7 @@ # Verify hostname if requested. peercert = self._sock.getpeercert() - if (self._server_hostname is not None and + if (self._server_hostname and self._sslcontext.verify_mode != ssl.CERT_NONE): try: ssl.match_hostname(peercert, self._server_hostname) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -444,6 +444,60 @@ self.assertRaises( OSError, self.loop.run_until_complete, coro) + def test_create_connection_server_hostname_default(self): + self.loop.getaddrinfo = unittest.mock.Mock() + def mock_getaddrinfo(*args, **kwds): + f = futures.Future(loop=self.loop) + f.set_result([(socket.AF_INET, socket.SOCK_STREAM, + socket.SOL_TCP, '', ('1.2.3.4', 80))]) + return f + self.loop.getaddrinfo.side_effect = mock_getaddrinfo + self.loop.sock_connect = unittest.mock.Mock() + self.loop.sock_connect.return_value = () + self.loop._make_ssl_transport = unittest.mock.Mock() + def mock_make_ssl_transport(sock, protocol, sslcontext, waiter, **kwds): + waiter.set_result(None) + self.loop._make_ssl_transport.side_effect = mock_make_ssl_transport + ANY = unittest.mock.ANY + # First try the default server_hostname. + self.loop._make_ssl_transport.reset_mock() + coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True) + self.loop.run_until_complete(coro) + self.loop._make_ssl_transport.assert_called_with(ANY, ANY, ANY, ANY, + server_side=False, + server_hostname='python.org') + # Next try an explicit server_hostname. + self.loop._make_ssl_transport.reset_mock() + coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True, + server_hostname='perl.com') + self.loop.run_until_complete(coro) + self.loop._make_ssl_transport.assert_called_with(ANY, ANY, ANY, ANY, + server_side=False, + server_hostname='perl.com') + # Finally try an explicit empty server_hostname. + self.loop._make_ssl_transport.reset_mock() + coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True, + server_hostname='') + self.loop.run_until_complete(coro) + self.loop._make_ssl_transport.assert_called_with(ANY, ANY, ANY, ANY, + server_side=False, + server_hostname='') + + def test_create_connection_server_hostname_errors(self): + # When not using ssl, server_hostname must be None (but '' is OK). + coro = self.loop.create_connection(MyProto, 'python.org', 80, server_hostname='') + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + coro = self.loop.create_connection(MyProto, 'python.org', 80, server_hostname='python.org') + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + + # When using ssl, server_hostname may be None if host is non-empty. + coro = self.loop.create_connection(MyProto, '', 80, ssl=True) + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + coro = self.loop.create_connection(MyProto, None, 80, ssl=True) + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + coro = self.loop.create_connection(MyProto, None, None, ssl=True, sock=socket.socket()) + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + def test_create_server_empty_host(self): # if host is empty string use None instead host = object() -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:51 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:51 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Refactor_ssl_tr?= =?utf-8?q?ansport_ready_loop_=28Nikolay_Kim=29=2E?= Message-ID: <3dBGfR273sz7Lk7@mail.python.org> http://hg.python.org/cpython/rev/10a518b26ed1 changeset: 86828:10a518b26ed1 user: Guido van Rossum date: Fri Nov 01 14:18:02 2013 -0700 summary: asyncio: Refactor ssl transport ready loop (Nikolay Kim). files: Lib/asyncio/selector_events.py | 94 +++--- Lib/test/test_asyncio/test_selector_events.py | 134 ++++++--- 2 files changed, 136 insertions(+), 92 deletions(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -286,7 +286,7 @@ err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if err != 0: # Jump to the except clause below. - raise OSError(err, 'Connect call failed') + raise OSError(err, 'Connect call failed %s' % (address,)) except (BlockingIOError, InterruptedError): self.add_writer(fd, self._sock_connect, fut, True, sock, address) except Exception as exc: @@ -413,7 +413,7 @@ try: self._protocol.pause_writing() except Exception: - tulip_log.exception('pause_writing() failed') + logger.exception('pause_writing() failed') def _maybe_resume_protocol(self): if (self._protocol_paused and @@ -422,7 +422,7 @@ try: self._protocol.resume_writing() except Exception: - tulip_log.exception('resume_writing() failed') + logger.exception('resume_writing() failed') def set_write_buffer_limits(self, high=None, low=None): if high is None: @@ -635,15 +635,16 @@ compression=self._sock.compression(), ) - self._loop.add_reader(self._sock_fd, self._on_ready) - self._loop.add_writer(self._sock_fd, self._on_ready) + self._read_wants_write = False + self._write_wants_read = False + self._loop.add_reader(self._sock_fd, self._read_ready) self._loop.call_soon(self._protocol.connection_made, self) if self._waiter is not None: self._loop.call_soon(self._waiter.set_result, None) def pause_reading(self): # XXX This is a bit icky, given the comment at the top of - # _on_ready(). Is it possible to evoke a deadlock? I don't + # _read_ready(). Is it possible to evoke a deadlock? I don't # know, although it doesn't look like it; write() will still # accept more data for the buffer and eventually the app will # call resume_reading() again, and things will flow again. @@ -658,41 +659,55 @@ self._paused = False if self._closing: return - self._loop.add_reader(self._sock_fd, self._on_ready) + self._loop.add_reader(self._sock_fd, self._read_ready) - def _on_ready(self): - # Because of renegotiations (?), there's no difference between - # readable and writable. We just try both. XXX This may be - # incorrect; we probably need to keep state about what we - # should do next. + def _read_ready(self): + if self._write_wants_read: + self._write_wants_read = False + self._write_ready() - # First try reading. - if not self._closing and not self._paused: - try: - data = self._sock.recv(self.max_size) - except (BlockingIOError, InterruptedError, - ssl.SSLWantReadError, ssl.SSLWantWriteError): - pass - except Exception as exc: - self._fatal_error(exc) + if self._buffer: + self._loop.add_writer(self._sock_fd, self._write_ready) + + try: + data = self._sock.recv(self.max_size) + except (BlockingIOError, InterruptedError, ssl.SSLWantReadError): + pass + except ssl.SSLWantWriteError: + self._read_wants_write = True + self._loop.remove_reader(self._sock_fd) + self._loop.add_writer(self._sock_fd, self._write_ready) + except Exception as exc: + self._fatal_error(exc) + else: + if data: + self._protocol.data_received(data) else: - if data: - self._protocol.data_received(data) - else: - try: - self._protocol.eof_received() - finally: - self.close() + try: + self._protocol.eof_received() + finally: + self.close() - # Now try writing, if there's anything to write. + def _write_ready(self): + if self._read_wants_write: + self._read_wants_write = False + self._read_ready() + + if not (self._paused or self._closing): + self._loop.add_reader(self._sock_fd, self._read_ready) + if self._buffer: data = b''.join(self._buffer) self._buffer.clear() try: n = self._sock.send(data) except (BlockingIOError, InterruptedError, - ssl.SSLWantReadError, ssl.SSLWantWriteError): + ssl.SSLWantWriteError): n = 0 + except ssl.SSLWantReadError: + n = 0 + self._loop.remove_writer(self._sock_fd) + self._write_wants_read = True except Exception as exc: self._loop.remove_writer(self._sock_fd) self._fatal_error(exc) @@ -701,11 +716,12 @@ if n < len(data): self._buffer.append(data[n:]) - self._maybe_resume_protocol() # May append to buffer. + self._maybe_resume_protocol() # May append to buffer. - if self._closing and not self._buffer: + if not self._buffer: self._loop.remove_writer(self._sock_fd) - self._call_connection_lost(None) + if self._closing: + self._call_connection_lost(None) def write(self, data): assert isinstance(data, bytes), repr(type(data)) @@ -718,20 +734,16 @@ self._conn_lost += 1 return - # We could optimize, but the callback can do this for now. + if not self._buffer: + self._loop.add_writer(self._sock_fd, self._write_ready) + + # Add it to the buffer. self._buffer.append(data) self._maybe_pause_protocol() def can_write_eof(self): return False - def close(self): - if self._closing: - return - self._closing = True - self._conn_lost += 1 - self._loop.remove_reader(self._sock_fd) - class _SelectorDatagramTransport(_SelectorTransport): diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -1003,8 +1003,7 @@ self.loop, self.sock, self.protocol, self.sslcontext, waiter=waiter) self.assertTrue(self.sslsock.do_handshake.called) - self.loop.assert_reader(1, tr._on_ready) - self.loop.assert_writer(1, tr._on_ready) + self.loop.assert_reader(1, tr._read_ready) test_utils.run_briefly(self.loop) self.assertIsNone(waiter.result()) @@ -1047,13 +1046,13 @@ def test_pause_resume_reading(self): tr = self._make_one() self.assertFalse(tr._paused) - self.loop.assert_reader(1, tr._on_ready) + self.loop.assert_reader(1, tr._read_ready) tr.pause_reading() self.assertTrue(tr._paused) self.assertFalse(1 in self.loop.readers) tr.resume_reading() self.assertFalse(tr._paused) - self.loop.assert_reader(1, tr._on_ready) + self.loop.assert_reader(1, tr._read_ready) def test_write_no_data(self): transport = self._make_one() @@ -1084,140 +1083,173 @@ transport.write(b'data') m_log.warning.assert_called_with('socket.send() raised exception.') - def test_on_ready_recv(self): + def test_read_ready_recv(self): self.sslsock.recv.return_value = b'data' transport = self._make_one() - transport._on_ready() + transport._read_ready() self.assertTrue(self.sslsock.recv.called) self.assertEqual((b'data',), self.protocol.data_received.call_args[0]) - def test_on_ready_recv_eof(self): + def test_read_ready_write_wants_read(self): + self.loop.add_writer = unittest.mock.Mock() + self.sslsock.recv.side_effect = BlockingIOError + transport = self._make_one() + transport._write_wants_read = True + transport._write_ready = unittest.mock.Mock() + transport._buffer.append(b'data') + transport._read_ready() + + self.assertFalse(transport._write_wants_read) + transport._write_ready.assert_called_with() + self.loop.add_writer.assert_called_with( + transport._sock_fd, transport._write_ready) + + def test_read_ready_recv_eof(self): self.sslsock.recv.return_value = b'' transport = self._make_one() transport.close = unittest.mock.Mock() - transport._on_ready() + transport._read_ready() transport.close.assert_called_with() self.protocol.eof_received.assert_called_with() - def test_on_ready_recv_conn_reset(self): + def test_read_ready_recv_conn_reset(self): err = self.sslsock.recv.side_effect = ConnectionResetError() transport = self._make_one() transport._force_close = unittest.mock.Mock() - transport._on_ready() + transport._read_ready() transport._force_close.assert_called_with(err) - def test_on_ready_recv_retry(self): + def test_read_ready_recv_retry(self): self.sslsock.recv.side_effect = ssl.SSLWantReadError transport = self._make_one() - transport._on_ready() + transport._read_ready() self.assertTrue(self.sslsock.recv.called) self.assertFalse(self.protocol.data_received.called) - self.sslsock.recv.side_effect = ssl.SSLWantWriteError - transport._on_ready() - self.assertFalse(self.protocol.data_received.called) - self.sslsock.recv.side_effect = BlockingIOError - transport._on_ready() + transport._read_ready() self.assertFalse(self.protocol.data_received.called) self.sslsock.recv.side_effect = InterruptedError - transport._on_ready() + transport._read_ready() self.assertFalse(self.protocol.data_received.called) - def test_on_ready_recv_exc(self): + def test_read_ready_recv_write(self): + self.loop.remove_reader = unittest.mock.Mock() + self.loop.add_writer = unittest.mock.Mock() + self.sslsock.recv.side_effect = ssl.SSLWantWriteError + transport = self._make_one() + transport._read_ready() + self.assertFalse(self.protocol.data_received.called) + self.assertTrue(transport._read_wants_write) + + self.loop.remove_reader.assert_called_with(transport._sock_fd) + self.loop.add_writer.assert_called_with( + transport._sock_fd, transport._write_ready) + + def test_read_ready_recv_exc(self): err = self.sslsock.recv.side_effect = OSError() transport = self._make_one() transport._fatal_error = unittest.mock.Mock() - transport._on_ready() + transport._read_ready() transport._fatal_error.assert_called_with(err) - def test_on_ready_send(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send(self): self.sslsock.send.return_value = 4 transport = self._make_one() transport._buffer = collections.deque([b'data']) - transport._on_ready() + transport._write_ready() self.assertEqual(collections.deque(), transport._buffer) self.assertTrue(self.sslsock.send.called) - def test_on_ready_send_none(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_none(self): self.sslsock.send.return_value = 0 transport = self._make_one() transport._buffer = collections.deque([b'data1', b'data2']) - transport._on_ready() + transport._write_ready() self.assertTrue(self.sslsock.send.called) self.assertEqual(collections.deque([b'data1data2']), transport._buffer) - def test_on_ready_send_partial(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_partial(self): self.sslsock.send.return_value = 2 transport = self._make_one() transport._buffer = collections.deque([b'data1', b'data2']) - transport._on_ready() + transport._write_ready() self.assertTrue(self.sslsock.send.called) self.assertEqual(collections.deque([b'ta1data2']), transport._buffer) - def test_on_ready_send_closing_partial(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_closing_partial(self): self.sslsock.send.return_value = 2 transport = self._make_one() transport._buffer = collections.deque([b'data1', b'data2']) - transport._on_ready() + transport._write_ready() self.assertTrue(self.sslsock.send.called) self.assertFalse(self.sslsock.close.called) - def test_on_ready_send_closing(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_closing(self): self.sslsock.send.return_value = 4 transport = self._make_one() transport.close() transport._buffer = collections.deque([b'data']) - transport._on_ready() + transport._write_ready() self.assertFalse(self.loop.writers) self.protocol.connection_lost.assert_called_with(None) - def test_on_ready_send_closing_empty_buffer(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_closing_empty_buffer(self): self.sslsock.send.return_value = 4 transport = self._make_one() transport.close() transport._buffer = collections.deque() - transport._on_ready() + transport._write_ready() self.assertFalse(self.loop.writers) self.protocol.connection_lost.assert_called_with(None) - def test_on_ready_send_retry(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError - + def test_write_ready_send_retry(self): transport = self._make_one() transport._buffer = collections.deque([b'data']) - self.sslsock.send.side_effect = ssl.SSLWantReadError - transport._on_ready() - self.assertTrue(self.sslsock.send.called) - self.assertEqual(collections.deque([b'data']), transport._buffer) - self.sslsock.send.side_effect = ssl.SSLWantWriteError - transport._on_ready() + transport._write_ready() self.assertEqual(collections.deque([b'data']), transport._buffer) self.sslsock.send.side_effect = BlockingIOError() - transport._on_ready() + transport._write_ready() self.assertEqual(collections.deque([b'data']), transport._buffer) - def test_on_ready_send_exc(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_read(self): + transport = self._make_one() + transport._buffer = collections.deque([b'data']) + + self.loop.remove_writer = unittest.mock.Mock() + self.sslsock.send.side_effect = ssl.SSLWantReadError + transport._write_ready() + self.assertFalse(self.protocol.data_received.called) + self.assertTrue(transport._write_wants_read) + self.loop.remove_writer.assert_called_with(transport._sock_fd) + + def test_write_ready_send_exc(self): err = self.sslsock.send.side_effect = OSError() transport = self._make_one() transport._buffer = collections.deque([b'data']) transport._fatal_error = unittest.mock.Mock() - transport._on_ready() + transport._write_ready() transport._fatal_error.assert_called_with(err) self.assertEqual(collections.deque(), transport._buffer) + def test_write_ready_read_wants_write(self): + self.loop.add_reader = unittest.mock.Mock() + self.sslsock.send.side_effect = BlockingIOError + transport = self._make_one() + transport._read_wants_write = True + transport._read_ready = unittest.mock.Mock() + transport._write_ready() + + self.assertFalse(transport._read_wants_write) + transport._read_ready.assert_called_with() + self.loop.add_reader.assert_called_with( + transport._sock_fd, transport._read_ready) + def test_write_eof(self): tr = self._make_one() self.assertFalse(tr.can_write_eof()) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:52 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:52 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Document_EventL?= =?utf-8?b?b29wLmNsb3NlKCku?= Message-ID: <3dBGfS3tHRz7Lk6@mail.python.org> http://hg.python.org/cpython/rev/2ceb3fd38280 changeset: 86829:2ceb3fd38280 user: Guido van Rossum date: Fri Nov 01 14:19:04 2013 -0700 summary: asyncio: Document EventLoop.close(). files: Lib/asyncio/base_events.py | 5 +++++ Lib/asyncio/events.py | 13 +++++++++++++ Lib/test/test_asyncio/test_events.py | 2 ++ 3 files changed, 20 insertions(+), 0 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -186,6 +186,11 @@ self.call_soon(_raise_stop_error) def close(self): + """Close the event loop. + + This clears the queues and shuts down the executor, + but does not wait for the executor to finish. + """ self._ready.clear() self._scheduled.clear() executor = self._default_executor diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -137,6 +137,17 @@ """Return whether the event loop is currently running.""" raise NotImplementedError + def close(self): + """Close the loop. + + The loop should not be running. + + This is idempotent and irreversible. + + No other methods should be called after this one. + """ + raise NotImplementedError + # Methods scheduling callbacks. All these return Handles. def call_soon(self, callback, *args): @@ -214,6 +225,8 @@ family=0, proto=0, flags=0): raise NotImplementedError + # Pipes and subprocesses. + def connect_read_pipe(self, protocol_factory, pipe): """Register read pipe in eventloop. diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1472,6 +1472,8 @@ self.assertRaises( NotImplementedError, loop.is_running) self.assertRaises( + NotImplementedError, loop.close) + self.assertRaises( NotImplementedError, loop.call_later, None, None) self.assertRaises( NotImplementedError, loop.call_at, f, f) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:53 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:53 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Log_a_warning_w?= =?utf-8?q?hen_eof=5Freceived=28=29_returns_true_and_using_ssl=2E?= Message-ID: <3dBGfT5cS8z7Ljp@mail.python.org> http://hg.python.org/cpython/rev/0c5f2223026f changeset: 86830:0c5f2223026f user: Guido van Rossum date: Fri Nov 01 14:19:35 2013 -0700 summary: asyncio: Log a warning when eof_received() returns true and using ssl. files: Lib/asyncio/selector_events.py | 5 ++++- 1 files changed, 4 insertions(+), 1 deletions(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -684,7 +684,10 @@ self._protocol.data_received(data) else: try: - self._protocol.eof_received() + keep_open = self._protocol.eof_received() + if keep_open: + logger.warning('returning true from eof_received() ' + 'has no effect when using ssl') finally: self.close() -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:55 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:55 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Various_style_n?= =?utf-8?q?its=2E?= Message-ID: <3dBGfW1L2tz7Lk5@mail.python.org> http://hg.python.org/cpython/rev/c793f3347c3c changeset: 86831:c793f3347c3c user: Guido van Rossum date: Fri Nov 01 14:20:55 2013 -0700 summary: asyncio: Various style nits. files: Lib/asyncio/base_events.py | 2 +- Lib/asyncio/windows_events.py | 16 +++++ Lib/asyncio/windows_utils.py | 20 +++--- Lib/test/test_asyncio/test_base_events.py | 30 ++++++--- Lib/test/test_asyncio/test_events.py | 1 - Lib/test/test_asyncio/test_windows_events.py | 2 +- 6 files changed, 48 insertions(+), 23 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -300,7 +300,7 @@ raise ValueError('You must set server_hostname ' 'when using ssl without a host') server_hostname = host - + if host is not None or port is not None: if sock is not None: raise ValueError( diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -138,6 +138,7 @@ @tasks.coroutine def start_serving_pipe(self, protocol_factory, address): server = PipeServer(address) + def loop(f=None): pipe = None try: @@ -160,6 +161,7 @@ pipe.close() else: f.add_done_callback(loop) + self.call_soon(loop) return [server] @@ -209,6 +211,7 @@ ov.WSARecv(conn.fileno(), nbytes, flags) else: ov.ReadFile(conn.fileno(), nbytes) + def finish(trans, key, ov): try: return ov.getresult() @@ -217,6 +220,7 @@ raise ConnectionResetError(*exc.args) else: raise + return self._register(ov, conn, finish) def send(self, conn, buf, flags=0): @@ -226,6 +230,7 @@ ov.WSASend(conn.fileno(), buf, flags) else: ov.WriteFile(conn.fileno(), buf) + def finish(trans, key, ov): try: return ov.getresult() @@ -234,6 +239,7 @@ raise ConnectionResetError(*exc.args) else: raise + return self._register(ov, conn, finish) def accept(self, listener): @@ -241,6 +247,7 @@ conn = self._get_accept_socket(listener.family) ov = _overlapped.Overlapped(NULL) ov.AcceptEx(listener.fileno(), conn.fileno()) + def finish_accept(trans, key, ov): ov.getresult() # Use SO_UPDATE_ACCEPT_CONTEXT so getsockname() etc work. @@ -249,6 +256,7 @@ _overlapped.SO_UPDATE_ACCEPT_CONTEXT, buf) conn.settimeout(listener.gettimeout()) return conn, conn.getpeername() + return self._register(ov, listener, finish_accept) def connect(self, conn, address): @@ -264,26 +272,31 @@ raise ov = _overlapped.Overlapped(NULL) ov.ConnectEx(conn.fileno(), address) + def finish_connect(trans, key, ov): ov.getresult() # Use SO_UPDATE_CONNECT_CONTEXT so getsockname() etc work. conn.setsockopt(socket.SOL_SOCKET, _overlapped.SO_UPDATE_CONNECT_CONTEXT, 0) return conn + return self._register(ov, conn, finish_connect) def accept_pipe(self, pipe): self._register_with_iocp(pipe) ov = _overlapped.Overlapped(NULL) ov.ConnectNamedPipe(pipe.fileno()) + def finish(trans, key, ov): ov.getresult() return pipe + return self._register(ov, pipe, finish) def connect_pipe(self, address): ov = _overlapped.Overlapped(NULL) ov.WaitNamedPipeAndConnect(address, self._iocp, ov.address) + def finish(err, handle, ov): # err, handle were arguments passed to PostQueuedCompletionStatus() # in a function run in a thread pool. @@ -296,6 +309,7 @@ raise OSError(0, msg, None, err) else: return windows_utils.PipeHandle(handle) + return self._register(ov, None, finish, wait_for_post=True) def wait_for_handle(self, handle, timeout=None): @@ -432,8 +446,10 @@ self._proc = windows_utils.Popen( args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr, bufsize=bufsize, **kwargs) + def callback(f): returncode = self._proc.poll() self._process_exited(returncode) + f = self._loop._proactor.wait_for_handle(int(self._proc._handle)) f.add_done_callback(callback) diff --git a/Lib/asyncio/windows_utils.py b/Lib/asyncio/windows_utils.py --- a/Lib/asyncio/windows_utils.py +++ b/Lib/asyncio/windows_utils.py @@ -18,18 +18,18 @@ __all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle'] -# + # Constants/globals -# + BUFSIZE = 8192 PIPE = subprocess.PIPE STDOUT = subprocess.STDOUT _mmap_counter = itertools.count() -# + # Replacement for socket.socketpair() -# + def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0): """A socket pair usable as a self-pipe, for Windows. @@ -57,9 +57,9 @@ lsock.close() return (ssock, csock) -# + # Replacement for os.pipe() using handles instead of fds -# + def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE): """Like os.pipe() but with overlapped support and using handles not fds.""" @@ -105,9 +105,9 @@ _winapi.CloseHandle(h2) raise -# + # Wrapper for a pipe handle -# + class PipeHandle: """Wrapper for an overlapped pipe handle which is vaguely file-object like. @@ -137,9 +137,9 @@ def __exit__(self, t, v, tb): self.close() -# + # Replacement for subprocess.Popen using overlapped pipe handles -# + class Popen(subprocess.Popen): """Replacement for subprocess.Popen using overlapped pipe handles. diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -446,34 +446,41 @@ def test_create_connection_server_hostname_default(self): self.loop.getaddrinfo = unittest.mock.Mock() + def mock_getaddrinfo(*args, **kwds): f = futures.Future(loop=self.loop) f.set_result([(socket.AF_INET, socket.SOCK_STREAM, socket.SOL_TCP, '', ('1.2.3.4', 80))]) return f + self.loop.getaddrinfo.side_effect = mock_getaddrinfo self.loop.sock_connect = unittest.mock.Mock() self.loop.sock_connect.return_value = () self.loop._make_ssl_transport = unittest.mock.Mock() - def mock_make_ssl_transport(sock, protocol, sslcontext, waiter, **kwds): + + def mock_make_ssl_transport(sock, protocol, sslcontext, waiter, + **kwds): waiter.set_result(None) + self.loop._make_ssl_transport.side_effect = mock_make_ssl_transport ANY = unittest.mock.ANY # First try the default server_hostname. self.loop._make_ssl_transport.reset_mock() coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True) self.loop.run_until_complete(coro) - self.loop._make_ssl_transport.assert_called_with(ANY, ANY, ANY, ANY, - server_side=False, - server_hostname='python.org') + self.loop._make_ssl_transport.assert_called_with( + ANY, ANY, ANY, ANY, + server_side=False, + server_hostname='python.org') # Next try an explicit server_hostname. self.loop._make_ssl_transport.reset_mock() coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True, server_hostname='perl.com') self.loop.run_until_complete(coro) - self.loop._make_ssl_transport.assert_called_with(ANY, ANY, ANY, ANY, - server_side=False, - server_hostname='perl.com') + self.loop._make_ssl_transport.assert_called_with( + ANY, ANY, ANY, ANY, + server_side=False, + server_hostname='perl.com') # Finally try an explicit empty server_hostname. self.loop._make_ssl_transport.reset_mock() coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True, @@ -485,9 +492,11 @@ def test_create_connection_server_hostname_errors(self): # When not using ssl, server_hostname must be None (but '' is OK). - coro = self.loop.create_connection(MyProto, 'python.org', 80, server_hostname='') + coro = self.loop.create_connection(MyProto, 'python.org', 80, + server_hostname='') self.assertRaises(ValueError, self.loop.run_until_complete, coro) - coro = self.loop.create_connection(MyProto, 'python.org', 80, server_hostname='python.org') + coro = self.loop.create_connection(MyProto, 'python.org', 80, + server_hostname='python.org') self.assertRaises(ValueError, self.loop.run_until_complete, coro) # When using ssl, server_hostname may be None if host is non-empty. @@ -495,7 +504,8 @@ self.assertRaises(ValueError, self.loop.run_until_complete, coro) coro = self.loop.create_connection(MyProto, None, 80, ssl=True) self.assertRaises(ValueError, self.loop.run_until_complete, coro) - coro = self.loop.create_connection(MyProto, None, None, ssl=True, sock=socket.socket()) + coro = self.loop.create_connection(MyProto, None, None, + ssl=True, sock=socket.socket()) self.assertRaises(ValueError, self.loop.run_until_complete, coro) def test_create_server_empty_host(self): diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1276,7 +1276,6 @@ def create_event_loop(self): return windows_events.SelectorEventLoop() - class ProactorEventLoopTests(EventLoopTestsMixin, SubprocessTestsMixin, unittest.TestCase): diff --git a/Lib/test/test_asyncio/test_windows_events.py b/Lib/test/test_asyncio/test_windows_events.py --- a/Lib/test/test_asyncio/test_windows_events.py +++ b/Lib/test/test_asyncio/test_windows_events.py @@ -77,7 +77,7 @@ stream_reader = streams.StreamReader(loop=self.loop) protocol = streams.StreamReaderProtocol(stream_reader) trans, proto = yield from self.loop.create_pipe_connection( - lambda:protocol, ADDRESS) + lambda: protocol, ADDRESS) self.assertIsInstance(trans, transports.Transport) self.assertEqual(protocol, proto) clients.append((stream_reader, trans)) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:56 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:56 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Better-looking_?= =?utf-8?q?errors_when_ssl_module_cannot_be_imported=2E_In_part_by?= Message-ID: <3dBGfX4HDSz7Lk4@mail.python.org> http://hg.python.org/cpython/rev/8d67a5d8cfa4 changeset: 86832:8d67a5d8cfa4 user: Guido van Rossum date: Fri Nov 01 14:22:30 2013 -0700 summary: asyncio: Better-looking errors when ssl module cannot be imported. In part by Arnaud Faure. files: Lib/asyncio/base_events.py | 2 + Lib/asyncio/selector_events.py | 31 ++++++--- Lib/test/test_asyncio/test_selector_events.py | 20 ++++++ 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -466,6 +466,8 @@ ssl=None, reuse_address=None): """XXX""" + if isinstance(ssl, bool): + raise TypeError('ssl argument must be an SSLContext or None') if host is not None or port is not None: if sock is not None: raise ValueError( diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -90,12 +90,13 @@ except (BlockingIOError, InterruptedError): pass - def _start_serving(self, protocol_factory, sock, ssl=None, server=None): + def _start_serving(self, protocol_factory, sock, + sslcontext=None, server=None): self.add_reader(sock.fileno(), self._accept_connection, - protocol_factory, sock, ssl, server) + protocol_factory, sock, sslcontext, server) - def _accept_connection(self, protocol_factory, sock, ssl=None, - server=None): + def _accept_connection(self, protocol_factory, sock, + sslcontext=None, server=None): try: conn, addr = sock.accept() conn.setblocking(False) @@ -113,13 +114,13 @@ self.remove_reader(sock.fileno()) self.call_later(constants.ACCEPT_RETRY_DELAY, self._start_serving, - protocol_factory, sock, ssl, server) + protocol_factory, sock, sslcontext, server) else: raise # The event loop will catch, log and ignore it. else: - if ssl: + if sslcontext: self._make_ssl_transport( - conn, protocol_factory(), ssl, None, + conn, protocol_factory(), sslcontext, None, server_side=True, extra={'peername': addr}, server=server) else: self._make_socket_transport( @@ -558,17 +559,23 @@ def __init__(self, loop, rawsock, protocol, sslcontext, waiter=None, server_side=False, server_hostname=None, extra=None, server=None): + if ssl is None: + raise RuntimeError('stdlib ssl module not available') + if server_side: - assert isinstance( - sslcontext, ssl.SSLContext), 'Must pass an SSLContext' + if not sslcontext: + raise ValueError('Server side ssl needs a valid SSLContext') else: - # Client-side may pass ssl=True to use a default context. - # The default is the same as used by urllib. - if sslcontext is None: + if not sslcontext: + # Client side may pass ssl=True to use a default + # context; in that case the sslcontext passed is None. + # The default is the same as used by urllib with + # cadefault=True. sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) sslcontext.options |= ssl.OP_NO_SSLv2 sslcontext.set_default_verify_paths() sslcontext.verify_mode = ssl.CERT_REQUIRED + wrap_kwargs = { 'server_side': server_side, 'do_handshake_on_connect': False, diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -43,6 +43,7 @@ self.assertIsInstance( self.loop._make_socket_transport(m, m), _SelectorSocketTransport) + @unittest.skipIf(ssl is None, 'No ssl module') def test_make_ssl_transport(self): m = unittest.mock.Mock() self.loop.add_reader = unittest.mock.Mock() @@ -52,6 +53,16 @@ self.assertIsInstance( self.loop._make_ssl_transport(m, m, m, m), _SelectorSslTransport) + @unittest.mock.patch('asyncio.selector_events.ssl', None) + def test_make_ssl_transport_without_ssl_error(self): + m = unittest.mock.Mock() + self.loop.add_reader = unittest.mock.Mock() + self.loop.add_writer = unittest.mock.Mock() + self.loop.remove_reader = unittest.mock.Mock() + self.loop.remove_writer = unittest.mock.Mock() + with self.assertRaises(RuntimeError): + self.loop._make_ssl_transport(m, m, m, m) + def test_close(self): ssock = self.loop._ssock ssock.fileno.return_value = 7 @@ -1277,6 +1288,15 @@ server_hostname='localhost') +class SelectorSslWithoutSslTransportTests(unittest.TestCase): + + @unittest.mock.patch('asyncio.selector_events.ssl', None) + def test_ssl_transport_requires_ssl_module(self): + Mock = unittest.mock.Mock + with self.assertRaises(RuntimeError): + transport = _SelectorSslTransport(Mock(), Mock(), Mock(), Mock()) + + class SelectorDatagramTransportTests(unittest.TestCase): def setUp(self): -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 22:25:57 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 22:25:57 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Slight_rearrang?= =?utf-8?q?ement_of_tests_for_server=5Fhostname=3D=2E=2E=2E?= Message-ID: <3dBGfY63xWz7Lk1@mail.python.org> http://hg.python.org/cpython/rev/123804a72a8f changeset: 86833:123804a72a8f user: Guido van Rossum date: Fri Nov 01 14:24:28 2013 -0700 summary: asyncio: Slight rearrangement of tests for server_hostname=... files: Lib/test/test_asyncio/test_base_events.py | 7 ++++--- 1 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -444,7 +444,7 @@ self.assertRaises( OSError, self.loop.run_until_complete, coro) - def test_create_connection_server_hostname_default(self): + def test_create_connection_ssl_server_hostname_default(self): self.loop.getaddrinfo = unittest.mock.Mock() def mock_getaddrinfo(*args, **kwds): @@ -490,8 +490,8 @@ server_side=False, server_hostname='') - def test_create_connection_server_hostname_errors(self): - # When not using ssl, server_hostname must be None (but '' is OK). + def test_create_connection_no_ssl_server_hostname_errors(self): + # When not using ssl, server_hostname must be None. coro = self.loop.create_connection(MyProto, 'python.org', 80, server_hostname='') self.assertRaises(ValueError, self.loop.run_until_complete, coro) @@ -499,6 +499,7 @@ server_hostname='python.org') self.assertRaises(ValueError, self.loop.run_until_complete, coro) + def test_create_connection_ssl_server_hostname_errors(self): # When using ssl, server_hostname may be None if host is non-empty. coro = self.loop.create_connection(MyProto, '', 80, ssl=True) self.assertRaises(ValueError, self.loop.run_until_complete, coro) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Fri Nov 1 23:14:42 2013 From: python-checkins at python.org (guido.van.rossum) Date: Fri, 1 Nov 2013 23:14:42 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_Resolve_a_few_TBD/TODOs=2E?= Message-ID: <3dBHkp3SVNz7LjZ@mail.python.org> http://hg.python.org/peps/rev/c12ea420a840 changeset: 5243:c12ea420a840 user: Guido van Rossum date: Fri Nov 01 15:14:39 2013 -0700 summary: Resolve a few TBD/TODOs. files: pep-3156.txt | 70 ++++++++++++++++----------------------- 1 files changed, 29 insertions(+), 41 deletions(-) diff --git a/pep-3156.txt b/pep-3156.txt --- a/pep-3156.txt +++ b/pep-3156.txt @@ -740,8 +740,7 @@ ``None``. Note: the name uses ``sendall`` instead of ``send``, to reflect that the semantics and signature of this method echo those of the standard library socket method ``sendall()`` rather than - ``send()``. (TBD: but maybe it would be better to emulate - ``send()`` after all? That would be better for datagram sockets.) + ``send()``. - ``sock_connect(sock, address)``. Connect to the given address. Returns a Future whose result on success will be ``None``. @@ -962,11 +961,16 @@ ``concurrent.futures.Future`` is explicitly mentioned. The supported public API is as follows, indicating the differences with PEP 3148: -- ``cancel()``. If the Future is already done (or cancelled), return - ``False``. Otherwise, change the Future's state to cancelled (this - implies done), schedule the callbacks, and return ``True``. +- ``cancel()``. If the Future is already done (or cancelled), do + nothing and return ``False``. Otherwise, this attempts to cancel + the Future and returns ``True``. If the the cancellation attempt is + successful, eventually the Future's state will change to cancelled + and the callbacks will be scheduled. For regular Futures, + cancellation will always succeed immediately; but for Tasks (see + below) the task may ignore or delay the cancellation attempt. -- ``cancelled()``. Returns ``True`` if the Future was cancelled. +- ``cancelled()``. Returns ``True`` if the Future was successfully + cancelled. - ``done()``. Returns ``True`` if the Future is done. Note that a cancelled Future is considered done too (here and everywhere). @@ -1031,8 +1035,7 @@ - ``TimeoutError``. An alias for ``concurrent.futures.TimeoutError``. May be raised by ``run_until_complete()``. -A Future is associated with the default event loop when it is created. -(TBD: Optionally pass in an alternative event loop instance?) +A Future is associated with an event loop when it is created. A ``asyncio.Future`` object is not acceptable to the ``wait()`` and ``as_completed()`` functions in the ``concurrent.futures`` package. @@ -1045,9 +1048,10 @@ and the Scheduler" below. When a Future is garbage-collected, if it has an associated exception -but neither ``result()`` nor ``exception()`` nor ``__iter__()`` has -ever been called (or the latter hasn't raised the exception yet -- -details TBD), the exception is logged. +but neither ``result()`` nor ``exception()`` has ever been called, the +exception is logged. (When a coroutine uses ``yield from`` to wait +for a Future, that Future's ``result()`` method is called once the +coroutine is resumed.) In the future (pun intended) we may unify ``asyncio.Future`` and ``concurrent.futures.Future``, e.g. by adding an ``__iter__()`` method @@ -1171,10 +1175,13 @@ - ``pause_reading()``. Suspend delivery of data to the protocol until a subsequent ``resume_reading()`` call. Between ``pause_reading()`` and ``resume_reading()``, the protocol's ``data_received()`` method - will not be called. This has no effect on ``write()``. + will not be called. - ``resume_reading()``. Restart delivery of data to the protocol via - ``data_received()``. + ``data_received()``. Note that "paused" is a binary state -- + ``pause_reading()`` should only be called when the transport is not + paused, while ``resume_reading()`` should only be called when the + transport is paused. - ``close()``. Sever the connection with the entity at the other end. Any data buffered by ``write()`` will (eventually) be transferred @@ -1187,17 +1194,7 @@ - ``abort()``. Immediately sever the connection. Any data still buffered by the transport is thrown away. Soon, the protocol's ``connection_lost()`` method will be called with ``None`` as - argument. (TBD: Distinguish in the ``connection_lost()`` argument - between ``close()``, ``abort()`` or a close initated by the other - end? Or add a transport method to inquire about this? Glyph's - proposal was to pass different exceptions for this purpose.) - -TBD: Provide flow control the other way -- the transport may need to -suspend the protocol if the amount of data buffered becomes a burden. -Proposal: let the transport call ``protocol.pause_writing()`` and -``protocol.resume_writing()`` if they exist; if they don't exist, the -protocol doesn't support flow control. (Perhaps different names -to avoid confusion between protocols and transports?) + argument. Unidirectional Stream Transports '''''''''''''''''''''''''''''''' @@ -1245,8 +1242,8 @@ transport is closed. The ``connection_refused()`` method is called before ``connection_lost()`` when ``remote_addr`` was given and an explicit negative acknowledgement was received (this is a UDP -feature). (TBD: Do we need the latter? It seems easy enough to -implement this in the protocol if it needs to make the distinction.) +feature). (TBD: Should fix `connection_refused()`` to not close the +transport.) Subprocess Transports ''''''''''''''''''''' @@ -1267,8 +1264,6 @@ of protocol is a bidirectional stream protocol. (There are no unidirectional protocols.) -(TBD: should protocol callbacks be allowed to be coroutines?) - Stream Protocols '''''''''''''''' @@ -1296,6 +1291,9 @@ ``write_eof()`` (or something equivalent). If this returns a false value (including None), the transport will close itself. If it returns a true value, closing the transport is up to the protocol. + However, for SSL/TLS connections this is ignored, because the TLS + standard requires that no more data is sent and the connection is + closed as soon as a "closure alert" is received. The default implementation returns None. @@ -1303,8 +1301,7 @@ has detected that the other end has closed the connection cleanly, or has encountered an unexpected error. In the first three cases the argument is ``None``; for an unexpected error, the argument is - the exception that caused the transport to give up. (TBD: Do we - need to distinguish between the first three cases?) + the exception that caused the transport to give up. Here is a chart indicating the order and multiplicity of calls: @@ -1313,8 +1310,7 @@ 3. ``eof_received()`` -- at most once 4. ``connection_lost()`` -- exactly once -TBD: Discuss whether user code needs to do anything to make sure that -protocol and transport aren't garbage-collected prematurely. +TBD: Document ``pause_writing()`` and ``resume_writing()``. Datagram Protocols '''''''''''''''''' @@ -1502,9 +1498,6 @@ The coroutine ``asyncio.sleep(delay)`` returns after a given time delay. -(TBD: Should the optional second argument, ``result``, be part of the -spec?) - Tasks ----- @@ -1565,17 +1558,12 @@ TO DO ===== -- Document pause/resume reading/writing. +- Document pause/resume_writing. - Document subprocess/pipe protocols/transports. - Document locks and queues. -- Describe task cancellation details (and update future cancellation - spec to promise less). - -- Explain that eof_received() shouldn't return True with SSL/TLS. - - Document SIGCHILD handling API (once it lands). - Compare all APIs with the source code to be sure there aren't any -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Fri Nov 1 23:23:26 2013 From: python-checkins at python.org (eric.snow) Date: Fri, 1 Nov 2013 23:23:26 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_=5BPEP_451=5D_Update_the_sign?= =?utf-8?q?ature_of_find=5Fspec=28=29_and_remove_supports=5Freload=28=29?= =?utf-8?q?=2E?= Message-ID: <3dBHwt3L5Zz7LjZ@mail.python.org> http://hg.python.org/peps/rev/845e08302be8 changeset: 5244:845e08302be8 user: Eric Snow date: Fri Nov 01 16:19:06 2013 -0600 summary: [PEP 451] Update the signature of find_spec() and remove supports_reload(). files: pep-0451.txt | 238 +++++++++++++++++++++++++------------- 1 files changed, 156 insertions(+), 82 deletions(-) diff --git a/pep-0451.txt b/pep-0451.txt --- a/pep-0451.txt +++ b/pep-0451.txt @@ -278,9 +278,9 @@ Other API Additions ------------------- -* importlib.find_spec(name, path=None) will work exactly the same as - importlib.find_loader() (which it replaces), but return a spec instead - of a loader. +* importlib.find_spec(name, path=None, existing=None) will work exactly + the same as importlib.find_loader() (which it replaces), but return a + spec instead of a loader. For finders: @@ -295,8 +295,6 @@ over its module execution functionality. * importlib.abc.Loader.create_module(spec) (optional) will return the module to use for loading. -* importlib.abc.Loader.supports_reload(name) (optional) will return True - (the default) if the loader supports reloading the module. For modules: @@ -374,13 +372,13 @@ finders: -* create loader -* create spec +* create/identify a loader that can load the module. +* create the spec for the module. loaders: -* create module (optional) -* execute module +* create the module (optional). +* execute the module. ModuleSpec: @@ -404,11 +402,9 @@ * Implement exec_module() on loaders, if possible. The ModuleSpec factory functions in importlib.util are intended to be -helpful for converting existing finders. from_loader() and -from_file_location() are both straight-forward utilities in this -regard. In the case where loaders already expose methods for creating -and preparing modules, ModuleSpec.from_module() may be useful to -the corresponding finder. +helpful for converting existing finders. spec_from_loader() and +spec_from_file_location() are both straight-forward utilities in this +regard. For existing loaders, exec_module() should be a relatively direct conversion from the non-boilerplate portion of load_module(). In some @@ -497,7 +493,7 @@ name = module.__spec__.name except AttributeError: name = module.__name__ - spec = find_spec(name) + spec = find_spec(name, existing=module) if sys.modules.get(name) is not module: raise ImportError @@ -509,8 +505,6 @@ # namespace loader _init_module_attrs(spec, module) return module - if not spec.loader.supports_reload(name): - raise ImportError if spec.parent and spec.parent not in sys.modules: raise ImportError @@ -520,6 +514,19 @@ finally: del _RELOADING[name] +A key point here is the switch to Loader.exec_module() means that +loaders will no longer have an easy way to know at execution time if it +is a reload or not. Before this proposal, they could simply check to +see if the module was already in sys.modules. Now, by the time +exec_module() is called during load (not reload) the import machinery +would already have placed the module in sys.modules. This is part of +the reason why find_spec() has +`the "existing" parameter `_. + +The semantics of reload will remain essentially the same as they exist +already [reload-semantics-fix]_. The impact of this PEP on some kinds +of lazy loading modules was a point of discussion. [lazy_import_concerns]_ + ModuleSpec ========== @@ -627,7 +634,7 @@ * "submodule_search_locations" can be deduced from loader.is_package() and from os.path.dirname(location) if location is a filename. -**from_loader(name, loader, \*, origin=None, is_package=None)** +**spec_from_loader(name, loader, \*, origin=None, is_package=None)** Build a spec with missing information filled in by using loader APIs. @@ -636,45 +643,6 @@ * "submodule_search_locations" can be deduced from loader.is_package() and from os.path.dirname(location) if location is a filename. -**spec_from_module(module, loader=None)** - -Build a spec based on the import-related attributes of an existing -module. The spec attributes are set to the corresponding import- -related module attributes. See the table in `Attributes`_. - -Omitted Attributes and Methods ------------------------------- - -There is no "PathModuleSpec" subclass of ModuleSpec that separates out -has_location, cached, and submodule_search_locations. While that might -make the separation cleaner, module objects don't have that distinction. -ModuleSpec will support both cases equally well. - -While "is_package" would be a simple additional attribute (aliasing -self.submodule_search_locations is not None), it perpetuates the -artificial (and mostly erroneous) distinction between modules and -packages. - -Conceivably, a ModuleSpec.load() method could optionally take a list of -modules with which to interact instead of sys.modules. That -capability is left out of this PEP, but may be pursued separately at -some other time, including relative to PEP 406 (import engine). - -Likewise load() could be leveraged to implement multi-version -imports. While interesting, doing so is outside the scope of this -proposal. - -Others: - -* Add ModuleSpec.submodules (RO-property) - returns possible submodules - relative to the spec. -* Add ModuleSpec.loaded (RO-property) - the module in sys.module, if - any. -* Add ModuleSpec.data - a descriptor that wraps the data API of the - spec's loader. -* Also see [cleaner_reload_support]_. - - Backward Compatibility ---------------------- @@ -722,15 +690,16 @@ Finders ------- -Finders are still responsible for creating the loader. That loader will +Finders are still responsible for identifying, an typically creating, +the loader that should be used to load a module. That loader will now be stored in the module spec returned by find_spec() rather than returned directly. As is currently the case without the PEP, if a loader would be costly to create, that loader can be designed to defer the cost until later. -**MetaPathFinder.find_spec(name, path=None)** +**MetaPathFinder.find_spec(name, path=None, existing=None)** -**PathEntryFinder.find_spec(name)** +**PathEntryFinder.find_spec(name, existing=None)** Finders must return ModuleSpec objects when find_spec() is called. This new method replaces find_module() and @@ -745,6 +714,42 @@ added in Python 3.3. However, the extra complexity and a less-than- explicit method name aren't worth it. +The "existing" parameter of find_spec() +--------------------------------------- + +A module object with the same name as the "name" argument (or None, the +default) should be passed in to "exising". This argument allows the +finder to build the module spec with more information than is otherwise +available. This is particularly relevant in identifying the loader to +use. + +Through find_spec() the finder will always identify the loader it +will return in the spec. In the case of reload, at this point the +finder should also decide whether or not the loader supports loading +into the module-to-be-reloaded (which was passed in to find_spec() as +"existing"). This decision may entail consulting with the loader. If +the finder determines that the loader does not support reloading that +module, it should either find another loader or return None (indicating +that it could not "find" the module). This reload decision is important +since, as noted in `How Reloading Will Work`_, loaders will no longer be +able to trivially identify a reload situation on their own. + +Two alternatives were presented to the "existing" parameter: +Loader.supports_reload() and adding "existing" to Loader.exec_module() +instead of find_spec(). supports_reload() was the initial approach to +the reload situation. [supports_reload]_ However, there was some +opposition to the loader-specific, reload-centric approach. +[supports_reload_considered_harmful]_ + +As to "existing" on exec_module(), the loader may need other information +from the existing module (or spec) during reload, more than just "does +this loader support reloading this module", that is no longer available +with the move away from load_module(). A proposal on the table was to +add something like "existing" to exec_module(). [exec_module_existing]_ +However, putting "existing" on find_spec() instead is more in line with +the goals of this PEP. Furthermore, it obviates the need for +supports_reload(). + Namespace Packages ------------------ @@ -791,13 +796,6 @@ module attributes. The fact that load_module() does is a design flaw that this proposal aims to correct. -**Loader.supports_reload(name)** - -In cases where a module should not be reloaded, Loaders should implement -supports_reload() and have it return False. If the method is defined -and returns a false value, importlib.reload() will raise an ImportError. -Otherwise reloading proceeds as normal. - Other changes: PEP 420 introduced the optional module_repr() loader method to limit @@ -837,24 +835,27 @@ * importlib.reload() will now make use of the per-module import lock. +Open Issues +=========== + +* In the `Finders`_ section, the PEP specifies returning None (or using +a different loader) when the found loader does not support loading into +an existing module (e.g during reload). An alternative to returning +None would be to raise ImportError with a message like "the loader does +not support reloading the module". This may actually be a better +approach since "could not find a loader" and "the found loader won't +work" are different situations that a single return value (None) may not +sufficiently represent. + + Reference Implementation ======================== -A reference implementation will be available at +A reference implementation is available at http://bugs.python.org/issue18864. - -Open Issues -============== - -\* Impact on some kinds of lazy loading modules. [lazy_import_concerns]_ - -This should not be an issue since the PEP does not change the semantics -of this behavior. - - Implementation Notes -==================== +-------------------- \* The implementation of this PEP needs to be cognizant of its impact on pkgutil (and setuptools). pkgutil has some generic function-based @@ -868,17 +869,90 @@ at ``module.__spec__.name``. +Rejected Additions to the PEP +============================= + +There were a few proposed additions to this proposal that did not fit +well enough into its scope. + +There is no "PathModuleSpec" subclass of ModuleSpec that separates out +has_location, cached, and submodule_search_locations. While that might +make the separation cleaner, module objects don't have that distinction. +ModuleSpec will support both cases equally well. + +While "ModuleSpec.is_package" would be a simple additional attribute +(aliasing self.submodule_search_locations is not None), it perpetuates +the artificial (and mostly erroneous) distinction between modules and +packages. + +Others left out: + +* Add ModuleSpec.submodules (RO-property) - returns possible submodules + relative to the spec. +* Add ModuleSpec.loaded (RO-property) - the module in sys.module, if + any. +* Add ModuleSpec.data - a descriptor that wraps the data API of the + spec's loader. +* Also see [cleaner_reload_support]_. + +The module spec `Factory Functions`_ could be classmethods on +ModuleSpec. However that would expose them on *all* modules via +``__spec__``, which has the potential to unnecessarily confuse +non-advanced Python users. The factory functions have a specific use +case, to support finder authors. See `ModuleSpec Users`_. + +Likewise, several other methods could be added to ModuleSpec that expose +the specific uses of module specs by the import machinery: + +* create() - a wrapper around Loader.create_module(). +* exec(module) - a wrapper around Loader.exec_module(). +* load() - an analogue to the deprecated Loader.load_module(). + +As with the factory functions, exposing these methods via +module.__spec__ is less than desireable. They would end up being an +attractive nuisance, even if only exposed as "private" attributes (as +they were in previous versions of this PEP). If someone finds a need +for these methods later, we can expose the via an appropriate API +(separate from ModuleSpec) at that point, perhaps relative to PEP 406 +(import engine). + +Conceivably, the load() method could optionally take a list of +modules with which to interact instead of sys.modules. Also, load() +could be leveraged to implement multi-version imports. Both are +interesting ideas, but definitely outside the scope of this proposal. + +Others left out: + +* Add ModuleSpec.submodules (RO-property) - returns possible submodules + relative to the spec. +* Add ModuleSpec.loaded (RO-property) - the module in sys.module, if + any. +* Add ModuleSpec.data - a descriptor that wraps the data API of the + spec's loader. +* Also see [cleaner_reload_support]_. + + References ========== -.. [ref_files_pep] http://mail.python.org/pipermail/import-sig/2013-August/000658.html +.. [ref_files_pep] + http://mail.python.org/pipermail/import-sig/2013-August/000658.html .. [import_system_docs] http://docs.python.org/3/reference/import.html -.. [cleaner_reload_support] https://mail.python.org/pipermail/import-sig/2013-September/000735.html +.. [cleaner_reload_support] + https://mail.python.org/pipermail/import-sig/2013-September/000735.html -.. [lazy_import_concerns] https://mail.python.org/pipermail/python-dev/2013-August/128129.html +.. [lazy_import_concerns] + https://mail.python.org/pipermail/python-dev/2013-August/128129.html +.. [reload-semantics-fix] http://bugs.python.org/issue19413 + +.. [supports_reload_considered_harmful] + https://mail.python.org/pipermail/python-dev/2013-October/129971.html + +.. [exec_module_existing] + https://mail.python.org/pipermail/python-dev/2013-October/129933.html Copyright ========= -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Fri Nov 1 23:38:05 2013 From: python-checkins at python.org (eric.snow) Date: Fri, 1 Nov 2013 23:38:05 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_=5BPEP_451=5D_It_pays_to_run_?= =?utf-8?q?make_before_committing=2E?= Message-ID: <3dBJFn3xFRz7LjZ@mail.python.org> http://hg.python.org/peps/rev/1ad0ddb3070b changeset: 5245:1ad0ddb3070b user: Eric Snow date: Fri Nov 01 16:33:49 2013 -0600 summary: [PEP 451] It pays to run make before committing. files: pep-0451.txt | 44 ++++++++++++++++----------------------- 1 files changed, 18 insertions(+), 26 deletions(-) diff --git a/pep-0451.txt b/pep-0451.txt --- a/pep-0451.txt +++ b/pep-0451.txt @@ -35,7 +35,7 @@ with which people may not be so familiar. For the sake of context, here is a brief summary of all three groups of terms and concepts. A more detailed explanation of the import system is found at -[import_system_docs]_. +[#import_system_docs]_. name ---- @@ -180,7 +180,7 @@ there's an API void between finders and loaders that causes undue complexity when encountered. The PEP 420 (namespace packages) implementation had to work around this. The complexity surfaced again -during recent efforts on a separate proposal. [ref_files_pep]_ +during recent efforts on a separate proposal. [#ref_files_pep]_ The `finder`_ and `loader`_ sections above detail current responsibility of both. Notably, loaders are not required to provide any of the @@ -524,8 +524,8 @@ `the "existing" parameter `_. The semantics of reload will remain essentially the same as they exist -already [reload-semantics-fix]_. The impact of this PEP on some kinds -of lazy loading modules was a point of discussion. [lazy_import_concerns]_ +already [#reload-semantics-fix]_. The impact of this PEP on some kinds +of lazy loading modules was a point of discussion. [#lazy_import_concerns]_ ModuleSpec @@ -737,15 +737,15 @@ Two alternatives were presented to the "existing" parameter: Loader.supports_reload() and adding "existing" to Loader.exec_module() instead of find_spec(). supports_reload() was the initial approach to -the reload situation. [supports_reload]_ However, there was some +the reload situation. [#supports_reload]_ However, there was some opposition to the loader-specific, reload-centric approach. -[supports_reload_considered_harmful]_ +[#supports_reload_considered_harmful]_ As to "existing" on exec_module(), the loader may need other information from the existing module (or spec) during reload, more than just "does this loader support reloading this module", that is no longer available with the move away from load_module(). A proposal on the table was to -add something like "existing" to exec_module(). [exec_module_existing]_ +add something like "existing" to exec_module(). [#exec_module_existing]_ However, putting "existing" on find_spec() instead is more in line with the goals of this PEP. Furthermore, it obviates the need for supports_reload(). @@ -838,7 +838,7 @@ Open Issues =========== -* In the `Finders`_ section, the PEP specifies returning None (or using +\* In the `Finders`_ section, the PEP specifies returning None (or using a different loader) when the found loader does not support loading into an existing module (e.g during reload). An alternative to returning None would be to raise ImportError with a message like "the loader does @@ -885,16 +885,6 @@ the artificial (and mostly erroneous) distinction between modules and packages. -Others left out: - -* Add ModuleSpec.submodules (RO-property) - returns possible submodules - relative to the spec. -* Add ModuleSpec.loaded (RO-property) - the module in sys.module, if - any. -* Add ModuleSpec.data - a descriptor that wraps the data API of the - spec's loader. -* Also see [cleaner_reload_support]_. - The module spec `Factory Functions`_ could be classmethods on ModuleSpec. However that would expose them on *all* modules via ``__spec__``, which has the potential to unnecessarily confuse @@ -929,29 +919,31 @@ any. * Add ModuleSpec.data - a descriptor that wraps the data API of the spec's loader. -* Also see [cleaner_reload_support]_. +* Also see [#cleaner_reload_support]_. References ========== -.. [ref_files_pep] +.. [#ref_files_pep] http://mail.python.org/pipermail/import-sig/2013-August/000658.html -.. [import_system_docs] http://docs.python.org/3/reference/import.html +.. [#import_system_docs] http://docs.python.org/3/reference/import.html -.. [cleaner_reload_support] +.. [#cleaner_reload_support] https://mail.python.org/pipermail/import-sig/2013-September/000735.html -.. [lazy_import_concerns] +.. [#lazy_import_concerns] https://mail.python.org/pipermail/python-dev/2013-August/128129.html -.. [reload-semantics-fix] http://bugs.python.org/issue19413 +.. [#reload-semantics-fix] http://bugs.python.org/issue19413 -.. [supports_reload_considered_harmful] +.. [#supports_reload] + https://mail.python.org/pipermail/python-dev/2013-October/129913.html +.. [#supports_reload_considered_harmful] https://mail.python.org/pipermail/python-dev/2013-October/129971.html -.. [exec_module_existing] +.. [#exec_module_existing] https://mail.python.org/pipermail/python-dev/2013-October/129933.html Copyright -- Repository URL: http://hg.python.org/peps From solipsis at pitrou.net Sat Nov 2 07:32:44 2013 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Sat, 02 Nov 2013 07:32:44 +0100 Subject: [Python-checkins] Daily reference leaks (123804a72a8f): sum=8 Message-ID: results for 123804a72a8f on branch "default" -------------------------------------------- test_asyncio leaked [0, 0, 4] memory blocks, sum=4 test_site leaked [2, -2, 2] references, sum=2 test_site leaked [2, -2, 2] memory blocks, sum=2 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/antoine/cpython/refleaks/reflogDVDFGr', '-x'] From python-checkins at python.org Sat Nov 2 09:48:35 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 09:48:35 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Added_basic_tests_for_all_tkinter_widget_options=2E?= Message-ID: <3dBYpC5vfXz7Ljf@mail.python.org> http://hg.python.org/cpython/rev/92e268f2719e changeset: 86834:92e268f2719e branch: 3.3 parent: 86814:731bdec35fdd user: Serhiy Storchaka date: Sat Nov 02 10:41:48 2013 +0200 summary: Issue #19085: Added basic tests for all tkinter widget options. files: Lib/tkinter/test/support.py | 39 + Lib/tkinter/test/test_tkinter/test_widgets.py | 919 ++++++++++ Lib/tkinter/test/test_ttk/test_widgets.py | 493 +++++- Lib/tkinter/test/widget_tests.py | 487 +++++ Misc/NEWS | 5 + 5 files changed, 1920 insertions(+), 23 deletions(-) diff --git a/Lib/tkinter/test/support.py b/Lib/tkinter/test/support.py --- a/Lib/tkinter/test/support.py +++ b/Lib/tkinter/test/support.py @@ -77,3 +77,42 @@ widget.event_generate('', x=x, y=y) widget.event_generate('', x=x, y=y) widget.event_generate('', x=x, y=y) + + +import _tkinter +tcl_version = tuple(map(int, _tkinter.TCL_VERSION.split('.'))) + +def requires_tcl(*version): + return unittest.skipUnless(tcl_version >= version, + 'requires Tcl version >= ' + '.'.join(map(str, version))) + +units = { + 'c': 72 / 2.54, # centimeters + 'i': 72, # inches + 'm': 72 / 25.4, # millimeters + 'p': 1, # points +} + +def pixels_conv(value): + return float(value[:-1]) * units[value[-1:]] + +def tcl_obj_eq(actual, expected): + if actual == expected: + return True + if isinstance(actual, _tkinter.Tcl_Obj): + if isinstance(expected, str): + return str(actual) == expected + if isinstance(actual, tuple): + if isinstance(expected, tuple): + return (len(actual) == len(expected) and + all(tcl_obj_eq(act, exp) + for act, exp in zip(actual, expected))) + return False + +def widget_eq(actual, expected): + if actual == expected: + return True + if isinstance(actual, (str, tkinter.Widget)): + if isinstance(expected, (str, tkinter.Widget)): + return str(actual) == str(expected) + return False diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py new file mode 100644 --- /dev/null +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -0,0 +1,919 @@ +import unittest +import tkinter +import os +from test.support import requires + +from tkinter.test.support import tcl_version, requires_tcl, widget_eq +from tkinter.test.widget_tests import (add_standard_options, noconv, + AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) + +requires('gui') + + +def float_round(x): + return float(round(x)) + + +class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): + _conv_pad_pixels = noconv + + def test_class(self): + widget = self.create() + self.assertEqual(widget['class'], + widget.__class__.__name__.title()) + self.checkInvalidParam(widget, 'class', 'Foo', + errmsg="can't modify -class option after widget is created") + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + + def test_colormap(self): + widget = self.create() + self.assertEqual(widget['colormap'], '') + self.checkInvalidParam(widget, 'colormap', 'new', + errmsg="can't modify -colormap option after widget is created") + widget2 = self.create(colormap='new') + self.assertEqual(widget2['colormap'], 'new') + + def test_container(self): + widget = self.create() + self.assertEqual(widget['container'], 0 if self.wantobjects else '0') + self.checkInvalidParam(widget, 'container', 1, + errmsg="can't modify -container option after widget is created") + widget2 = self.create(container=True) + self.assertEqual(widget2['container'], 1 if self.wantobjects else '1') + + def test_visual(self): + widget = self.create() + self.assertEqual(widget['visual'], '') + self.checkInvalidParam(widget, 'visual', 'default', + errmsg="can't modify -visual option after widget is created") + widget2 = self.create(visual='default') + self.assertEqual(widget2['visual'], 'default') + + + at add_standard_options(StandardOptionsTests) +class ToplevelTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'menu', 'padx', 'pady', 'relief', 'screen', + 'takefocus', 'use', 'visual', 'width', + ) + + def _create(self, **kwargs): + return tkinter.Toplevel(self.root, **kwargs) + + def test_menu(self): + widget = self.create() + menu = tkinter.Menu(self.root) + self.checkParam(widget, 'menu', menu, eq=widget_eq) + self.checkParam(widget, 'menu', '') + + def test_screen(self): + widget = self.create() + self.assertEqual(widget['screen'], '') + display = os.environ['DISPLAY'] + self.checkInvalidParam(widget, 'screen', display, + errmsg="can't modify -screen option after widget is created") + widget2 = self.create(screen=display) + self.assertEqual(widget2['screen'], display) + + def test_use(self): + widget = self.create() + self.assertEqual(widget['use'], '') + widget1 = self.create(container=True) + self.assertEqual(widget1['use'], '') + self.checkInvalidParam(widget1, 'use', '0x44022', + errmsg="can't modify -use option after widget is created") + wid = hex(widget1.winfo_id()) + widget2 = self.create(use=wid) + self.assertEqual(widget2['use'], wid) + + + at add_standard_options(StandardOptionsTests) +class FrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'relief', 'takefocus', 'visual', 'width', + ) + + def _create(self, **kwargs): + return tkinter.Frame(self.root, **kwargs) + + + at add_standard_options(StandardOptionsTests) +class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'labelanchor', 'labelwidget', 'padx', 'pady', 'relief', + 'takefocus', 'text', 'visual', 'width', + ) + + def _create(self, **kwargs): + return tkinter.LabelFrame(self.root, **kwargs) + + def test_labelanchor(self): + widget = self.create() + self.checkEnumParam(widget, 'labelanchor', + 'e', 'en', 'es', 'n', 'ne', 'nw', + 's', 'se', 'sw', 'w', 'wn', 'ws') + self.checkInvalidParam(widget, 'labelanchor', 'center') + + def test_labelwidget(self): + widget = self.create() + label = tkinter.Label(self.root, text='Mupp', name='foo') + self.checkParam(widget, 'labelwidget', label, expected='.foo') + label.destroy() + + +class AbstractLabelTest(AbstractWidgetTest, IntegerSizeTests): + _conv_pixels = noconv + + def test_highlightthickness(self): + widget = self.create() + self.checkPixelsParam(widget, 'highlightthickness', + 0, 1.3, 2.6, 6, -2, '10p') + + + at add_standard_options(StandardOptionsTests) +class LabelTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'justify', 'padx', 'pady', 'relief', 'state', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return tkinter.Label(self.root, **kwargs) + + + at add_standard_options(StandardOptionsTests) +class ButtonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', 'default', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'justify', 'overrelief', 'padx', 'pady', 'relief', + 'repeatdelay', 'repeatinterval', + 'state', 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength') + + def _create(self, **kwargs): + return tkinter.Button(self.root, **kwargs) + + def test_default(self): + widget = self.create() + self.checkEnumParam(widget, 'default', 'active', 'disabled', 'normal') + + + at add_standard_options(StandardOptionsTests) +class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', + 'offrelief', 'offvalue', 'onvalue', 'overrelief', + 'padx', 'pady', 'relief', 'selectcolor', 'selectimage', 'state', + 'takefocus', 'text', 'textvariable', + 'tristateimage', 'tristatevalue', + 'underline', 'variable', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return tkinter.Checkbutton(self.root, **kwargs) + + + def test_offvalue(self): + widget = self.create() + self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') + + def test_onvalue(self): + widget = self.create() + self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') + + + at add_standard_options(StandardOptionsTests) +class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', 'offrelief', 'overrelief', + 'padx', 'pady', 'relief', 'selectcolor', 'selectimage', 'state', + 'takefocus', 'text', 'textvariable', + 'tristateimage', 'tristatevalue', + 'underline', 'value', 'variable', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return tkinter.Radiobutton(self.root, **kwargs) + + def test_value(self): + widget = self.create() + self.checkParams(widget, 'value', 1, 2.3, '', 'any string') + + + at add_standard_options(StandardOptionsTests) +class MenubuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'compound', 'cursor', 'direction', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', 'menu', + 'padx', 'pady', 'relief', 'state', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + _conv_pixels = AbstractWidgetTest._conv_pixels + + def _create(self, **kwargs): + return tkinter.Menubutton(self.root, **kwargs) + + def test_direction(self): + widget = self.create() + self.checkEnumParam(widget, 'direction', + 'above', 'below', 'flush', 'left', 'right') + + def test_height(self): + widget = self.create() + self.checkIntegerParam(widget, 'height', 100, -100, 0, conv=str) + + test_highlightthickness = StandardOptionsTests.test_highlightthickness + + def test_image(self): + widget = self.create() + image = tkinter.PhotoImage('image1') + self.checkParam(widget, 'image', image, conv=str) + errmsg = 'image "spam" doesn\'t exist' + with self.assertRaises(tkinter.TclError) as cm: + widget['image'] = 'spam' + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + with self.assertRaises(tkinter.TclError) as cm: + widget.configure({'image': 'spam'}) + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + + def test_menu(self): + widget = self.create() + menu = tkinter.Menu(widget, name='menu') + self.checkParam(widget, 'menu', menu, eq=widget_eq) + menu.destroy() + + def test_padx(self): + widget = self.create() + self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m') + self.checkParam(widget, 'padx', -2, expected=0) + + def test_pady(self): + widget = self.create() + self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m') + self.checkParam(widget, 'pady', -2, expected=0) + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402, -402, 0, conv=str) + + +class OptionMenuTest(MenubuttonTest, unittest.TestCase): + + def _create(self, default='b', values=('a', 'b', 'c'), **kwargs): + return tkinter.OptionMenu(self.root, None, default, *values, **kwargs) + + + at add_standard_options(IntegerSizeTests, StandardOptionsTests) +class EntryTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', 'cursor', + 'disabledbackground', 'disabledforeground', + 'exportselection', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'invalidcommand', 'justify', 'readonlybackground', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'show', 'state', 'takefocus', 'textvariable', + 'validate', 'validatecommand', 'width', 'xscrollcommand', + ) + + def _create(self, **kwargs): + return tkinter.Entry(self.root, **kwargs) + + def test_disabledbackground(self): + widget = self.create() + self.checkColorParam(widget, 'disabledbackground') + + def test_insertborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, -2) + self.checkParam(widget, 'insertborderwidth', 2, expected=1) + self.checkParam(widget, 'insertborderwidth', '10p', expected=1) + + def test_insertwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertwidth', 1.3, 3.6, '10p') + if tcl_version[:2] == (8, 5): + self.checkParam(widget, 'insertwidth', 0.9, expected=2) + else: + self.checkParam(widget, 'insertwidth', 0.9, expected=1) + self.checkParam(widget, 'insertwidth', 0.1, expected=2) + self.checkParam(widget, 'insertwidth', -2, expected=2) + + def test_invalidcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'invalidcommand') + self.checkCommandParam(widget, 'invcmd') + + def test_readonlybackground(self): + widget = self.create() + self.checkColorParam(widget, 'readonlybackground') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', '*') + self.checkParam(widget, 'show', '') + self.checkParam(widget, 'show', ' ') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', + 'disabled', 'normal', 'readonly') + + def test_validate(self): + widget = self.create() + self.checkEnumParam(widget, 'validate', + 'all', 'key', 'focus', 'focusin', 'focusout', 'none') + + def test_validatecommand(self): + widget = self.create() + self.checkCommandParam(widget, 'validatecommand') + self.checkCommandParam(widget, 'vcmd') + + + at add_standard_options(StandardOptionsTests) +class SpinboxTest(EntryTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'background', 'borderwidth', + 'buttonbackground', 'buttoncursor', 'buttondownrelief', 'buttonuprelief', + 'command', 'cursor', 'disabledbackground', 'disabledforeground', + 'exportselection', 'font', 'foreground', 'format', 'from', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'increment', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'invalidcommand', 'justify', 'relief', 'readonlybackground', + 'repeatdelay', 'repeatinterval', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'state', 'takefocus', 'textvariable', 'to', + 'validate', 'validatecommand', 'values', + 'width', 'wrap', 'xscrollcommand', + ) + + def _create(self, **kwargs): + return tkinter.Spinbox(self.root, **kwargs) + + test_show = None + + def test_buttonbackground(self): + widget = self.create() + self.checkColorParam(widget, 'buttonbackground') + + def test_buttoncursor(self): + widget = self.create() + self.checkCursorParam(widget, 'buttoncursor') + + def test_buttondownrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'buttondownrelief') + + def test_buttonuprelief(self): + widget = self.create() + self.checkReliefParam(widget, 'buttonuprelief') + + def test_format(self): + widget = self.create() + self.checkParam(widget, 'format', '%2f') + self.checkParam(widget, 'format', '%2.2f') + self.checkParam(widget, 'format', '%.2f') + self.checkParam(widget, 'format', '%2.f') + self.checkInvalidParam(widget, 'format', '%2e-1f') + self.checkInvalidParam(widget, 'format', '2.2') + self.checkInvalidParam(widget, 'format', '%2.-2f') + self.checkParam(widget, 'format', '%-2.02f') + self.checkParam(widget, 'format', '% 2.02f') + self.checkParam(widget, 'format', '% -2.200f') + self.checkParam(widget, 'format', '%09.200f') + self.checkInvalidParam(widget, 'format', '%d') + + def test_from(self): + widget = self.create() + self.checkParam(widget, 'to', 100.0) + self.checkFloatParam(widget, 'from', -10, 10.2, 11.7) + self.checkInvalidParam(widget, 'from', 200, + errmsg='-to value must be greater than -from value') + + def test_increment(self): + widget = self.create() + self.checkFloatParam(widget, 'increment', -1, 1, 10.2, 12.8, 0) + + def test_to(self): + widget = self.create() + self.checkParam(widget, 'from', -100.0) + self.checkFloatParam(widget, 'to', -10, 10.2, 11.7) + self.checkInvalidParam(widget, 'to', -200, + errmsg='-to value must be greater than -from value') + + def test_values(self): + # XXX + widget = self.create() + self.assertEqual(widget['values'], '') + self.checkParam(widget, 'values', 'mon tue wed thur') + self.checkParam(widget, 'values', ('mon', 'tue', 'wed', 'thur'), + expected='mon tue wed thur') + self.checkParam(widget, 'values', (42, 3.14, '', 'any string'), + expected='42 3.14 {} {any string}') + self.checkParam(widget, 'values', '') + + def test_wrap(self): + widget = self.create() + self.checkBooleanParam(widget, 'wrap') + + + at add_standard_options(StandardOptionsTests) +class TextTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'autoseparators', 'background', 'blockcursor', 'borderwidth', + 'cursor', 'endline', 'exportselection', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'inactiveselectbackground', 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertunfocussed', 'insertwidth', + 'maxundo', 'padx', 'pady', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'setgrid', 'spacing1', 'spacing2', 'spacing3', 'startline', 'state', + 'tabs', 'tabstyle', 'takefocus', 'undo', 'width', 'wrap', + 'xscrollcommand', 'yscrollcommand', + ) + if tcl_version < (8, 5): + wantobjects = False + + def _create(self, **kwargs): + return tkinter.Text(self.root, **kwargs) + + def test_autoseparators(self): + widget = self.create() + self.checkBooleanParam(widget, 'autoseparators') + + @requires_tcl(8, 5) + def test_blockcursor(self): + widget = self.create() + self.checkBooleanParam(widget, 'blockcursor') + + @requires_tcl(8, 5) + def test_endline(self): + widget = self.create() + text = '\n'.join('Line %d' for i in range(100)) + widget.insert('end', text) + self.checkParam(widget, 'endline', 200, expected='') + self.checkParam(widget, 'endline', -10, expected='') + self.checkInvalidParam(widget, 'endline', 'spam', + errmsg='expected integer but got "spam"') + self.checkParam(widget, 'endline', 50) + self.checkParam(widget, 'startline', 15) + self.checkInvalidParam(widget, 'endline', 10, + errmsg='-startline must be less than or equal to -endline') + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, '3c') + self.checkParam(widget, 'height', -100, expected=1) + self.checkParam(widget, 'height', 0, expected=1) + + def test_maxundo(self): + widget = self.create() + self.checkIntegerParam(widget, 'maxundo', 0, 5, -1) + + @requires_tcl(8, 5) + def test_inactiveselectbackground(self): + widget = self.create() + self.checkColorParam(widget, 'inactiveselectbackground') + + @requires_tcl(8, 6) + def test_insertunfocussed(self): + widget = self.create() + self.checkEnumParam(widget, 'insertunfocussed', + 'hollow', 'none', 'solid') + + def test_selectborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'selectborderwidth', + 1.3, 2.6, -2, '10p', conv=False, + keep_orig=tcl_version >= (8, 5)) + + def test_spacing1(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing1', 20, 21.4, 22.6, '0.5c') + self.checkParam(widget, 'spacing1', -5, expected=0) + + def test_spacing2(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing2', 5, 6.4, 7.6, '0.1c') + self.checkParam(widget, 'spacing2', -1, expected=0) + + def test_spacing3(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing3', 20, 21.4, 22.6, '0.5c') + self.checkParam(widget, 'spacing3', -10, expected=0) + + @requires_tcl(8, 5) + def test_startline(self): + widget = self.create() + text = '\n'.join('Line %d' for i in range(100)) + widget.insert('end', text) + self.checkParam(widget, 'startline', 200, expected='') + self.checkParam(widget, 'startline', -10, expected='') + self.checkInvalidParam(widget, 'startline', 'spam', + errmsg='expected integer but got "spam"') + self.checkParam(widget, 'startline', 10) + self.checkParam(widget, 'endline', 50) + self.checkInvalidParam(widget, 'startline', 70, + errmsg='-startline must be less than or equal to -endline') + + def test_state(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkParams(widget, 'state', 'disabled', 'normal') + else: + self.checkEnumParam(widget, 'state', 'disabled', 'normal') + + def test_tabs(self): + widget = self.create() + self.checkParam(widget, 'tabs', (10.2, 20.7, '1i', '2i')) + self.checkParam(widget, 'tabs', '10.2 20.7 1i 2i', + expected=('10.2', '20.7', '1i', '2i')) + self.checkParam(widget, 'tabs', '2c left 4c 6c center', + expected=('2c', 'left', '4c', '6c', 'center')) + self.checkInvalidParam(widget, 'tabs', 'spam', + errmsg='bad screen distance "spam"', + keep_orig=tcl_version >= (8, 5)) + + @requires_tcl(8, 5) + def test_tabstyle(self): + widget = self.create() + self.checkEnumParam(widget, 'tabstyle', 'tabular', 'wordprocessor') + + def test_undo(self): + widget = self.create() + self.checkBooleanParam(widget, 'undo') + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402) + self.checkParam(widget, 'width', -402, expected=1) + self.checkParam(widget, 'width', 0, expected=1) + + def test_wrap(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkParams(widget, 'wrap', 'char', 'none', 'word') + else: + self.checkEnumParam(widget, 'wrap', 'char', 'none', 'word') + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class CanvasTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'closeenough', 'confine', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'relief', 'scrollregion', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'state', 'takefocus', + 'xscrollcommand', 'xscrollincrement', + 'yscrollcommand', 'yscrollincrement', 'width', + ) + + _conv_pixels = round + wantobjects = False + + def _create(self, **kwargs): + return tkinter.Canvas(self.root, **kwargs) + + def test_closeenough(self): + widget = self.create() + self.checkFloatParam(widget, 'closeenough', 24, 2.4, 3.6, -3, + conv=float) + + def test_confine(self): + widget = self.create() + self.checkBooleanParam(widget, 'confine') + + def test_scrollregion(self): + widget = self.create() + self.checkParam(widget, 'scrollregion', '0 0 200 150') + self.checkParam(widget, 'scrollregion', (0, 0, 200, 150), + expected='0 0 200 150') + self.checkParam(widget, 'scrollregion', '') + self.checkInvalidParam(widget, 'scrollregion', 'spam', + errmsg='bad scrollRegion "spam"') + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 'spam')) + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200)) + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 150, 0)) + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'disabled', 'normal', + errmsg='bad state value "{}": must be normal or disabled') + + def test_xscrollincrement(self): + widget = self.create() + self.checkPixelsParam(widget, 'xscrollincrement', + 40, 0, 41.2, 43.6, -40, '0.5i') + + def test_yscrollincrement(self): + widget = self.create() + self.checkPixelsParam(widget, 'yscrollincrement', + 10, 0, 11.2, 13.6, -10, '0.1i') + + + at add_standard_options(IntegerSizeTests, StandardOptionsTests) +class ListboxTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activestyle', 'background', 'borderwidth', 'cursor', + 'disabledforeground', 'exportselection', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'listvariable', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'selectmode', 'setgrid', 'state', + 'takefocus', 'width', 'xscrollcommand', 'yscrollcommand', + ) + + def _create(self, **kwargs): + return tkinter.Listbox(self.root, **kwargs) + + def test_activestyle(self): + widget = self.create() + self.checkEnumParam(widget, 'activestyle', + 'dotbox', 'none', 'underline') + + def test_listvariable(self): + widget = self.create() + var = tkinter.DoubleVar() + self.checkVariableParam(widget, 'listvariable', var) + + def test_selectmode(self): + widget = self.create() + self.checkParam(widget, 'selectmode', 'single') + self.checkParam(widget, 'selectmode', 'browse') + self.checkParam(widget, 'selectmode', 'multiple') + self.checkParam(widget, 'selectmode', 'extended') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'disabled', 'normal') + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class ScaleTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'background', 'bigincrement', 'borderwidth', + 'command', 'cursor', 'digits', 'font', 'foreground', 'from', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'label', 'length', 'orient', 'relief', + 'repeatdelay', 'repeatinterval', + 'resolution', 'showvalue', 'sliderlength', 'sliderrelief', 'state', + 'takefocus', 'tickinterval', 'to', 'troughcolor', 'variable', 'width', + ) + default_orient = 'vertical' + + def _create(self, **kwargs): + return tkinter.Scale(self.root, **kwargs) + + def test_bigincrement(self): + widget = self.create() + self.checkFloatParam(widget, 'bigincrement', 12.4, 23.6, -5) + + def test_digits(self): + widget = self.create() + self.checkIntegerParam(widget, 'digits', 5, 0) + + def test_from(self): + widget = self.create() + self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=float_round) + + def test_label(self): + widget = self.create() + self.checkParam(widget, 'label', 'any string') + self.checkParam(widget, 'label', '') + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') + + def test_resolution(self): + widget = self.create() + self.checkFloatParam(widget, 'resolution', 4.2, 0, 6.7, -2) + + def test_showvalue(self): + widget = self.create() + self.checkBooleanParam(widget, 'showvalue') + + def test_sliderlength(self): + widget = self.create() + self.checkPixelsParam(widget, 'sliderlength', + 10, 11.2, 15.6, -3, '3m') + + def test_sliderrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'sliderrelief') + + def test_tickinterval(self): + widget = self.create() + self.checkFloatParam(widget, 'tickinterval', 1, 4.3, 7.6, 0, + conv=float_round) + self.checkParam(widget, 'tickinterval', -2, expected=2, + conv=float_round) + + def test_to(self): + widget = self.create() + self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, + conv=float_round) + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activerelief', + 'background', 'borderwidth', + 'command', 'cursor', 'elementborderwidth', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'jump', 'orient', 'relief', + 'repeatdelay', 'repeatinterval', + 'takefocus', 'troughcolor', 'width', + ) + _conv_pixels = round + wantobjects = False + default_orient = 'vertical' + + def _create(self, **kwargs): + return tkinter.Scrollbar(self.root, **kwargs) + + def test_activerelief(self): + widget = self.create() + self.checkReliefParam(widget, 'activerelief') + + def test_elementborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'elementborderwidth', 4.3, 5.6, -2, '1m') + + def test_orient(self): + widget = self.create() + self.checkEnumParam(widget, 'orient', 'vertical', 'horizontal', + errmsg='bad orientation "{}": must be vertical or horizontal') + + + at add_standard_options(StandardOptionsTests) +class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', 'cursor', + 'handlepad', 'handlesize', 'height', + 'opaqueresize', 'orient', 'relief', + 'sashcursor', 'sashpad', 'sashrelief', 'sashwidth', + 'showhandle', 'width', + ) + default_orient = 'horizontal' + + def _create(self, **kwargs): + return tkinter.PanedWindow(self.root, **kwargs) + + def test_handlepad(self): + widget = self.create() + self.checkPixelsParam(widget, 'handlepad', 5, 6.4, 7.6, -3, '1m') + + def test_handlesize(self): + widget = self.create() + self.checkPixelsParam(widget, 'handlesize', 8, 9.4, 10.6, -3, '2m', + conv=noconv) + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i', + conv=noconv) + + def test_opaqueresize(self): + widget = self.create() + self.checkBooleanParam(widget, 'opaqueresize') + + def test_sashcursor(self): + widget = self.create() + self.checkCursorParam(widget, 'sashcursor') + + def test_sashpad(self): + widget = self.create() + self.checkPixelsParam(widget, 'sashpad', 8, 1.3, 2.6, -2, '2m') + + def test_sashrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'sashrelief') + + def test_sashwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'sashwidth', 10, 11.1, 15.6, -3, '1m', + conv=noconv) + + def test_showhandle(self): + widget = self.create() + self.checkBooleanParam(widget, 'showhandle') + + def test_width(self): + widget = self.create() + self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i', + conv=noconv) + + + at add_standard_options(StandardOptionsTests) +class MenuTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeborderwidth', 'activeforeground', + 'background', 'borderwidth', 'cursor', + 'disabledforeground', 'font', 'foreground', + 'postcommand', 'relief', 'selectcolor', 'takefocus', + 'tearoff', 'tearoffcommand', 'title', 'type', + ) + _conv_pixels = noconv + + def _create(self, **kwargs): + return tkinter.Menu(self.root, **kwargs) + + def test_postcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'postcommand') + + def test_tearoff(self): + widget = self.create() + self.checkBooleanParam(widget, 'tearoff') + + def test_tearoffcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'tearoffcommand') + + def test_title(self): + widget = self.create() + self.checkParam(widget, 'title', 'any string') + + def test_type(self): + widget = self.create() + self.checkEnumParam(widget, 'type', + 'normal', 'tearoff', 'menubar') + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class MessageTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'anchor', 'aspect', 'background', 'borderwidth', + 'cursor', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'justify', 'padx', 'pady', 'relief', + 'takefocus', 'text', 'textvariable', 'width', + ) + _conv_pad_pixels = noconv + + def _create(self, **kwargs): + return tkinter.Message(self.root, **kwargs) + + def test_aspect(self): + widget = self.create() + self.checkIntegerParam(widget, 'aspect', 250, 0, -300) + + +tests_gui = ( + ButtonTest, CanvasTest, CheckbuttonTest, EntryTest, + FrameTest, LabelFrameTest,LabelTest, ListboxTest, + MenubuttonTest, MenuTest, MessageTest, OptionMenuTest, + PanedWindowTest, RadiobuttonTest, ScaleTest, ScrollbarTest, + SpinboxTest, TextTest, ToplevelTest, +) + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1,15 +1,57 @@ import unittest import tkinter -import os from tkinter import ttk -from test.support import requires, run_unittest +from test.support import requires import sys import tkinter.test.support as support -from tkinter.test.test_ttk.test_functions import MockTclObj, MockStateSpec +from tkinter.test.test_ttk.test_functions import MockTclObj +from tkinter.test.support import tcl_version +from tkinter.test.widget_tests import (add_standard_options, noconv, + AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) requires('gui') + +class StandardTtkOptionsTests(StandardOptionsTests): + + def test_class(self): + widget = self.create() + self.assertEqual(widget['class'], '') + errmsg='attempt to change read-only option' + if tcl_version < (8, 6): + errmsg='Attempt to change read-only option' + self.checkInvalidParam(widget, 'class', 'Foo', errmsg=errmsg) + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + + def test_padding(self): + widget = self.create() + self.checkParam(widget, 'padding', 0, expected=('0',)) + self.checkParam(widget, 'padding', 5, expected=('5',)) + self.checkParam(widget, 'padding', (5, 6), expected=('5', '6')) + self.checkParam(widget, 'padding', (5, 6, 7), + expected=('5', '6', '7')) + self.checkParam(widget, 'padding', (5, 6, 7, 8), + expected=('5', '6', '7', '8')) + self.checkParam(widget, 'padding', ('5p', '6p', '7p', '8p')) + self.checkParam(widget, 'padding', (), expected='') + + def test_style(self): + widget = self.create() + self.assertEqual(widget['style'], '') + errmsg = 'Layout Foo not found' + if hasattr(self, 'default_orient'): + errmsg = ('Layout %s.Foo not found' % + getattr(self, 'default_orient').title()) + self.checkInvalidParam(widget, 'style', 'Foo', + errmsg=errmsg) + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + # XXX + pass + + class WidgetTest(unittest.TestCase): """Tests methods available in every ttk widget.""" @@ -73,7 +115,112 @@ self.assertEqual(self.widget.state(), ('active', )) -class ButtonTest(unittest.TestCase): +class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): + _conv_pixels = noconv + + + at add_standard_options(StandardTtkOptionsTests) +class FrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'borderwidth', 'class', 'cursor', 'height', + 'padding', 'relief', 'style', 'takefocus', + 'width', + ) + + def _create(self, **kwargs): + return ttk.Frame(self.root, **kwargs) + + + at add_standard_options(StandardTtkOptionsTests) +class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'borderwidth', 'class', 'cursor', 'height', + 'labelanchor', 'labelwidget', + 'padding', 'relief', 'style', 'takefocus', + 'text', 'underline', 'width', + ) + + def _create(self, **kwargs): + return ttk.LabelFrame(self.root, **kwargs) + + def test_labelanchor(self): + widget = self.create() + self.checkEnumParam(widget, 'labelanchor', + 'e', 'en', 'es', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'wn', 'ws', + errmsg='Bad label anchor specification {}') + self.checkInvalidParam(widget, 'labelanchor', 'center') + + def test_labelwidget(self): + widget = self.create() + label = ttk.Label(self.root, text='Mupp', name='foo') + self.checkParam(widget, 'labelwidget', label, expected='.foo') + label.destroy() + + +class AbstractLabelTest(AbstractWidgetTest): + + def checkImageParam(self, widget, name): + image = tkinter.PhotoImage('image1') + image2 = tkinter.PhotoImage('image2') + self.checkParam(widget, name, image, expected=('image1',)) + self.checkParam(widget, name, 'image1', expected=('image1',)) + self.checkParam(widget, name, (image,), expected=('image1',)) + self.checkParam(widget, name, (image, 'active', image2), + expected=('image1', 'active', 'image2')) + self.checkParam(widget, name, 'image1 active image2', + expected=('image1', 'active', 'image2')) + self.checkInvalidParam(widget, name, 'spam', + errmsg='image "spam" doesn\'t exist') + + def test_compound(self): + widget = self.create() + self.checkEnumParam(widget, 'compound', + 'none', 'text', 'image', 'center', + 'top', 'bottom', 'left', 'right') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', 'active', 'disabled', 'normal') + + def test_width(self): + widget = self.create() + self.checkParams(widget, 'width', 402, -402, 0) + + + at add_standard_options(StandardTtkOptionsTests) +class LabelTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'anchor', 'background', + 'class', 'compound', 'cursor', 'font', 'foreground', + 'image', 'justify', 'padding', 'relief', 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + _conv_pixels = noconv + + def _create(self, **kwargs): + return ttk.Label(self.root, **kwargs) + + def test_font(self): + widget = self.create() + self.checkParam(widget, 'font', + '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') + + + at add_standard_options(StandardTtkOptionsTests) +class ButtonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', 'default', + 'image', 'state', 'style', 'takefocus', 'text', 'textvariable', + 'underline', 'width', + ) + + def _create(self, **kwargs): + return ttk.Button(self.root, **kwargs) + + def test_default(self): + widget = self.create() + self.checkEnumParam(widget, 'default', 'normal', 'active', 'disabled') def test_invoke(self): success = [] @@ -82,7 +229,27 @@ self.assertTrue(success) -class CheckbuttonTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', + 'image', + 'offvalue', 'onvalue', + 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'variable', 'width', + ) + + def _create(self, **kwargs): + return ttk.Checkbutton(self.root, **kwargs) + + def test_offvalue(self): + widget = self.create() + self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') + + def test_onvalue(self): + widget = self.create() + self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') def test_invoke(self): success = [] @@ -105,21 +272,40 @@ cbtn['command'] = '' res = cbtn.invoke() - self.assertEqual(str(res), '') + self.assertFalse(str(res)) self.assertFalse(len(success) > 1) self.assertEqual(cbtn['offvalue'], cbtn.tk.globalgetvar(cbtn['variable'])) -class ComboboxTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class ComboboxTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'exportselection', 'height', + 'justify', 'postcommand', 'state', 'style', + 'takefocus', 'textvariable', 'values', 'width', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.combo = ttk.Combobox() + self.combo = self.create() def tearDown(self): self.combo.destroy() support.root_withdraw() + super().tearDown() + + def _create(self, **kwargs): + return ttk.Combobox(self.root, **kwargs) + + def test_height(self): + widget = self.create() + self.checkParams(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', 'active', 'disabled', 'normal') def _show_drop_down_listbox(self): width = self.combo.winfo_width() @@ -167,8 +353,16 @@ self.assertEqual(self.combo.get(), getval) self.assertEqual(self.combo.current(), currval) + self.assertEqual(self.combo['values'], + () if tcl_version < (8, 5) else '') check_get_current('', -1) + self.checkParam(self.combo, 'values', 'mon tue wed thur', + expected=('mon', 'tue', 'wed', 'thur')) + self.checkParam(self.combo, 'values', ('mon', 'tue', 'wed', 'thur')) + self.checkParam(self.combo, 'values', (42, 3.14, '', 'any string')) + self.checkParam(self.combo, 'values', '', expected=()) + self.combo['values'] = ['a', 1, 'c'] self.combo.set('c') @@ -209,15 +403,52 @@ combo2.destroy() -class EntryTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class EntryTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'class', 'cursor', + 'exportselection', 'font', + 'invalidcommand', 'justify', + 'show', 'state', 'style', 'takefocus', 'textvariable', + 'validate', 'validatecommand', 'width', 'xscrollcommand', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.entry = ttk.Entry() + self.entry = self.create() def tearDown(self): self.entry.destroy() support.root_withdraw() + super().tearDown() + + def _create(self, **kwargs): + return ttk.Entry(self.root, **kwargs) + + def test_invalidcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'invalidcommand') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', '*') + self.checkParam(widget, 'show', '') + self.checkParam(widget, 'show', ' ') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', + 'disabled', 'normal', 'readonly') + + def test_validate(self): + widget = self.create() + self.checkEnumParam(widget, 'validate', + 'all', 'key', 'focus', 'focusin', 'focusout', 'none') + + def test_validatecommand(self): + widget = self.create() + self.checkCommandParam(widget, 'validatecommand') def test_bbox(self): @@ -313,16 +544,36 @@ self.assertEqual(self.entry.state(), ()) -class PanedwindowTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'height', + 'orient', 'style', 'takefocus', 'width', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.paned = ttk.Panedwindow() + self.paned = self.create() def tearDown(self): self.paned.destroy() support.root_withdraw() + super().tearDown() + def _create(self, **kwargs): + return ttk.PanedWindow(self.root, **kwargs) + + def test_orient(self): + widget = self.create() + self.assertEqual(str(widget['orient']), 'vertical') + errmsg='attempt to change read-only option' + if tcl_version < (8, 6): + errmsg='Attempt to change read-only option' + self.checkInvalidParam(widget, 'orient', 'horizontal', + errmsg=errmsg) + widget2 = self.create(orient='horizontal') + self.assertEqual(str(widget2['orient']), 'horizontal') def test_add(self): # attempt to add a child that is not a direct child of the paned window @@ -432,7 +683,22 @@ self.assertTrue(isinstance(self.paned.sashpos(0), int)) -class RadiobuttonTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', + 'image', + 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'value', 'variable', 'width', + ) + + def _create(self, **kwargs): + return ttk.Radiobutton(self.root, **kwargs) + + def test_value(self): + widget = self.create() + self.checkParams(widget, 'value', 1, 2.3, '', 'any string') def test_invoke(self): success = [] @@ -462,19 +728,68 @@ self.assertEqual(str(cbtn['variable']), str(cbtn2['variable'])) +class MenubuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'compound', 'cursor', 'direction', + 'image', 'menu', 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', + ) -class ScaleTest(unittest.TestCase): + def _create(self, **kwargs): + return ttk.Menubutton(self.root, **kwargs) + + def test_direction(self): + widget = self.create() + self.checkEnumParam(widget, 'direction', + 'above', 'below', 'left', 'right', 'flush') + + def test_menu(self): + widget = self.create() + menu = tkinter.Menu(widget, name='menu') + self.checkParam(widget, 'menu', menu, conv=str) + menu.destroy() + + + at add_standard_options(StandardTtkOptionsTests) +class ScaleTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'cursor', 'from', 'length', + 'orient', 'style', 'takefocus', 'to', 'value', 'variable', + ) + _conv_pixels = noconv + default_orient = 'horizontal' def setUp(self): + super().setUp() support.root_deiconify() - self.scale = ttk.Scale() + self.scale = self.create() self.scale.pack() self.scale.update() def tearDown(self): self.scale.destroy() support.root_withdraw() + super().tearDown() + def _create(self, **kwargs): + return ttk.Scale(self.root, **kwargs) + + def test_from(self): + widget = self.create() + self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=False) + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') + + def test_to(self): + widget = self.create() + self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, conv=False) + + def test_value(self): + widget = self.create() + self.checkFloatParam(widget, 'value', 300, 14.9, 15.1, -10, conv=False) def test_custom_event(self): failure = [1, 1, 1] # will need to be empty @@ -539,11 +854,64 @@ self.assertRaises(tkinter.TclError, self.scale.set, None) -class NotebookTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class ProgressbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'orient', 'length', + 'mode', 'maximum', 'phase', + 'style', 'takefocus', 'value', 'variable', + ) + _conv_pixels = noconv + default_orient = 'horizontal' + + def _create(self, **kwargs): + return ttk.Progressbar(self.root, **kwargs) + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 100.1, 56.7, '2i') + + def test_maximum(self): + widget = self.create() + self.checkFloatParam(widget, 'maximum', 150.2, 77.7, 0, -10, conv=False) + + def test_mode(self): + widget = self.create() + self.checkEnumParam(widget, 'mode', 'determinate', 'indeterminate') + + def test_phase(self): + # XXX + pass + + def test_value(self): + widget = self.create() + self.checkFloatParam(widget, 'value', 150.2, 77.7, 0, -10, + conv=False) + + + at unittest.skipIf(sys.platform == 'darwin', + 'ttk.Scrollbar is special on MacOSX') + at add_standard_options(StandardTtkOptionsTests) +class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'cursor', 'orient', 'style', 'takefocus', + ) + default_orient = 'vertical' + + def _create(self, **kwargs): + return ttk.Scrollbar(self.root, **kwargs) + + + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class NotebookTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'height', 'padding', 'style', 'takefocus', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.nb = ttk.Notebook(padding=0) + self.nb = self.create(padding=0) self.child1 = ttk.Label() self.child2 = ttk.Label() self.nb.add(self.child1, text='a') @@ -554,7 +922,10 @@ self.child2.destroy() self.nb.destroy() support.root_withdraw() + super().tearDown() + def _create(self, **kwargs): + return ttk.Notebook(self.root, **kwargs) def test_tab_identifiers(self): self.nb.forget(0) @@ -746,16 +1117,68 @@ self.assertEqual(self.nb.select(), str(self.child1)) -class TreeviewTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class TreeviewTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'columns', 'cursor', 'displaycolumns', + 'height', 'padding', 'selectmode', 'show', + 'style', 'takefocus', 'xscrollcommand', 'yscrollcommand', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.tv = ttk.Treeview(padding=0) + self.tv = self.create(padding=0) def tearDown(self): self.tv.destroy() support.root_withdraw() + super().tearDown() + def _create(self, **kwargs): + return ttk.Treeview(self.root, **kwargs) + + def test_columns(self): + widget = self.create() + self.checkParam(widget, 'columns', 'a b c', + expected=('a', 'b', 'c')) + self.checkParam(widget, 'columns', ('a', 'b', 'c')) + self.checkParam(widget, 'columns', ()) + + def test_displaycolumns(self): + widget = self.create() + widget['columns'] = ('a', 'b', 'c') + self.checkParam(widget, 'displaycolumns', 'b a c', + expected=('b', 'a', 'c')) + self.checkParam(widget, 'displaycolumns', ('b', 'a', 'c')) + self.checkParam(widget, 'displaycolumns', '#all', + expected=('#all',)) + self.checkParam(widget, 'displaycolumns', (2, 1, 0)) + self.checkInvalidParam(widget, 'displaycolumns', ('a', 'b', 'd'), + errmsg='Invalid column index d') + self.checkInvalidParam(widget, 'displaycolumns', (1, 2, 3), + errmsg='Column index 3 out of bounds') + self.checkInvalidParam(widget, 'displaycolumns', (1, -2), + errmsg='Column index -2 out of bounds') + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, -100, 0, '3c', conv=False) + self.checkPixelsParam(widget, 'height', 101.2, 102.6, conv=noconv) + + def test_selectmode(self): + widget = self.create() + self.checkEnumParam(widget, 'selectmode', + 'none', 'browse', 'extended') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', 'tree headings', + expected=('tree', 'headings')) + self.checkParam(widget, 'show', ('tree', 'headings')) + self.checkParam(widget, 'show', ('headings', 'tree')) + self.checkParam(widget, 'show', 'tree', expected=('tree',)) + self.checkParam(widget, 'show', 'headings', expected=('headings',)) def test_bbox(self): self.tv.pack() @@ -1149,11 +1572,35 @@ self.assertTrue(isinstance(self.tv.tag_configure('test'), dict)) + at add_standard_options(StandardTtkOptionsTests) +class SeparatorTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'orient', 'style', 'takefocus', + # 'state'? + ) + default_orient = 'horizontal' + + def _create(self, **kwargs): + return ttk.Separator(self.root, **kwargs) + + + at add_standard_options(StandardTtkOptionsTests) +class SizegripTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'style', 'takefocus', + # 'state'? + ) + + def _create(self, **kwargs): + return ttk.Sizegrip(self.root, **kwargs) + tests_gui = ( - WidgetTest, ButtonTest, CheckbuttonTest, RadiobuttonTest, - ComboboxTest, EntryTest, PanedwindowTest, ScaleTest, NotebookTest, - TreeviewTest + ButtonTest, CheckbuttonTest, ComboboxTest, EntryTest, + FrameTest, LabelFrameTest, LabelTest, MenubuttonTest, + NotebookTest, PanedWindowTest, ProgressbarTest, + RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest, + SizegripTest, TreeviewTest, WidgetTest, ) if __name__ == "__main__": - run_unittest(*tests_gui) + unittest.main() diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py new file mode 100644 --- /dev/null +++ b/Lib/tkinter/test/widget_tests.py @@ -0,0 +1,487 @@ +# Common tests for test_tkinter/test_widgets.py and test_ttk/test_widgets.py + +import tkinter +from tkinter.ttk import setup_master, Scale +from tkinter.test.support import (tcl_version, requires_tcl, pixels_conv, + tcl_obj_eq) + + +noconv = str if tcl_version < (8, 5) else False + +_sentinel = object() + +class AbstractWidgetTest: + _conv_pixels = round if tcl_version[:2] != (8, 5) else int + _conv_pad_pixels = None + wantobjects = True + + def setUp(self): + self.root = setup_master() + self.scaling = float(self.root.call('tk', 'scaling')) + if not self.root.wantobjects(): + self.wantobjects = False + + def create(self, **kwargs): + widget = self._create(**kwargs) + self.addCleanup(widget.destroy) + return widget + + def assertEqual2(self, actual, expected, msg=None, eq=object.__eq__): + if eq(actual, expected): + return + self.assertEqual(actual, expected, msg) + + def checkParam(self, widget, name, value, *, expected=_sentinel, + conv=False, eq=None): + widget[name] = value + if expected is _sentinel: + expected = value + if conv: + expected = conv(expected) + if not self.wantobjects: + if isinstance(expected, tuple): + expected = tkinter._join(expected) + else: + expected = str(expected) + if eq is None: + eq = tcl_obj_eq + self.assertEqual2(widget[name], expected, eq=eq) + self.assertEqual2(widget.cget(name), expected, eq=eq) + # XXX + if not isinstance(widget, Scale): + t = widget.configure(name) + self.assertEqual(len(t), 5) + ## XXX + if not isinstance(t[4], tuple): + self.assertEqual2(t[4], expected, eq=eq) + + def checkInvalidParam(self, widget, name, value, errmsg=None, *, + keep_orig=True): + orig = widget[name] + if errmsg is not None: + errmsg = errmsg.format(value) + with self.assertRaises(tkinter.TclError) as cm: + widget[name] = value + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + if keep_orig: + self.assertEqual(widget[name], orig) + else: + widget[name] = orig + with self.assertRaises(tkinter.TclError) as cm: + widget.configure({name: value}) + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + if keep_orig: + self.assertEqual(widget[name], orig) + else: + widget[name] = orig + + def checkParams(self, widget, name, *values, **kwargs): + for value in values: + self.checkParam(widget, name, value, **kwargs) + + def checkIntegerParam(self, widget, name, *values, **kwargs): + self.checkParams(widget, name, *values, **kwargs) + self.checkInvalidParam(widget, name, '', + errmsg='expected integer but got ""') + self.checkInvalidParam(widget, name, '10p', + errmsg='expected integer but got "10p"') + self.checkInvalidParam(widget, name, 3.2, + errmsg='expected integer but got "3.2"') + + def checkFloatParam(self, widget, name, *values, conv=float, **kwargs): + for value in values: + self.checkParam(widget, name, value, conv=conv, **kwargs) + self.checkInvalidParam(widget, name, '', + errmsg='expected floating-point number but got ""') + self.checkInvalidParam(widget, name, 'spam', + errmsg='expected floating-point number but got "spam"') + + def checkBooleanParam(self, widget, name): + for value in (False, 0, 'false', 'no', 'off'): + self.checkParam(widget, name, value, expected=0) + for value in (True, 1, 'true', 'yes', 'on'): + self.checkParam(widget, name, value, expected=1) + self.checkInvalidParam(widget, name, '', + errmsg='expected boolean value but got ""') + self.checkInvalidParam(widget, name, 'spam', + errmsg='expected boolean value but got "spam"') + + def checkColorParam(self, widget, name, *, allow_empty=None, **kwargs): + self.checkParams(widget, name, + '#ff0000', '#00ff00', '#0000ff', '#123456', + 'red', 'green', 'blue', 'white', 'black', 'grey', + **kwargs) + self.checkInvalidParam(widget, name, 'spam', + errmsg='unknown color name "spam"') + + def checkCursorParam(self, widget, name, **kwargs): + self.checkParams(widget, name, 'arrow', 'watch', 'cross', '',**kwargs) + if tcl_version >= (8, 5): + self.checkParam(widget, name, 'none') + self.checkInvalidParam(widget, name, 'spam', + errmsg='bad cursor spec "spam"') + + def checkCommandParam(self, widget, name): + def command(*args): + pass + widget[name] = command + self.assertTrue(widget[name]) + self.checkParams(widget, name, '') + + def checkEnumParam(self, widget, name, *values, errmsg=None, **kwargs): + self.checkParams(widget, name, *values, **kwargs) + if errmsg is None: + errmsg2 = ' %s "{}": must be %s%s or %s' % ( + name, + ', '.join(values[:-1]), + ',' if len(values) > 2 else '', + values[-1]) + self.checkInvalidParam(widget, name, '', + errmsg='ambiguous' + errmsg2) + errmsg = 'bad' + errmsg2 + self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg) + + def checkPixelsParam(self, widget, name, *values, + conv=None, keep_orig=True, **kwargs): + if conv is None: + conv = self._conv_pixels + for value in values: + expected = _sentinel + conv1 = conv + if isinstance(value, str): + if conv1 and conv1 is not str: + expected = pixels_conv(value) * self.scaling + conv1 = round + self.checkParam(widget, name, value, expected=expected, + conv=conv1, **kwargs) + self.checkInvalidParam(widget, name, '6x', + errmsg='bad screen distance "6x"', keep_orig=keep_orig) + self.checkInvalidParam(widget, name, 'spam', + errmsg='bad screen distance "spam"', keep_orig=keep_orig) + + def checkReliefParam(self, widget, name): + self.checkParams(widget, name, + 'flat', 'groove', 'raised', 'ridge', 'solid', 'sunken') + errmsg='bad relief "spam": must be '\ + 'flat, groove, raised, ridge, solid, or sunken' + if tcl_version < (8, 6): + errmsg = None + self.checkInvalidParam(widget, name, 'spam', + errmsg=errmsg) + + def checkImageParam(self, widget, name): + image = tkinter.PhotoImage('image1') + self.checkParam(widget, name, image, conv=str) + self.checkInvalidParam(widget, name, 'spam', + errmsg='image "spam" doesn\'t exist') + widget[name] = '' + + def checkVariableParam(self, widget, name, var): + self.checkParam(widget, name, var, conv=str) + + +class StandardOptionsTests: + STANDARD_OPTIONS = ( + 'activebackground', 'activeborderwidth', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', + 'disabledforeground', 'exportselection', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'jump', 'justify', 'orient', 'padx', 'pady', 'relief', + 'repeatdelay', 'repeatinterval', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'setgrid', 'takefocus', 'text', 'textvariable', 'troughcolor', + 'underline', 'wraplength', 'xscrollcommand', 'yscrollcommand', + ) + + def test_activebackground(self): + widget = self.create() + self.checkColorParam(widget, 'activebackground') + + def test_activeborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'activeborderwidth', + 0, 1.3, 2.9, 6, -2, '10p') + + def test_activeforeground(self): + widget = self.create() + self.checkColorParam(widget, 'activeforeground') + + def test_anchor(self): + widget = self.create() + self.checkEnumParam(widget, 'anchor', + 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center') + + def test_background(self): + widget = self.create() + self.checkColorParam(widget, 'background') + if 'bg' in self.OPTIONS: + self.checkColorParam(widget, 'bg') + + def test_bitmap(self): + widget = self.create() + self.checkParam(widget, 'bitmap', 'questhead') + self.checkParam(widget, 'bitmap', 'gray50') + self.checkInvalidParam(widget, 'bitmap', 'spam', + errmsg='bitmap "spam" not defined') + + def test_borderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'borderwidth', + 0, 1.3, 2.6, 6, -2, '10p') + if 'bd' in self.OPTIONS: + self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, -2, '10p') + + def test_compound(self): + widget = self.create() + self.checkEnumParam(widget, 'compound', + 'bottom', 'center', 'left', 'none', 'right', 'top') + + def test_cursor(self): + widget = self.create() + self.checkCursorParam(widget, 'cursor') + + def test_disabledforeground(self): + widget = self.create() + self.checkColorParam(widget, 'disabledforeground') + + def test_exportselection(self): + widget = self.create() + self.checkBooleanParam(widget, 'exportselection') + + def test_font(self): + widget = self.create() + self.checkParam(widget, 'font', + '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') + self.checkInvalidParam(widget, 'font', '', + errmsg='font "" doesn\'t exist') + + def test_foreground(self): + widget = self.create() + self.checkColorParam(widget, 'foreground') + if 'fg' in self.OPTIONS: + self.checkColorParam(widget, 'fg') + + def test_highlightbackground(self): + widget = self.create() + self.checkColorParam(widget, 'highlightbackground') + + def test_highlightcolor(self): + widget = self.create() + self.checkColorParam(widget, 'highlightcolor') + + def test_highlightthickness(self): + widget = self.create() + self.checkPixelsParam(widget, 'highlightthickness', + 0, 1.3, 2.6, 6, '10p') + self.checkParam(widget, 'highlightthickness', -2, expected=0, + conv=self._conv_pixels) + + def test_image(self): + widget = self.create() + self.checkImageParam(widget, 'image') + + def test_insertbackground(self): + widget = self.create() + self.checkColorParam(widget, 'insertbackground') + + def test_insertborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertborderwidth', + 0, 1.3, 2.6, 6, -2, '10p') + + def test_insertofftime(self): + widget = self.create() + self.checkIntegerParam(widget, 'insertofftime', 100) + + def test_insertontime(self): + widget = self.create() + self.checkIntegerParam(widget, 'insertontime', 100) + + def test_insertwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p') + + def test_jump(self): + widget = self.create() + self.checkBooleanParam(widget, 'jump') + + def test_justify(self): + widget = self.create() + self.checkEnumParam(widget, 'justify', 'left', 'right', 'center', + errmsg='bad justification "{}": must be ' + 'left, right, or center') + self.checkInvalidParam(widget, 'justify', '', + errmsg='ambiguous justification "": must be ' + 'left, right, or center') + + def test_orient(self): + widget = self.create() + self.assertEqual(str(widget['orient']), self.default_orient) + self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical') + + def test_padx(self): + widget = self.create() + self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, -2, '12m', + conv=self._conv_pad_pixels) + + def test_pady(self): + widget = self.create() + self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, -2, '12m', + conv=self._conv_pad_pixels) + + def test_relief(self): + widget = self.create() + self.checkReliefParam(widget, 'relief') + + def test_repeatdelay(self): + widget = self.create() + self.checkIntegerParam(widget, 'repeatdelay', -500, 500) + + def test_repeatinterval(self): + widget = self.create() + self.checkIntegerParam(widget, 'repeatinterval', -500, 500) + + def test_selectbackground(self): + widget = self.create() + self.checkColorParam(widget, 'selectbackground') + + def test_selectborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p') + + def test_selectforeground(self): + widget = self.create() + self.checkColorParam(widget, 'selectforeground') + + def test_setgrid(self): + widget = self.create() + self.checkBooleanParam(widget, 'setgrid') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'active', 'disabled', 'normal') + + def test_takefocus(self): + widget = self.create() + self.checkParams(widget, 'takefocus', '0', '1', '') + + def test_text(self): + widget = self.create() + self.checkParams(widget, 'text', '', 'any string') + + def test_textvariable(self): + widget = self.create() + var = tkinter.StringVar() + self.checkVariableParam(widget, 'textvariable', var) + + def test_troughcolor(self): + widget = self.create() + self.checkColorParam(widget, 'troughcolor') + + def test_underline(self): + widget = self.create() + self.checkIntegerParam(widget, 'underline', 0, 1, 10) + + def test_wraplength(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkPixelsParam(widget, 'wraplength', 100) + else: + self.checkParams(widget, 'wraplength', 100) + + def test_xscrollcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'xscrollcommand') + + def test_yscrollcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'yscrollcommand') + + # non-standard but common options + + def test_command(self): + widget = self.create() + self.checkCommandParam(widget, 'command') + + def test_indicatoron(self): + widget = self.create() + self.checkBooleanParam(widget, 'indicatoron') + + def test_offrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'offrelief') + + def test_overrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'overrelief') + + def test_selectcolor(self): + widget = self.create() + self.checkColorParam(widget, 'selectcolor') + + def test_selectimage(self): + widget = self.create() + self.checkImageParam(widget, 'selectimage') + + @requires_tcl(8, 5) + def test_tristateimage(self): + widget = self.create() + self.checkImageParam(widget, 'tristateimage') + + @requires_tcl(8, 5) + def test_tristatevalue(self): + widget = self.create() + self.checkParam(widget, 'tristatevalue', 'unknowable') + + def test_variable(self): + widget = self.create() + var = tkinter.DoubleVar() + self.checkVariableParam(widget, 'variable', var) + + +class IntegerSizeTests: + def test_height(self): + widget = self.create() + self.checkIntegerParam(widget, 'height', 100, -100, 0) + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402, -402, 0) + + +class PixelSizeTests: + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c') + + def test_width(self): + widget = self.create() + self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i') + + +def add_standard_options(*source_classes): + # This decorator adds test_xxx methods from source classes for every xxx + # option in the OPTIONS class attribute if they are not defined explicitly. + def decorator(cls): + for option in cls.OPTIONS: + methodname = 'test_' + option + if not hasattr(cls, methodname): + for source_class in source_classes: + if hasattr(source_class, methodname): + setattr(cls, methodname, + getattr(source_class, methodname)) + break + else: + def test(self, option=option): + widget = self.create() + widget[option] + raise AssertionError('Option "%s" is not tested in %s' % + (option, cls.__name__)) + test.__name__ = methodname + setattr(cls, methodname, test) + return cls + return decorator diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -15,6 +15,11 @@ - Issue #19435: Fix directory traversal attack on CGIHttpRequestHandler. +Tests +----- + +- Issue #19085: Added basic tests for all tkinter widget options. + What's New in Python 3.3.3 release candidate 1? =============================================== -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 09:48:37 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 09:48:37 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2319085=3A_Added_basic_tests_for_all_tkinter_widg?= =?utf-8?q?et_options=2E?= Message-ID: <3dBYpF5BW0z7Ljq@mail.python.org> http://hg.python.org/cpython/rev/ab7c2c1d349c changeset: 86835:ab7c2c1d349c parent: 86824:e071977772bf parent: 86834:92e268f2719e user: Serhiy Storchaka date: Sat Nov 02 10:44:55 2013 +0200 summary: Issue #19085: Added basic tests for all tkinter widget options. files: Lib/tkinter/test/support.py | 39 + Lib/tkinter/test/test_tkinter/test_widgets.py | 919 ++++++++++ Lib/tkinter/test/test_ttk/test_widgets.py | 493 +++++- Lib/tkinter/test/widget_tests.py | 487 +++++ Misc/NEWS | 2 + 5 files changed, 1917 insertions(+), 23 deletions(-) diff --git a/Lib/tkinter/test/support.py b/Lib/tkinter/test/support.py --- a/Lib/tkinter/test/support.py +++ b/Lib/tkinter/test/support.py @@ -77,3 +77,42 @@ widget.event_generate('', x=x, y=y) widget.event_generate('', x=x, y=y) widget.event_generate('', x=x, y=y) + + +import _tkinter +tcl_version = tuple(map(int, _tkinter.TCL_VERSION.split('.'))) + +def requires_tcl(*version): + return unittest.skipUnless(tcl_version >= version, + 'requires Tcl version >= ' + '.'.join(map(str, version))) + +units = { + 'c': 72 / 2.54, # centimeters + 'i': 72, # inches + 'm': 72 / 25.4, # millimeters + 'p': 1, # points +} + +def pixels_conv(value): + return float(value[:-1]) * units[value[-1:]] + +def tcl_obj_eq(actual, expected): + if actual == expected: + return True + if isinstance(actual, _tkinter.Tcl_Obj): + if isinstance(expected, str): + return str(actual) == expected + if isinstance(actual, tuple): + if isinstance(expected, tuple): + return (len(actual) == len(expected) and + all(tcl_obj_eq(act, exp) + for act, exp in zip(actual, expected))) + return False + +def widget_eq(actual, expected): + if actual == expected: + return True + if isinstance(actual, (str, tkinter.Widget)): + if isinstance(expected, (str, tkinter.Widget)): + return str(actual) == str(expected) + return False diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py new file mode 100644 --- /dev/null +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -0,0 +1,919 @@ +import unittest +import tkinter +import os +from test.support import requires + +from tkinter.test.support import tcl_version, requires_tcl, widget_eq +from tkinter.test.widget_tests import (add_standard_options, noconv, + AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) + +requires('gui') + + +def float_round(x): + return float(round(x)) + + +class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): + _conv_pad_pixels = noconv + + def test_class(self): + widget = self.create() + self.assertEqual(widget['class'], + widget.__class__.__name__.title()) + self.checkInvalidParam(widget, 'class', 'Foo', + errmsg="can't modify -class option after widget is created") + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + + def test_colormap(self): + widget = self.create() + self.assertEqual(widget['colormap'], '') + self.checkInvalidParam(widget, 'colormap', 'new', + errmsg="can't modify -colormap option after widget is created") + widget2 = self.create(colormap='new') + self.assertEqual(widget2['colormap'], 'new') + + def test_container(self): + widget = self.create() + self.assertEqual(widget['container'], 0 if self.wantobjects else '0') + self.checkInvalidParam(widget, 'container', 1, + errmsg="can't modify -container option after widget is created") + widget2 = self.create(container=True) + self.assertEqual(widget2['container'], 1 if self.wantobjects else '1') + + def test_visual(self): + widget = self.create() + self.assertEqual(widget['visual'], '') + self.checkInvalidParam(widget, 'visual', 'default', + errmsg="can't modify -visual option after widget is created") + widget2 = self.create(visual='default') + self.assertEqual(widget2['visual'], 'default') + + + at add_standard_options(StandardOptionsTests) +class ToplevelTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'menu', 'padx', 'pady', 'relief', 'screen', + 'takefocus', 'use', 'visual', 'width', + ) + + def _create(self, **kwargs): + return tkinter.Toplevel(self.root, **kwargs) + + def test_menu(self): + widget = self.create() + menu = tkinter.Menu(self.root) + self.checkParam(widget, 'menu', menu, eq=widget_eq) + self.checkParam(widget, 'menu', '') + + def test_screen(self): + widget = self.create() + self.assertEqual(widget['screen'], '') + display = os.environ['DISPLAY'] + self.checkInvalidParam(widget, 'screen', display, + errmsg="can't modify -screen option after widget is created") + widget2 = self.create(screen=display) + self.assertEqual(widget2['screen'], display) + + def test_use(self): + widget = self.create() + self.assertEqual(widget['use'], '') + widget1 = self.create(container=True) + self.assertEqual(widget1['use'], '') + self.checkInvalidParam(widget1, 'use', '0x44022', + errmsg="can't modify -use option after widget is created") + wid = hex(widget1.winfo_id()) + widget2 = self.create(use=wid) + self.assertEqual(widget2['use'], wid) + + + at add_standard_options(StandardOptionsTests) +class FrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'relief', 'takefocus', 'visual', 'width', + ) + + def _create(self, **kwargs): + return tkinter.Frame(self.root, **kwargs) + + + at add_standard_options(StandardOptionsTests) +class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'labelanchor', 'labelwidget', 'padx', 'pady', 'relief', + 'takefocus', 'text', 'visual', 'width', + ) + + def _create(self, **kwargs): + return tkinter.LabelFrame(self.root, **kwargs) + + def test_labelanchor(self): + widget = self.create() + self.checkEnumParam(widget, 'labelanchor', + 'e', 'en', 'es', 'n', 'ne', 'nw', + 's', 'se', 'sw', 'w', 'wn', 'ws') + self.checkInvalidParam(widget, 'labelanchor', 'center') + + def test_labelwidget(self): + widget = self.create() + label = tkinter.Label(self.root, text='Mupp', name='foo') + self.checkParam(widget, 'labelwidget', label, expected='.foo') + label.destroy() + + +class AbstractLabelTest(AbstractWidgetTest, IntegerSizeTests): + _conv_pixels = noconv + + def test_highlightthickness(self): + widget = self.create() + self.checkPixelsParam(widget, 'highlightthickness', + 0, 1.3, 2.6, 6, -2, '10p') + + + at add_standard_options(StandardOptionsTests) +class LabelTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'justify', 'padx', 'pady', 'relief', 'state', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return tkinter.Label(self.root, **kwargs) + + + at add_standard_options(StandardOptionsTests) +class ButtonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', 'default', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'justify', 'overrelief', 'padx', 'pady', 'relief', + 'repeatdelay', 'repeatinterval', + 'state', 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength') + + def _create(self, **kwargs): + return tkinter.Button(self.root, **kwargs) + + def test_default(self): + widget = self.create() + self.checkEnumParam(widget, 'default', 'active', 'disabled', 'normal') + + + at add_standard_options(StandardOptionsTests) +class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', + 'offrelief', 'offvalue', 'onvalue', 'overrelief', + 'padx', 'pady', 'relief', 'selectcolor', 'selectimage', 'state', + 'takefocus', 'text', 'textvariable', + 'tristateimage', 'tristatevalue', + 'underline', 'variable', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return tkinter.Checkbutton(self.root, **kwargs) + + + def test_offvalue(self): + widget = self.create() + self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') + + def test_onvalue(self): + widget = self.create() + self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') + + + at add_standard_options(StandardOptionsTests) +class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', 'offrelief', 'overrelief', + 'padx', 'pady', 'relief', 'selectcolor', 'selectimage', 'state', + 'takefocus', 'text', 'textvariable', + 'tristateimage', 'tristatevalue', + 'underline', 'value', 'variable', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return tkinter.Radiobutton(self.root, **kwargs) + + def test_value(self): + widget = self.create() + self.checkParams(widget, 'value', 1, 2.3, '', 'any string') + + + at add_standard_options(StandardOptionsTests) +class MenubuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'compound', 'cursor', 'direction', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', 'menu', + 'padx', 'pady', 'relief', 'state', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + _conv_pixels = AbstractWidgetTest._conv_pixels + + def _create(self, **kwargs): + return tkinter.Menubutton(self.root, **kwargs) + + def test_direction(self): + widget = self.create() + self.checkEnumParam(widget, 'direction', + 'above', 'below', 'flush', 'left', 'right') + + def test_height(self): + widget = self.create() + self.checkIntegerParam(widget, 'height', 100, -100, 0, conv=str) + + test_highlightthickness = StandardOptionsTests.test_highlightthickness + + def test_image(self): + widget = self.create() + image = tkinter.PhotoImage('image1') + self.checkParam(widget, 'image', image, conv=str) + errmsg = 'image "spam" doesn\'t exist' + with self.assertRaises(tkinter.TclError) as cm: + widget['image'] = 'spam' + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + with self.assertRaises(tkinter.TclError) as cm: + widget.configure({'image': 'spam'}) + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + + def test_menu(self): + widget = self.create() + menu = tkinter.Menu(widget, name='menu') + self.checkParam(widget, 'menu', menu, eq=widget_eq) + menu.destroy() + + def test_padx(self): + widget = self.create() + self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m') + self.checkParam(widget, 'padx', -2, expected=0) + + def test_pady(self): + widget = self.create() + self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m') + self.checkParam(widget, 'pady', -2, expected=0) + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402, -402, 0, conv=str) + + +class OptionMenuTest(MenubuttonTest, unittest.TestCase): + + def _create(self, default='b', values=('a', 'b', 'c'), **kwargs): + return tkinter.OptionMenu(self.root, None, default, *values, **kwargs) + + + at add_standard_options(IntegerSizeTests, StandardOptionsTests) +class EntryTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', 'cursor', + 'disabledbackground', 'disabledforeground', + 'exportselection', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'invalidcommand', 'justify', 'readonlybackground', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'show', 'state', 'takefocus', 'textvariable', + 'validate', 'validatecommand', 'width', 'xscrollcommand', + ) + + def _create(self, **kwargs): + return tkinter.Entry(self.root, **kwargs) + + def test_disabledbackground(self): + widget = self.create() + self.checkColorParam(widget, 'disabledbackground') + + def test_insertborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, -2) + self.checkParam(widget, 'insertborderwidth', 2, expected=1) + self.checkParam(widget, 'insertborderwidth', '10p', expected=1) + + def test_insertwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertwidth', 1.3, 3.6, '10p') + if tcl_version[:2] == (8, 5): + self.checkParam(widget, 'insertwidth', 0.9, expected=2) + else: + self.checkParam(widget, 'insertwidth', 0.9, expected=1) + self.checkParam(widget, 'insertwidth', 0.1, expected=2) + self.checkParam(widget, 'insertwidth', -2, expected=2) + + def test_invalidcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'invalidcommand') + self.checkCommandParam(widget, 'invcmd') + + def test_readonlybackground(self): + widget = self.create() + self.checkColorParam(widget, 'readonlybackground') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', '*') + self.checkParam(widget, 'show', '') + self.checkParam(widget, 'show', ' ') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', + 'disabled', 'normal', 'readonly') + + def test_validate(self): + widget = self.create() + self.checkEnumParam(widget, 'validate', + 'all', 'key', 'focus', 'focusin', 'focusout', 'none') + + def test_validatecommand(self): + widget = self.create() + self.checkCommandParam(widget, 'validatecommand') + self.checkCommandParam(widget, 'vcmd') + + + at add_standard_options(StandardOptionsTests) +class SpinboxTest(EntryTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'background', 'borderwidth', + 'buttonbackground', 'buttoncursor', 'buttondownrelief', 'buttonuprelief', + 'command', 'cursor', 'disabledbackground', 'disabledforeground', + 'exportselection', 'font', 'foreground', 'format', 'from', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'increment', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'invalidcommand', 'justify', 'relief', 'readonlybackground', + 'repeatdelay', 'repeatinterval', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'state', 'takefocus', 'textvariable', 'to', + 'validate', 'validatecommand', 'values', + 'width', 'wrap', 'xscrollcommand', + ) + + def _create(self, **kwargs): + return tkinter.Spinbox(self.root, **kwargs) + + test_show = None + + def test_buttonbackground(self): + widget = self.create() + self.checkColorParam(widget, 'buttonbackground') + + def test_buttoncursor(self): + widget = self.create() + self.checkCursorParam(widget, 'buttoncursor') + + def test_buttondownrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'buttondownrelief') + + def test_buttonuprelief(self): + widget = self.create() + self.checkReliefParam(widget, 'buttonuprelief') + + def test_format(self): + widget = self.create() + self.checkParam(widget, 'format', '%2f') + self.checkParam(widget, 'format', '%2.2f') + self.checkParam(widget, 'format', '%.2f') + self.checkParam(widget, 'format', '%2.f') + self.checkInvalidParam(widget, 'format', '%2e-1f') + self.checkInvalidParam(widget, 'format', '2.2') + self.checkInvalidParam(widget, 'format', '%2.-2f') + self.checkParam(widget, 'format', '%-2.02f') + self.checkParam(widget, 'format', '% 2.02f') + self.checkParam(widget, 'format', '% -2.200f') + self.checkParam(widget, 'format', '%09.200f') + self.checkInvalidParam(widget, 'format', '%d') + + def test_from(self): + widget = self.create() + self.checkParam(widget, 'to', 100.0) + self.checkFloatParam(widget, 'from', -10, 10.2, 11.7) + self.checkInvalidParam(widget, 'from', 200, + errmsg='-to value must be greater than -from value') + + def test_increment(self): + widget = self.create() + self.checkFloatParam(widget, 'increment', -1, 1, 10.2, 12.8, 0) + + def test_to(self): + widget = self.create() + self.checkParam(widget, 'from', -100.0) + self.checkFloatParam(widget, 'to', -10, 10.2, 11.7) + self.checkInvalidParam(widget, 'to', -200, + errmsg='-to value must be greater than -from value') + + def test_values(self): + # XXX + widget = self.create() + self.assertEqual(widget['values'], '') + self.checkParam(widget, 'values', 'mon tue wed thur') + self.checkParam(widget, 'values', ('mon', 'tue', 'wed', 'thur'), + expected='mon tue wed thur') + self.checkParam(widget, 'values', (42, 3.14, '', 'any string'), + expected='42 3.14 {} {any string}') + self.checkParam(widget, 'values', '') + + def test_wrap(self): + widget = self.create() + self.checkBooleanParam(widget, 'wrap') + + + at add_standard_options(StandardOptionsTests) +class TextTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'autoseparators', 'background', 'blockcursor', 'borderwidth', + 'cursor', 'endline', 'exportselection', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'inactiveselectbackground', 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertunfocussed', 'insertwidth', + 'maxundo', 'padx', 'pady', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'setgrid', 'spacing1', 'spacing2', 'spacing3', 'startline', 'state', + 'tabs', 'tabstyle', 'takefocus', 'undo', 'width', 'wrap', + 'xscrollcommand', 'yscrollcommand', + ) + if tcl_version < (8, 5): + wantobjects = False + + def _create(self, **kwargs): + return tkinter.Text(self.root, **kwargs) + + def test_autoseparators(self): + widget = self.create() + self.checkBooleanParam(widget, 'autoseparators') + + @requires_tcl(8, 5) + def test_blockcursor(self): + widget = self.create() + self.checkBooleanParam(widget, 'blockcursor') + + @requires_tcl(8, 5) + def test_endline(self): + widget = self.create() + text = '\n'.join('Line %d' for i in range(100)) + widget.insert('end', text) + self.checkParam(widget, 'endline', 200, expected='') + self.checkParam(widget, 'endline', -10, expected='') + self.checkInvalidParam(widget, 'endline', 'spam', + errmsg='expected integer but got "spam"') + self.checkParam(widget, 'endline', 50) + self.checkParam(widget, 'startline', 15) + self.checkInvalidParam(widget, 'endline', 10, + errmsg='-startline must be less than or equal to -endline') + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, '3c') + self.checkParam(widget, 'height', -100, expected=1) + self.checkParam(widget, 'height', 0, expected=1) + + def test_maxundo(self): + widget = self.create() + self.checkIntegerParam(widget, 'maxundo', 0, 5, -1) + + @requires_tcl(8, 5) + def test_inactiveselectbackground(self): + widget = self.create() + self.checkColorParam(widget, 'inactiveselectbackground') + + @requires_tcl(8, 6) + def test_insertunfocussed(self): + widget = self.create() + self.checkEnumParam(widget, 'insertunfocussed', + 'hollow', 'none', 'solid') + + def test_selectborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'selectborderwidth', + 1.3, 2.6, -2, '10p', conv=False, + keep_orig=tcl_version >= (8, 5)) + + def test_spacing1(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing1', 20, 21.4, 22.6, '0.5c') + self.checkParam(widget, 'spacing1', -5, expected=0) + + def test_spacing2(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing2', 5, 6.4, 7.6, '0.1c') + self.checkParam(widget, 'spacing2', -1, expected=0) + + def test_spacing3(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing3', 20, 21.4, 22.6, '0.5c') + self.checkParam(widget, 'spacing3', -10, expected=0) + + @requires_tcl(8, 5) + def test_startline(self): + widget = self.create() + text = '\n'.join('Line %d' for i in range(100)) + widget.insert('end', text) + self.checkParam(widget, 'startline', 200, expected='') + self.checkParam(widget, 'startline', -10, expected='') + self.checkInvalidParam(widget, 'startline', 'spam', + errmsg='expected integer but got "spam"') + self.checkParam(widget, 'startline', 10) + self.checkParam(widget, 'endline', 50) + self.checkInvalidParam(widget, 'startline', 70, + errmsg='-startline must be less than or equal to -endline') + + def test_state(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkParams(widget, 'state', 'disabled', 'normal') + else: + self.checkEnumParam(widget, 'state', 'disabled', 'normal') + + def test_tabs(self): + widget = self.create() + self.checkParam(widget, 'tabs', (10.2, 20.7, '1i', '2i')) + self.checkParam(widget, 'tabs', '10.2 20.7 1i 2i', + expected=('10.2', '20.7', '1i', '2i')) + self.checkParam(widget, 'tabs', '2c left 4c 6c center', + expected=('2c', 'left', '4c', '6c', 'center')) + self.checkInvalidParam(widget, 'tabs', 'spam', + errmsg='bad screen distance "spam"', + keep_orig=tcl_version >= (8, 5)) + + @requires_tcl(8, 5) + def test_tabstyle(self): + widget = self.create() + self.checkEnumParam(widget, 'tabstyle', 'tabular', 'wordprocessor') + + def test_undo(self): + widget = self.create() + self.checkBooleanParam(widget, 'undo') + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402) + self.checkParam(widget, 'width', -402, expected=1) + self.checkParam(widget, 'width', 0, expected=1) + + def test_wrap(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkParams(widget, 'wrap', 'char', 'none', 'word') + else: + self.checkEnumParam(widget, 'wrap', 'char', 'none', 'word') + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class CanvasTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'closeenough', 'confine', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'relief', 'scrollregion', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'state', 'takefocus', + 'xscrollcommand', 'xscrollincrement', + 'yscrollcommand', 'yscrollincrement', 'width', + ) + + _conv_pixels = round + wantobjects = False + + def _create(self, **kwargs): + return tkinter.Canvas(self.root, **kwargs) + + def test_closeenough(self): + widget = self.create() + self.checkFloatParam(widget, 'closeenough', 24, 2.4, 3.6, -3, + conv=float) + + def test_confine(self): + widget = self.create() + self.checkBooleanParam(widget, 'confine') + + def test_scrollregion(self): + widget = self.create() + self.checkParam(widget, 'scrollregion', '0 0 200 150') + self.checkParam(widget, 'scrollregion', (0, 0, 200, 150), + expected='0 0 200 150') + self.checkParam(widget, 'scrollregion', '') + self.checkInvalidParam(widget, 'scrollregion', 'spam', + errmsg='bad scrollRegion "spam"') + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 'spam')) + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200)) + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 150, 0)) + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'disabled', 'normal', + errmsg='bad state value "{}": must be normal or disabled') + + def test_xscrollincrement(self): + widget = self.create() + self.checkPixelsParam(widget, 'xscrollincrement', + 40, 0, 41.2, 43.6, -40, '0.5i') + + def test_yscrollincrement(self): + widget = self.create() + self.checkPixelsParam(widget, 'yscrollincrement', + 10, 0, 11.2, 13.6, -10, '0.1i') + + + at add_standard_options(IntegerSizeTests, StandardOptionsTests) +class ListboxTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activestyle', 'background', 'borderwidth', 'cursor', + 'disabledforeground', 'exportselection', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'listvariable', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'selectmode', 'setgrid', 'state', + 'takefocus', 'width', 'xscrollcommand', 'yscrollcommand', + ) + + def _create(self, **kwargs): + return tkinter.Listbox(self.root, **kwargs) + + def test_activestyle(self): + widget = self.create() + self.checkEnumParam(widget, 'activestyle', + 'dotbox', 'none', 'underline') + + def test_listvariable(self): + widget = self.create() + var = tkinter.DoubleVar() + self.checkVariableParam(widget, 'listvariable', var) + + def test_selectmode(self): + widget = self.create() + self.checkParam(widget, 'selectmode', 'single') + self.checkParam(widget, 'selectmode', 'browse') + self.checkParam(widget, 'selectmode', 'multiple') + self.checkParam(widget, 'selectmode', 'extended') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'disabled', 'normal') + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class ScaleTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'background', 'bigincrement', 'borderwidth', + 'command', 'cursor', 'digits', 'font', 'foreground', 'from', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'label', 'length', 'orient', 'relief', + 'repeatdelay', 'repeatinterval', + 'resolution', 'showvalue', 'sliderlength', 'sliderrelief', 'state', + 'takefocus', 'tickinterval', 'to', 'troughcolor', 'variable', 'width', + ) + default_orient = 'vertical' + + def _create(self, **kwargs): + return tkinter.Scale(self.root, **kwargs) + + def test_bigincrement(self): + widget = self.create() + self.checkFloatParam(widget, 'bigincrement', 12.4, 23.6, -5) + + def test_digits(self): + widget = self.create() + self.checkIntegerParam(widget, 'digits', 5, 0) + + def test_from(self): + widget = self.create() + self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=float_round) + + def test_label(self): + widget = self.create() + self.checkParam(widget, 'label', 'any string') + self.checkParam(widget, 'label', '') + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') + + def test_resolution(self): + widget = self.create() + self.checkFloatParam(widget, 'resolution', 4.2, 0, 6.7, -2) + + def test_showvalue(self): + widget = self.create() + self.checkBooleanParam(widget, 'showvalue') + + def test_sliderlength(self): + widget = self.create() + self.checkPixelsParam(widget, 'sliderlength', + 10, 11.2, 15.6, -3, '3m') + + def test_sliderrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'sliderrelief') + + def test_tickinterval(self): + widget = self.create() + self.checkFloatParam(widget, 'tickinterval', 1, 4.3, 7.6, 0, + conv=float_round) + self.checkParam(widget, 'tickinterval', -2, expected=2, + conv=float_round) + + def test_to(self): + widget = self.create() + self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, + conv=float_round) + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activerelief', + 'background', 'borderwidth', + 'command', 'cursor', 'elementborderwidth', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'jump', 'orient', 'relief', + 'repeatdelay', 'repeatinterval', + 'takefocus', 'troughcolor', 'width', + ) + _conv_pixels = round + wantobjects = False + default_orient = 'vertical' + + def _create(self, **kwargs): + return tkinter.Scrollbar(self.root, **kwargs) + + def test_activerelief(self): + widget = self.create() + self.checkReliefParam(widget, 'activerelief') + + def test_elementborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'elementborderwidth', 4.3, 5.6, -2, '1m') + + def test_orient(self): + widget = self.create() + self.checkEnumParam(widget, 'orient', 'vertical', 'horizontal', + errmsg='bad orientation "{}": must be vertical or horizontal') + + + at add_standard_options(StandardOptionsTests) +class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', 'cursor', + 'handlepad', 'handlesize', 'height', + 'opaqueresize', 'orient', 'relief', + 'sashcursor', 'sashpad', 'sashrelief', 'sashwidth', + 'showhandle', 'width', + ) + default_orient = 'horizontal' + + def _create(self, **kwargs): + return tkinter.PanedWindow(self.root, **kwargs) + + def test_handlepad(self): + widget = self.create() + self.checkPixelsParam(widget, 'handlepad', 5, 6.4, 7.6, -3, '1m') + + def test_handlesize(self): + widget = self.create() + self.checkPixelsParam(widget, 'handlesize', 8, 9.4, 10.6, -3, '2m', + conv=noconv) + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i', + conv=noconv) + + def test_opaqueresize(self): + widget = self.create() + self.checkBooleanParam(widget, 'opaqueresize') + + def test_sashcursor(self): + widget = self.create() + self.checkCursorParam(widget, 'sashcursor') + + def test_sashpad(self): + widget = self.create() + self.checkPixelsParam(widget, 'sashpad', 8, 1.3, 2.6, -2, '2m') + + def test_sashrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'sashrelief') + + def test_sashwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'sashwidth', 10, 11.1, 15.6, -3, '1m', + conv=noconv) + + def test_showhandle(self): + widget = self.create() + self.checkBooleanParam(widget, 'showhandle') + + def test_width(self): + widget = self.create() + self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i', + conv=noconv) + + + at add_standard_options(StandardOptionsTests) +class MenuTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeborderwidth', 'activeforeground', + 'background', 'borderwidth', 'cursor', + 'disabledforeground', 'font', 'foreground', + 'postcommand', 'relief', 'selectcolor', 'takefocus', + 'tearoff', 'tearoffcommand', 'title', 'type', + ) + _conv_pixels = noconv + + def _create(self, **kwargs): + return tkinter.Menu(self.root, **kwargs) + + def test_postcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'postcommand') + + def test_tearoff(self): + widget = self.create() + self.checkBooleanParam(widget, 'tearoff') + + def test_tearoffcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'tearoffcommand') + + def test_title(self): + widget = self.create() + self.checkParam(widget, 'title', 'any string') + + def test_type(self): + widget = self.create() + self.checkEnumParam(widget, 'type', + 'normal', 'tearoff', 'menubar') + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class MessageTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'anchor', 'aspect', 'background', 'borderwidth', + 'cursor', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'justify', 'padx', 'pady', 'relief', + 'takefocus', 'text', 'textvariable', 'width', + ) + _conv_pad_pixels = noconv + + def _create(self, **kwargs): + return tkinter.Message(self.root, **kwargs) + + def test_aspect(self): + widget = self.create() + self.checkIntegerParam(widget, 'aspect', 250, 0, -300) + + +tests_gui = ( + ButtonTest, CanvasTest, CheckbuttonTest, EntryTest, + FrameTest, LabelFrameTest,LabelTest, ListboxTest, + MenubuttonTest, MenuTest, MessageTest, OptionMenuTest, + PanedWindowTest, RadiobuttonTest, ScaleTest, ScrollbarTest, + SpinboxTest, TextTest, ToplevelTest, +) + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1,15 +1,57 @@ import unittest import tkinter -import os from tkinter import ttk -from test.support import requires, run_unittest +from test.support import requires import sys import tkinter.test.support as support -from tkinter.test.test_ttk.test_functions import MockTclObj, MockStateSpec +from tkinter.test.test_ttk.test_functions import MockTclObj +from tkinter.test.support import tcl_version +from tkinter.test.widget_tests import (add_standard_options, noconv, + AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) requires('gui') + +class StandardTtkOptionsTests(StandardOptionsTests): + + def test_class(self): + widget = self.create() + self.assertEqual(widget['class'], '') + errmsg='attempt to change read-only option' + if tcl_version < (8, 6): + errmsg='Attempt to change read-only option' + self.checkInvalidParam(widget, 'class', 'Foo', errmsg=errmsg) + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + + def test_padding(self): + widget = self.create() + self.checkParam(widget, 'padding', 0, expected=('0',)) + self.checkParam(widget, 'padding', 5, expected=('5',)) + self.checkParam(widget, 'padding', (5, 6), expected=('5', '6')) + self.checkParam(widget, 'padding', (5, 6, 7), + expected=('5', '6', '7')) + self.checkParam(widget, 'padding', (5, 6, 7, 8), + expected=('5', '6', '7', '8')) + self.checkParam(widget, 'padding', ('5p', '6p', '7p', '8p')) + self.checkParam(widget, 'padding', (), expected='') + + def test_style(self): + widget = self.create() + self.assertEqual(widget['style'], '') + errmsg = 'Layout Foo not found' + if hasattr(self, 'default_orient'): + errmsg = ('Layout %s.Foo not found' % + getattr(self, 'default_orient').title()) + self.checkInvalidParam(widget, 'style', 'Foo', + errmsg=errmsg) + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + # XXX + pass + + class WidgetTest(unittest.TestCase): """Tests methods available in every ttk widget.""" @@ -73,7 +115,112 @@ self.assertEqual(self.widget.state(), ('active', )) -class ButtonTest(unittest.TestCase): +class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): + _conv_pixels = noconv + + + at add_standard_options(StandardTtkOptionsTests) +class FrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'borderwidth', 'class', 'cursor', 'height', + 'padding', 'relief', 'style', 'takefocus', + 'width', + ) + + def _create(self, **kwargs): + return ttk.Frame(self.root, **kwargs) + + + at add_standard_options(StandardTtkOptionsTests) +class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'borderwidth', 'class', 'cursor', 'height', + 'labelanchor', 'labelwidget', + 'padding', 'relief', 'style', 'takefocus', + 'text', 'underline', 'width', + ) + + def _create(self, **kwargs): + return ttk.LabelFrame(self.root, **kwargs) + + def test_labelanchor(self): + widget = self.create() + self.checkEnumParam(widget, 'labelanchor', + 'e', 'en', 'es', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'wn', 'ws', + errmsg='Bad label anchor specification {}') + self.checkInvalidParam(widget, 'labelanchor', 'center') + + def test_labelwidget(self): + widget = self.create() + label = ttk.Label(self.root, text='Mupp', name='foo') + self.checkParam(widget, 'labelwidget', label, expected='.foo') + label.destroy() + + +class AbstractLabelTest(AbstractWidgetTest): + + def checkImageParam(self, widget, name): + image = tkinter.PhotoImage('image1') + image2 = tkinter.PhotoImage('image2') + self.checkParam(widget, name, image, expected=('image1',)) + self.checkParam(widget, name, 'image1', expected=('image1',)) + self.checkParam(widget, name, (image,), expected=('image1',)) + self.checkParam(widget, name, (image, 'active', image2), + expected=('image1', 'active', 'image2')) + self.checkParam(widget, name, 'image1 active image2', + expected=('image1', 'active', 'image2')) + self.checkInvalidParam(widget, name, 'spam', + errmsg='image "spam" doesn\'t exist') + + def test_compound(self): + widget = self.create() + self.checkEnumParam(widget, 'compound', + 'none', 'text', 'image', 'center', + 'top', 'bottom', 'left', 'right') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', 'active', 'disabled', 'normal') + + def test_width(self): + widget = self.create() + self.checkParams(widget, 'width', 402, -402, 0) + + + at add_standard_options(StandardTtkOptionsTests) +class LabelTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'anchor', 'background', + 'class', 'compound', 'cursor', 'font', 'foreground', + 'image', 'justify', 'padding', 'relief', 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + _conv_pixels = noconv + + def _create(self, **kwargs): + return ttk.Label(self.root, **kwargs) + + def test_font(self): + widget = self.create() + self.checkParam(widget, 'font', + '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') + + + at add_standard_options(StandardTtkOptionsTests) +class ButtonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', 'default', + 'image', 'state', 'style', 'takefocus', 'text', 'textvariable', + 'underline', 'width', + ) + + def _create(self, **kwargs): + return ttk.Button(self.root, **kwargs) + + def test_default(self): + widget = self.create() + self.checkEnumParam(widget, 'default', 'normal', 'active', 'disabled') def test_invoke(self): success = [] @@ -82,7 +229,27 @@ self.assertTrue(success) -class CheckbuttonTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', + 'image', + 'offvalue', 'onvalue', + 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'variable', 'width', + ) + + def _create(self, **kwargs): + return ttk.Checkbutton(self.root, **kwargs) + + def test_offvalue(self): + widget = self.create() + self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') + + def test_onvalue(self): + widget = self.create() + self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') def test_invoke(self): success = [] @@ -105,21 +272,40 @@ cbtn['command'] = '' res = cbtn.invoke() - self.assertEqual(str(res), '') + self.assertFalse(str(res)) self.assertFalse(len(success) > 1) self.assertEqual(cbtn['offvalue'], cbtn.tk.globalgetvar(cbtn['variable'])) -class ComboboxTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class ComboboxTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'exportselection', 'height', + 'justify', 'postcommand', 'state', 'style', + 'takefocus', 'textvariable', 'values', 'width', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.combo = ttk.Combobox() + self.combo = self.create() def tearDown(self): self.combo.destroy() support.root_withdraw() + super().tearDown() + + def _create(self, **kwargs): + return ttk.Combobox(self.root, **kwargs) + + def test_height(self): + widget = self.create() + self.checkParams(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', 'active', 'disabled', 'normal') def _show_drop_down_listbox(self): width = self.combo.winfo_width() @@ -167,8 +353,16 @@ self.assertEqual(self.combo.get(), getval) self.assertEqual(self.combo.current(), currval) + self.assertEqual(self.combo['values'], + () if tcl_version < (8, 5) else '') check_get_current('', -1) + self.checkParam(self.combo, 'values', 'mon tue wed thur', + expected=('mon', 'tue', 'wed', 'thur')) + self.checkParam(self.combo, 'values', ('mon', 'tue', 'wed', 'thur')) + self.checkParam(self.combo, 'values', (42, 3.14, '', 'any string')) + self.checkParam(self.combo, 'values', '', expected=()) + self.combo['values'] = ['a', 1, 'c'] self.combo.set('c') @@ -209,15 +403,52 @@ combo2.destroy() -class EntryTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class EntryTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'class', 'cursor', + 'exportselection', 'font', + 'invalidcommand', 'justify', + 'show', 'state', 'style', 'takefocus', 'textvariable', + 'validate', 'validatecommand', 'width', 'xscrollcommand', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.entry = ttk.Entry() + self.entry = self.create() def tearDown(self): self.entry.destroy() support.root_withdraw() + super().tearDown() + + def _create(self, **kwargs): + return ttk.Entry(self.root, **kwargs) + + def test_invalidcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'invalidcommand') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', '*') + self.checkParam(widget, 'show', '') + self.checkParam(widget, 'show', ' ') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', + 'disabled', 'normal', 'readonly') + + def test_validate(self): + widget = self.create() + self.checkEnumParam(widget, 'validate', + 'all', 'key', 'focus', 'focusin', 'focusout', 'none') + + def test_validatecommand(self): + widget = self.create() + self.checkCommandParam(widget, 'validatecommand') def test_bbox(self): @@ -313,16 +544,36 @@ self.assertEqual(self.entry.state(), ()) -class PanedwindowTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'height', + 'orient', 'style', 'takefocus', 'width', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.paned = ttk.Panedwindow() + self.paned = self.create() def tearDown(self): self.paned.destroy() support.root_withdraw() + super().tearDown() + def _create(self, **kwargs): + return ttk.PanedWindow(self.root, **kwargs) + + def test_orient(self): + widget = self.create() + self.assertEqual(str(widget['orient']), 'vertical') + errmsg='attempt to change read-only option' + if tcl_version < (8, 6): + errmsg='Attempt to change read-only option' + self.checkInvalidParam(widget, 'orient', 'horizontal', + errmsg=errmsg) + widget2 = self.create(orient='horizontal') + self.assertEqual(str(widget2['orient']), 'horizontal') def test_add(self): # attempt to add a child that is not a direct child of the paned window @@ -432,7 +683,22 @@ self.assertTrue(isinstance(self.paned.sashpos(0), int)) -class RadiobuttonTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', + 'image', + 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'value', 'variable', 'width', + ) + + def _create(self, **kwargs): + return ttk.Radiobutton(self.root, **kwargs) + + def test_value(self): + widget = self.create() + self.checkParams(widget, 'value', 1, 2.3, '', 'any string') def test_invoke(self): success = [] @@ -462,19 +728,68 @@ self.assertEqual(str(cbtn['variable']), str(cbtn2['variable'])) +class MenubuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'compound', 'cursor', 'direction', + 'image', 'menu', 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', + ) -class ScaleTest(unittest.TestCase): + def _create(self, **kwargs): + return ttk.Menubutton(self.root, **kwargs) + + def test_direction(self): + widget = self.create() + self.checkEnumParam(widget, 'direction', + 'above', 'below', 'left', 'right', 'flush') + + def test_menu(self): + widget = self.create() + menu = tkinter.Menu(widget, name='menu') + self.checkParam(widget, 'menu', menu, conv=str) + menu.destroy() + + + at add_standard_options(StandardTtkOptionsTests) +class ScaleTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'cursor', 'from', 'length', + 'orient', 'style', 'takefocus', 'to', 'value', 'variable', + ) + _conv_pixels = noconv + default_orient = 'horizontal' def setUp(self): + super().setUp() support.root_deiconify() - self.scale = ttk.Scale() + self.scale = self.create() self.scale.pack() self.scale.update() def tearDown(self): self.scale.destroy() support.root_withdraw() + super().tearDown() + def _create(self, **kwargs): + return ttk.Scale(self.root, **kwargs) + + def test_from(self): + widget = self.create() + self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=False) + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') + + def test_to(self): + widget = self.create() + self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, conv=False) + + def test_value(self): + widget = self.create() + self.checkFloatParam(widget, 'value', 300, 14.9, 15.1, -10, conv=False) def test_custom_event(self): failure = [1, 1, 1] # will need to be empty @@ -539,11 +854,64 @@ self.assertRaises(tkinter.TclError, self.scale.set, None) -class NotebookTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class ProgressbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'orient', 'length', + 'mode', 'maximum', 'phase', + 'style', 'takefocus', 'value', 'variable', + ) + _conv_pixels = noconv + default_orient = 'horizontal' + + def _create(self, **kwargs): + return ttk.Progressbar(self.root, **kwargs) + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 100.1, 56.7, '2i') + + def test_maximum(self): + widget = self.create() + self.checkFloatParam(widget, 'maximum', 150.2, 77.7, 0, -10, conv=False) + + def test_mode(self): + widget = self.create() + self.checkEnumParam(widget, 'mode', 'determinate', 'indeterminate') + + def test_phase(self): + # XXX + pass + + def test_value(self): + widget = self.create() + self.checkFloatParam(widget, 'value', 150.2, 77.7, 0, -10, + conv=False) + + + at unittest.skipIf(sys.platform == 'darwin', + 'ttk.Scrollbar is special on MacOSX') + at add_standard_options(StandardTtkOptionsTests) +class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'cursor', 'orient', 'style', 'takefocus', + ) + default_orient = 'vertical' + + def _create(self, **kwargs): + return ttk.Scrollbar(self.root, **kwargs) + + + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class NotebookTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'height', 'padding', 'style', 'takefocus', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.nb = ttk.Notebook(padding=0) + self.nb = self.create(padding=0) self.child1 = ttk.Label() self.child2 = ttk.Label() self.nb.add(self.child1, text='a') @@ -554,7 +922,10 @@ self.child2.destroy() self.nb.destroy() support.root_withdraw() + super().tearDown() + def _create(self, **kwargs): + return ttk.Notebook(self.root, **kwargs) def test_tab_identifiers(self): self.nb.forget(0) @@ -746,16 +1117,68 @@ self.assertEqual(self.nb.select(), str(self.child1)) -class TreeviewTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class TreeviewTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'columns', 'cursor', 'displaycolumns', + 'height', 'padding', 'selectmode', 'show', + 'style', 'takefocus', 'xscrollcommand', 'yscrollcommand', + ) def setUp(self): + super().setUp() support.root_deiconify() - self.tv = ttk.Treeview(padding=0) + self.tv = self.create(padding=0) def tearDown(self): self.tv.destroy() support.root_withdraw() + super().tearDown() + def _create(self, **kwargs): + return ttk.Treeview(self.root, **kwargs) + + def test_columns(self): + widget = self.create() + self.checkParam(widget, 'columns', 'a b c', + expected=('a', 'b', 'c')) + self.checkParam(widget, 'columns', ('a', 'b', 'c')) + self.checkParam(widget, 'columns', ()) + + def test_displaycolumns(self): + widget = self.create() + widget['columns'] = ('a', 'b', 'c') + self.checkParam(widget, 'displaycolumns', 'b a c', + expected=('b', 'a', 'c')) + self.checkParam(widget, 'displaycolumns', ('b', 'a', 'c')) + self.checkParam(widget, 'displaycolumns', '#all', + expected=('#all',)) + self.checkParam(widget, 'displaycolumns', (2, 1, 0)) + self.checkInvalidParam(widget, 'displaycolumns', ('a', 'b', 'd'), + errmsg='Invalid column index d') + self.checkInvalidParam(widget, 'displaycolumns', (1, 2, 3), + errmsg='Column index 3 out of bounds') + self.checkInvalidParam(widget, 'displaycolumns', (1, -2), + errmsg='Column index -2 out of bounds') + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, -100, 0, '3c', conv=False) + self.checkPixelsParam(widget, 'height', 101.2, 102.6, conv=noconv) + + def test_selectmode(self): + widget = self.create() + self.checkEnumParam(widget, 'selectmode', + 'none', 'browse', 'extended') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', 'tree headings', + expected=('tree', 'headings')) + self.checkParam(widget, 'show', ('tree', 'headings')) + self.checkParam(widget, 'show', ('headings', 'tree')) + self.checkParam(widget, 'show', 'tree', expected=('tree',)) + self.checkParam(widget, 'show', 'headings', expected=('headings',)) def test_bbox(self): self.tv.pack() @@ -1149,11 +1572,35 @@ self.assertTrue(isinstance(self.tv.tag_configure('test'), dict)) + at add_standard_options(StandardTtkOptionsTests) +class SeparatorTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'orient', 'style', 'takefocus', + # 'state'? + ) + default_orient = 'horizontal' + + def _create(self, **kwargs): + return ttk.Separator(self.root, **kwargs) + + + at add_standard_options(StandardTtkOptionsTests) +class SizegripTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'style', 'takefocus', + # 'state'? + ) + + def _create(self, **kwargs): + return ttk.Sizegrip(self.root, **kwargs) + tests_gui = ( - WidgetTest, ButtonTest, CheckbuttonTest, RadiobuttonTest, - ComboboxTest, EntryTest, PanedwindowTest, ScaleTest, NotebookTest, - TreeviewTest + ButtonTest, CheckbuttonTest, ComboboxTest, EntryTest, + FrameTest, LabelFrameTest, LabelTest, MenubuttonTest, + NotebookTest, PanedWindowTest, ProgressbarTest, + RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest, + SizegripTest, TreeviewTest, WidgetTest, ) if __name__ == "__main__": - run_unittest(*tests_gui) + unittest.main() diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py new file mode 100644 --- /dev/null +++ b/Lib/tkinter/test/widget_tests.py @@ -0,0 +1,487 @@ +# Common tests for test_tkinter/test_widgets.py and test_ttk/test_widgets.py + +import tkinter +from tkinter.ttk import setup_master, Scale +from tkinter.test.support import (tcl_version, requires_tcl, pixels_conv, + tcl_obj_eq) + + +noconv = str if tcl_version < (8, 5) else False + +_sentinel = object() + +class AbstractWidgetTest: + _conv_pixels = round if tcl_version[:2] != (8, 5) else int + _conv_pad_pixels = None + wantobjects = True + + def setUp(self): + self.root = setup_master() + self.scaling = float(self.root.call('tk', 'scaling')) + if not self.root.wantobjects(): + self.wantobjects = False + + def create(self, **kwargs): + widget = self._create(**kwargs) + self.addCleanup(widget.destroy) + return widget + + def assertEqual2(self, actual, expected, msg=None, eq=object.__eq__): + if eq(actual, expected): + return + self.assertEqual(actual, expected, msg) + + def checkParam(self, widget, name, value, *, expected=_sentinel, + conv=False, eq=None): + widget[name] = value + if expected is _sentinel: + expected = value + if conv: + expected = conv(expected) + if not self.wantobjects: + if isinstance(expected, tuple): + expected = tkinter._join(expected) + else: + expected = str(expected) + if eq is None: + eq = tcl_obj_eq + self.assertEqual2(widget[name], expected, eq=eq) + self.assertEqual2(widget.cget(name), expected, eq=eq) + # XXX + if not isinstance(widget, Scale): + t = widget.configure(name) + self.assertEqual(len(t), 5) + ## XXX + if not isinstance(t[4], tuple): + self.assertEqual2(t[4], expected, eq=eq) + + def checkInvalidParam(self, widget, name, value, errmsg=None, *, + keep_orig=True): + orig = widget[name] + if errmsg is not None: + errmsg = errmsg.format(value) + with self.assertRaises(tkinter.TclError) as cm: + widget[name] = value + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + if keep_orig: + self.assertEqual(widget[name], orig) + else: + widget[name] = orig + with self.assertRaises(tkinter.TclError) as cm: + widget.configure({name: value}) + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + if keep_orig: + self.assertEqual(widget[name], orig) + else: + widget[name] = orig + + def checkParams(self, widget, name, *values, **kwargs): + for value in values: + self.checkParam(widget, name, value, **kwargs) + + def checkIntegerParam(self, widget, name, *values, **kwargs): + self.checkParams(widget, name, *values, **kwargs) + self.checkInvalidParam(widget, name, '', + errmsg='expected integer but got ""') + self.checkInvalidParam(widget, name, '10p', + errmsg='expected integer but got "10p"') + self.checkInvalidParam(widget, name, 3.2, + errmsg='expected integer but got "3.2"') + + def checkFloatParam(self, widget, name, *values, conv=float, **kwargs): + for value in values: + self.checkParam(widget, name, value, conv=conv, **kwargs) + self.checkInvalidParam(widget, name, '', + errmsg='expected floating-point number but got ""') + self.checkInvalidParam(widget, name, 'spam', + errmsg='expected floating-point number but got "spam"') + + def checkBooleanParam(self, widget, name): + for value in (False, 0, 'false', 'no', 'off'): + self.checkParam(widget, name, value, expected=0) + for value in (True, 1, 'true', 'yes', 'on'): + self.checkParam(widget, name, value, expected=1) + self.checkInvalidParam(widget, name, '', + errmsg='expected boolean value but got ""') + self.checkInvalidParam(widget, name, 'spam', + errmsg='expected boolean value but got "spam"') + + def checkColorParam(self, widget, name, *, allow_empty=None, **kwargs): + self.checkParams(widget, name, + '#ff0000', '#00ff00', '#0000ff', '#123456', + 'red', 'green', 'blue', 'white', 'black', 'grey', + **kwargs) + self.checkInvalidParam(widget, name, 'spam', + errmsg='unknown color name "spam"') + + def checkCursorParam(self, widget, name, **kwargs): + self.checkParams(widget, name, 'arrow', 'watch', 'cross', '',**kwargs) + if tcl_version >= (8, 5): + self.checkParam(widget, name, 'none') + self.checkInvalidParam(widget, name, 'spam', + errmsg='bad cursor spec "spam"') + + def checkCommandParam(self, widget, name): + def command(*args): + pass + widget[name] = command + self.assertTrue(widget[name]) + self.checkParams(widget, name, '') + + def checkEnumParam(self, widget, name, *values, errmsg=None, **kwargs): + self.checkParams(widget, name, *values, **kwargs) + if errmsg is None: + errmsg2 = ' %s "{}": must be %s%s or %s' % ( + name, + ', '.join(values[:-1]), + ',' if len(values) > 2 else '', + values[-1]) + self.checkInvalidParam(widget, name, '', + errmsg='ambiguous' + errmsg2) + errmsg = 'bad' + errmsg2 + self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg) + + def checkPixelsParam(self, widget, name, *values, + conv=None, keep_orig=True, **kwargs): + if conv is None: + conv = self._conv_pixels + for value in values: + expected = _sentinel + conv1 = conv + if isinstance(value, str): + if conv1 and conv1 is not str: + expected = pixels_conv(value) * self.scaling + conv1 = round + self.checkParam(widget, name, value, expected=expected, + conv=conv1, **kwargs) + self.checkInvalidParam(widget, name, '6x', + errmsg='bad screen distance "6x"', keep_orig=keep_orig) + self.checkInvalidParam(widget, name, 'spam', + errmsg='bad screen distance "spam"', keep_orig=keep_orig) + + def checkReliefParam(self, widget, name): + self.checkParams(widget, name, + 'flat', 'groove', 'raised', 'ridge', 'solid', 'sunken') + errmsg='bad relief "spam": must be '\ + 'flat, groove, raised, ridge, solid, or sunken' + if tcl_version < (8, 6): + errmsg = None + self.checkInvalidParam(widget, name, 'spam', + errmsg=errmsg) + + def checkImageParam(self, widget, name): + image = tkinter.PhotoImage('image1') + self.checkParam(widget, name, image, conv=str) + self.checkInvalidParam(widget, name, 'spam', + errmsg='image "spam" doesn\'t exist') + widget[name] = '' + + def checkVariableParam(self, widget, name, var): + self.checkParam(widget, name, var, conv=str) + + +class StandardOptionsTests: + STANDARD_OPTIONS = ( + 'activebackground', 'activeborderwidth', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', + 'disabledforeground', 'exportselection', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'jump', 'justify', 'orient', 'padx', 'pady', 'relief', + 'repeatdelay', 'repeatinterval', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'setgrid', 'takefocus', 'text', 'textvariable', 'troughcolor', + 'underline', 'wraplength', 'xscrollcommand', 'yscrollcommand', + ) + + def test_activebackground(self): + widget = self.create() + self.checkColorParam(widget, 'activebackground') + + def test_activeborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'activeborderwidth', + 0, 1.3, 2.9, 6, -2, '10p') + + def test_activeforeground(self): + widget = self.create() + self.checkColorParam(widget, 'activeforeground') + + def test_anchor(self): + widget = self.create() + self.checkEnumParam(widget, 'anchor', + 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center') + + def test_background(self): + widget = self.create() + self.checkColorParam(widget, 'background') + if 'bg' in self.OPTIONS: + self.checkColorParam(widget, 'bg') + + def test_bitmap(self): + widget = self.create() + self.checkParam(widget, 'bitmap', 'questhead') + self.checkParam(widget, 'bitmap', 'gray50') + self.checkInvalidParam(widget, 'bitmap', 'spam', + errmsg='bitmap "spam" not defined') + + def test_borderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'borderwidth', + 0, 1.3, 2.6, 6, -2, '10p') + if 'bd' in self.OPTIONS: + self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, -2, '10p') + + def test_compound(self): + widget = self.create() + self.checkEnumParam(widget, 'compound', + 'bottom', 'center', 'left', 'none', 'right', 'top') + + def test_cursor(self): + widget = self.create() + self.checkCursorParam(widget, 'cursor') + + def test_disabledforeground(self): + widget = self.create() + self.checkColorParam(widget, 'disabledforeground') + + def test_exportselection(self): + widget = self.create() + self.checkBooleanParam(widget, 'exportselection') + + def test_font(self): + widget = self.create() + self.checkParam(widget, 'font', + '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') + self.checkInvalidParam(widget, 'font', '', + errmsg='font "" doesn\'t exist') + + def test_foreground(self): + widget = self.create() + self.checkColorParam(widget, 'foreground') + if 'fg' in self.OPTIONS: + self.checkColorParam(widget, 'fg') + + def test_highlightbackground(self): + widget = self.create() + self.checkColorParam(widget, 'highlightbackground') + + def test_highlightcolor(self): + widget = self.create() + self.checkColorParam(widget, 'highlightcolor') + + def test_highlightthickness(self): + widget = self.create() + self.checkPixelsParam(widget, 'highlightthickness', + 0, 1.3, 2.6, 6, '10p') + self.checkParam(widget, 'highlightthickness', -2, expected=0, + conv=self._conv_pixels) + + def test_image(self): + widget = self.create() + self.checkImageParam(widget, 'image') + + def test_insertbackground(self): + widget = self.create() + self.checkColorParam(widget, 'insertbackground') + + def test_insertborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertborderwidth', + 0, 1.3, 2.6, 6, -2, '10p') + + def test_insertofftime(self): + widget = self.create() + self.checkIntegerParam(widget, 'insertofftime', 100) + + def test_insertontime(self): + widget = self.create() + self.checkIntegerParam(widget, 'insertontime', 100) + + def test_insertwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p') + + def test_jump(self): + widget = self.create() + self.checkBooleanParam(widget, 'jump') + + def test_justify(self): + widget = self.create() + self.checkEnumParam(widget, 'justify', 'left', 'right', 'center', + errmsg='bad justification "{}": must be ' + 'left, right, or center') + self.checkInvalidParam(widget, 'justify', '', + errmsg='ambiguous justification "": must be ' + 'left, right, or center') + + def test_orient(self): + widget = self.create() + self.assertEqual(str(widget['orient']), self.default_orient) + self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical') + + def test_padx(self): + widget = self.create() + self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, -2, '12m', + conv=self._conv_pad_pixels) + + def test_pady(self): + widget = self.create() + self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, -2, '12m', + conv=self._conv_pad_pixels) + + def test_relief(self): + widget = self.create() + self.checkReliefParam(widget, 'relief') + + def test_repeatdelay(self): + widget = self.create() + self.checkIntegerParam(widget, 'repeatdelay', -500, 500) + + def test_repeatinterval(self): + widget = self.create() + self.checkIntegerParam(widget, 'repeatinterval', -500, 500) + + def test_selectbackground(self): + widget = self.create() + self.checkColorParam(widget, 'selectbackground') + + def test_selectborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p') + + def test_selectforeground(self): + widget = self.create() + self.checkColorParam(widget, 'selectforeground') + + def test_setgrid(self): + widget = self.create() + self.checkBooleanParam(widget, 'setgrid') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'active', 'disabled', 'normal') + + def test_takefocus(self): + widget = self.create() + self.checkParams(widget, 'takefocus', '0', '1', '') + + def test_text(self): + widget = self.create() + self.checkParams(widget, 'text', '', 'any string') + + def test_textvariable(self): + widget = self.create() + var = tkinter.StringVar() + self.checkVariableParam(widget, 'textvariable', var) + + def test_troughcolor(self): + widget = self.create() + self.checkColorParam(widget, 'troughcolor') + + def test_underline(self): + widget = self.create() + self.checkIntegerParam(widget, 'underline', 0, 1, 10) + + def test_wraplength(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkPixelsParam(widget, 'wraplength', 100) + else: + self.checkParams(widget, 'wraplength', 100) + + def test_xscrollcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'xscrollcommand') + + def test_yscrollcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'yscrollcommand') + + # non-standard but common options + + def test_command(self): + widget = self.create() + self.checkCommandParam(widget, 'command') + + def test_indicatoron(self): + widget = self.create() + self.checkBooleanParam(widget, 'indicatoron') + + def test_offrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'offrelief') + + def test_overrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'overrelief') + + def test_selectcolor(self): + widget = self.create() + self.checkColorParam(widget, 'selectcolor') + + def test_selectimage(self): + widget = self.create() + self.checkImageParam(widget, 'selectimage') + + @requires_tcl(8, 5) + def test_tristateimage(self): + widget = self.create() + self.checkImageParam(widget, 'tristateimage') + + @requires_tcl(8, 5) + def test_tristatevalue(self): + widget = self.create() + self.checkParam(widget, 'tristatevalue', 'unknowable') + + def test_variable(self): + widget = self.create() + var = tkinter.DoubleVar() + self.checkVariableParam(widget, 'variable', var) + + +class IntegerSizeTests: + def test_height(self): + widget = self.create() + self.checkIntegerParam(widget, 'height', 100, -100, 0) + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402, -402, 0) + + +class PixelSizeTests: + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c') + + def test_width(self): + widget = self.create() + self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i') + + +def add_standard_options(*source_classes): + # This decorator adds test_xxx methods from source classes for every xxx + # option in the OPTIONS class attribute if they are not defined explicitly. + def decorator(cls): + for option in cls.OPTIONS: + methodname = 'test_' + option + if not hasattr(cls, methodname): + for source_class in source_classes: + if hasattr(source_class, methodname): + setattr(cls, methodname, + getattr(source_class, methodname)) + break + else: + def test(self, option=option): + widget = self.create() + widget[option] + raise AssertionError('Option "%s" is not tested in %s' % + (option, cls.__name__)) + test.__name__ = methodname + setattr(cls, methodname, test) + return cls + return decorator diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -147,6 +147,8 @@ Tests ----- +- Issue #19085: Added basic tests for all tkinter widget options. + - Issue 19384: Fix test_py_compile for root user, patch by Claudiu Popa. Build -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 09:48:39 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 09:48:39 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Added_basic_tests_for_all_tkinter_widget_options=2E?= Message-ID: <3dBYpH4dW3z7Ljr@mail.python.org> http://hg.python.org/cpython/rev/ced345326151 changeset: 86836:ced345326151 branch: 2.7 parent: 86818:01087a302721 user: Serhiy Storchaka date: Sat Nov 02 10:46:21 2013 +0200 summary: Issue #19085: Added basic tests for all tkinter widget options. files: Lib/lib-tk/test/test_tkinter/test_widgets.py | 916 ++++++++++ Lib/lib-tk/test/test_ttk/support.py | 40 + Lib/lib-tk/test/test_ttk/test_widgets.py | 488 +++++- Lib/lib-tk/test/widget_tests.py | 504 +++++ Misc/NEWS | 2 + 5 files changed, 1931 insertions(+), 19 deletions(-) diff --git a/Lib/lib-tk/test/test_tkinter/test_widgets.py b/Lib/lib-tk/test/test_tkinter/test_widgets.py new file mode 100644 --- /dev/null +++ b/Lib/lib-tk/test/test_tkinter/test_widgets.py @@ -0,0 +1,916 @@ +import unittest +import Tkinter +import os +from test.test_support import requires, run_unittest + +from test_ttk.support import tcl_version, requires_tcl, widget_eq +from widget_tests import (add_standard_options, noconv, int_round, + AbstractWidgetTest, StandardOptionsTests, + IntegerSizeTests, PixelSizeTests) + +requires('gui') + + +class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): + _conv_pad_pixels = noconv + + def test_class(self): + widget = self.create() + self.assertEqual(widget['class'], + widget.__class__.__name__.title()) + self.checkInvalidParam(widget, 'class', 'Foo', + errmsg="can't modify -class option after widget is created") + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + + def test_colormap(self): + widget = self.create() + self.assertEqual(widget['colormap'], '') + self.checkInvalidParam(widget, 'colormap', 'new', + errmsg="can't modify -colormap option after widget is created") + widget2 = self.create(colormap='new') + self.assertEqual(widget2['colormap'], 'new') + + def test_container(self): + widget = self.create() + self.assertEqual(widget['container'], 0 if self.wantobjects else '0') + self.checkInvalidParam(widget, 'container', 1, + errmsg="can't modify -container option after widget is created") + widget2 = self.create(container=True) + self.assertEqual(widget2['container'], 1 if self.wantobjects else '1') + + def test_visual(self): + widget = self.create() + self.assertEqual(widget['visual'], '') + self.checkInvalidParam(widget, 'visual', 'default', + errmsg="can't modify -visual option after widget is created") + widget2 = self.create(visual='default') + self.assertEqual(widget2['visual'], 'default') + + + at add_standard_options(StandardOptionsTests) +class ToplevelTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'menu', 'padx', 'pady', 'relief', 'screen', + 'takefocus', 'use', 'visual', 'width', + ) + + def _create(self, **kwargs): + return Tkinter.Toplevel(self.root, **kwargs) + + def test_menu(self): + widget = self.create() + menu = Tkinter.Menu(self.root) + self.checkParam(widget, 'menu', menu, eq=widget_eq) + self.checkParam(widget, 'menu', '') + + def test_screen(self): + widget = self.create() + self.assertEqual(widget['screen'], '') + display = os.environ['DISPLAY'] + self.checkInvalidParam(widget, 'screen', display, + errmsg="can't modify -screen option after widget is created") + widget2 = self.create(screen=display) + self.assertEqual(widget2['screen'], display) + + def test_use(self): + widget = self.create() + self.assertEqual(widget['use'], '') + widget1 = self.create(container=True) + self.assertEqual(widget1['use'], '') + self.checkInvalidParam(widget1, 'use', '0x44022', + errmsg="can't modify -use option after widget is created") + wid = hex(widget1.winfo_id()) + widget2 = self.create(use=wid) + self.assertEqual(widget2['use'], wid) + + + at add_standard_options(StandardOptionsTests) +class FrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'relief', 'takefocus', 'visual', 'width', + ) + + def _create(self, **kwargs): + return Tkinter.Frame(self.root, **kwargs) + + + at add_standard_options(StandardOptionsTests) +class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'class', 'colormap', 'container', 'cursor', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'labelanchor', 'labelwidget', 'padx', 'pady', 'relief', + 'takefocus', 'text', 'visual', 'width', + ) + + def _create(self, **kwargs): + return Tkinter.LabelFrame(self.root, **kwargs) + + def test_labelanchor(self): + widget = self.create() + self.checkEnumParam(widget, 'labelanchor', + 'e', 'en', 'es', 'n', 'ne', 'nw', + 's', 'se', 'sw', 'w', 'wn', 'ws') + self.checkInvalidParam(widget, 'labelanchor', 'center') + + def test_labelwidget(self): + widget = self.create() + label = Tkinter.Label(self.root, text='Mupp', name='foo') + self.checkParam(widget, 'labelwidget', label, expected='.foo') + label.destroy() + + +class AbstractLabelTest(AbstractWidgetTest, IntegerSizeTests): + _conv_pixels = noconv + + def test_highlightthickness(self): + widget = self.create() + self.checkPixelsParam(widget, 'highlightthickness', + 0, 1.3, 2.6, 6, -2, '10p') + + + at add_standard_options(StandardOptionsTests) +class LabelTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'justify', 'padx', 'pady', 'relief', 'state', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return Tkinter.Label(self.root, **kwargs) + + + at add_standard_options(StandardOptionsTests) +class ButtonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', 'default', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'justify', 'overrelief', 'padx', 'pady', 'relief', + 'repeatdelay', 'repeatinterval', + 'state', 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength') + + def _create(self, **kwargs): + return Tkinter.Button(self.root, **kwargs) + + def test_default(self): + widget = self.create() + self.checkEnumParam(widget, 'default', 'active', 'disabled', 'normal') + + + at add_standard_options(StandardOptionsTests) +class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', + 'offrelief', 'offvalue', 'onvalue', 'overrelief', + 'padx', 'pady', 'relief', 'selectcolor', 'selectimage', 'state', + 'takefocus', 'text', 'textvariable', + 'tristateimage', 'tristatevalue', + 'underline', 'variable', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return Tkinter.Checkbutton(self.root, **kwargs) + + + def test_offvalue(self): + widget = self.create() + self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') + + def test_onvalue(self): + widget = self.create() + self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') + + + at add_standard_options(StandardOptionsTests) +class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'command', 'compound', 'cursor', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', 'offrelief', 'overrelief', + 'padx', 'pady', 'relief', 'selectcolor', 'selectimage', 'state', + 'takefocus', 'text', 'textvariable', + 'tristateimage', 'tristatevalue', + 'underline', 'value', 'variable', 'width', 'wraplength', + ) + + def _create(self, **kwargs): + return Tkinter.Radiobutton(self.root, **kwargs) + + def test_value(self): + widget = self.create() + self.checkParams(widget, 'value', 1, 2.3, '', 'any string') + + + at add_standard_options(StandardOptionsTests) +class MenubuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', + 'compound', 'cursor', 'direction', + 'disabledforeground', 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'indicatoron', 'justify', 'menu', + 'padx', 'pady', 'relief', 'state', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + _conv_pixels = AbstractWidgetTest._conv_pixels + + def _create(self, **kwargs): + return Tkinter.Menubutton(self.root, **kwargs) + + def test_direction(self): + widget = self.create() + self.checkEnumParam(widget, 'direction', + 'above', 'below', 'flush', 'left', 'right') + + def test_height(self): + widget = self.create() + self.checkIntegerParam(widget, 'height', 100, -100, 0, conv=str) + + test_highlightthickness = StandardOptionsTests.test_highlightthickness.im_func + + def test_image(self): + widget = self.create() + image = Tkinter.PhotoImage('image1') + self.checkParam(widget, 'image', image, conv=str) + errmsg = 'image "spam" doesn\'t exist' + with self.assertRaises(Tkinter.TclError) as cm: + widget['image'] = 'spam' + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + with self.assertRaises(Tkinter.TclError) as cm: + widget.configure({'image': 'spam'}) + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + + def test_menu(self): + widget = self.create() + menu = Tkinter.Menu(widget, name='menu') + self.checkParam(widget, 'menu', menu, eq=widget_eq) + menu.destroy() + + def test_padx(self): + widget = self.create() + self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m') + self.checkParam(widget, 'padx', -2, expected=0) + + def test_pady(self): + widget = self.create() + self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m') + self.checkParam(widget, 'pady', -2, expected=0) + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402, -402, 0, conv=str) + + +class OptionMenuTest(MenubuttonTest, unittest.TestCase): + + def _create(self, default='b', values=('a', 'b', 'c'), **kwargs): + return Tkinter.OptionMenu(self.root, None, default, *values, **kwargs) + + + at add_standard_options(IntegerSizeTests, StandardOptionsTests) +class EntryTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', 'cursor', + 'disabledbackground', 'disabledforeground', + 'exportselection', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'invalidcommand', 'justify', 'readonlybackground', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'show', 'state', 'takefocus', 'textvariable', + 'validate', 'validatecommand', 'width', 'xscrollcommand', + ) + + def _create(self, **kwargs): + return Tkinter.Entry(self.root, **kwargs) + + def test_disabledbackground(self): + widget = self.create() + self.checkColorParam(widget, 'disabledbackground') + + def test_insertborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, -2) + self.checkParam(widget, 'insertborderwidth', 2, expected=1) + self.checkParam(widget, 'insertborderwidth', '10p', expected=1) + + def test_insertwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertwidth', 1.3, 3.6, '10p') + if tcl_version[:2] == (8, 5): + self.checkParam(widget, 'insertwidth', 0.9, expected=2) + else: + self.checkParam(widget, 'insertwidth', 0.9, expected=1) + self.checkParam(widget, 'insertwidth', 0.1, expected=2) + self.checkParam(widget, 'insertwidth', -2, expected=2) + + def test_invalidcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'invalidcommand') + self.checkCommandParam(widget, 'invcmd') + + def test_readonlybackground(self): + widget = self.create() + self.checkColorParam(widget, 'readonlybackground') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', '*') + self.checkParam(widget, 'show', '') + self.checkParam(widget, 'show', ' ') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', + 'disabled', 'normal', 'readonly') + + def test_validate(self): + widget = self.create() + self.checkEnumParam(widget, 'validate', + 'all', 'key', 'focus', 'focusin', 'focusout', 'none') + + def test_validatecommand(self): + widget = self.create() + self.checkCommandParam(widget, 'validatecommand') + self.checkCommandParam(widget, 'vcmd') + + + at add_standard_options(StandardOptionsTests) +class SpinboxTest(EntryTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'background', 'borderwidth', + 'buttonbackground', 'buttoncursor', 'buttondownrelief', 'buttonuprelief', + 'command', 'cursor', 'disabledbackground', 'disabledforeground', + 'exportselection', 'font', 'foreground', 'format', 'from', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'increment', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'invalidcommand', 'justify', 'relief', 'readonlybackground', + 'repeatdelay', 'repeatinterval', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'state', 'takefocus', 'textvariable', 'to', + 'validate', 'validatecommand', 'values', + 'width', 'wrap', 'xscrollcommand', + ) + + def _create(self, **kwargs): + return Tkinter.Spinbox(self.root, **kwargs) + + test_show = None + + def test_buttonbackground(self): + widget = self.create() + self.checkColorParam(widget, 'buttonbackground') + + def test_buttoncursor(self): + widget = self.create() + self.checkCursorParam(widget, 'buttoncursor') + + def test_buttondownrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'buttondownrelief') + + def test_buttonuprelief(self): + widget = self.create() + self.checkReliefParam(widget, 'buttonuprelief') + + def test_format(self): + widget = self.create() + self.checkParam(widget, 'format', '%2f') + self.checkParam(widget, 'format', '%2.2f') + self.checkParam(widget, 'format', '%.2f') + self.checkParam(widget, 'format', '%2.f') + self.checkInvalidParam(widget, 'format', '%2e-1f') + self.checkInvalidParam(widget, 'format', '2.2') + self.checkInvalidParam(widget, 'format', '%2.-2f') + self.checkParam(widget, 'format', '%-2.02f') + self.checkParam(widget, 'format', '% 2.02f') + self.checkParam(widget, 'format', '% -2.200f') + self.checkParam(widget, 'format', '%09.200f') + self.checkInvalidParam(widget, 'format', '%d') + + def test_from(self): + widget = self.create() + self.checkParam(widget, 'to', 100.0) + self.checkFloatParam(widget, 'from', -10, 10.2, 11.7) + self.checkInvalidParam(widget, 'from', 200, + errmsg='-to value must be greater than -from value') + + def test_increment(self): + widget = self.create() + self.checkFloatParam(widget, 'increment', -1, 1, 10.2, 12.8, 0) + + def test_to(self): + widget = self.create() + self.checkParam(widget, 'from', -100.0) + self.checkFloatParam(widget, 'to', -10, 10.2, 11.7) + self.checkInvalidParam(widget, 'to', -200, + errmsg='-to value must be greater than -from value') + + def test_values(self): + # XXX + widget = self.create() + self.assertEqual(widget['values'], '') + self.checkParam(widget, 'values', 'mon tue wed thur') + self.checkParam(widget, 'values', ('mon', 'tue', 'wed', 'thur'), + expected='mon tue wed thur') + self.checkParam(widget, 'values', (42, 3.14, '', 'any string'), + expected='42 3.14 {} {any string}') + self.checkParam(widget, 'values', '') + + def test_wrap(self): + widget = self.create() + self.checkBooleanParam(widget, 'wrap') + + + at add_standard_options(StandardOptionsTests) +class TextTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'autoseparators', 'background', 'blockcursor', 'borderwidth', + 'cursor', 'endline', 'exportselection', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'inactiveselectbackground', 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertunfocussed', 'insertwidth', + 'maxundo', 'padx', 'pady', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'setgrid', 'spacing1', 'spacing2', 'spacing3', 'startline', 'state', + 'tabs', 'tabstyle', 'takefocus', 'undo', 'width', 'wrap', + 'xscrollcommand', 'yscrollcommand', + ) + if tcl_version < (8, 5): + wantobjects = False + + def _create(self, **kwargs): + return Tkinter.Text(self.root, **kwargs) + + def test_autoseparators(self): + widget = self.create() + self.checkBooleanParam(widget, 'autoseparators') + + @requires_tcl(8, 5) + def test_blockcursor(self): + widget = self.create() + self.checkBooleanParam(widget, 'blockcursor') + + @requires_tcl(8, 5) + def test_endline(self): + widget = self.create() + text = '\n'.join('Line %d' for i in range(100)) + widget.insert('end', text) + self.checkParam(widget, 'endline', 200, expected='') + self.checkParam(widget, 'endline', -10, expected='') + self.checkInvalidParam(widget, 'endline', 'spam', + errmsg='expected integer but got "spam"') + self.checkParam(widget, 'endline', 50) + self.checkParam(widget, 'startline', 15) + self.checkInvalidParam(widget, 'endline', 10, + errmsg='-startline must be less than or equal to -endline') + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, '3c') + self.checkParam(widget, 'height', -100, expected=1) + self.checkParam(widget, 'height', 0, expected=1) + + def test_maxundo(self): + widget = self.create() + self.checkIntegerParam(widget, 'maxundo', 0, 5, -1) + + @requires_tcl(8, 5) + def test_inactiveselectbackground(self): + widget = self.create() + self.checkColorParam(widget, 'inactiveselectbackground') + + @requires_tcl(8, 6) + def test_insertunfocussed(self): + widget = self.create() + self.checkEnumParam(widget, 'insertunfocussed', + 'hollow', 'none', 'solid') + + def test_selectborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'selectborderwidth', + 1.3, 2.6, -2, '10p', conv=False, + keep_orig=tcl_version >= (8, 5)) + + def test_spacing1(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing1', 20, 21.4, 22.6, '0.5c') + self.checkParam(widget, 'spacing1', -5, expected=0) + + def test_spacing2(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing2', 5, 6.4, 7.6, '0.1c') + self.checkParam(widget, 'spacing2', -1, expected=0) + + def test_spacing3(self): + widget = self.create() + self.checkPixelsParam(widget, 'spacing3', 20, 21.4, 22.6, '0.5c') + self.checkParam(widget, 'spacing3', -10, expected=0) + + @requires_tcl(8, 5) + def test_startline(self): + widget = self.create() + text = '\n'.join('Line %d' for i in range(100)) + widget.insert('end', text) + self.checkParam(widget, 'startline', 200, expected='') + self.checkParam(widget, 'startline', -10, expected='') + self.checkInvalidParam(widget, 'startline', 'spam', + errmsg='expected integer but got "spam"') + self.checkParam(widget, 'startline', 10) + self.checkParam(widget, 'endline', 50) + self.checkInvalidParam(widget, 'startline', 70, + errmsg='-startline must be less than or equal to -endline') + + def test_state(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkParams(widget, 'state', 'disabled', 'normal') + else: + self.checkEnumParam(widget, 'state', 'disabled', 'normal') + + def test_tabs(self): + widget = self.create() + self.checkParam(widget, 'tabs', (10.2, 20.7, '1i', '2i')) + self.checkParam(widget, 'tabs', '10.2 20.7 1i 2i', + expected=('10.2', '20.7', '1i', '2i')) + self.checkParam(widget, 'tabs', '2c left 4c 6c center', + expected=('2c', 'left', '4c', '6c', 'center')) + self.checkInvalidParam(widget, 'tabs', 'spam', + errmsg='bad screen distance "spam"', + keep_orig=tcl_version >= (8, 5)) + + @requires_tcl(8, 5) + def test_tabstyle(self): + widget = self.create() + self.checkEnumParam(widget, 'tabstyle', 'tabular', 'wordprocessor') + + def test_undo(self): + widget = self.create() + self.checkBooleanParam(widget, 'undo') + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402) + self.checkParam(widget, 'width', -402, expected=1) + self.checkParam(widget, 'width', 0, expected=1) + + def test_wrap(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkParams(widget, 'wrap', 'char', 'none', 'word') + else: + self.checkEnumParam(widget, 'wrap', 'char', 'none', 'word') + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class CanvasTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', + 'closeenough', 'confine', 'cursor', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'relief', 'scrollregion', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'state', 'takefocus', + 'xscrollcommand', 'xscrollincrement', + 'yscrollcommand', 'yscrollincrement', 'width', + ) + + _conv_pixels = staticmethod(int_round) + wantobjects = False + + def _create(self, **kwargs): + return Tkinter.Canvas(self.root, **kwargs) + + def test_closeenough(self): + widget = self.create() + self.checkFloatParam(widget, 'closeenough', 24, 2.4, 3.6, -3, + conv=float) + + def test_confine(self): + widget = self.create() + self.checkBooleanParam(widget, 'confine') + + def test_scrollregion(self): + widget = self.create() + self.checkParam(widget, 'scrollregion', '0 0 200 150') + self.checkParam(widget, 'scrollregion', (0, 0, 200, 150), + expected='0 0 200 150') + self.checkParam(widget, 'scrollregion', '') + self.checkInvalidParam(widget, 'scrollregion', 'spam', + errmsg='bad scrollRegion "spam"') + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 'spam')) + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200)) + self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 150, 0)) + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'disabled', 'normal', + errmsg='bad state value "{}": must be normal or disabled') + + def test_xscrollincrement(self): + widget = self.create() + self.checkPixelsParam(widget, 'xscrollincrement', + 40, 0, 41.2, 43.6, -40, '0.5i') + + def test_yscrollincrement(self): + widget = self.create() + self.checkPixelsParam(widget, 'yscrollincrement', + 10, 0, 11.2, 13.6, -10, '0.1i') + + + at add_standard_options(IntegerSizeTests, StandardOptionsTests) +class ListboxTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activestyle', 'background', 'borderwidth', 'cursor', + 'disabledforeground', 'exportselection', + 'font', 'foreground', 'height', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'listvariable', 'relief', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'selectmode', 'setgrid', 'state', + 'takefocus', 'width', 'xscrollcommand', 'yscrollcommand', + ) + + def _create(self, **kwargs): + return Tkinter.Listbox(self.root, **kwargs) + + def test_activestyle(self): + widget = self.create() + self.checkEnumParam(widget, 'activestyle', + 'dotbox', 'none', 'underline') + + def test_listvariable(self): + widget = self.create() + var = Tkinter.DoubleVar() + self.checkVariableParam(widget, 'listvariable', var) + + def test_selectmode(self): + widget = self.create() + self.checkParam(widget, 'selectmode', 'single') + self.checkParam(widget, 'selectmode', 'browse') + self.checkParam(widget, 'selectmode', 'multiple') + self.checkParam(widget, 'selectmode', 'extended') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'disabled', 'normal') + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class ScaleTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'background', 'bigincrement', 'borderwidth', + 'command', 'cursor', 'digits', 'font', 'foreground', 'from', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'label', 'length', 'orient', 'relief', + 'repeatdelay', 'repeatinterval', + 'resolution', 'showvalue', 'sliderlength', 'sliderrelief', 'state', + 'takefocus', 'tickinterval', 'to', 'troughcolor', 'variable', 'width', + ) + default_orient = 'vertical' + + def _create(self, **kwargs): + return Tkinter.Scale(self.root, **kwargs) + + def test_bigincrement(self): + widget = self.create() + self.checkFloatParam(widget, 'bigincrement', 12.4, 23.6, -5) + + def test_digits(self): + widget = self.create() + self.checkIntegerParam(widget, 'digits', 5, 0) + + def test_from(self): + widget = self.create() + self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=round) + + def test_label(self): + widget = self.create() + self.checkParam(widget, 'label', 'any string') + self.checkParam(widget, 'label', '') + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') + + def test_resolution(self): + widget = self.create() + self.checkFloatParam(widget, 'resolution', 4.2, 0, 6.7, -2) + + def test_showvalue(self): + widget = self.create() + self.checkBooleanParam(widget, 'showvalue') + + def test_sliderlength(self): + widget = self.create() + self.checkPixelsParam(widget, 'sliderlength', + 10, 11.2, 15.6, -3, '3m') + + def test_sliderrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'sliderrelief') + + def test_tickinterval(self): + widget = self.create() + self.checkFloatParam(widget, 'tickinterval', 1, 4.3, 7.6, 0, + conv=round) + self.checkParam(widget, 'tickinterval', -2, expected=2, + conv=round) + + def test_to(self): + widget = self.create() + self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, + conv=round) + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activerelief', + 'background', 'borderwidth', + 'command', 'cursor', 'elementborderwidth', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'jump', 'orient', 'relief', + 'repeatdelay', 'repeatinterval', + 'takefocus', 'troughcolor', 'width', + ) + _conv_pixels = staticmethod(int_round) + wantobjects = False + default_orient = 'vertical' + + def _create(self, **kwargs): + return Tkinter.Scrollbar(self.root, **kwargs) + + def test_activerelief(self): + widget = self.create() + self.checkReliefParam(widget, 'activerelief') + + def test_elementborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'elementborderwidth', 4.3, 5.6, -2, '1m') + + def test_orient(self): + widget = self.create() + self.checkEnumParam(widget, 'orient', 'vertical', 'horizontal', + errmsg='bad orientation "{}": must be vertical or horizontal') + + + at add_standard_options(StandardOptionsTests) +class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'borderwidth', 'cursor', + 'handlepad', 'handlesize', 'height', + 'opaqueresize', 'orient', 'relief', + 'sashcursor', 'sashpad', 'sashrelief', 'sashwidth', + 'showhandle', 'width', + ) + default_orient = 'horizontal' + + def _create(self, **kwargs): + return Tkinter.PanedWindow(self.root, **kwargs) + + def test_handlepad(self): + widget = self.create() + self.checkPixelsParam(widget, 'handlepad', 5, 6.4, 7.6, -3, '1m') + + def test_handlesize(self): + widget = self.create() + self.checkPixelsParam(widget, 'handlesize', 8, 9.4, 10.6, -3, '2m', + conv=noconv) + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i', + conv=noconv) + + def test_opaqueresize(self): + widget = self.create() + self.checkBooleanParam(widget, 'opaqueresize') + + def test_sashcursor(self): + widget = self.create() + self.checkCursorParam(widget, 'sashcursor') + + def test_sashpad(self): + widget = self.create() + self.checkPixelsParam(widget, 'sashpad', 8, 1.3, 2.6, -2, '2m') + + def test_sashrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'sashrelief') + + def test_sashwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'sashwidth', 10, 11.1, 15.6, -3, '1m', + conv=noconv) + + def test_showhandle(self): + widget = self.create() + self.checkBooleanParam(widget, 'showhandle') + + def test_width(self): + widget = self.create() + self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i', + conv=noconv) + + + at add_standard_options(StandardOptionsTests) +class MenuTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'activebackground', 'activeborderwidth', 'activeforeground', + 'background', 'borderwidth', 'cursor', + 'disabledforeground', 'font', 'foreground', + 'postcommand', 'relief', 'selectcolor', 'takefocus', + 'tearoff', 'tearoffcommand', 'title', 'type', + ) + _conv_pixels = noconv + + def _create(self, **kwargs): + return Tkinter.Menu(self.root, **kwargs) + + def test_postcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'postcommand') + + def test_tearoff(self): + widget = self.create() + self.checkBooleanParam(widget, 'tearoff') + + def test_tearoffcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'tearoffcommand') + + def test_title(self): + widget = self.create() + self.checkParam(widget, 'title', 'any string') + + def test_type(self): + widget = self.create() + self.checkEnumParam(widget, 'type', + 'normal', 'tearoff', 'menubar') + + + at add_standard_options(PixelSizeTests, StandardOptionsTests) +class MessageTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'anchor', 'aspect', 'background', 'borderwidth', + 'cursor', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'justify', 'padx', 'pady', 'relief', + 'takefocus', 'text', 'textvariable', 'width', + ) + _conv_pad_pixels = noconv + + def _create(self, **kwargs): + return Tkinter.Message(self.root, **kwargs) + + def test_aspect(self): + widget = self.create() + self.checkIntegerParam(widget, 'aspect', 250, 0, -300) + + +tests_gui = [ + ButtonTest, CanvasTest, CheckbuttonTest, EntryTest, + FrameTest, LabelFrameTest,LabelTest, ListboxTest, + MenubuttonTest, MenuTest, MessageTest, OptionMenuTest, + PanedWindowTest, RadiobuttonTest, ScaleTest, ScrollbarTest, + SpinboxTest, TextTest, ToplevelTest, +] + +if __name__ == '__main__': + run_unittest(*tests_gui) diff --git a/Lib/lib-tk/test/test_ttk/support.py b/Lib/lib-tk/test/test_ttk/support.py --- a/Lib/lib-tk/test/test_ttk/support.py +++ b/Lib/lib-tk/test/test_ttk/support.py @@ -1,3 +1,4 @@ +import unittest import Tkinter def get_tk_root(): @@ -31,3 +32,42 @@ widget.event_generate('', x=x, y=y) widget.event_generate('', x=x, y=y) widget.event_generate('', x=x, y=y) + + +import _tkinter +tcl_version = tuple(map(int, _tkinter.TCL_VERSION.split('.'))) + +def requires_tcl(*version): + return unittest.skipUnless(tcl_version >= version, + 'requires Tcl version >= ' + '.'.join(map(str, version))) + +units = { + 'c': 72 / 2.54, # centimeters + 'i': 72, # inches + 'm': 72 / 25.4, # millimeters + 'p': 1, # points +} + +def pixels_conv(value): + return float(value[:-1]) * units[value[-1:]] + +def tcl_obj_eq(actual, expected): + if actual == expected: + return True + if isinstance(actual, _tkinter.Tcl_Obj): + if isinstance(expected, str): + return str(actual) == expected + if isinstance(actual, tuple): + if isinstance(expected, tuple): + return (len(actual) == len(expected) and + all(tcl_obj_eq(act, exp) + for act, exp in zip(actual, expected))) + return False + +def widget_eq(actual, expected): + if actual == expected: + return True + if isinstance(actual, (str, Tkinter.Widget)): + if isinstance(expected, (str, Tkinter.Widget)): + return str(actual) == str(expected) + return False diff --git a/Lib/lib-tk/test/test_ttk/test_widgets.py b/Lib/lib-tk/test/test_ttk/test_widgets.py --- a/Lib/lib-tk/test/test_ttk/test_widgets.py +++ b/Lib/lib-tk/test/test_ttk/test_widgets.py @@ -6,9 +6,53 @@ import support from test_functions import MockTclObj, MockStateSpec +from support import tcl_version +from widget_tests import (add_standard_options, noconv, + AbstractWidgetTest, StandardOptionsTests, + IntegerSizeTests, PixelSizeTests) requires('gui') + +class StandardTtkOptionsTests(StandardOptionsTests): + + def test_class(self): + widget = self.create() + self.assertEqual(widget['class'], '') + errmsg='attempt to change read-only option' + if tcl_version < (8, 6): + errmsg='Attempt to change read-only option' + self.checkInvalidParam(widget, 'class', 'Foo', errmsg=errmsg) + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + + def test_padding(self): + widget = self.create() + self.checkParam(widget, 'padding', 0, expected=('0',)) + self.checkParam(widget, 'padding', 5, expected=('5',)) + self.checkParam(widget, 'padding', (5, 6), expected=('5', '6')) + self.checkParam(widget, 'padding', (5, 6, 7), + expected=('5', '6', '7')) + self.checkParam(widget, 'padding', (5, 6, 7, 8), + expected=('5', '6', '7', '8')) + self.checkParam(widget, 'padding', ('5p', '6p', '7p', '8p')) + self.checkParam(widget, 'padding', (), expected='') + + def test_style(self): + widget = self.create() + self.assertEqual(widget['style'], '') + errmsg = 'Layout Foo not found' + if hasattr(self, 'default_orient'): + errmsg = ('Layout %s.Foo not found' % + getattr(self, 'default_orient').title()) + self.checkInvalidParam(widget, 'style', 'Foo', + errmsg=errmsg) + widget2 = self.create(class_='Foo') + self.assertEqual(widget2['class'], 'Foo') + # XXX + pass + + class WidgetTest(unittest.TestCase): """Tests methods available in every ttk widget.""" @@ -72,7 +116,112 @@ self.assertEqual(self.widget.state(), ('active', )) -class ButtonTest(unittest.TestCase): +class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): + _conv_pixels = noconv + + + at add_standard_options(StandardTtkOptionsTests) +class FrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'borderwidth', 'class', 'cursor', 'height', + 'padding', 'relief', 'style', 'takefocus', + 'width', + ) + + def _create(self, **kwargs): + return ttk.Frame(self.root, **kwargs) + + + at add_standard_options(StandardTtkOptionsTests) +class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): + OPTIONS = ( + 'borderwidth', 'class', 'cursor', 'height', + 'labelanchor', 'labelwidget', + 'padding', 'relief', 'style', 'takefocus', + 'text', 'underline', 'width', + ) + + def _create(self, **kwargs): + return ttk.LabelFrame(self.root, **kwargs) + + def test_labelanchor(self): + widget = self.create() + self.checkEnumParam(widget, 'labelanchor', + 'e', 'en', 'es', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'wn', 'ws', + errmsg='Bad label anchor specification {}') + self.checkInvalidParam(widget, 'labelanchor', 'center') + + def test_labelwidget(self): + widget = self.create() + label = ttk.Label(self.root, text='Mupp', name='foo') + self.checkParam(widget, 'labelwidget', label, expected='.foo') + label.destroy() + + +class AbstractLabelTest(AbstractWidgetTest): + + def checkImageParam(self, widget, name): + image = Tkinter.PhotoImage('image1') + image2 = Tkinter.PhotoImage('image2') + self.checkParam(widget, name, image, expected=('image1',)) + self.checkParam(widget, name, 'image1', expected=('image1',)) + self.checkParam(widget, name, (image,), expected=('image1',)) + self.checkParam(widget, name, (image, 'active', image2), + expected=('image1', 'active', 'image2')) + self.checkParam(widget, name, 'image1 active image2', + expected=('image1', 'active', 'image2')) + self.checkInvalidParam(widget, name, 'spam', + errmsg='image "spam" doesn\'t exist') + + def test_compound(self): + widget = self.create() + self.checkEnumParam(widget, 'compound', + 'none', 'text', 'image', 'center', + 'top', 'bottom', 'left', 'right') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', 'active', 'disabled', 'normal') + + def test_width(self): + widget = self.create() + self.checkParams(widget, 'width', 402, -402, 0) + + + at add_standard_options(StandardTtkOptionsTests) +class LabelTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'anchor', 'background', + 'class', 'compound', 'cursor', 'font', 'foreground', + 'image', 'justify', 'padding', 'relief', 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', 'wraplength', + ) + _conv_pixels = noconv + + def _create(self, **kwargs): + return ttk.Label(self.root, **kwargs) + + def test_font(self): + widget = self.create() + self.checkParam(widget, 'font', + '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') + + + at add_standard_options(StandardTtkOptionsTests) +class ButtonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', 'default', + 'image', 'state', 'style', 'takefocus', 'text', 'textvariable', + 'underline', 'width', + ) + + def _create(self, **kwargs): + return ttk.Button(self.root, **kwargs) + + def test_default(self): + widget = self.create() + self.checkEnumParam(widget, 'default', 'normal', 'active', 'disabled') def test_invoke(self): success = [] @@ -81,7 +230,27 @@ self.assertTrue(success) -class CheckbuttonTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', + 'image', + 'offvalue', 'onvalue', + 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'variable', 'width', + ) + + def _create(self, **kwargs): + return ttk.Checkbutton(self.root, **kwargs) + + def test_offvalue(self): + widget = self.create() + self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') + + def test_onvalue(self): + widget = self.create() + self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') def test_invoke(self): success = [] @@ -104,21 +273,40 @@ cbtn['command'] = '' res = cbtn.invoke() - self.assertEqual(str(res), '') + self.assertFalse(str(res)) self.assertFalse(len(success) > 1) self.assertEqual(cbtn['offvalue'], cbtn.tk.globalgetvar(cbtn['variable'])) -class ComboboxTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class ComboboxTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'exportselection', 'height', + 'justify', 'postcommand', 'state', 'style', + 'takefocus', 'textvariable', 'values', 'width', + ) def setUp(self): + super(ComboboxTest, self).setUp() support.root_deiconify() - self.combo = ttk.Combobox() + self.combo = self.create() def tearDown(self): self.combo.destroy() support.root_withdraw() + super(ComboboxTest, self).tearDown() + + def _create(self, **kwargs): + return ttk.Combobox(self.root, **kwargs) + + def test_height(self): + widget = self.create() + self.checkParams(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', 'active', 'disabled', 'normal') def _show_drop_down_listbox(self): width = self.combo.winfo_width() @@ -166,8 +354,16 @@ self.assertEqual(self.combo.get(), getval) self.assertEqual(self.combo.current(), currval) + self.assertEqual(self.combo['values'], + () if tcl_version < (8, 5) else '') check_get_current('', -1) + self.checkParam(self.combo, 'values', 'mon tue wed thur', + expected=('mon', 'tue', 'wed', 'thur')) + self.checkParam(self.combo, 'values', ('mon', 'tue', 'wed', 'thur')) + self.checkParam(self.combo, 'values', (42, 3.14, '', 'any string')) + self.checkParam(self.combo, 'values', '') + self.combo['values'] = ['a', 1, 'c'] self.combo.set('c') @@ -208,15 +404,52 @@ combo2.destroy() -class EntryTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class EntryTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'background', 'class', 'cursor', + 'exportselection', 'font', + 'invalidcommand', 'justify', + 'show', 'state', 'style', 'takefocus', 'textvariable', + 'validate', 'validatecommand', 'width', 'xscrollcommand', + ) def setUp(self): + super(EntryTest, self).setUp() support.root_deiconify() - self.entry = ttk.Entry() + self.entry = self.create() def tearDown(self): self.entry.destroy() support.root_withdraw() + super(EntryTest, self).tearDown() + + def _create(self, **kwargs): + return ttk.Entry(self.root, **kwargs) + + def test_invalidcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'invalidcommand') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', '*') + self.checkParam(widget, 'show', '') + self.checkParam(widget, 'show', ' ') + + def test_state(self): + widget = self.create() + self.checkParams(widget, 'state', + 'disabled', 'normal', 'readonly') + + def test_validate(self): + widget = self.create() + self.checkEnumParam(widget, 'validate', + 'all', 'key', 'focus', 'focusin', 'focusout', 'none') + + def test_validatecommand(self): + widget = self.create() + self.checkCommandParam(widget, 'validatecommand') def test_bbox(self): @@ -312,16 +545,36 @@ self.assertEqual(self.entry.state(), ()) -class PanedwindowTest(unittest.TestCase): + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'height', + 'orient', 'style', 'takefocus', 'width', + ) def setUp(self): + super(PanedWindowTest, self).setUp() support.root_deiconify() - self.paned = ttk.Panedwindow() + self.paned = self.create() def tearDown(self): self.paned.destroy() support.root_withdraw() + super(PanedWindowTest, self).tearDown() + def _create(self, **kwargs): + return ttk.PanedWindow(self.root, **kwargs) + + def test_orient(self): + widget = self.create() + self.assertEqual(str(widget['orient']), 'vertical') + errmsg='attempt to change read-only option' + if tcl_version < (8, 6): + errmsg='Attempt to change read-only option' + self.checkInvalidParam(widget, 'orient', 'horizontal', + errmsg=errmsg) + widget2 = self.create(orient='horizontal') + self.assertEqual(str(widget2['orient']), 'horizontal') def test_add(self): # attempt to add a child that is not a direct child of the paned window @@ -431,7 +684,22 @@ self.assertTrue(isinstance(self.paned.sashpos(0), int)) -class RadiobuttonTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'compound', 'cursor', + 'image', + 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'value', 'variable', 'width', + ) + + def _create(self, **kwargs): + return ttk.Radiobutton(self.root, **kwargs) + + def test_value(self): + widget = self.create() + self.checkParams(widget, 'value', 1, 2.3, '', 'any string') def test_invoke(self): success = [] @@ -461,19 +729,68 @@ self.assertEqual(str(cbtn['variable']), str(cbtn2['variable'])) +class MenubuttonTest(AbstractLabelTest, unittest.TestCase): + OPTIONS = ( + 'class', 'compound', 'cursor', 'direction', + 'image', 'menu', 'state', 'style', + 'takefocus', 'text', 'textvariable', + 'underline', 'width', + ) -class ScaleTest(unittest.TestCase): + def _create(self, **kwargs): + return ttk.Menubutton(self.root, **kwargs) + + def test_direction(self): + widget = self.create() + self.checkEnumParam(widget, 'direction', + 'above', 'below', 'left', 'right', 'flush') + + def test_menu(self): + widget = self.create() + menu = Tkinter.Menu(widget, name='menu') + self.checkParam(widget, 'menu', menu, conv=str) + menu.destroy() + + + at add_standard_options(StandardTtkOptionsTests) +class ScaleTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'cursor', 'from', 'length', + 'orient', 'style', 'takefocus', 'to', 'value', 'variable', + ) + _conv_pixels = noconv + default_orient = 'horizontal' def setUp(self): + super(ScaleTest, self).setUp() support.root_deiconify() - self.scale = ttk.Scale() + self.scale = self.create() self.scale.pack() self.scale.update() def tearDown(self): self.scale.destroy() support.root_withdraw() + super(ScaleTest, self).tearDown() + def _create(self, **kwargs): + return ttk.Scale(self.root, **kwargs) + + def test_from(self): + widget = self.create() + self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=False) + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') + + def test_to(self): + widget = self.create() + self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, conv=False) + + def test_value(self): + widget = self.create() + self.checkFloatParam(widget, 'value', 300, 14.9, 15.1, -10, conv=False) def test_custom_event(self): failure = [1, 1, 1] # will need to be empty @@ -538,11 +855,64 @@ self.assertRaises(Tkinter.TclError, self.scale.set, None) -class NotebookTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class ProgressbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'orient', 'length', + 'mode', 'maximum', 'phase', + 'style', 'takefocus', 'value', 'variable', + ) + _conv_pixels = noconv + default_orient = 'horizontal' + + def _create(self, **kwargs): + return ttk.Progressbar(self.root, **kwargs) + + def test_length(self): + widget = self.create() + self.checkPixelsParam(widget, 'length', 100.1, 56.7, '2i') + + def test_maximum(self): + widget = self.create() + self.checkFloatParam(widget, 'maximum', 150.2, 77.7, 0, -10, conv=False) + + def test_mode(self): + widget = self.create() + self.checkEnumParam(widget, 'mode', 'determinate', 'indeterminate') + + def test_phase(self): + # XXX + pass + + def test_value(self): + widget = self.create() + self.checkFloatParam(widget, 'value', 150.2, 77.7, 0, -10, + conv=False) + + + at unittest.skipIf(sys.platform == 'darwin', + 'ttk.Scrollbar is special on MacOSX') + at add_standard_options(StandardTtkOptionsTests) +class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'command', 'cursor', 'orient', 'style', 'takefocus', + ) + default_orient = 'vertical' + + def _create(self, **kwargs): + return ttk.Scrollbar(self.root, **kwargs) + + + at add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +class NotebookTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'height', 'padding', 'style', 'takefocus', + ) def setUp(self): + super(NotebookTest, self).setUp() support.root_deiconify() - self.nb = ttk.Notebook(padding=0) + self.nb = self.create(padding=0) self.child1 = ttk.Label() self.child2 = ttk.Label() self.nb.add(self.child1, text='a') @@ -553,7 +923,10 @@ self.child2.destroy() self.nb.destroy() support.root_withdraw() + super(NotebookTest, self).tearDown() + def _create(self, **kwargs): + return ttk.Notebook(self.root, **kwargs) def test_tab_identifiers(self): self.nb.forget(0) @@ -745,16 +1118,68 @@ self.assertEqual(self.nb.select(), str(self.child1)) -class TreeviewTest(unittest.TestCase): + at add_standard_options(StandardTtkOptionsTests) +class TreeviewTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'columns', 'cursor', 'displaycolumns', + 'height', 'padding', 'selectmode', 'show', + 'style', 'takefocus', 'xscrollcommand', 'yscrollcommand', + ) def setUp(self): + super(TreeviewTest, self).setUp() support.root_deiconify() - self.tv = ttk.Treeview(padding=0) + self.tv = self.create(padding=0) def tearDown(self): self.tv.destroy() support.root_withdraw() + super(TreeviewTest, self).tearDown() + def _create(self, **kwargs): + return ttk.Treeview(self.root, **kwargs) + + def test_columns(self): + widget = self.create() + self.checkParam(widget, 'columns', 'a b c', + expected=('a', 'b', 'c')) + self.checkParam(widget, 'columns', ('a', 'b', 'c')) + self.checkParam(widget, 'columns', '') + + def test_displaycolumns(self): + widget = self.create() + widget['columns'] = ('a', 'b', 'c') + self.checkParam(widget, 'displaycolumns', 'b a c', + expected=('b', 'a', 'c')) + self.checkParam(widget, 'displaycolumns', ('b', 'a', 'c')) + self.checkParam(widget, 'displaycolumns', '#all', + expected=('#all',)) + self.checkParam(widget, 'displaycolumns', (2, 1, 0)) + self.checkInvalidParam(widget, 'displaycolumns', ('a', 'b', 'd'), + errmsg='Invalid column index d') + self.checkInvalidParam(widget, 'displaycolumns', (1, 2, 3), + errmsg='Column index 3 out of bounds') + self.checkInvalidParam(widget, 'displaycolumns', (1, -2), + errmsg='Column index -2 out of bounds') + + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, -100, 0, '3c', conv=False) + self.checkPixelsParam(widget, 'height', 101.2, 102.6, conv=noconv) + + def test_selectmode(self): + widget = self.create() + self.checkEnumParam(widget, 'selectmode', + 'none', 'browse', 'extended') + + def test_show(self): + widget = self.create() + self.checkParam(widget, 'show', 'tree headings', + expected=('tree', 'headings')) + self.checkParam(widget, 'show', ('tree', 'headings')) + self.checkParam(widget, 'show', ('headings', 'tree')) + self.checkParam(widget, 'show', 'tree', expected=('tree',)) + self.checkParam(widget, 'show', 'headings', expected=('headings',)) def test_bbox(self): self.tv.pack() @@ -1148,10 +1573,35 @@ self.assertTrue(isinstance(self.tv.tag_configure('test'), dict)) + at add_standard_options(StandardTtkOptionsTests) +class SeparatorTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'orient', 'style', 'takefocus', + # 'state'? + ) + default_orient = 'horizontal' + + def _create(self, **kwargs): + return ttk.Separator(self.root, **kwargs) + + + at add_standard_options(StandardTtkOptionsTests) +class SizegripTest(AbstractWidgetTest, unittest.TestCase): + OPTIONS = ( + 'class', 'cursor', 'style', 'takefocus', + # 'state'? + ) + + def _create(self, **kwargs): + return ttk.Sizegrip(self.root, **kwargs) + + tests_gui = ( - WidgetTest, ButtonTest, CheckbuttonTest, RadiobuttonTest, - ComboboxTest, EntryTest, PanedwindowTest, ScaleTest, NotebookTest, - TreeviewTest + ButtonTest, CheckbuttonTest, ComboboxTest, EntryTest, + FrameTest, LabelFrameTest, LabelTest, MenubuttonTest, + NotebookTest, PanedWindowTest, ProgressbarTest, + RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest, + SizegripTest, TreeviewTest, WidgetTest, ) if __name__ == "__main__": diff --git a/Lib/lib-tk/test/widget_tests.py b/Lib/lib-tk/test/widget_tests.py new file mode 100644 --- /dev/null +++ b/Lib/lib-tk/test/widget_tests.py @@ -0,0 +1,504 @@ +# Common tests for test_tkinter/test_widgets.py and test_ttk/test_widgets.py + +import Tkinter +from ttk import setup_master, Scale +from test_ttk.support import tcl_version, requires_tcl, pixels_conv, tcl_obj_eq + + +noconv = str if tcl_version < (8, 5) else False + +def int_round(x): + return int(round(x)) + +_sentinel = object() + +class AbstractWidgetTest(object): + _conv_pixels = staticmethod(int_round) if tcl_version[:2] != (8, 5) else int + _conv_pad_pixels = None + wantobjects = True + + def setUp(self): + self.root = setup_master() + self.scaling = float(self.root.call('tk', 'scaling')) + if not self.root.wantobjects(): + self.wantobjects = False + + def create(self, **kwargs): + widget = self._create(**kwargs) + self.addCleanup(widget.destroy) + return widget + + def assertEqual2(self, actual, expected, msg=None, eq=object.__eq__): + if eq(actual, expected): + return + self.assertEqual(actual, expected, msg) + + def checkParam(self, widget, name, value, expected=_sentinel, + conv=False, eq=None): + widget[name] = value + if expected is _sentinel: + expected = value + if conv: + expected = conv(expected) + if not self.wantobjects: + if isinstance(expected, tuple): + expected = Tkinter._join(expected) + else: + expected = str(expected) + if eq is None: + eq = tcl_obj_eq + self.assertEqual2(widget[name], expected, eq=eq) + self.assertEqual2(widget.cget(name), expected, eq=eq) + # XXX + if not isinstance(widget, Scale): + t = widget.configure(name) + self.assertEqual(len(t), 5) + ## XXX + if not isinstance(t[4], tuple): + self.assertEqual2(t[4], expected, eq=eq) + + def checkInvalidParam(self, widget, name, value, errmsg=None, + keep_orig=True): + orig = widget[name] + if errmsg is not None: + errmsg = errmsg.format(value) + with self.assertRaises(Tkinter.TclError) as cm: + widget[name] = value + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + if keep_orig: + self.assertEqual(widget[name], orig) + else: + widget[name] = orig + with self.assertRaises(Tkinter.TclError) as cm: + widget.configure({name: value}) + if errmsg is not None: + self.assertEqual(str(cm.exception), errmsg) + if keep_orig: + self.assertEqual(widget[name], orig) + else: + widget[name] = orig + + def checkParams(self, widget, name, *values, **kwargs): + for value in values: + self.checkParam(widget, name, value, **kwargs) + + def checkIntegerParam(self, widget, name, *values, **kwargs): + self.checkParams(widget, name, *values, **kwargs) + self.checkInvalidParam(widget, name, '', + errmsg='expected integer but got ""') + self.checkInvalidParam(widget, name, '10p', + errmsg='expected integer but got "10p"') + self.checkInvalidParam(widget, name, 3.2, + errmsg='expected integer but got "3.2"') + + def checkFloatParam(self, widget, name, *values, **kwargs): + if 'conv' in kwargs: + conv = kwargs.pop('conv') + else: + conv = float + for value in values: + self.checkParam(widget, name, value, conv=conv, **kwargs) + self.checkInvalidParam(widget, name, '', + errmsg='expected floating-point number but got ""') + self.checkInvalidParam(widget, name, 'spam', + errmsg='expected floating-point number but got "spam"') + + def checkBooleanParam(self, widget, name): + for value in (False, 0, 'false', 'no', 'off'): + self.checkParam(widget, name, value, expected=0) + for value in (True, 1, 'true', 'yes', 'on'): + self.checkParam(widget, name, value, expected=1) + self.checkInvalidParam(widget, name, '', + errmsg='expected boolean value but got ""') + self.checkInvalidParam(widget, name, 'spam', + errmsg='expected boolean value but got "spam"') + + def checkColorParam(self, widget, name, allow_empty=None, **kwargs): + self.checkParams(widget, name, + '#ff0000', '#00ff00', '#0000ff', '#123456', + 'red', 'green', 'blue', 'white', 'black', 'grey', + **kwargs) + self.checkInvalidParam(widget, name, 'spam', + errmsg='unknown color name "spam"') + + def checkCursorParam(self, widget, name, **kwargs): + self.checkParams(widget, name, 'arrow', 'watch', 'cross', '',**kwargs) + if tcl_version >= (8, 5): + self.checkParam(widget, name, 'none') + self.checkInvalidParam(widget, name, 'spam', + errmsg='bad cursor spec "spam"') + + def checkCommandParam(self, widget, name): + def command(*args): + pass + widget[name] = command + self.assertTrue(widget[name]) + self.checkParams(widget, name, '') + + def checkEnumParam(self, widget, name, *values, **kwargs): + if 'errmsg' in kwargs: + errmsg = kwargs.pop('errmsg') + else: + errmsg = None + self.checkParams(widget, name, *values, **kwargs) + if errmsg is None: + errmsg2 = ' %s "{}": must be %s%s or %s' % ( + name, + ', '.join(values[:-1]), + ',' if len(values) > 2 else '', + values[-1]) + self.checkInvalidParam(widget, name, '', + errmsg='ambiguous' + errmsg2) + errmsg = 'bad' + errmsg2 + self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg) + + def checkPixelsParam(self, widget, name, *values, **kwargs): + if 'conv' in kwargs: + conv = kwargs.pop('conv') + else: + conv = None + if conv is None: + conv = self._conv_pixels + if 'keep_orig' in kwargs: + keep_orig = kwargs.pop('keep_orig') + else: + keep_orig = True + for value in values: + expected = _sentinel + conv1 = conv + if isinstance(value, str): + if conv1 and conv1 is not str: + expected = pixels_conv(value) * self.scaling + conv1 = int_round + self.checkParam(widget, name, value, expected=expected, + conv=conv1, **kwargs) + self.checkInvalidParam(widget, name, '6x', + errmsg='bad screen distance "6x"', keep_orig=keep_orig) + self.checkInvalidParam(widget, name, 'spam', + errmsg='bad screen distance "spam"', keep_orig=keep_orig) + + def checkReliefParam(self, widget, name): + self.checkParams(widget, name, + 'flat', 'groove', 'raised', 'ridge', 'solid', 'sunken') + errmsg='bad relief "spam": must be '\ + 'flat, groove, raised, ridge, solid, or sunken' + if tcl_version < (8, 6): + errmsg = None + self.checkInvalidParam(widget, name, 'spam', + errmsg=errmsg) + + def checkImageParam(self, widget, name): + image = Tkinter.PhotoImage('image1') + self.checkParam(widget, name, image, conv=str) + self.checkInvalidParam(widget, name, 'spam', + errmsg='image "spam" doesn\'t exist') + widget[name] = '' + + def checkVariableParam(self, widget, name, var): + self.checkParam(widget, name, var, conv=str) + + +class StandardOptionsTests(object): + STANDARD_OPTIONS = ( + 'activebackground', 'activeborderwidth', 'activeforeground', 'anchor', + 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', + 'disabledforeground', 'exportselection', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'highlightthickness', + 'image', 'insertbackground', 'insertborderwidth', + 'insertofftime', 'insertontime', 'insertwidth', + 'jump', 'justify', 'orient', 'padx', 'pady', 'relief', + 'repeatdelay', 'repeatinterval', + 'selectbackground', 'selectborderwidth', 'selectforeground', + 'setgrid', 'takefocus', 'text', 'textvariable', 'troughcolor', + 'underline', 'wraplength', 'xscrollcommand', 'yscrollcommand', + ) + + def test_activebackground(self): + widget = self.create() + self.checkColorParam(widget, 'activebackground') + + def test_activeborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'activeborderwidth', + 0, 1.3, 2.9, 6, -2, '10p') + + def test_activeforeground(self): + widget = self.create() + self.checkColorParam(widget, 'activeforeground') + + def test_anchor(self): + widget = self.create() + self.checkEnumParam(widget, 'anchor', + 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center') + + def test_background(self): + widget = self.create() + self.checkColorParam(widget, 'background') + if 'bg' in self.OPTIONS: + self.checkColorParam(widget, 'bg') + + def test_bitmap(self): + widget = self.create() + self.checkParam(widget, 'bitmap', 'questhead') + self.checkParam(widget, 'bitmap', 'gray50') + self.checkInvalidParam(widget, 'bitmap', 'spam', + errmsg='bitmap "spam" not defined') + + def test_borderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'borderwidth', + 0, 1.3, 2.6, 6, -2, '10p') + if 'bd' in self.OPTIONS: + self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, -2, '10p') + + def test_compound(self): + widget = self.create() + self.checkEnumParam(widget, 'compound', + 'bottom', 'center', 'left', 'none', 'right', 'top') + + def test_cursor(self): + widget = self.create() + self.checkCursorParam(widget, 'cursor') + + def test_disabledforeground(self): + widget = self.create() + self.checkColorParam(widget, 'disabledforeground') + + def test_exportselection(self): + widget = self.create() + self.checkBooleanParam(widget, 'exportselection') + + def test_font(self): + widget = self.create() + self.checkParam(widget, 'font', + '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') + self.checkInvalidParam(widget, 'font', '', + errmsg='font "" doesn\'t exist') + + def test_foreground(self): + widget = self.create() + self.checkColorParam(widget, 'foreground') + if 'fg' in self.OPTIONS: + self.checkColorParam(widget, 'fg') + + def test_highlightbackground(self): + widget = self.create() + self.checkColorParam(widget, 'highlightbackground') + + def test_highlightcolor(self): + widget = self.create() + self.checkColorParam(widget, 'highlightcolor') + + def test_highlightthickness(self): + widget = self.create() + self.checkPixelsParam(widget, 'highlightthickness', + 0, 1.3, 2.6, 6, '10p') + self.checkParam(widget, 'highlightthickness', -2, expected=0, + conv=self._conv_pixels) + + def test_image(self): + widget = self.create() + self.checkImageParam(widget, 'image') + + def test_insertbackground(self): + widget = self.create() + self.checkColorParam(widget, 'insertbackground') + + def test_insertborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertborderwidth', + 0, 1.3, 2.6, 6, -2, '10p') + + def test_insertofftime(self): + widget = self.create() + self.checkIntegerParam(widget, 'insertofftime', 100) + + def test_insertontime(self): + widget = self.create() + self.checkIntegerParam(widget, 'insertontime', 100) + + def test_insertwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p') + + def test_jump(self): + widget = self.create() + self.checkBooleanParam(widget, 'jump') + + def test_justify(self): + widget = self.create() + self.checkEnumParam(widget, 'justify', 'left', 'right', 'center', + errmsg='bad justification "{}": must be ' + 'left, right, or center') + self.checkInvalidParam(widget, 'justify', '', + errmsg='ambiguous justification "": must be ' + 'left, right, or center') + + def test_orient(self): + widget = self.create() + self.assertEqual(str(widget['orient']), self.default_orient) + self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical') + + def test_padx(self): + widget = self.create() + self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, -2, '12m', + conv=self._conv_pad_pixels) + + def test_pady(self): + widget = self.create() + self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, -2, '12m', + conv=self._conv_pad_pixels) + + def test_relief(self): + widget = self.create() + self.checkReliefParam(widget, 'relief') + + def test_repeatdelay(self): + widget = self.create() + self.checkIntegerParam(widget, 'repeatdelay', -500, 500) + + def test_repeatinterval(self): + widget = self.create() + self.checkIntegerParam(widget, 'repeatinterval', -500, 500) + + def test_selectbackground(self): + widget = self.create() + self.checkColorParam(widget, 'selectbackground') + + def test_selectborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p') + + def test_selectforeground(self): + widget = self.create() + self.checkColorParam(widget, 'selectforeground') + + def test_setgrid(self): + widget = self.create() + self.checkBooleanParam(widget, 'setgrid') + + def test_state(self): + widget = self.create() + self.checkEnumParam(widget, 'state', 'active', 'disabled', 'normal') + + def test_takefocus(self): + widget = self.create() + self.checkParams(widget, 'takefocus', '0', '1', '') + + def test_text(self): + widget = self.create() + self.checkParams(widget, 'text', '', 'any string') + + def test_textvariable(self): + widget = self.create() + var = Tkinter.StringVar() + self.checkVariableParam(widget, 'textvariable', var) + + def test_troughcolor(self): + widget = self.create() + self.checkColorParam(widget, 'troughcolor') + + def test_underline(self): + widget = self.create() + self.checkIntegerParam(widget, 'underline', 0, 1, 10) + + def test_wraplength(self): + widget = self.create() + if tcl_version < (8, 5): + self.checkPixelsParam(widget, 'wraplength', 100) + else: + self.checkParams(widget, 'wraplength', 100) + + def test_xscrollcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'xscrollcommand') + + def test_yscrollcommand(self): + widget = self.create() + self.checkCommandParam(widget, 'yscrollcommand') + + # non-standard but common options + + def test_command(self): + widget = self.create() + self.checkCommandParam(widget, 'command') + + def test_indicatoron(self): + widget = self.create() + self.checkBooleanParam(widget, 'indicatoron') + + def test_offrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'offrelief') + + def test_overrelief(self): + widget = self.create() + self.checkReliefParam(widget, 'overrelief') + + def test_selectcolor(self): + widget = self.create() + self.checkColorParam(widget, 'selectcolor') + + def test_selectimage(self): + widget = self.create() + self.checkImageParam(widget, 'selectimage') + + @requires_tcl(8, 5) + def test_tristateimage(self): + widget = self.create() + self.checkImageParam(widget, 'tristateimage') + + @requires_tcl(8, 5) + def test_tristatevalue(self): + widget = self.create() + self.checkParam(widget, 'tristatevalue', 'unknowable') + + def test_variable(self): + widget = self.create() + var = Tkinter.DoubleVar() + self.checkVariableParam(widget, 'variable', var) + + +class IntegerSizeTests(object): + def test_height(self): + widget = self.create() + self.checkIntegerParam(widget, 'height', 100, -100, 0) + + def test_width(self): + widget = self.create() + self.checkIntegerParam(widget, 'width', 402, -402, 0) + + +class PixelSizeTests(object): + def test_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c') + + def test_width(self): + widget = self.create() + self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i') + + +def add_standard_options(*source_classes): + # This decorator adds test_xxx methods from source classes for every xxx + # option in the OPTIONS class attribute if they are not defined explicitly. + def decorator(cls): + for option in cls.OPTIONS: + methodname = 'test_' + option + if not hasattr(cls, methodname): + for source_class in source_classes: + if hasattr(source_class, methodname): + setattr(cls, methodname, + getattr(source_class, methodname).im_func) + break + else: + def test(self, option=option): + widget = self.create() + widget[option] + raise AssertionError('Option "%s" is not tested in %s' % + (option, cls.__name__)) + test.__name__ = methodname + setattr(cls, methodname, test) + return cls + return decorator diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -15,6 +15,8 @@ Tests ----- +- Issue #19085: Added basic tests for all tkinter widget options. + Whats' New in Python 2.7.6? =========================== -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 09:48:41 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 09:48:41 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_heads?= Message-ID: <3dBYpK3Y04z7Ljq@mail.python.org> http://hg.python.org/cpython/rev/e92bba5b53db changeset: 86837:e92bba5b53db parent: 86835:ab7c2c1d349c parent: 86833:123804a72a8f user: Serhiy Storchaka date: Sat Nov 02 10:47:57 2013 +0200 summary: Merge heads files: Lib/asyncio/base_events.py | 30 +- Lib/asyncio/constants.py | 5 +- Lib/asyncio/events.py | 15 +- Lib/asyncio/selector_events.py | 154 ++++++--- Lib/asyncio/tasks.py | 5 +- Lib/asyncio/windows_events.py | 16 + Lib/asyncio/windows_utils.py | 20 +- Lib/test/test_asyncio/test_base_events.py | 78 ++++- Lib/test/test_asyncio/test_events.py | 3 +- Lib/test/test_asyncio/test_selector_events.py | 154 ++++++--- Lib/test/test_asyncio/test_windows_events.py | 2 +- 11 files changed, 350 insertions(+), 132 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -186,6 +186,11 @@ self.call_soon(_raise_stop_error) def close(self): + """Close the event loop. + + This clears the queues and shuts down the executor, + but does not wait for the executor to finish. + """ self._ready.clear() self._scheduled.clear() executor = self._default_executor @@ -275,8 +280,27 @@ @tasks.coroutine def create_connection(self, protocol_factory, host=None, port=None, *, ssl=None, family=0, proto=0, flags=0, sock=None, - local_addr=None): + local_addr=None, server_hostname=None): """XXX""" + if server_hostname is not None and not ssl: + raise ValueError('server_hostname is only meaningful with ssl') + + if server_hostname is None and ssl: + # Use host as default for server_hostname. It is an error + # if host is empty or not set, e.g. when an + # already-connected socket was passed or when only a port + # is given. To avoid this error, you can pass + # server_hostname='' -- this will bypass the hostname + # check. (This also means that if host is a numeric + # IP/IPv6 address, we will attempt to verify that exact + # address; this will probably fail, but it is possible to + # create a certificate for a specific IP address, so we + # don't judge it here.) + if not host: + raise ValueError('You must set server_hostname ' + 'when using ssl without a host') + server_hostname = host + if host is not None or port is not None: if sock is not None: raise ValueError( @@ -357,7 +381,7 @@ sslcontext = None if isinstance(ssl, bool) else ssl transport = self._make_ssl_transport( sock, protocol, sslcontext, waiter, - server_side=False, server_hostname=host) + server_side=False, server_hostname=server_hostname) else: transport = self._make_socket_transport(sock, protocol, waiter) @@ -442,6 +466,8 @@ ssl=None, reuse_address=None): """XXX""" + if isinstance(ssl, bool): + raise TypeError('ssl argument must be an SSLContext or None') if host is not None or port is not None: if sock is not None: raise ValueError( diff --git a/Lib/asyncio/constants.py b/Lib/asyncio/constants.py --- a/Lib/asyncio/constants.py +++ b/Lib/asyncio/constants.py @@ -1,4 +1,7 @@ """Constants.""" +# After the connection is lost, log warnings after this many write()s. +LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 -LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 +# Seconds to wait before retrying accept(). +ACCEPT_RETRY_DELAY = 1 diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -137,6 +137,17 @@ """Return whether the event loop is currently running.""" raise NotImplementedError + def close(self): + """Close the loop. + + The loop should not be running. + + This is idempotent and irreversible. + + No other methods should be called after this one. + """ + raise NotImplementedError + # Methods scheduling callbacks. All these return Handles. def call_soon(self, callback, *args): @@ -172,7 +183,7 @@ def create_connection(self, protocol_factory, host=None, port=None, *, ssl=None, family=0, proto=0, flags=0, sock=None, - local_addr=None): + local_addr=None, server_hostname=None): raise NotImplementedError def create_server(self, protocol_factory, host=None, port=None, *, @@ -214,6 +225,8 @@ family=0, proto=0, flags=0): raise NotImplementedError + # Pipes and subprocesses. + def connect_read_pipe(self, protocol_factory, pipe): """Register read pipe in eventloop. diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -5,6 +5,7 @@ """ import collections +import errno import socket try: import ssl @@ -89,28 +90,37 @@ except (BlockingIOError, InterruptedError): pass - def _start_serving(self, protocol_factory, sock, ssl=None, server=None): + def _start_serving(self, protocol_factory, sock, + sslcontext=None, server=None): self.add_reader(sock.fileno(), self._accept_connection, - protocol_factory, sock, ssl, server) + protocol_factory, sock, sslcontext, server) - def _accept_connection(self, protocol_factory, sock, ssl=None, - server=None): + def _accept_connection(self, protocol_factory, sock, + sslcontext=None, server=None): try: conn, addr = sock.accept() conn.setblocking(False) - except (BlockingIOError, InterruptedError): + except (BlockingIOError, InterruptedError, ConnectionAbortedError): pass # False alarm. - except Exception: - # Bad error. Stop serving. - self.remove_reader(sock.fileno()) - sock.close() + except OSError as exc: # There's nowhere to send the error, so just log it. # TODO: Someone will want an error handler for this. - logger.exception('Accept failed') + if exc.errno in (errno.EMFILE, errno.ENFILE, + errno.ENOBUFS, errno.ENOMEM): + # Some platforms (e.g. Linux keep reporting the FD as + # ready, so we remove the read handler temporarily. + # We'll try again in a while. + logger.exception('Accept out of system resource (%s)', exc) + self.remove_reader(sock.fileno()) + self.call_later(constants.ACCEPT_RETRY_DELAY, + self._start_serving, + protocol_factory, sock, sslcontext, server) + else: + raise # The event loop will catch, log and ignore it. else: - if ssl: + if sslcontext: self._make_ssl_transport( - conn, protocol_factory(), ssl, None, + conn, protocol_factory(), sslcontext, None, server_side=True, extra={'peername': addr}, server=server) else: self._make_socket_transport( @@ -277,7 +287,7 @@ err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if err != 0: # Jump to the except clause below. - raise OSError(err, 'Connect call failed') + raise OSError(err, 'Connect call failed %s' % (address,)) except (BlockingIOError, InterruptedError): self.add_writer(fd, self._sock_connect, fut, True, sock, address) except Exception as exc: @@ -404,15 +414,16 @@ try: self._protocol.pause_writing() except Exception: - tulip_log.exception('pause_writing() failed') + logger.exception('pause_writing() failed') def _maybe_resume_protocol(self): - if self._protocol_paused and self.get_write_buffer_size() <= self._low_water: + if (self._protocol_paused and + self.get_write_buffer_size() <= self._low_water): self._protocol_paused = False try: self._protocol.resume_writing() except Exception: - tulip_log.exception('resume_writing() failed') + logger.exception('resume_writing() failed') def set_write_buffer_limits(self, high=None, low=None): if high is None: @@ -548,22 +559,28 @@ def __init__(self, loop, rawsock, protocol, sslcontext, waiter=None, server_side=False, server_hostname=None, extra=None, server=None): + if ssl is None: + raise RuntimeError('stdlib ssl module not available') + if server_side: - assert isinstance( - sslcontext, ssl.SSLContext), 'Must pass an SSLContext' + if not sslcontext: + raise ValueError('Server side ssl needs a valid SSLContext') else: - # Client-side may pass ssl=True to use a default context. - # The default is the same as used by urllib. - if sslcontext is None: + if not sslcontext: + # Client side may pass ssl=True to use a default + # context; in that case the sslcontext passed is None. + # The default is the same as used by urllib with + # cadefault=True. sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) sslcontext.options |= ssl.OP_NO_SSLv2 sslcontext.set_default_verify_paths() sslcontext.verify_mode = ssl.CERT_REQUIRED + wrap_kwargs = { 'server_side': server_side, 'do_handshake_on_connect': False, } - if server_hostname is not None and not server_side and ssl.HAS_SNI: + if server_hostname and not server_side and ssl.HAS_SNI: wrap_kwargs['server_hostname'] = server_hostname sslsock = sslcontext.wrap_socket(rawsock, **wrap_kwargs) @@ -609,7 +626,7 @@ # Verify hostname if requested. peercert = self._sock.getpeercert() - if (self._server_hostname is not None and + if (self._server_hostname and self._sslcontext.verify_mode != ssl.CERT_NONE): try: ssl.match_hostname(peercert, self._server_hostname) @@ -625,15 +642,16 @@ compression=self._sock.compression(), ) - self._loop.add_reader(self._sock_fd, self._on_ready) - self._loop.add_writer(self._sock_fd, self._on_ready) + self._read_wants_write = False + self._write_wants_read = False + self._loop.add_reader(self._sock_fd, self._read_ready) self._loop.call_soon(self._protocol.connection_made, self) if self._waiter is not None: self._loop.call_soon(self._waiter.set_result, None) def pause_reading(self): # XXX This is a bit icky, given the comment at the top of - # _on_ready(). Is it possible to evoke a deadlock? I don't + # _read_ready(). Is it possible to evoke a deadlock? I don't # know, although it doesn't look like it; write() will still # accept more data for the buffer and eventually the app will # call resume_reading() again, and things will flow again. @@ -648,41 +666,58 @@ self._paused = False if self._closing: return - self._loop.add_reader(self._sock_fd, self._on_ready) + self._loop.add_reader(self._sock_fd, self._read_ready) - def _on_ready(self): - # Because of renegotiations (?), there's no difference between - # readable and writable. We just try both. XXX This may be - # incorrect; we probably need to keep state about what we - # should do next. + def _read_ready(self): + if self._write_wants_read: + self._write_wants_read = False + self._write_ready() - # First try reading. - if not self._closing and not self._paused: - try: - data = self._sock.recv(self.max_size) - except (BlockingIOError, InterruptedError, - ssl.SSLWantReadError, ssl.SSLWantWriteError): - pass - except Exception as exc: - self._fatal_error(exc) + if self._buffer: + self._loop.add_writer(self._sock_fd, self._write_ready) + + try: + data = self._sock.recv(self.max_size) + except (BlockingIOError, InterruptedError, ssl.SSLWantReadError): + pass + except ssl.SSLWantWriteError: + self._read_wants_write = True + self._loop.remove_reader(self._sock_fd) + self._loop.add_writer(self._sock_fd, self._write_ready) + except Exception as exc: + self._fatal_error(exc) + else: + if data: + self._protocol.data_received(data) else: - if data: - self._protocol.data_received(data) - else: - try: - self._protocol.eof_received() - finally: - self.close() + try: + keep_open = self._protocol.eof_received() + if keep_open: + logger.warning('returning true from eof_received() ' + 'has no effect when using ssl') + finally: + self.close() - # Now try writing, if there's anything to write. + def _write_ready(self): + if self._read_wants_write: + self._read_wants_write = False + self._read_ready() + + if not (self._paused or self._closing): + self._loop.add_reader(self._sock_fd, self._read_ready) + if self._buffer: data = b''.join(self._buffer) self._buffer.clear() try: n = self._sock.send(data) except (BlockingIOError, InterruptedError, - ssl.SSLWantReadError, ssl.SSLWantWriteError): + ssl.SSLWantWriteError): n = 0 + except ssl.SSLWantReadError: + n = 0 + self._loop.remove_writer(self._sock_fd) + self._write_wants_read = True except Exception as exc: self._loop.remove_writer(self._sock_fd) self._fatal_error(exc) @@ -691,11 +726,12 @@ if n < len(data): self._buffer.append(data[n:]) - self._maybe_resume_protocol() # May append to buffer. + self._maybe_resume_protocol() # May append to buffer. - if self._closing and not self._buffer: + if not self._buffer: self._loop.remove_writer(self._sock_fd) - self._call_connection_lost(None) + if self._closing: + self._call_connection_lost(None) def write(self, data): assert isinstance(data, bytes), repr(type(data)) @@ -708,20 +744,16 @@ self._conn_lost += 1 return - # We could optimize, but the callback can do this for now. + if not self._buffer: + self._loop.add_writer(self._sock_fd, self._write_ready) + + # Add it to the buffer. self._buffer.append(data) self._maybe_pause_protocol() def can_write_eof(self): return False - def close(self): - if self._closing: - return - self._closing = True - self._conn_lost += 1 - self._loop.remove_reader(self._sock_fd) - class _SelectorDatagramTransport(_SelectorTransport): diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -62,8 +62,9 @@ code = func.__code__ filename = code.co_filename lineno = code.co_firstlineno - logger.error('Coroutine %r defined at %s:%s was never yielded from', - func.__name__, filename, lineno) + logger.error( + 'Coroutine %r defined at %s:%s was never yielded from', + func.__name__, filename, lineno) def coroutine(func): diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -138,6 +138,7 @@ @tasks.coroutine def start_serving_pipe(self, protocol_factory, address): server = PipeServer(address) + def loop(f=None): pipe = None try: @@ -160,6 +161,7 @@ pipe.close() else: f.add_done_callback(loop) + self.call_soon(loop) return [server] @@ -209,6 +211,7 @@ ov.WSARecv(conn.fileno(), nbytes, flags) else: ov.ReadFile(conn.fileno(), nbytes) + def finish(trans, key, ov): try: return ov.getresult() @@ -217,6 +220,7 @@ raise ConnectionResetError(*exc.args) else: raise + return self._register(ov, conn, finish) def send(self, conn, buf, flags=0): @@ -226,6 +230,7 @@ ov.WSASend(conn.fileno(), buf, flags) else: ov.WriteFile(conn.fileno(), buf) + def finish(trans, key, ov): try: return ov.getresult() @@ -234,6 +239,7 @@ raise ConnectionResetError(*exc.args) else: raise + return self._register(ov, conn, finish) def accept(self, listener): @@ -241,6 +247,7 @@ conn = self._get_accept_socket(listener.family) ov = _overlapped.Overlapped(NULL) ov.AcceptEx(listener.fileno(), conn.fileno()) + def finish_accept(trans, key, ov): ov.getresult() # Use SO_UPDATE_ACCEPT_CONTEXT so getsockname() etc work. @@ -249,6 +256,7 @@ _overlapped.SO_UPDATE_ACCEPT_CONTEXT, buf) conn.settimeout(listener.gettimeout()) return conn, conn.getpeername() + return self._register(ov, listener, finish_accept) def connect(self, conn, address): @@ -264,26 +272,31 @@ raise ov = _overlapped.Overlapped(NULL) ov.ConnectEx(conn.fileno(), address) + def finish_connect(trans, key, ov): ov.getresult() # Use SO_UPDATE_CONNECT_CONTEXT so getsockname() etc work. conn.setsockopt(socket.SOL_SOCKET, _overlapped.SO_UPDATE_CONNECT_CONTEXT, 0) return conn + return self._register(ov, conn, finish_connect) def accept_pipe(self, pipe): self._register_with_iocp(pipe) ov = _overlapped.Overlapped(NULL) ov.ConnectNamedPipe(pipe.fileno()) + def finish(trans, key, ov): ov.getresult() return pipe + return self._register(ov, pipe, finish) def connect_pipe(self, address): ov = _overlapped.Overlapped(NULL) ov.WaitNamedPipeAndConnect(address, self._iocp, ov.address) + def finish(err, handle, ov): # err, handle were arguments passed to PostQueuedCompletionStatus() # in a function run in a thread pool. @@ -296,6 +309,7 @@ raise OSError(0, msg, None, err) else: return windows_utils.PipeHandle(handle) + return self._register(ov, None, finish, wait_for_post=True) def wait_for_handle(self, handle, timeout=None): @@ -432,8 +446,10 @@ self._proc = windows_utils.Popen( args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr, bufsize=bufsize, **kwargs) + def callback(f): returncode = self._proc.poll() self._process_exited(returncode) + f = self._loop._proactor.wait_for_handle(int(self._proc._handle)) f.add_done_callback(callback) diff --git a/Lib/asyncio/windows_utils.py b/Lib/asyncio/windows_utils.py --- a/Lib/asyncio/windows_utils.py +++ b/Lib/asyncio/windows_utils.py @@ -18,18 +18,18 @@ __all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle'] -# + # Constants/globals -# + BUFSIZE = 8192 PIPE = subprocess.PIPE STDOUT = subprocess.STDOUT _mmap_counter = itertools.count() -# + # Replacement for socket.socketpair() -# + def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0): """A socket pair usable as a self-pipe, for Windows. @@ -57,9 +57,9 @@ lsock.close() return (ssock, csock) -# + # Replacement for os.pipe() using handles instead of fds -# + def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE): """Like os.pipe() but with overlapped support and using handles not fds.""" @@ -105,9 +105,9 @@ _winapi.CloseHandle(h2) raise -# + # Wrapper for a pipe handle -# + class PipeHandle: """Wrapper for an overlapped pipe handle which is vaguely file-object like. @@ -137,9 +137,9 @@ def __exit__(self, t, v, tb): self.close() -# + # Replacement for subprocess.Popen using overlapped pipe handles -# + class Popen(subprocess.Popen): """Replacement for subprocess.Popen using overlapped pipe handles. diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1,5 +1,6 @@ """Tests for base_events.py""" +import errno import logging import socket import time @@ -8,6 +9,7 @@ from test.support import find_unused_port, IPV6_ENABLED from asyncio import base_events +from asyncio import constants from asyncio import events from asyncio import futures from asyncio import protocols @@ -442,6 +444,71 @@ self.assertRaises( OSError, self.loop.run_until_complete, coro) + def test_create_connection_ssl_server_hostname_default(self): + self.loop.getaddrinfo = unittest.mock.Mock() + + def mock_getaddrinfo(*args, **kwds): + f = futures.Future(loop=self.loop) + f.set_result([(socket.AF_INET, socket.SOCK_STREAM, + socket.SOL_TCP, '', ('1.2.3.4', 80))]) + return f + + self.loop.getaddrinfo.side_effect = mock_getaddrinfo + self.loop.sock_connect = unittest.mock.Mock() + self.loop.sock_connect.return_value = () + self.loop._make_ssl_transport = unittest.mock.Mock() + + def mock_make_ssl_transport(sock, protocol, sslcontext, waiter, + **kwds): + waiter.set_result(None) + + self.loop._make_ssl_transport.side_effect = mock_make_ssl_transport + ANY = unittest.mock.ANY + # First try the default server_hostname. + self.loop._make_ssl_transport.reset_mock() + coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True) + self.loop.run_until_complete(coro) + self.loop._make_ssl_transport.assert_called_with( + ANY, ANY, ANY, ANY, + server_side=False, + server_hostname='python.org') + # Next try an explicit server_hostname. + self.loop._make_ssl_transport.reset_mock() + coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True, + server_hostname='perl.com') + self.loop.run_until_complete(coro) + self.loop._make_ssl_transport.assert_called_with( + ANY, ANY, ANY, ANY, + server_side=False, + server_hostname='perl.com') + # Finally try an explicit empty server_hostname. + self.loop._make_ssl_transport.reset_mock() + coro = self.loop.create_connection(MyProto, 'python.org', 80, ssl=True, + server_hostname='') + self.loop.run_until_complete(coro) + self.loop._make_ssl_transport.assert_called_with(ANY, ANY, ANY, ANY, + server_side=False, + server_hostname='') + + def test_create_connection_no_ssl_server_hostname_errors(self): + # When not using ssl, server_hostname must be None. + coro = self.loop.create_connection(MyProto, 'python.org', 80, + server_hostname='') + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + coro = self.loop.create_connection(MyProto, 'python.org', 80, + server_hostname='python.org') + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + + def test_create_connection_ssl_server_hostname_errors(self): + # When using ssl, server_hostname may be None if host is non-empty. + coro = self.loop.create_connection(MyProto, '', 80, ssl=True) + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + coro = self.loop.create_connection(MyProto, None, 80, ssl=True) + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + coro = self.loop.create_connection(MyProto, None, None, + ssl=True, sock=socket.socket()) + self.assertRaises(ValueError, self.loop.run_until_complete, coro) + def test_create_server_empty_host(self): # if host is empty string use None instead host = object() @@ -585,11 +652,18 @@ def test_accept_connection_exception(self, m_log): sock = unittest.mock.Mock() sock.fileno.return_value = 10 - sock.accept.side_effect = OSError() + sock.accept.side_effect = OSError(errno.EMFILE, 'Too many open files') + self.loop.remove_reader = unittest.mock.Mock() + self.loop.call_later = unittest.mock.Mock() self.loop._accept_connection(MyProto, sock) - self.assertTrue(sock.close.called) self.assertTrue(m_log.exception.called) + self.assertFalse(sock.close.called) + self.loop.remove_reader.assert_called_with(10) + self.loop.call_later.assert_called_with(constants.ACCEPT_RETRY_DELAY, + # self.loop._start_serving + unittest.mock.ANY, + MyProto, sock, None, None) if __name__ == '__main__': diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1276,7 +1276,6 @@ def create_event_loop(self): return windows_events.SelectorEventLoop() - class ProactorEventLoopTests(EventLoopTestsMixin, SubprocessTestsMixin, unittest.TestCase): @@ -1472,6 +1471,8 @@ self.assertRaises( NotImplementedError, loop.is_running) self.assertRaises( + NotImplementedError, loop.close) + self.assertRaises( NotImplementedError, loop.call_later, None, None) self.assertRaises( NotImplementedError, loop.call_at, f, f) diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -43,6 +43,7 @@ self.assertIsInstance( self.loop._make_socket_transport(m, m), _SelectorSocketTransport) + @unittest.skipIf(ssl is None, 'No ssl module') def test_make_ssl_transport(self): m = unittest.mock.Mock() self.loop.add_reader = unittest.mock.Mock() @@ -52,6 +53,16 @@ self.assertIsInstance( self.loop._make_ssl_transport(m, m, m, m), _SelectorSslTransport) + @unittest.mock.patch('asyncio.selector_events.ssl', None) + def test_make_ssl_transport_without_ssl_error(self): + m = unittest.mock.Mock() + self.loop.add_reader = unittest.mock.Mock() + self.loop.add_writer = unittest.mock.Mock() + self.loop.remove_reader = unittest.mock.Mock() + self.loop.remove_writer = unittest.mock.Mock() + with self.assertRaises(RuntimeError): + self.loop._make_ssl_transport(m, m, m, m) + def test_close(self): ssock = self.loop._ssock ssock.fileno.return_value = 7 @@ -1003,8 +1014,7 @@ self.loop, self.sock, self.protocol, self.sslcontext, waiter=waiter) self.assertTrue(self.sslsock.do_handshake.called) - self.loop.assert_reader(1, tr._on_ready) - self.loop.assert_writer(1, tr._on_ready) + self.loop.assert_reader(1, tr._read_ready) test_utils.run_briefly(self.loop) self.assertIsNone(waiter.result()) @@ -1047,13 +1057,13 @@ def test_pause_resume_reading(self): tr = self._make_one() self.assertFalse(tr._paused) - self.loop.assert_reader(1, tr._on_ready) + self.loop.assert_reader(1, tr._read_ready) tr.pause_reading() self.assertTrue(tr._paused) self.assertFalse(1 in self.loop.readers) tr.resume_reading() self.assertFalse(tr._paused) - self.loop.assert_reader(1, tr._on_ready) + self.loop.assert_reader(1, tr._read_ready) def test_write_no_data(self): transport = self._make_one() @@ -1084,140 +1094,173 @@ transport.write(b'data') m_log.warning.assert_called_with('socket.send() raised exception.') - def test_on_ready_recv(self): + def test_read_ready_recv(self): self.sslsock.recv.return_value = b'data' transport = self._make_one() - transport._on_ready() + transport._read_ready() self.assertTrue(self.sslsock.recv.called) self.assertEqual((b'data',), self.protocol.data_received.call_args[0]) - def test_on_ready_recv_eof(self): + def test_read_ready_write_wants_read(self): + self.loop.add_writer = unittest.mock.Mock() + self.sslsock.recv.side_effect = BlockingIOError + transport = self._make_one() + transport._write_wants_read = True + transport._write_ready = unittest.mock.Mock() + transport._buffer.append(b'data') + transport._read_ready() + + self.assertFalse(transport._write_wants_read) + transport._write_ready.assert_called_with() + self.loop.add_writer.assert_called_with( + transport._sock_fd, transport._write_ready) + + def test_read_ready_recv_eof(self): self.sslsock.recv.return_value = b'' transport = self._make_one() transport.close = unittest.mock.Mock() - transport._on_ready() + transport._read_ready() transport.close.assert_called_with() self.protocol.eof_received.assert_called_with() - def test_on_ready_recv_conn_reset(self): + def test_read_ready_recv_conn_reset(self): err = self.sslsock.recv.side_effect = ConnectionResetError() transport = self._make_one() transport._force_close = unittest.mock.Mock() - transport._on_ready() + transport._read_ready() transport._force_close.assert_called_with(err) - def test_on_ready_recv_retry(self): + def test_read_ready_recv_retry(self): self.sslsock.recv.side_effect = ssl.SSLWantReadError transport = self._make_one() - transport._on_ready() + transport._read_ready() self.assertTrue(self.sslsock.recv.called) self.assertFalse(self.protocol.data_received.called) - self.sslsock.recv.side_effect = ssl.SSLWantWriteError - transport._on_ready() - self.assertFalse(self.protocol.data_received.called) - self.sslsock.recv.side_effect = BlockingIOError - transport._on_ready() + transport._read_ready() self.assertFalse(self.protocol.data_received.called) self.sslsock.recv.side_effect = InterruptedError - transport._on_ready() + transport._read_ready() self.assertFalse(self.protocol.data_received.called) - def test_on_ready_recv_exc(self): + def test_read_ready_recv_write(self): + self.loop.remove_reader = unittest.mock.Mock() + self.loop.add_writer = unittest.mock.Mock() + self.sslsock.recv.side_effect = ssl.SSLWantWriteError + transport = self._make_one() + transport._read_ready() + self.assertFalse(self.protocol.data_received.called) + self.assertTrue(transport._read_wants_write) + + self.loop.remove_reader.assert_called_with(transport._sock_fd) + self.loop.add_writer.assert_called_with( + transport._sock_fd, transport._write_ready) + + def test_read_ready_recv_exc(self): err = self.sslsock.recv.side_effect = OSError() transport = self._make_one() transport._fatal_error = unittest.mock.Mock() - transport._on_ready() + transport._read_ready() transport._fatal_error.assert_called_with(err) - def test_on_ready_send(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send(self): self.sslsock.send.return_value = 4 transport = self._make_one() transport._buffer = collections.deque([b'data']) - transport._on_ready() + transport._write_ready() self.assertEqual(collections.deque(), transport._buffer) self.assertTrue(self.sslsock.send.called) - def test_on_ready_send_none(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_none(self): self.sslsock.send.return_value = 0 transport = self._make_one() transport._buffer = collections.deque([b'data1', b'data2']) - transport._on_ready() + transport._write_ready() self.assertTrue(self.sslsock.send.called) self.assertEqual(collections.deque([b'data1data2']), transport._buffer) - def test_on_ready_send_partial(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_partial(self): self.sslsock.send.return_value = 2 transport = self._make_one() transport._buffer = collections.deque([b'data1', b'data2']) - transport._on_ready() + transport._write_ready() self.assertTrue(self.sslsock.send.called) self.assertEqual(collections.deque([b'ta1data2']), transport._buffer) - def test_on_ready_send_closing_partial(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_closing_partial(self): self.sslsock.send.return_value = 2 transport = self._make_one() transport._buffer = collections.deque([b'data1', b'data2']) - transport._on_ready() + transport._write_ready() self.assertTrue(self.sslsock.send.called) self.assertFalse(self.sslsock.close.called) - def test_on_ready_send_closing(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_closing(self): self.sslsock.send.return_value = 4 transport = self._make_one() transport.close() transport._buffer = collections.deque([b'data']) - transport._on_ready() + transport._write_ready() self.assertFalse(self.loop.writers) self.protocol.connection_lost.assert_called_with(None) - def test_on_ready_send_closing_empty_buffer(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_closing_empty_buffer(self): self.sslsock.send.return_value = 4 transport = self._make_one() transport.close() transport._buffer = collections.deque() - transport._on_ready() + transport._write_ready() self.assertFalse(self.loop.writers) self.protocol.connection_lost.assert_called_with(None) - def test_on_ready_send_retry(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError - + def test_write_ready_send_retry(self): transport = self._make_one() transport._buffer = collections.deque([b'data']) - self.sslsock.send.side_effect = ssl.SSLWantReadError - transport._on_ready() - self.assertTrue(self.sslsock.send.called) - self.assertEqual(collections.deque([b'data']), transport._buffer) - self.sslsock.send.side_effect = ssl.SSLWantWriteError - transport._on_ready() + transport._write_ready() self.assertEqual(collections.deque([b'data']), transport._buffer) self.sslsock.send.side_effect = BlockingIOError() - transport._on_ready() + transport._write_ready() self.assertEqual(collections.deque([b'data']), transport._buffer) - def test_on_ready_send_exc(self): - self.sslsock.recv.side_effect = ssl.SSLWantReadError + def test_write_ready_send_read(self): + transport = self._make_one() + transport._buffer = collections.deque([b'data']) + + self.loop.remove_writer = unittest.mock.Mock() + self.sslsock.send.side_effect = ssl.SSLWantReadError + transport._write_ready() + self.assertFalse(self.protocol.data_received.called) + self.assertTrue(transport._write_wants_read) + self.loop.remove_writer.assert_called_with(transport._sock_fd) + + def test_write_ready_send_exc(self): err = self.sslsock.send.side_effect = OSError() transport = self._make_one() transport._buffer = collections.deque([b'data']) transport._fatal_error = unittest.mock.Mock() - transport._on_ready() + transport._write_ready() transport._fatal_error.assert_called_with(err) self.assertEqual(collections.deque(), transport._buffer) + def test_write_ready_read_wants_write(self): + self.loop.add_reader = unittest.mock.Mock() + self.sslsock.send.side_effect = BlockingIOError + transport = self._make_one() + transport._read_wants_write = True + transport._read_ready = unittest.mock.Mock() + transport._write_ready() + + self.assertFalse(transport._read_wants_write) + transport._read_ready.assert_called_with() + self.loop.add_reader.assert_called_with( + transport._sock_fd, transport._read_ready) + def test_write_eof(self): tr = self._make_one() self.assertFalse(tr.can_write_eof()) @@ -1245,6 +1288,15 @@ server_hostname='localhost') +class SelectorSslWithoutSslTransportTests(unittest.TestCase): + + @unittest.mock.patch('asyncio.selector_events.ssl', None) + def test_ssl_transport_requires_ssl_module(self): + Mock = unittest.mock.Mock + with self.assertRaises(RuntimeError): + transport = _SelectorSslTransport(Mock(), Mock(), Mock(), Mock()) + + class SelectorDatagramTransportTests(unittest.TestCase): def setUp(self): diff --git a/Lib/test/test_asyncio/test_windows_events.py b/Lib/test/test_asyncio/test_windows_events.py --- a/Lib/test/test_asyncio/test_windows_events.py +++ b/Lib/test/test_asyncio/test_windows_events.py @@ -77,7 +77,7 @@ stream_reader = streams.StreamReader(loop=self.loop) protocol = streams.StreamReaderProtocol(stream_reader) trans, proto = yield from self.loop.create_pipe_connection( - lambda:protocol, ADDRESS) + lambda: protocol, ADDRESS) self.assertIsInstance(trans, transports.Transport) self.assertEqual(protocol, proto) clients.append((stream_reader, trans)) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 09:55:26 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 09:55:26 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzEwNzM0?= =?utf-8?q?=3A_Fix_and_re-enable_test=5Fttk_test=5Fheading=5Fcallback=2E?= Message-ID: <3dBYy65pQzz7Ljf@mail.python.org> http://hg.python.org/cpython/rev/0554e2d37bf8 changeset: 86838:0554e2d37bf8 branch: 2.7 parent: 86836:ced345326151 user: Serhiy Storchaka date: Sat Nov 02 10:54:17 2013 +0200 summary: Issue #10734: Fix and re-enable test_ttk test_heading_callback. files: Lib/lib-tk/test/test_ttk/test_widgets.py | 4 +--- 1 files changed, 1 insertions(+), 3 deletions(-) diff --git a/Lib/lib-tk/test/test_ttk/test_widgets.py b/Lib/lib-tk/test/test_ttk/test_widgets.py --- a/Lib/lib-tk/test/test_ttk/test_widgets.py +++ b/Lib/lib-tk/test/test_ttk/test_widgets.py @@ -1370,12 +1370,10 @@ self.assertRaises(Tkinter.TclError, self.tv.heading, '#0', anchor=1) - # XXX skipping for now; should be fixed to work with newer ttk - @unittest.skip("skipping pending resolution of Issue #10734") def test_heading_callback(self): def simulate_heading_click(x, y): support.simulate_mouse_click(self.tv, x, y) - self.tv.update_idletasks() + self.tv.update() success = [] # no success for now -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 09:55:28 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 09:55:28 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzEwNzM0?= =?utf-8?q?=3A_Fix_and_re-enable_test=5Fttk_test=5Fheading=5Fcallback=2E?= Message-ID: <3dBYy80PHMz7Ljn@mail.python.org> http://hg.python.org/cpython/rev/a58fce53e873 changeset: 86839:a58fce53e873 branch: 3.3 parent: 86834:92e268f2719e user: Serhiy Storchaka date: Sat Nov 02 10:54:31 2013 +0200 summary: Issue #10734: Fix and re-enable test_ttk test_heading_callback. files: Lib/tkinter/test/test_ttk/test_widgets.py | 4 +--- 1 files changed, 1 insertions(+), 3 deletions(-) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1369,12 +1369,10 @@ self.assertRaises(tkinter.TclError, self.tv.heading, '#0', anchor=1) - # XXX skipping for now; should be fixed to work with newer ttk - @unittest.skip("skipping pending resolution of Issue #10734") def test_heading_callback(self): def simulate_heading_click(x, y): support.simulate_mouse_click(self.tv, x, y) - self.tv.update_idletasks() + self.tv.update() success = [] # no success for now -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 09:55:29 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 09:55:29 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2310734=3A_Fix_and_re-enable_test=5Fttk_test=5Fhe?= =?utf-8?q?ading=5Fcallback=2E?= Message-ID: <3dBYy9292Lz7Lk0@mail.python.org> http://hg.python.org/cpython/rev/f647a2c5f290 changeset: 86840:f647a2c5f290 parent: 86837:e92bba5b53db parent: 86839:a58fce53e873 user: Serhiy Storchaka date: Sat Nov 02 10:54:58 2013 +0200 summary: Issue #10734: Fix and re-enable test_ttk test_heading_callback. files: Lib/tkinter/test/test_ttk/test_widgets.py | 4 +--- 1 files changed, 1 insertions(+), 3 deletions(-) diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -1369,12 +1369,10 @@ self.assertRaises(tkinter.TclError, self.tv.heading, '#0', anchor=1) - # XXX skipping for now; should be fixed to work with newer ttk - @unittest.skip("skipping pending resolution of Issue #10734") def test_heading_callback(self): def simulate_heading_click(x, y): support.simulate_mouse_click(self.tv, x, y) - self.tv.update_idletasks() + self.tv.update() success = [] # no success for now -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 15:41:46 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 15:41:46 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2319085=2E_Try_to_f?= =?utf-8?q?ix_tkinter_tests_on_Windows=2E?= Message-ID: <3dBjdk24Znz7LjT@mail.python.org> http://hg.python.org/cpython/rev/cee56ef59a6a changeset: 86841:cee56ef59a6a user: Serhiy Storchaka date: Sat Nov 02 16:41:23 2013 +0200 summary: Issue #19085. Try to fix tkinter tests on Windows. files: Lib/tkinter/test/test_tkinter/test_widgets.py | 14 +++++----- 1 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -73,7 +73,10 @@ def test_screen(self): widget = self.create() self.assertEqual(widget['screen'], '') - display = os.environ['DISPLAY'] + try: + display = os.environ['DISPLAY'] + except KeyError: + self.skipTest('No $DISPLAY set.') self.checkInvalidParam(widget, 'screen', display, errmsg="can't modify -screen option after widget is created") widget2 = self.create(screen=display) @@ -82,13 +85,10 @@ def test_use(self): widget = self.create() self.assertEqual(widget['use'], '') - widget1 = self.create(container=True) - self.assertEqual(widget1['use'], '') - self.checkInvalidParam(widget1, 'use', '0x44022', - errmsg="can't modify -use option after widget is created") - wid = hex(widget1.winfo_id()) + parent = self.create(container=True) + wid = parent.winfo_id() widget2 = self.create(use=wid) - self.assertEqual(widget2['use'], wid) + self.assertEqual(int(widget2['use']), wid) @add_standard_options(StandardOptionsTests) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 16:08:39 2013 From: python-checkins at python.org (ezio.melotti) Date: Sat, 2 Nov 2013 16:08:39 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_=2315114=3A_The_html=2Epar?= =?utf-8?q?ser_module_now_raises_a_DeprecationWarning_when_the_strict?= Message-ID: <3dBkDl6QK4z7LjV@mail.python.org> http://hg.python.org/cpython/rev/0a56709eb798 changeset: 86842:0a56709eb798 user: Ezio Melotti date: Sat Nov 02 17:08:24 2013 +0200 summary: #15114: The html.parser module now raises a DeprecationWarning when the strict argument of HTMLParser or the HTMLParser.error method are used. files: Doc/library/html.parser.rst | 4 ++-- Lib/html/parser.py | 14 ++++++++++---- Lib/test/test_htmlparser.py | 17 ++++++++++++++--- Misc/NEWS | 3 +++ 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Doc/library/html.parser.rst b/Doc/library/html.parser.rst --- a/Doc/library/html.parser.rst +++ b/Doc/library/html.parser.rst @@ -74,7 +74,7 @@ def handle_data(self, data): print("Encountered some data :", data) - parser = MyHTMLParser(strict=False) + parser = MyHTMLParser() parser.feed('Test' '

Parse me!

') @@ -272,7 +272,7 @@ def handle_decl(self, data): print("Decl :", data) - parser = MyHTMLParser(strict=False) + parser = MyHTMLParser() Parsing a doctype:: diff --git a/Lib/html/parser.py b/Lib/html/parser.py --- a/Lib/html/parser.py +++ b/Lib/html/parser.py @@ -94,6 +94,8 @@ return result +_strict_sentinel = object() + class HTMLParser(_markupbase.ParserBase): """Find tags and other markup and call handler functions. @@ -116,16 +118,18 @@ CDATA_CONTENT_ELEMENTS = ("script", "style") - def __init__(self, strict=False): + def __init__(self, strict=_strict_sentinel): """Initialize and reset this instance. If strict is set to False (the default) the parser will parse invalid markup, otherwise it will raise an error. Note that the strict mode - is deprecated. + and argument are deprecated. """ - if strict: - warnings.warn("The strict mode is deprecated.", + if strict is not _strict_sentinel: + warnings.warn("The strict argument and mode are deprecated.", DeprecationWarning, stacklevel=2) + else: + strict = False # default self.strict = strict self.reset() @@ -151,6 +155,8 @@ self.goahead(1) def error(self, message): + warnings.warn("The 'error' method is deprecated.", + DeprecationWarning, stacklevel=2) raise HTMLParseError(message, self.getpos()) __starttag_text = None diff --git a/Lib/test/test_htmlparser.py b/Lib/test/test_htmlparser.py --- a/Lib/test/test_htmlparser.py +++ b/Lib/test/test_htmlparser.py @@ -96,7 +96,9 @@ parser = self.get_collector() parser.feed(source) parser.close() - self.assertRaises(html.parser.HTMLParseError, parse) + with self.assertRaises(html.parser.HTMLParseError): + with self.assertWarns(DeprecationWarning): + parse() class HTMLParserStrictTestCase(TestCaseBase): @@ -360,7 +362,16 @@ class HTMLParserTolerantTestCase(HTMLParserStrictTestCase): def get_collector(self): - return EventCollector(strict=False) + return EventCollector() + + def test_deprecation_warnings(self): + with self.assertWarns(DeprecationWarning): + EventCollector(strict=True) + with self.assertWarns(DeprecationWarning): + EventCollector(strict=False) + with self.assertRaises(html.parser.HTMLParseError): + with self.assertWarns(DeprecationWarning): + EventCollector().error('test') def test_tolerant_parsing(self): self._run_check('te>>xt&a<\n' @@ -676,7 +687,7 @@ class AttributesTolerantTestCase(AttributesStrictTestCase): def get_collector(self): - return EventCollector(strict=False) + return EventCollector() def test_attr_funky_names2(self): self._run_check( diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,9 @@ Library ------- +- Issue #15114: The html.parser module now raises a DeprecationWarning when the + strict argument of HTMLParser or the HTMLParser.error method are used. + - Issue #19410: Undo the special-casing removal of '' for importlib.machinery.FileFinder. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 16:28:24 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 16:28:24 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fix_Tkinter_tests_with_Tcl/Tk_8=2E4=2E?= Message-ID: <3dBkgX282jz7LjV@mail.python.org> http://hg.python.org/cpython/rev/278d15021d9a changeset: 86843:278d15021d9a branch: 2.7 parent: 86838:0554e2d37bf8 user: Serhiy Storchaka date: Sat Nov 02 17:27:59 2013 +0200 summary: Issue #19085: Fix Tkinter tests with Tcl/Tk 8.4. files: Lib/lib-tk/test/test_tkinter/test_widgets.py | 12 ++++---- Lib/lib-tk/test/test_ttk/test_widgets.py | 14 +++++----- Lib/lib-tk/test/widget_tests.py | 3 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Lib/lib-tk/test/test_tkinter/test_widgets.py b/Lib/lib-tk/test/test_tkinter/test_widgets.py --- a/Lib/lib-tk/test/test_tkinter/test_widgets.py +++ b/Lib/lib-tk/test/test_tkinter/test_widgets.py @@ -4,7 +4,7 @@ from test.test_support import requires, run_unittest from test_ttk.support import tcl_version, requires_tcl, widget_eq -from widget_tests import (add_standard_options, noconv, int_round, +from widget_tests import (add_standard_options, noconv, noconv_meth, int_round, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) @@ -12,7 +12,7 @@ class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): - _conv_pad_pixels = noconv + _conv_pad_pixels = noconv_meth def test_class(self): widget = self.create() @@ -130,7 +130,7 @@ class AbstractLabelTest(AbstractWidgetTest, IntegerSizeTests): - _conv_pixels = noconv + _conv_pixels = noconv_meth def test_highlightthickness(self): widget = self.create() @@ -240,7 +240,7 @@ 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength', ) - _conv_pixels = AbstractWidgetTest._conv_pixels + _conv_pixels = staticmethod(AbstractWidgetTest._conv_pixels) def _create(self, **kwargs): return Tkinter.Menubutton(self.root, **kwargs) @@ -858,7 +858,7 @@ 'postcommand', 'relief', 'selectcolor', 'takefocus', 'tearoff', 'tearoffcommand', 'title', 'type', ) - _conv_pixels = noconv + _conv_pixels = noconv_meth def _create(self, **kwargs): return Tkinter.Menu(self.root, **kwargs) @@ -894,7 +894,7 @@ 'justify', 'padx', 'pady', 'relief', 'takefocus', 'text', 'textvariable', 'width', ) - _conv_pad_pixels = noconv + _conv_pad_pixels = noconv_meth def _create(self, **kwargs): return Tkinter.Message(self.root, **kwargs) diff --git a/Lib/lib-tk/test/test_ttk/test_widgets.py b/Lib/lib-tk/test/test_ttk/test_widgets.py --- a/Lib/lib-tk/test/test_ttk/test_widgets.py +++ b/Lib/lib-tk/test/test_ttk/test_widgets.py @@ -7,7 +7,7 @@ import support from test_functions import MockTclObj, MockStateSpec from support import tcl_version -from widget_tests import (add_standard_options, noconv, +from widget_tests import (add_standard_options, noconv, noconv_meth, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) @@ -117,7 +117,7 @@ class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): - _conv_pixels = noconv + _conv_pixels = noconv_meth @add_standard_options(StandardTtkOptionsTests) @@ -197,7 +197,7 @@ 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength', ) - _conv_pixels = noconv + _conv_pixels = noconv_meth def _create(self, **kwargs): return ttk.Label(self.root, **kwargs) @@ -362,7 +362,7 @@ expected=('mon', 'tue', 'wed', 'thur')) self.checkParam(self.combo, 'values', ('mon', 'tue', 'wed', 'thur')) self.checkParam(self.combo, 'values', (42, 3.14, '', 'any string')) - self.checkParam(self.combo, 'values', '') + self.checkParam(self.combo, 'values', () if tcl_version < (8, 5) else '') self.combo['values'] = ['a', 1, 'c'] @@ -758,7 +758,7 @@ 'class', 'command', 'cursor', 'from', 'length', 'orient', 'style', 'takefocus', 'to', 'value', 'variable', ) - _conv_pixels = noconv + _conv_pixels = noconv_meth default_orient = 'horizontal' def setUp(self): @@ -862,7 +862,7 @@ 'mode', 'maximum', 'phase', 'style', 'takefocus', 'value', 'variable', ) - _conv_pixels = noconv + _conv_pixels = noconv_meth default_orient = 'horizontal' def _create(self, **kwargs): @@ -1144,7 +1144,7 @@ self.checkParam(widget, 'columns', 'a b c', expected=('a', 'b', 'c')) self.checkParam(widget, 'columns', ('a', 'b', 'c')) - self.checkParam(widget, 'columns', '') + self.checkParam(widget, 'columns', () if tcl_version < (8, 5) else '') def test_displaycolumns(self): widget = self.create() diff --git a/Lib/lib-tk/test/widget_tests.py b/Lib/lib-tk/test/widget_tests.py --- a/Lib/lib-tk/test/widget_tests.py +++ b/Lib/lib-tk/test/widget_tests.py @@ -6,6 +6,7 @@ noconv = str if tcl_version < (8, 5) else False +noconv_meth = noconv and staticmethod(noconv) def int_round(x): return int(round(x)) @@ -13,7 +14,7 @@ _sentinel = object() class AbstractWidgetTest(object): - _conv_pixels = staticmethod(int_round) if tcl_version[:2] != (8, 5) else int + _conv_pixels = staticmethod(int_round if tcl_version[:2] != (8, 5) else int) _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 16:50:14 2013 From: python-checkins at python.org (ezio.melotti) Date: Sat, 2 Nov 2013 16:50:14 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=283=2E3=29=3A_Use_unittest?= =?utf-8?b?Lm1haW4oKSBpbiB0ZXN0X2h0bWxwYXJzZXIu?= Message-ID: <3dBl8k4LgDz7LjS@mail.python.org> http://hg.python.org/cpython/rev/b77c1a19028e changeset: 86844:b77c1a19028e branch: 3.3 parent: 86839:a58fce53e873 user: Ezio Melotti date: Sat Nov 02 17:49:08 2013 +0200 summary: Use unittest.main() in test_htmlparser. files: Lib/test/test_htmlparser.py | 8 +------- 1 files changed, 1 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_htmlparser.py b/Lib/test/test_htmlparser.py --- a/Lib/test/test_htmlparser.py +++ b/Lib/test/test_htmlparser.py @@ -753,11 +753,5 @@ ("data", "spam"), ("endtag", "a")]) - -def test_main(): - support.run_unittest(HTMLParserStrictTestCase, HTMLParserTolerantTestCase, - AttributesStrictTestCase, AttributesTolerantTestCase) - - if __name__ == "__main__": - test_main() + unittest.main() -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 16:50:15 2013 From: python-checkins at python.org (ezio.melotti) Date: Sat, 2 Nov 2013 16:50:15 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Merge_test=5Fhtmlparser_changes_from_3=2E3=2E?= Message-ID: <3dBl8l68Ldz7Ljf@mail.python.org> http://hg.python.org/cpython/rev/0ce6c0a47676 changeset: 86845:0ce6c0a47676 parent: 86842:0a56709eb798 parent: 86844:b77c1a19028e user: Ezio Melotti date: Sat Nov 02 17:50:02 2013 +0200 summary: Merge test_htmlparser changes from 3.3. files: Lib/test/test_htmlparser.py | 8 +------- 1 files changed, 1 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_htmlparser.py b/Lib/test/test_htmlparser.py --- a/Lib/test/test_htmlparser.py +++ b/Lib/test/test_htmlparser.py @@ -764,11 +764,5 @@ ("data", "spam"), ("endtag", "a")]) - -def test_main(): - support.run_unittest(HTMLParserStrictTestCase, HTMLParserTolerantTestCase, - AttributesStrictTestCase, AttributesTolerantTestCase) - - if __name__ == "__main__": - test_main() + unittest.main() -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 16:58:18 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 16:58:18 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5Mjg2?= =?utf-8?q?=3A_Adding_test_demonstrating_the_failure_when_a_directory_is_f?= =?utf-8?q?ound?= Message-ID: <3dBlL24Stcz7Ljf@mail.python.org> http://hg.python.org/cpython/rev/22bac968e226 changeset: 86846:22bac968e226 branch: 2.7 parent: 86838:0554e2d37bf8 user: Jason R. Coombs date: Sat Nov 02 11:29:33 2013 -0400 summary: Issue #19286: Adding test demonstrating the failure when a directory is found in the package_data globs. files: Lib/distutils/tests/test_build_py.py | 31 ++++++++++++++++ 1 files changed, 31 insertions(+), 0 deletions(-) diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py --- a/Lib/distutils/tests/test_build_py.py +++ b/Lib/distutils/tests/test_build_py.py @@ -99,6 +99,37 @@ os.chdir(cwd) sys.stdout = old_stdout + def test_dir_in_package_data(self): + """ + A directory in package_data should not be added to the filelist. + """ + # See bug 19286 + sources = self.mkdtemp() + pkg_dir = os.path.join(sources, "pkg") + + os.mkdir(pkg_dir) + open(os.path.join(pkg_dir, "__init__.py"), "w").close() + + docdir = os.path.join(pkg_dir, "doc") + os.mkdir(docdir) + open(os.path.join(docdir, "testfile"), "w").close() + + # create the directory that could be incorrectly detected as a file + os.mkdir(os.path.join(docdir, 'otherdir')) + + os.chdir(sources) + dist = Distribution({"packages": ["pkg"], + "package_data": {"pkg": ["doc/*"]}}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(sources, "setup.py") + dist.script_args = ["build"] + dist.parse_command_line() + + try: + dist.run_commands() + except DistutilsFileError: + self.fail("failed package_data when data dir includes a dir") + def test_dont_write_bytecode(self): # makes sure byte_compile is not used pkg_dir, dist = self.create_dist() -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 16:58:19 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 16:58:19 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5Mjg2?= =?utf-8?q?=3A_=5Bdistutils=5D_Only_match_files_in_build=5Fpy=2Efind=5Fdat?= =?utf-8?q?a=5Ffiles=2E?= Message-ID: <3dBlL369dSz7Ljf@mail.python.org> http://hg.python.org/cpython/rev/0a1cf947eff6 changeset: 86847:0a1cf947eff6 branch: 2.7 user: Jason R. Coombs date: Sat Nov 02 11:07:35 2013 -0400 summary: Issue #19286: [distutils] Only match files in build_py.find_data_files. files: Lib/distutils/command/build_py.py | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -128,7 +128,8 @@ # Each pattern has to be converted to a platform-specific path filelist = glob(os.path.join(src_dir, convert_path(pattern))) # Files that match more than one pattern are only added once - files.extend([fn for fn in filelist if fn not in files]) + files.extend([fn for fn in filelist if fn not in files + and os.path.isfile(fn)]) return files def build_package_data(self): -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 16:58:21 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 16:58:21 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=282=2E7=29=3A_Update_NEWS?= Message-ID: <3dBlL50ywhz7Ljq@mail.python.org> http://hg.python.org/cpython/rev/4db15e465624 changeset: 86848:4db15e465624 branch: 2.7 user: Jason R. Coombs date: Sat Nov 02 11:43:40 2013 -0400 summary: Update NEWS files: Misc/NEWS | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ Library ------- +- Issue #19286: Directories in ``package_data`` are no longer added to + the filelist, preventing failure outlined in the ticket. + Tests ----- -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 16:58:22 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 16:58:22 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAobWVyZ2UgMi43IC0+IDIuNyk6?= =?utf-8?q?_Merge?= Message-ID: <3dBlL62sdnz7Lk8@mail.python.org> http://hg.python.org/cpython/rev/2b085aba89f9 changeset: 86849:2b085aba89f9 branch: 2.7 parent: 86843:278d15021d9a parent: 86848:4db15e465624 user: Jason R. Coombs date: Sat Nov 02 11:57:54 2013 -0400 summary: Merge files: Lib/distutils/command/build_py.py | 3 +- Lib/distutils/tests/test_build_py.py | 31 ++++++++++++++++ Misc/NEWS | 3 + 3 files changed, 36 insertions(+), 1 deletions(-) diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -128,7 +128,8 @@ # Each pattern has to be converted to a platform-specific path filelist = glob(os.path.join(src_dir, convert_path(pattern))) # Files that match more than one pattern are only added once - files.extend([fn for fn in filelist if fn not in files]) + files.extend([fn for fn in filelist if fn not in files + and os.path.isfile(fn)]) return files def build_package_data(self): diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py --- a/Lib/distutils/tests/test_build_py.py +++ b/Lib/distutils/tests/test_build_py.py @@ -99,6 +99,37 @@ os.chdir(cwd) sys.stdout = old_stdout + def test_dir_in_package_data(self): + """ + A directory in package_data should not be added to the filelist. + """ + # See bug 19286 + sources = self.mkdtemp() + pkg_dir = os.path.join(sources, "pkg") + + os.mkdir(pkg_dir) + open(os.path.join(pkg_dir, "__init__.py"), "w").close() + + docdir = os.path.join(pkg_dir, "doc") + os.mkdir(docdir) + open(os.path.join(docdir, "testfile"), "w").close() + + # create the directory that could be incorrectly detected as a file + os.mkdir(os.path.join(docdir, 'otherdir')) + + os.chdir(sources) + dist = Distribution({"packages": ["pkg"], + "package_data": {"pkg": ["doc/*"]}}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(sources, "setup.py") + dist.script_args = ["build"] + dist.parse_command_line() + + try: + dist.run_commands() + except DistutilsFileError: + self.fail("failed package_data when data dir includes a dir") + def test_dont_write_bytecode(self): # makes sure byte_compile is not used pkg_dir, dist = self.create_dist() diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ Library ------- +- Issue #19286: Directories in ``package_data`` are no longer added to + the filelist, preventing failure outlined in the ticket. + Tests ----- -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:40:03 2013 From: python-checkins at python.org (richard.oudkerk) Date: Sat, 2 Nov 2013 17:40:03 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Relax_test_for_process_ret?= =?utf-8?q?urn_code_on_Windows=2E?= Message-ID: <3dBmGC6NwSz7Ljf@mail.python.org> http://hg.python.org/cpython/rev/f051d3aaaef4 changeset: 86850:f051d3aaaef4 parent: 86845:0ce6c0a47676 user: Richard Oudkerk date: Sat Nov 02 16:38:58 2013 +0000 summary: Relax test for process return code on Windows. files: Lib/test/test_asyncio/test_events.py | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -961,14 +961,14 @@ def check_terminated(self, returncode): if sys.platform == 'win32': self.assertIsInstance(returncode, int) - self.assertNotEqual(0, returncode) + # expect 1 but sometimes get 0 else: self.assertEqual(-signal.SIGTERM, returncode) def check_killed(self, returncode): if sys.platform == 'win32': self.assertIsInstance(returncode, int) - self.assertNotEqual(0, returncode) + # expect 1 but sometimes get 0 else: self.assertEqual(-signal.SIGKILL, returncode) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:48:20 2013 From: python-checkins at python.org (richard.oudkerk) Date: Sat, 2 Nov 2013 17:48:20 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=283=2E3=29=3A_Relax_timeout_?= =?utf-8?q?test=2E?= Message-ID: <3dBmRm6nH5zMgW@mail.python.org> http://hg.python.org/cpython/rev/f70142e3799b changeset: 86851:f70142e3799b branch: 3.3 parent: 86844:b77c1a19028e user: Richard Oudkerk date: Sat Nov 02 16:46:32 2013 +0000 summary: Relax timeout test. files: Lib/test/test_multiprocessing.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_multiprocessing.py b/Lib/test/test_multiprocessing.py --- a/Lib/test/test_multiprocessing.py +++ b/Lib/test/test_multiprocessing.py @@ -716,7 +716,7 @@ start = time.time() self.assertRaises(pyqueue.Empty, q.get, True, 0.2) delta = time.time() - start - self.assertGreaterEqual(delta, 0.19) + self.assertGreaterEqual(delta, 0.18) # # -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:48:22 2013 From: python-checkins at python.org (richard.oudkerk) Date: Sat, 2 Nov 2013 17:48:22 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?b?KTogTWVyZ2Uu?= Message-ID: <3dBmRp29dxz7LkP@mail.python.org> http://hg.python.org/cpython/rev/722da29107b7 changeset: 86852:722da29107b7 parent: 86850:f051d3aaaef4 parent: 86851:f70142e3799b user: Richard Oudkerk date: Sat Nov 02 16:47:08 2013 +0000 summary: Merge. files: Lib/test/_test_multiprocessing.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -723,7 +723,7 @@ start = time.time() self.assertRaises(pyqueue.Empty, q.get, True, 0.2) delta = time.time() - start - self.assertGreaterEqual(delta, 0.19) + self.assertGreaterEqual(delta, 0.18) # # -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:54:17 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 17:54:17 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fixed_some_Tkinter_tests_on_Windows=2E?= Message-ID: <3dBmZd4FDcz7LjS@mail.python.org> http://hg.python.org/cpython/rev/f25679db52fb changeset: 86853:f25679db52fb branch: 3.3 parent: 86844:b77c1a19028e user: Serhiy Storchaka date: Sat Nov 02 18:50:42 2013 +0200 summary: Issue #19085: Fixed some Tkinter tests on Windows. files: Lib/tkinter/test/test_tkinter/test_widgets.py | 14 +++++----- 1 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -73,7 +73,10 @@ def test_screen(self): widget = self.create() self.assertEqual(widget['screen'], '') - display = os.environ['DISPLAY'] + try: + display = os.environ['DISPLAY'] + except KeyError: + self.skipTest('No $DISPLAY set.') self.checkInvalidParam(widget, 'screen', display, errmsg="can't modify -screen option after widget is created") widget2 = self.create(screen=display) @@ -82,13 +85,10 @@ def test_use(self): widget = self.create() self.assertEqual(widget['use'], '') - widget1 = self.create(container=True) - self.assertEqual(widget1['use'], '') - self.checkInvalidParam(widget1, 'use', '0x44022', - errmsg="can't modify -use option after widget is created") - wid = hex(widget1.winfo_id()) + parent = self.create(container=True) + wid = parent.winfo_id() widget2 = self.create(use=wid) - self.assertEqual(widget2['use'], wid) + self.assertEqual(int(widget2['use']), wid) @add_standard_options(StandardOptionsTests) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:54:18 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 17:54:18 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fixed_some_Tkinter_tests_on_Windows=2E?= Message-ID: <3dBmZf6wJ3z7LjS@mail.python.org> http://hg.python.org/cpython/rev/4a2afda8f187 changeset: 86854:4a2afda8f187 branch: 2.7 parent: 86849:2b085aba89f9 user: Serhiy Storchaka date: Sat Nov 02 18:50:53 2013 +0200 summary: Issue #19085: Fixed some Tkinter tests on Windows. files: Lib/lib-tk/test/test_tkinter/test_widgets.py | 14 +++++----- 1 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/lib-tk/test/test_tkinter/test_widgets.py b/Lib/lib-tk/test/test_tkinter/test_widgets.py --- a/Lib/lib-tk/test/test_tkinter/test_widgets.py +++ b/Lib/lib-tk/test/test_tkinter/test_widgets.py @@ -70,7 +70,10 @@ def test_screen(self): widget = self.create() self.assertEqual(widget['screen'], '') - display = os.environ['DISPLAY'] + try: + display = os.environ['DISPLAY'] + except KeyError: + self.skipTest('No $DISPLAY set.') self.checkInvalidParam(widget, 'screen', display, errmsg="can't modify -screen option after widget is created") widget2 = self.create(screen=display) @@ -79,13 +82,10 @@ def test_use(self): widget = self.create() self.assertEqual(widget['use'], '') - widget1 = self.create(container=True) - self.assertEqual(widget1['use'], '') - self.checkInvalidParam(widget1, 'use', '0x44022', - errmsg="can't modify -use option after widget is created") - wid = hex(widget1.winfo_id()) + parent = self.create(container=True) + wid = parent.winfo_id() widget2 = self.create(use=wid) - self.assertEqual(widget2['use'], wid) + self.assertEqual(int(widget2['use']), wid) @add_standard_options(StandardOptionsTests) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:54:20 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 17:54:20 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Null_merge?= Message-ID: <3dBmZh2c2hz7Lk3@mail.python.org> http://hg.python.org/cpython/rev/ab3b58f21fe7 changeset: 86855:ab3b58f21fe7 parent: 86850:f051d3aaaef4 parent: 86853:f25679db52fb user: Serhiy Storchaka date: Sat Nov 02 18:51:30 2013 +0200 summary: Null merge files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:54:21 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 17:54:21 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAobWVyZ2UgMy4zIC0+IDMuMyk6?= =?utf-8?q?_Merge_heads?= Message-ID: <3dBmZj5H1Vz7Ljq@mail.python.org> http://hg.python.org/cpython/rev/576ffbaa6363 changeset: 86856:576ffbaa6363 branch: 3.3 parent: 86853:f25679db52fb parent: 86851:f70142e3799b user: Serhiy Storchaka date: Sat Nov 02 18:53:06 2013 +0200 summary: Merge heads files: Lib/test/test_multiprocessing.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_multiprocessing.py b/Lib/test/test_multiprocessing.py --- a/Lib/test/test_multiprocessing.py +++ b/Lib/test/test_multiprocessing.py @@ -716,7 +716,7 @@ start = time.time() self.assertRaises(pyqueue.Empty, q.get, True, 0.2) delta = time.time() - start - self.assertGreaterEqual(delta, 0.19) + self.assertGreaterEqual(delta, 0.18) # # -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:54:23 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 17:54:23 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_heads?= Message-ID: <3dBmZl3Kppz7Lk9@mail.python.org> http://hg.python.org/cpython/rev/bc274d0aab88 changeset: 86857:bc274d0aab88 parent: 86855:ab3b58f21fe7 parent: 86852:722da29107b7 user: Serhiy Storchaka date: Sat Nov 02 18:53:19 2013 +0200 summary: Merge heads files: Lib/test/_test_multiprocessing.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -723,7 +723,7 @@ start = time.time() self.assertRaises(pyqueue.Empty, q.get, True, 0.2) delta = time.time() - start - self.assertGreaterEqual(delta, 0.19) + self.assertGreaterEqual(delta, 0.18) # # -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 17:54:25 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sat, 2 Nov 2013 17:54:25 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Null_merge?= Message-ID: <3dBmZn2q62z7LjS@mail.python.org> http://hg.python.org/cpython/rev/fdc10b073e79 changeset: 86858:fdc10b073e79 parent: 86857:bc274d0aab88 parent: 86856:576ffbaa6363 user: Serhiy Storchaka date: Sat Nov 02 18:53:39 2013 +0200 summary: Null merge files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 18:05:07 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 18:05:07 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4yKTogSXNzdWUgIzE5Mjg2?= =?utf-8?q?=3A_Adding_test_demonstrating_the_failure_when_a_directory_is_f?= =?utf-8?q?ound?= Message-ID: <3dBmq719xjz7LkF@mail.python.org> http://hg.python.org/cpython/rev/d80207d15294 changeset: 86859:d80207d15294 branch: 3.2 parent: 86778:dda1a32748e0 user: Jason R. Coombs date: Sat Nov 02 11:29:33 2013 -0400 summary: Issue #19286: Adding test demonstrating the failure when a directory is found in the package_data globs. files: Lib/distutils/tests/test_build_py.py | 31 ++++++++++++++++ 1 files changed, 31 insertions(+), 0 deletions(-) diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py --- a/Lib/distutils/tests/test_build_py.py +++ b/Lib/distutils/tests/test_build_py.py @@ -121,6 +121,37 @@ found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) self.assertEqual(sorted(found), ['boiledeggs.%s.pyo' % imp.get_tag()]) + def test_dir_in_package_data(self): + """ + A directory in package_data should not be added to the filelist. + """ + # See bug 19286 + sources = self.mkdtemp() + pkg_dir = os.path.join(sources, "pkg") + + os.mkdir(pkg_dir) + open(os.path.join(pkg_dir, "__init__.py"), "w").close() + + docdir = os.path.join(pkg_dir, "doc") + os.mkdir(docdir) + open(os.path.join(docdir, "testfile"), "w").close() + + # create the directory that could be incorrectly detected as a file + os.mkdir(os.path.join(docdir, 'otherdir')) + + os.chdir(sources) + dist = Distribution({"packages": ["pkg"], + "package_data": {"pkg": ["doc/*"]}}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(sources, "setup.py") + dist.script_args = ["build"] + dist.parse_command_line() + + try: + dist.run_commands() + except DistutilsFileError: + self.fail("failed package_data when data dir includes a dir") + def test_dont_write_bytecode(self): # makes sure byte_compile is not used dist = self.create_dist()[1] -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 18:05:08 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 18:05:08 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4yKTogSXNzdWUgIzE5Mjg2?= =?utf-8?q?=3A_=5Bdistutils=5D_Only_match_files_in_build=5Fpy=2Efind=5Fdat?= =?utf-8?q?a=5Ffiles=2E?= Message-ID: <3dBmq82rtGz7LkF@mail.python.org> http://hg.python.org/cpython/rev/265d369ad3b9 changeset: 86860:265d369ad3b9 branch: 3.2 user: Jason R. Coombs date: Sat Nov 02 11:07:35 2013 -0400 summary: Issue #19286: [distutils] Only match files in build_py.find_data_files. files: Lib/distutils/command/build_py.py | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -127,7 +127,8 @@ # Each pattern has to be converted to a platform-specific path filelist = glob(os.path.join(src_dir, convert_path(pattern))) # Files that match more than one pattern are only added once - files.extend([fn for fn in filelist if fn not in files]) + files.extend([fn for fn in filelist if fn not in files + and os.path.isfile(fn)]) return files def build_package_data(self): -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 18:05:09 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 18:05:09 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=283=2E2=29=3A_Update_NEWS_fo?= =?utf-8?q?r_265d369ad3b9=2E?= Message-ID: <3dBmq94f4bz7LkF@mail.python.org> http://hg.python.org/cpython/rev/7d399099334d changeset: 86861:7d399099334d branch: 3.2 user: Jason R. Coombs date: Sat Nov 02 13:00:01 2013 -0400 summary: Update NEWS for 265d369ad3b9. files: Misc/NEWS | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ Library ------- +- Issue #19286: Directories in ``package_data`` are no longer added to + the filelist, preventing failure outlined in the ticket. + - Issue #19435: Fix directory traversal attack on CGIHttpRequestHandler. - Issue #14984: On POSIX systems, when netrc is called without a filename -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 18:05:10 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 18:05:10 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAobWVyZ2UgMy4yIC0+IDMuMyk6?= =?utf-8?q?_Merge_with_3=2E2_for_Issue_=2319286=2E?= Message-ID: <3dBmqB6ZX2z7Ljg@mail.python.org> http://hg.python.org/cpython/rev/69f8288056eb changeset: 86862:69f8288056eb branch: 3.3 parent: 86856:576ffbaa6363 parent: 86861:7d399099334d user: Jason R. Coombs date: Sat Nov 02 13:01:46 2013 -0400 summary: Merge with 3.2 for Issue #19286. files: Lib/distutils/command/build_py.py | 3 +- Lib/distutils/tests/test_build_py.py | 31 ++++++++++++++++ Misc/NEWS | 3 + 3 files changed, 36 insertions(+), 1 deletions(-) diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -127,7 +127,8 @@ # Each pattern has to be converted to a platform-specific path filelist = glob(os.path.join(src_dir, convert_path(pattern))) # Files that match more than one pattern are only added once - files.extend([fn for fn in filelist if fn not in files]) + files.extend([fn for fn in filelist if fn not in files + and os.path.isfile(fn)]) return files def build_package_data(self): diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py --- a/Lib/distutils/tests/test_build_py.py +++ b/Lib/distutils/tests/test_build_py.py @@ -121,6 +121,37 @@ found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) self.assertEqual(sorted(found), ['boiledeggs.%s.pyo' % imp.get_tag()]) + def test_dir_in_package_data(self): + """ + A directory in package_data should not be added to the filelist. + """ + # See bug 19286 + sources = self.mkdtemp() + pkg_dir = os.path.join(sources, "pkg") + + os.mkdir(pkg_dir) + open(os.path.join(pkg_dir, "__init__.py"), "w").close() + + docdir = os.path.join(pkg_dir, "doc") + os.mkdir(docdir) + open(os.path.join(docdir, "testfile"), "w").close() + + # create the directory that could be incorrectly detected as a file + os.mkdir(os.path.join(docdir, 'otherdir')) + + os.chdir(sources) + dist = Distribution({"packages": ["pkg"], + "package_data": {"pkg": ["doc/*"]}}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(sources, "setup.py") + dist.script_args = ["build"] + dist.parse_command_line() + + try: + dist.run_commands() + except DistutilsFileError: + self.fail("failed package_data when data dir includes a dir") + def test_dont_write_bytecode(self): # makes sure byte_compile is not used dist = self.create_dist()[1] diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Library ------- +- Issue #19286: Directories in ``package_data`` are no longer added to + the filelist, preventing failure outlined in the ticket. + - Issue #19435: Fix directory traversal attack on CGIHttpRequestHandler. Tests -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 18:05:12 2013 From: python-checkins at python.org (jason.coombs) Date: Sat, 2 Nov 2013 18:05:12 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Merge_with_3=2E3_for_Issue_=2319286=2E?= Message-ID: <3dBmqD37Scz7Lk1@mail.python.org> http://hg.python.org/cpython/rev/9eafe31251b4 changeset: 86863:9eafe31251b4 parent: 86858:fdc10b073e79 parent: 86862:69f8288056eb user: Jason R. Coombs date: Sat Nov 02 13:04:51 2013 -0400 summary: Merge with 3.3 for Issue #19286. files: Lib/distutils/command/build_py.py | 3 +- Lib/distutils/tests/test_build_py.py | 31 ++++++++++++++++ Misc/NEWS | 3 + 3 files changed, 36 insertions(+), 1 deletions(-) diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -127,7 +127,8 @@ # Each pattern has to be converted to a platform-specific path filelist = glob(os.path.join(src_dir, convert_path(pattern))) # Files that match more than one pattern are only added once - files.extend([fn for fn in filelist if fn not in files]) + files.extend([fn for fn in filelist if fn not in files + and os.path.isfile(fn)]) return files def build_package_data(self): diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py --- a/Lib/distutils/tests/test_build_py.py +++ b/Lib/distutils/tests/test_build_py.py @@ -123,6 +123,37 @@ self.assertEqual(sorted(found), ['boiledeggs.%s.pyo' % sys.implementation.cache_tag]) + def test_dir_in_package_data(self): + """ + A directory in package_data should not be added to the filelist. + """ + # See bug 19286 + sources = self.mkdtemp() + pkg_dir = os.path.join(sources, "pkg") + + os.mkdir(pkg_dir) + open(os.path.join(pkg_dir, "__init__.py"), "w").close() + + docdir = os.path.join(pkg_dir, "doc") + os.mkdir(docdir) + open(os.path.join(docdir, "testfile"), "w").close() + + # create the directory that could be incorrectly detected as a file + os.mkdir(os.path.join(docdir, 'otherdir')) + + os.chdir(sources) + dist = Distribution({"packages": ["pkg"], + "package_data": {"pkg": ["doc/*"]}}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(sources, "setup.py") + dist.script_args = ["build"] + dist.parse_command_line() + + try: + dist.run_commands() + except DistutilsFileError: + self.fail("failed package_data when data dir includes a dir") + def test_dont_write_bytecode(self): # makes sure byte_compile is not used dist = self.create_dist()[1] diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,9 @@ Library ------- +- Issue #19286: Directories in ``package_data`` are no longer added to + the filelist, preventing failure outlined in the ticket. + - Issue #15114: The html.parser module now raises a DeprecationWarning when the strict argument of HTMLParser or the HTMLParser.error method are used. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 18:09:15 2013 From: python-checkins at python.org (richard.oudkerk) Date: Sat, 2 Nov 2013 18:09:15 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2319478=3A_Make_cho?= =?utf-8?q?ice_of_semaphore_prefix_more_flexible=2E?= Message-ID: <3dBmvv6xwtz7LjV@mail.python.org> http://hg.python.org/cpython/rev/1b5506fc6a50 changeset: 86864:1b5506fc6a50 parent: 86858:fdc10b073e79 user: Richard Oudkerk date: Sat Nov 02 17:05:07 2013 +0000 summary: Issue #19478: Make choice of semaphore prefix more flexible. files: Lib/multiprocessing/process.py | 10 ++++++++-- Lib/multiprocessing/synchronize.py | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -301,10 +301,16 @@ self._parent_pid = None self._popen = None self._config = {'authkey': AuthenticationString(os.urandom(32)), - 'semprefix': 'mp'} + 'semprefix': '/mp'} # Note that some versions of FreeBSD only allow named - # semaphores to have names of up to 14 characters. Therfore + # semaphores to have names of up to 14 characters. Therefore # we choose a short prefix. + # + # On MacOSX in a sandbox it may be necessary to use a + # different prefix -- see #19478. + # + # Everything in self._config will be inherited by descendant + # processes. _current_process = _MainProcess() diff --git a/Lib/multiprocessing/synchronize.py b/Lib/multiprocessing/synchronize.py --- a/Lib/multiprocessing/synchronize.py +++ b/Lib/multiprocessing/synchronize.py @@ -115,8 +115,8 @@ @staticmethod def _make_name(): - return '/%s-%s' % (process.current_process()._config['semprefix'], - next(SemLock._rand)) + return '%s-%s' % (process.current_process()._config['semprefix'], + next(SemLock._rand)) # # Semaphore -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 18:09:17 2013 From: python-checkins at python.org (richard.oudkerk) Date: Sat, 2 Nov 2013 18:09:17 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_default_-=3E_default?= =?utf-8?b?KTogTWVyZ2Uu?= Message-ID: <3dBmvx1fw8z7Lkb@mail.python.org> http://hg.python.org/cpython/rev/4abd7cbc2683 changeset: 86865:4abd7cbc2683 parent: 86864:1b5506fc6a50 parent: 86863:9eafe31251b4 user: Richard Oudkerk date: Sat Nov 02 17:08:01 2013 +0000 summary: Merge. files: Lib/distutils/command/build_py.py | 3 +- Lib/distutils/tests/test_build_py.py | 31 ++++++++++++++++ Misc/NEWS | 3 + 3 files changed, 36 insertions(+), 1 deletions(-) diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -127,7 +127,8 @@ # Each pattern has to be converted to a platform-specific path filelist = glob(os.path.join(src_dir, convert_path(pattern))) # Files that match more than one pattern are only added once - files.extend([fn for fn in filelist if fn not in files]) + files.extend([fn for fn in filelist if fn not in files + and os.path.isfile(fn)]) return files def build_package_data(self): diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py --- a/Lib/distutils/tests/test_build_py.py +++ b/Lib/distutils/tests/test_build_py.py @@ -123,6 +123,37 @@ self.assertEqual(sorted(found), ['boiledeggs.%s.pyo' % sys.implementation.cache_tag]) + def test_dir_in_package_data(self): + """ + A directory in package_data should not be added to the filelist. + """ + # See bug 19286 + sources = self.mkdtemp() + pkg_dir = os.path.join(sources, "pkg") + + os.mkdir(pkg_dir) + open(os.path.join(pkg_dir, "__init__.py"), "w").close() + + docdir = os.path.join(pkg_dir, "doc") + os.mkdir(docdir) + open(os.path.join(docdir, "testfile"), "w").close() + + # create the directory that could be incorrectly detected as a file + os.mkdir(os.path.join(docdir, 'otherdir')) + + os.chdir(sources) + dist = Distribution({"packages": ["pkg"], + "package_data": {"pkg": ["doc/*"]}}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(sources, "setup.py") + dist.script_args = ["build"] + dist.parse_command_line() + + try: + dist.run_commands() + except DistutilsFileError: + self.fail("failed package_data when data dir includes a dir") + def test_dont_write_bytecode(self): # makes sure byte_compile is not used dist = self.create_dist()[1] diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,9 @@ Library ------- +- Issue #19286: Directories in ``package_data`` are no longer added to + the filelist, preventing failure outlined in the ticket. + - Issue #15114: The html.parser module now raises a DeprecationWarning when the strict argument of HTMLParser or the HTMLParser.error method are used. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 19:21:48 2013 From: python-checkins at python.org (tim.peters) Date: Sat, 2 Nov 2013 19:21:48 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Mercurial=3A__ignore_preco?= =?utf-8?q?mpiled_header_files_on_Windows=2E?= Message-ID: <3dBpWc1VK7z7LjR@mail.python.org> http://hg.python.org/cpython/rev/9928a927975f changeset: 86866:9928a927975f user: Tim Peters date: Sat Nov 02 13:21:28 2013 -0500 summary: Mercurial: ignore precompiled header files on Windows. files: .hgignore | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -86,6 +86,7 @@ PCbuild/Win32-temp-* PCbuild/x64-temp-* PCbuild/amd64 +PCbuild/ipch BuildLog.htm __pycache__ Modules/_freeze_importlib -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sat Nov 2 22:12:47 2013 From: python-checkins at python.org (eric.snow) Date: Sat, 2 Nov 2013 22:12:47 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_=5BPEP_451=5D_Changes_related?= =?utf-8?q?_to_the_find=5Fspec=28=29_target_module=2E?= Message-ID: <3dBtJv4xDzz7LjV@mail.python.org> http://hg.python.org/peps/rev/63595acfe51d changeset: 5246:63595acfe51d user: Eric Snow date: Sat Nov 02 15:08:19 2013 -0600 summary: [PEP 451] Changes related to the find_spec() target module. files: pep-0451.txt | 51 ++++++++++++++------------------------- 1 files changed, 19 insertions(+), 32 deletions(-) diff --git a/pep-0451.txt b/pep-0451.txt --- a/pep-0451.txt +++ b/pep-0451.txt @@ -278,7 +278,7 @@ Other API Additions ------------------- -* importlib.find_spec(name, path=None, existing=None) will work exactly +* importlib.find_spec(name, path=None, target=None) will work exactly the same as importlib.find_loader() (which it replaces), but return a spec instead of a loader. @@ -493,7 +493,7 @@ name = module.__spec__.name except AttributeError: name = module.__name__ - spec = find_spec(name, existing=module) + spec = find_spec(name, target=module) if sys.modules.get(name) is not module: raise ImportError @@ -521,7 +521,7 @@ exec_module() is called during load (not reload) the import machinery would already have placed the module in sys.modules. This is part of the reason why find_spec() has -`the "existing" parameter `_. +`the "target" parameter `_. The semantics of reload will remain essentially the same as they exist already [#reload-semantics-fix]_. The impact of this PEP on some kinds @@ -697,9 +697,9 @@ loader would be costly to create, that loader can be designed to defer the cost until later. -**MetaPathFinder.find_spec(name, path=None, existing=None)** +**MetaPathFinder.find_spec(name, path=None, target=None)** -**PathEntryFinder.find_spec(name, existing=None)** +**PathEntryFinder.find_spec(name, target=None)** Finders must return ModuleSpec objects when find_spec() is called. This new method replaces find_module() and @@ -714,8 +714,8 @@ added in Python 3.3. However, the extra complexity and a less-than- explicit method name aren't worth it. -The "existing" parameter of find_spec() ---------------------------------------- +The "target" parameter of find_spec() +------------------------------------- A module object with the same name as the "name" argument (or None, the default) should be passed in to "exising". This argument allows the @@ -727,26 +727,26 @@ will return in the spec. In the case of reload, at this point the finder should also decide whether or not the loader supports loading into the module-to-be-reloaded (which was passed in to find_spec() as -"existing"). This decision may entail consulting with the loader. If +"target"). This decision may entail consulting with the loader. If the finder determines that the loader does not support reloading that -module, it should either find another loader or return None (indicating -that it could not "find" the module). This reload decision is important -since, as noted in `How Reloading Will Work`_, loaders will no longer be -able to trivially identify a reload situation on their own. +module, it should either find another loader or raise ImportError +(completely stopping import of the module). This reload decision is +important since, as noted in `How Reloading Will Work`_, loaders will +no longer be able to trivially identify a reload situation on their own. -Two alternatives were presented to the "existing" parameter: -Loader.supports_reload() and adding "existing" to Loader.exec_module() +Two alternatives were presented to the "target" parameter: +Loader.supports_reload() and adding "target" to Loader.exec_module() instead of find_spec(). supports_reload() was the initial approach to the reload situation. [#supports_reload]_ However, there was some opposition to the loader-specific, reload-centric approach. [#supports_reload_considered_harmful]_ -As to "existing" on exec_module(), the loader may need other information -from the existing module (or spec) during reload, more than just "does +As to "target" on exec_module(), the loader may need other information +from the target module (or spec) during reload, more than just "does this loader support reloading this module", that is no longer available with the move away from load_module(). A proposal on the table was to -add something like "existing" to exec_module(). [#exec_module_existing]_ -However, putting "existing" on find_spec() instead is more in line with +add something like "target" to exec_module(). [#exec_module_target]_ +However, putting "target" on find_spec() instead is more in line with the goals of this PEP. Furthermore, it obviates the need for supports_reload(). @@ -835,19 +835,6 @@ * importlib.reload() will now make use of the per-module import lock. -Open Issues -=========== - -\* In the `Finders`_ section, the PEP specifies returning None (or using -a different loader) when the found loader does not support loading into -an existing module (e.g during reload). An alternative to returning -None would be to raise ImportError with a message like "the loader does -not support reloading the module". This may actually be a better -approach since "could not find a loader" and "the found loader won't -work" are different situations that a single return value (None) may not -sufficiently represent. - - Reference Implementation ======================== @@ -943,7 +930,7 @@ .. [#supports_reload_considered_harmful] https://mail.python.org/pipermail/python-dev/2013-October/129971.html -.. [#exec_module_existing] +.. [#exec_module_target] https://mail.python.org/pipermail/python-dev/2013-October/129933.html Copyright -- Repository URL: http://hg.python.org/peps From solipsis at pitrou.net Sun Nov 3 07:34:13 2013 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Sun, 03 Nov 2013 07:34:13 +0100 Subject: [Python-checkins] Daily reference leaks (9928a927975f): sum=4 Message-ID: results for 9928a927975f on branch "default" -------------------------------------------- test_asyncio leaked [4, 0, 0] memory blocks, sum=4 test_site leaked [2, -2, 0] references, sum=0 test_site leaked [2, -2, 0] memory blocks, sum=0 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/antoine/cpython/refleaks/reflogPGFII9', '-x'] From python-checkins at python.org Sun Nov 3 07:42:40 2013 From: python-checkins at python.org (nick.coghlan) Date: Sun, 3 Nov 2013 07:42:40 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=234331=3A_Added_fun?= =?utf-8?q?ctools=2Epartialmethod?= Message-ID: <3dC6yS6ks8z7Ljv@mail.python.org> http://hg.python.org/cpython/rev/46d3c5539981 changeset: 86867:46d3c5539981 user: Nick Coghlan date: Sun Nov 03 16:41:46 2013 +1000 summary: Issue #4331: Added functools.partialmethod Initial patch by Alon Horev files: Doc/library/functools.rst | 43 +++++++++- Doc/whatsnew/3.4.rst | 20 ++++- Lib/functools.py | 78 ++++++++++++++++- Lib/test/test_functools.py | 116 +++++++++++++++++++++++++ Misc/NEWS | 2 + 5 files changed, 255 insertions(+), 4 deletions(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -194,6 +194,48 @@ 18 +.. class:: partialmethod(func, *args, **keywords) + + Return a new :class:`partialmethod` descriptor which behaves + like :class:`partial` except that it is designed to be used as a method + definition rather than being directly callable. + + *func* must be a :term:`descriptor` or a callable (objects which are both, + like normal functions, are handled as descriptors). + + When *func* is a descriptor (such as a normal Python function, + :func:`classmethod`, :func:`staticmethod`, :func:`abstractmethod` or + another instance of :class:`partialmethod`), calls to ``__get__`` are + delegated to the underlying descriptor, and an appropriate + :class:`partial` object returned as the result. + + When *func* is a non-descriptor callable, an appropriate bound method is + created dynamically. This behaves like a normal Python function when + used as a method: the *self* argument will be inserted as the first + positional argument, even before the *args* and *keywords* supplied to + the :class:`partialmethod` constructor. + + Example:: + + >>> class Cell(object): + ... @property + ... def alive(self): + ... return self._alive + ... def set_state(self, state): + ... self._alive = bool(state) + ... set_alive = partialmethod(set_alive, True) + ... set_dead = partialmethod(set_alive, False) + ... + >>> c = Cell() + >>> c.alive + False + >>> c.set_alive() + >>> c.alive + True + + .. versionadded:: 3.4 + + .. function:: reduce(function, iterable[, initializer]) Apply *function* of two arguments cumulatively to the items of *sequence*, from @@ -431,4 +473,3 @@ are not created automatically. Also, :class:`partial` objects defined in classes behave like static methods and do not transform into bound methods during instance attribute look-up. - diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -342,7 +342,25 @@ functools --------- -New :func:`functools.singledispatch` decorator: see the :pep:`443`. +The new :func:`~functools.partialmethod` descriptor bring partial argument +application to descriptors, just as :func:`~functools.partial` provides +for normal callables. The new descriptor also makes it easier to get +arbitrary callables (including :func:`~functools.partial` instances) +to behave like normal instance methods when included in a class definition. + +(Contributed by Alon Horev and Nick Coghlan in :issue:`4331`) + +The new :func:`~functools.singledispatch` decorator brings support for +single-dispatch generic functions to the Python standard library. Where +object oriented programming focuses on grouping multiple operations on a +common set of data into a class, a generic function focuses on grouping +multiple implementations of an operation that allows it to work with +*different* kinds of data. + +.. seealso:: + + :pep:`443` - Single-dispatch generic functions + PEP written and implemented by ?ukasz Langa. hashlib diff --git a/Lib/functools.py b/Lib/functools.py --- a/Lib/functools.py +++ b/Lib/functools.py @@ -19,7 +19,7 @@ pass from abc import get_cache_token from collections import namedtuple -from types import MappingProxyType +from types import MappingProxyType, MethodType from weakref import WeakKeyDictionary try: from _thread import RLock @@ -223,8 +223,9 @@ ### partial() argument application ################################################################################ +# Purely functional, no descriptor behaviour def partial(func, *args, **keywords): - """new function with partial application of the given arguments + """New function with partial application of the given arguments and keywords. """ def newfunc(*fargs, **fkeywords): @@ -241,6 +242,79 @@ except ImportError: pass +# Descriptor version +class partialmethod(object): + """Method descriptor with partial application of the given arguments + and keywords. + + Supports wrapping existing descriptors and handles non-descriptor + callables as instance methods. + """ + + def __init__(self, func, *args, **keywords): + if not callable(func) and not hasattr(func, "__get__"): + raise TypeError("{!r} is not callable or a descriptor" + .format(func)) + + # func could be a descriptor like classmethod which isn't callable, + # so we can't inherit from partial (it verifies func is callable) + if isinstance(func, partialmethod): + # flattening is mandatory in order to place cls/self before all + # other arguments + # it's also more efficient since only one function will be called + self.func = func.func + self.args = func.args + args + self.keywords = func.keywords.copy() + self.keywords.update(keywords) + else: + self.func = func + self.args = args + self.keywords = keywords + + def __repr__(self): + args = ", ".join(map(repr, self.args)) + keywords = ", ".join("{}={!r}".format(k, v) + for k, v in self.keywords.items()) + format_string = "{module}.{cls}({func}, {args}, {keywords})" + return format_string.format(module=self.__class__.__module__, + cls=self.__class__.__name__, + func=self.func, + args=args, + keywords=keywords) + + def _make_unbound_method(self): + def _method(*args, **keywords): + call_keywords = self.keywords.copy() + call_keywords.update(keywords) + cls_or_self, *rest = args + call_args = (cls_or_self,) + self.args + tuple(rest) + return self.func(*call_args, **call_keywords) + _method.__isabstractmethod__ = self.__isabstractmethod__ + return _method + + def __get__(self, obj, cls): + get = getattr(self.func, "__get__", None) + result = None + if get is not None: + new_func = get(obj, cls) + if new_func is not self.func: + # Assume __get__ returning something new indicates the + # creation of an appropriate callable + result = partial(new_func, *self.args, **self.keywords) + try: + result.__self__ = new_func.__self__ + except AttributeError: + pass + if result is None: + # If the underlying descriptor didn't do anything, treat this + # like an instance method + result = self._make_unbound_method().__get__(obj, cls) + return result + + @property + def __isabstractmethod__(self): + return getattr(self.func, "__isabstractmethod__", False) + ################################################################################ ### LRU Cache function decorator diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1,3 +1,4 @@ +import abc import collections from itertools import permutations import pickle @@ -217,6 +218,120 @@ partial = PartialSubclass +class TestPartialMethod(unittest.TestCase): + + class A(object): + nothing = functools.partialmethod(capture) + positional = functools.partialmethod(capture, 1) + keywords = functools.partialmethod(capture, a=2) + both = functools.partialmethod(capture, 3, b=4) + + nested = functools.partialmethod(positional, 5) + + over_partial = functools.partialmethod(functools.partial(capture, c=6), 7) + + static = functools.partialmethod(staticmethod(capture), 8) + cls = functools.partialmethod(classmethod(capture), d=9) + + a = A() + + def test_arg_combinations(self): + self.assertEqual(self.a.nothing(), ((self.a,), {})) + self.assertEqual(self.a.nothing(5), ((self.a, 5), {})) + self.assertEqual(self.a.nothing(c=6), ((self.a,), {'c': 6})) + self.assertEqual(self.a.nothing(5, c=6), ((self.a, 5), {'c': 6})) + + self.assertEqual(self.a.positional(), ((self.a, 1), {})) + self.assertEqual(self.a.positional(5), ((self.a, 1, 5), {})) + self.assertEqual(self.a.positional(c=6), ((self.a, 1), {'c': 6})) + self.assertEqual(self.a.positional(5, c=6), ((self.a, 1, 5), {'c': 6})) + + self.assertEqual(self.a.keywords(), ((self.a,), {'a': 2})) + self.assertEqual(self.a.keywords(5), ((self.a, 5), {'a': 2})) + self.assertEqual(self.a.keywords(c=6), ((self.a,), {'a': 2, 'c': 6})) + self.assertEqual(self.a.keywords(5, c=6), ((self.a, 5), {'a': 2, 'c': 6})) + + self.assertEqual(self.a.both(), ((self.a, 3), {'b': 4})) + self.assertEqual(self.a.both(5), ((self.a, 3, 5), {'b': 4})) + self.assertEqual(self.a.both(c=6), ((self.a, 3), {'b': 4, 'c': 6})) + self.assertEqual(self.a.both(5, c=6), ((self.a, 3, 5), {'b': 4, 'c': 6})) + + self.assertEqual(self.A.both(self.a, 5, c=6), ((self.a, 3, 5), {'b': 4, 'c': 6})) + + def test_nested(self): + self.assertEqual(self.a.nested(), ((self.a, 1, 5), {})) + self.assertEqual(self.a.nested(6), ((self.a, 1, 5, 6), {})) + self.assertEqual(self.a.nested(d=7), ((self.a, 1, 5), {'d': 7})) + self.assertEqual(self.a.nested(6, d=7), ((self.a, 1, 5, 6), {'d': 7})) + + self.assertEqual(self.A.nested(self.a, 6, d=7), ((self.a, 1, 5, 6), {'d': 7})) + + def test_over_partial(self): + self.assertEqual(self.a.over_partial(), ((self.a, 7), {'c': 6})) + self.assertEqual(self.a.over_partial(5), ((self.a, 7, 5), {'c': 6})) + self.assertEqual(self.a.over_partial(d=8), ((self.a, 7), {'c': 6, 'd': 8})) + self.assertEqual(self.a.over_partial(5, d=8), ((self.a, 7, 5), {'c': 6, 'd': 8})) + + self.assertEqual(self.A.over_partial(self.a, 5, d=8), ((self.a, 7, 5), {'c': 6, 'd': 8})) + + def test_bound_method_introspection(self): + obj = self.a + self.assertIs(obj.both.__self__, obj) + self.assertIs(obj.nested.__self__, obj) + self.assertIs(obj.over_partial.__self__, obj) + self.assertIs(obj.cls.__self__, self.A) + self.assertIs(self.A.cls.__self__, self.A) + + def test_unbound_method_retrieval(self): + obj = self.A + self.assertFalse(hasattr(obj.both, "__self__")) + self.assertFalse(hasattr(obj.nested, "__self__")) + self.assertFalse(hasattr(obj.over_partial, "__self__")) + self.assertFalse(hasattr(obj.static, "__self__")) + self.assertFalse(hasattr(self.a.static, "__self__")) + + def test_descriptors(self): + for obj in [self.A, self.a]: + with self.subTest(obj=obj): + self.assertEqual(obj.static(), ((8,), {})) + self.assertEqual(obj.static(5), ((8, 5), {})) + self.assertEqual(obj.static(d=8), ((8,), {'d': 8})) + self.assertEqual(obj.static(5, d=8), ((8, 5), {'d': 8})) + + self.assertEqual(obj.cls(), ((self.A,), {'d': 9})) + self.assertEqual(obj.cls(5), ((self.A, 5), {'d': 9})) + self.assertEqual(obj.cls(c=8), ((self.A,), {'c': 8, 'd': 9})) + self.assertEqual(obj.cls(5, c=8), ((self.A, 5), {'c': 8, 'd': 9})) + + def test_overriding_keywords(self): + self.assertEqual(self.a.keywords(a=3), ((self.a,), {'a': 3})) + self.assertEqual(self.A.keywords(self.a, a=3), ((self.a,), {'a': 3})) + + def test_invalid_args(self): + with self.assertRaises(TypeError): + class B(object): + method = functools.partialmethod(None, 1) + + def test_repr(self): + self.assertEqual(repr(vars(self.A)['both']), + 'functools.partialmethod({}, 3, b=4)'.format(capture)) + + def test_abstract(self): + class Abstract(abc.ABCMeta): + + @abc.abstractmethod + def add(self, x, y): + pass + + add5 = functools.partialmethod(add, 5) + + self.assertTrue(Abstract.add.__isabstractmethod__) + self.assertTrue(Abstract.add5.__isabstractmethod__) + + for func in [self.A.static, self.A.cls, self.A.over_partial, self.A.nested, self.A.both]: + self.assertFalse(getattr(func, '__isabstractmethod__', False)) + + class TestUpdateWrapper(unittest.TestCase): def check_wrapper(self, wrapper, wrapped, @@ -1433,6 +1548,7 @@ TestPartialC, TestPartialPy, TestPartialCSubclass, + TestPartialMethod, TestUpdateWrapper, TestTotalOrdering, TestCmpToKeyC, diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -1192,6 +1192,8 @@ Library ------- +- Issue #4331: Added functools.partialmethod (Initial patch by Alon Horev) + - Issue #13461: Fix a crash in the TextIOWrapper.tell method on 64-bit platforms. Patch by Yogesh Chaudhari. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 07:55:00 2013 From: python-checkins at python.org (nick.coghlan) Date: Sun, 3 Nov 2013 07:55:00 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Close_=2319439=3A_execute_?= =?utf-8?q?embedding_tests_on_Windows?= Message-ID: <3dC7Dh2gwFz7Llq@mail.python.org> http://hg.python.org/cpython/rev/c8c6c007ade3 changeset: 86868:c8c6c007ade3 user: Nick Coghlan date: Sun Nov 03 16:54:46 2013 +1000 summary: Close #19439: execute embedding tests on Windows Patch by Zachary Ware files: Lib/test/test_capi.py | 78 ++++--- Misc/NEWS | 3 + PCbuild/_testembed.vcxproj | 161 +++++++++++++++++ PCbuild/_testembed.vcxproj.filters | 22 ++ PCbuild/pcbuild.sln | 18 + 5 files changed, 250 insertions(+), 32 deletions(-) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -9,7 +9,6 @@ import sys import time import unittest -import textwrap from test import support try: import _posixsubprocess @@ -219,15 +218,17 @@ self.assertEqual(_testcapi.argparsing("Hello", "World"), 1) - at unittest.skipIf( - sys.platform.startswith('win'), - "interpreter embedding tests aren't built under Windows") class EmbeddingTests(unittest.TestCase): - # XXX only tested under Unix checkouts - def setUp(self): basepath = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - self.test_exe = exe = os.path.join(basepath, "Modules", "_testembed") + exename = "_testembed" + if sys.platform.startswith("win"): + ext = ("_d" if "_d" in sys.executable else "") + ".exe" + exename += ext + exepath = os.path.dirname(sys.executable) + else: + exepath = os.path.join(basepath, "Modules") + self.test_exe = exe = os.path.join(exepath, exename) if not os.path.exists(exe): self.skipTest("%r doesn't exist" % exe) # This is needed otherwise we get a fatal error: @@ -260,6 +261,16 @@ print(out) print(err) + @staticmethod + def _get_default_pipe_encoding(): + rp, wp = os.pipe() + try: + with os.fdopen(wp, 'w') as w: + default_pipe_encoding = w.encoding + finally: + os.close(rp) + return default_pipe_encoding + def test_forced_io_encoding(self): # Checks forced configuration of embedded interpreter IO streams out, err = self.run_embedded_interpreter("forced_io_encoding") @@ -267,31 +278,34 @@ print() print(out) print(err) - expected_output = textwrap.dedent("""\ - --- Use defaults --- - Expected encoding: default - Expected errors: default - stdin: {0.__stdin__.encoding}:strict - stdout: {0.__stdout__.encoding}:strict - stderr: {0.__stderr__.encoding}:backslashreplace - --- Set errors only --- - Expected encoding: default - Expected errors: surrogateescape - stdin: {0.__stdin__.encoding}:surrogateescape - stdout: {0.__stdout__.encoding}:surrogateescape - stderr: {0.__stderr__.encoding}:backslashreplace - --- Set encoding only --- - Expected encoding: latin-1 - Expected errors: default - stdin: latin-1:strict - stdout: latin-1:strict - stderr: latin-1:backslashreplace - --- Set encoding and errors --- - Expected encoding: latin-1 - Expected errors: surrogateescape - stdin: latin-1:surrogateescape - stdout: latin-1:surrogateescape - stderr: latin-1:backslashreplace""").format(sys) + expected_stdin_encoding = sys.__stdin__.encoding + expected_pipe_encoding = self._get_default_pipe_encoding() + expected_output = os.linesep.join([ + "--- Use defaults ---", + "Expected encoding: default", + "Expected errors: default", + "stdin: {0}:strict", + "stdout: {1}:strict", + "stderr: {1}:backslashreplace", + "--- Set errors only ---", + "Expected encoding: default", + "Expected errors: surrogateescape", + "stdin: {0}:surrogateescape", + "stdout: {1}:surrogateescape", + "stderr: {1}:backslashreplace", + "--- Set encoding only ---", + "Expected encoding: latin-1", + "Expected errors: default", + "stdin: latin-1:strict", + "stdout: latin-1:strict", + "stderr: latin-1:backslashreplace", + "--- Set encoding and errors ---", + "Expected encoding: latin-1", + "Expected errors: surrogateescape", + "stdin: latin-1:surrogateescape", + "stdout: latin-1:surrogateescape", + "stderr: latin-1:backslashreplace"]).format(expected_stdin_encoding, + expected_pipe_encoding) # This is useful if we ever trip over odd platform behaviour self.maxDiff = None self.assertEqual(out.strip(), expected_output) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -153,6 +153,9 @@ Tests ----- +- Issue #19439: interpreter embedding tests are now executed on Windows + (Patch by Zachary Ware) + - Issue #19085: Added basic tests for all tkinter widget options. - Issue 19384: Fix test_py_compile for root user, patch by Claudiu Popa. diff --git a/PCbuild/_testembed.vcxproj b/PCbuild/_testembed.vcxproj new file mode 100644 --- /dev/null +++ b/PCbuild/_testembed.vcxproj @@ -0,0 +1,161 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {6DAC66D9-E703-4624-BE03-49112AB5AA62} + Win32Proj + _testembed + + + + Application + true + Unicode + + + Application + true + Unicode + + + Application + false + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + {cf7ac3d1-e2df-41d2-bea6-1e2556cdea26} + true + true + false + true + false + + + + + + \ No newline at end of file diff --git a/PCbuild/_testembed.vcxproj.filters b/PCbuild/_testembed.vcxproj.filters new file mode 100644 --- /dev/null +++ b/PCbuild/_testembed.vcxproj.filters @@ -0,0 +1,22 @@ +? + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -78,6 +78,8 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_overlapped", "_overlapped.vcxproj", "{EB6E69DD-04BF-4543-9B92-49FAABCEAC2E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testembed", "_testembed.vcxproj", "{6DAC66D9-E703-4624-BE03-49112AB5AA62}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -631,6 +633,22 @@ {254A0C05-6696-4B08-8CB2-EF7D533AEE01}.Release|Win32.Build.0 = Release|Win32 {254A0C05-6696-4B08-8CB2-EF7D533AEE01}.Release|x64.ActiveCfg = Release|x64 {254A0C05-6696-4B08-8CB2-EF7D533AEE01}.Release|x64.Build.0 = Release|x64 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.Debug|Win32.ActiveCfg = Debug|Win32 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.Debug|Win32.Build.0 = Debug|Win32 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.Debug|x64.ActiveCfg = Debug|x64 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.Debug|x64.Build.0 = Debug|x64 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.Release|Win32.ActiveCfg = Release|Win32 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.Release|Win32.Build.0 = Release|Win32 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.Release|x64.ActiveCfg = Release|x64 + {6DAC66D9-E703-4624-BE03-49112AB5AA62}.Release|x64.Build.0 = Release|x64 {EB6E69DD-04BF-4543-9B92-49FAABCEAC2E}.Debug|Win32.ActiveCfg = Debug|Win32 {EB6E69DD-04BF-4543-9B92-49FAABCEAC2E}.Debug|Win32.Build.0 = Debug|Win32 {EB6E69DD-04BF-4543-9B92-49FAABCEAC2E}.Debug|x64.ActiveCfg = Debug|x64 -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 08:02:03 2013 From: python-checkins at python.org (nick.coghlan) Date: Sun, 3 Nov 2013 08:02:03 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Close_=2319403=3A_make_con?= =?utf-8?q?textlib=2Eredirect=5Fstdout_reentrant?= Message-ID: <3dC7Nq607Gz7LmX@mail.python.org> http://hg.python.org/cpython/rev/87d49e2cdd34 changeset: 86869:87d49e2cdd34 user: Nick Coghlan date: Sun Nov 03 17:00:51 2013 +1000 summary: Close #19403: make contextlib.redirect_stdout reentrant files: Doc/library/contextlib.rst | 111 ++++++++++++++++-------- Lib/contextlib.py | 12 +- Lib/test/test_contextlib.py | 24 +++- Misc/NEWS | 2 + 4 files changed, 97 insertions(+), 52 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -651,22 +651,33 @@ but may also be used *inside* a :keyword:`with` statement that is already using the same context manager. -:class:`threading.RLock` is an example of a reentrant context manager, as is -:func:`suppress`. Here's a toy example of reentrant use (real world -examples of reentrancy are more likely to occur with objects like recursive -locks and are likely to be far more complicated than this example):: +:class:`threading.RLock` is an example of a reentrant context manager, as are +:func:`suppress` and :func:`redirect_stdout`. Here's a very simple example of +reentrant use:: - >>> from contextlib import suppress - >>> ignore_raised_exception = suppress(ZeroDivisionError) - >>> with ignore_raised_exception: - ... with ignore_raised_exception: - ... 1/0 - ... print("This line runs") - ... 1/0 - ... print("This is skipped") + >>> from contextlib import redirect_stdout + >>> from io import StringIO + >>> stream = StringIO() + >>> write_to_stream = redirect_stdout(stream) + >>> with write_to_stream: + ... print("This is written to the stream rather than stdout") + ... with write_to_stream: + ... print("This is also written to the stream") ... - This line runs - >>> # The second exception is also suppressed + >>> print("This is written directly to stdout") + This is written directly to stdout + >>> print(stream.getvalue()) + This is written to the stream rather than stdout + This is also written to the stream + +Real world examples of reentrancy are more likely to involve multiple +functions calling each other and hence be far more complicated than this +example. + +Note also that being reentrant is *not* the same thing as being thread safe. +:func:`redirect_stdout`, for example, is definitely not thread safe, as it +makes a global modification to the system state by binding :data:`sys.stdout` +to a different stream. .. _reusable-cms: @@ -681,32 +692,58 @@ will fail (or otherwise not work correctly) if the specific context manager instance has already been used in a containing with statement. -An example of a reusable context manager is :func:`redirect_stdout`:: +:class:`threading.Lock` is an example of a reusable, but not reentrant, +context manager (for a reentrant lock, it is necessary to use +:class:`threading.RLock` instead). - >>> from contextlib import redirect_stdout - >>> from io import StringIO - >>> f = StringIO() - >>> collect_output = redirect_stdout(f) - >>> with collect_output: - ... print("Collected") +Another example of a reusable, but not reentrant, context manager is +:class:`ExitStack`, as it invokes *all* currently registered callbacks +when leaving any with statement, regardless of where those callbacks +were added:: + + >>> from contextlib import ExitStack + >>> stack = ExitStack() + >>> with stack: + ... stack.callback(print, "Callback: from first context") + ... print("Leaving first context") ... - >>> print("Not collected") - Not collected - >>> with collect_output: - ... print("Also collected") + Leaving first context + Callback: from first context + >>> with stack: + ... stack.callback(print, "Callback: from second context") + ... print("Leaving second context") ... - >>> print(f.getvalue()) - Collected - Also collected + Leaving second context + Callback: from second context + >>> with stack: + ... stack.callback(print, "Callback: from outer context") + ... with stack: + ... stack.callback(print, "Callback: from inner context") + ... print("Leaving inner context") + ... print("Leaving outer context") + ... + Leaving inner context + Callback: from inner context + Callback: from outer context + Leaving outer context -However, this context manager is not reentrant, so attempting to reuse it -within a containing with statement fails: +As the output from the example shows, reusing a single stack object across +multiple with statements works correctly, but attempting to nest them +will cause the stack to be cleared at the end of the innermost with +statement, which is unlikely to be desirable behaviour. - >>> with collect_output: - ... # Nested reuse is not permitted - ... with collect_output: - ... pass +Using separate :class:`ExitStack` instances instead of reusing a single +instance avoids that problem:: + + >>> from contextlib import ExitStack + >>> with ExitStack() as outer_stack: + ... outer_stack.callback(print, "Callback: from outer context") + ... with ExitStack() as inner_stack: + ... inner_stack.callback(print, "Callback: from inner context") + ... print("Leaving inner context") + ... print("Leaving outer context") ... - Traceback (most recent call last): - ... - RuntimeError: Cannot reenter <...> + Leaving inner context + Callback: from inner context + Leaving outer context + Callback: from outer context diff --git a/Lib/contextlib.py b/Lib/contextlib.py --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -166,20 +166,16 @@ def __init__(self, new_target): self._new_target = new_target - self._old_target = self._sentinel = object() + # We use a list of old targets to make this CM re-entrant + self._old_targets = [] def __enter__(self): - if self._old_target is not self._sentinel: - raise RuntimeError("Cannot reenter {!r}".format(self)) - self._old_target = sys.stdout + self._old_targets.append(sys.stdout) sys.stdout = self._new_target return self._new_target def __exit__(self, exctype, excinst, exctb): - restore_stdout = self._old_target - self._old_target = self._sentinel - sys.stdout = restore_stdout - + sys.stdout = self._old_targets.pop() class suppress: diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -666,11 +666,18 @@ obj = redirect_stdout(None) self.assertEqual(obj.__doc__, cm_docstring) + def test_no_redirect_in_init(self): + orig_stdout = sys.stdout + redirect_stdout(None) + self.assertIs(sys.stdout, orig_stdout) + def test_redirect_to_string_io(self): f = io.StringIO() msg = "Consider an API like help(), which prints directly to stdout" + orig_stdout = sys.stdout with redirect_stdout(f): print(msg) + self.assertIs(sys.stdout, orig_stdout) s = f.getvalue().strip() self.assertEqual(s, msg) @@ -682,23 +689,26 @@ def test_cm_is_reusable(self): f = io.StringIO() write_to_f = redirect_stdout(f) + orig_stdout = sys.stdout with write_to_f: print("Hello", end=" ") with write_to_f: print("World!") + self.assertIs(sys.stdout, orig_stdout) s = f.getvalue() self.assertEqual(s, "Hello World!\n") - # If this is ever made reentrant, update the reusable-but-not-reentrant - # example at the end of the contextlib docs accordingly. - def test_nested_reentry_fails(self): + def test_cm_is_reentrant(self): f = io.StringIO() write_to_f = redirect_stdout(f) - with self.assertRaisesRegex(RuntimeError, "Cannot reenter"): + orig_stdout = sys.stdout + with write_to_f: + print("Hello", end=" ") with write_to_f: - print("Hello", end=" ") - with write_to_f: - print("World!") + print("World!") + self.assertIs(sys.stdout, orig_stdout) + s = f.getvalue() + self.assertEqual(s, "Hello World!\n") class TestSuppress(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,8 @@ Library ------- +- Issue #19403: contextlib.redirect_stdout is now reentrant + - Issue #19286: Directories in ``package_data`` are no longer added to the filelist, preventing failure outlined in the ticket. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:01:45 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 13:01:45 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE5NDY0?= =?utf-8?q?_Suppress_compiler_warnings_during_clean=2E_Patch_by_Zachary_Wa?= =?utf-8?b?cmUu?= Message-ID: <3dCG2d5p2gz7Ljp@mail.python.org> http://hg.python.org/cpython/rev/dbff708e393f changeset: 86870:dbff708e393f branch: 3.3 parent: 86862:69f8288056eb user: Tim Golden date: Sun Nov 03 11:58:02 2013 +0000 summary: Issue #19464 Suppress compiler warnings during clean. Patch by Zachary Ware. files: PCbuild/ssl.vcxproj | 16 ++++++++-------- 1 files changed, 8 insertions(+), 8 deletions(-) diff --git a/PCbuild/ssl.vcxproj b/PCbuild/ssl.vcxproj --- a/PCbuild/ssl.vcxproj +++ b/PCbuild/ssl.vcxproj @@ -122,7 +122,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -133,7 +133,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -144,7 +144,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -155,7 +155,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -166,7 +166,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -177,7 +177,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -188,7 +188,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) @@ -199,7 +199,7 @@ "$(PythonExe)" build_ssl.py Release $(Platform) -a - + echo OpenSSL must be cleaned manually if you want to rebuild it. $(NMakePreprocessorDefinitions) $(NMakeIncludeSearchPath) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:01:47 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 13:01:47 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2319464_Null_merge_with_3=2E3?= Message-ID: <3dCG2g0SwLz7Ljp@mail.python.org> http://hg.python.org/cpython/rev/6e592d972b86 changeset: 86871:6e592d972b86 parent: 86869:87d49e2cdd34 parent: 86870:dbff708e393f user: Tim Golden date: Sun Nov 03 11:59:28 2013 +0000 summary: Issue #19464 Null merge with 3.3 files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:15:27 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 13:15:27 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzYxNjA6?= =?utf-8?q?_The_bbox=28=29_method_of_Tkinter=2ESpinbox_now_returns_a_tuple?= =?utf-8?q?_of?= Message-ID: <3dCGLR2JyMz7Ljl@mail.python.org> http://hg.python.org/cpython/rev/91453ba40b30 changeset: 86872:91453ba40b30 branch: 2.7 parent: 86854:4a2afda8f187 user: Serhiy Storchaka date: Sun Nov 03 14:13:08 2013 +0200 summary: Issue #6160: The bbox() method of Tkinter.Spinbox now returns a tuple of integers instead of a string. Based on patch by Guilherme Polo. files: Lib/lib-tk/Tkinter.py | 2 +- Lib/lib-tk/test/test_tkinter/test_widgets.py | 12 ++++++++++ Misc/NEWS | 3 ++ 3 files changed, 16 insertions(+), 1 deletions(-) diff --git a/Lib/lib-tk/Tkinter.py b/Lib/lib-tk/Tkinter.py --- a/Lib/lib-tk/Tkinter.py +++ b/Lib/lib-tk/Tkinter.py @@ -3411,7 +3411,7 @@ bounding box may refer to a region outside the visible area of the window. """ - return self.tk.call(self._w, 'bbox', index) + return self._getints(self.tk.call(self._w, 'bbox', index)) or None def delete(self, first, last=None): """Delete one or more elements of the spinbox. diff --git a/Lib/lib-tk/test/test_tkinter/test_widgets.py b/Lib/lib-tk/test/test_tkinter/test_widgets.py --- a/Lib/lib-tk/test/test_tkinter/test_widgets.py +++ b/Lib/lib-tk/test/test_tkinter/test_widgets.py @@ -454,6 +454,18 @@ widget = self.create() self.checkBooleanParam(widget, 'wrap') + def test_bbox(self): + widget = self.create() + bbox = widget.bbox(0) + self.assertEqual(len(bbox), 4) + for item in bbox: + self.assertIsInstance(item, int) + + self.assertRaises(Tkinter.TclError, widget.bbox, 'noindex') + self.assertRaises(Tkinter.TclError, widget.bbox, None) + self.assertRaises(TypeError, widget.bbox) + self.assertRaises(TypeError, widget.bbox, 0, 1) + @add_standard_options(StandardOptionsTests) class TextTest(AbstractWidgetTest, unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ Library ------- +- Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of + integers instead of a string. Based on patch by Guilherme Polo. + - Issue #19286: Directories in ``package_data`` are no longer added to the filelist, preventing failure outlined in the ticket. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:15:28 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 13:15:28 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzYxNjA6?= =?utf-8?q?_The_bbox=28=29_method_of_tkinter=2ESpinbox_now_returns_a_tuple?= =?utf-8?q?_of?= Message-ID: <3dCGLS4B20z7LkH@mail.python.org> http://hg.python.org/cpython/rev/5bdbf2258563 changeset: 86873:5bdbf2258563 branch: 3.3 parent: 86870:dbff708e393f user: Serhiy Storchaka date: Sun Nov 03 14:13:34 2013 +0200 summary: Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of integers instead of a string. Based on patch by Guilherme Polo. files: Lib/tkinter/__init__.py | 2 +- Lib/tkinter/test/test_tkinter/test_widgets.py | 12 ++++++++++ Misc/NEWS | 3 ++ 3 files changed, 16 insertions(+), 1 deletions(-) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -3511,7 +3511,7 @@ bounding box may refer to a region outside the visible area of the window. """ - return self.tk.call(self._w, 'bbox', index) + return self._getints(self.tk.call(self._w, 'bbox', index)) or None def delete(self, first, last=None): """Delete one or more elements of the spinbox. diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -457,6 +457,18 @@ widget = self.create() self.checkBooleanParam(widget, 'wrap') + def test_bbox(self): + widget = self.create() + bbox = widget.bbox(0) + self.assertEqual(len(bbox), 4) + for item in bbox: + self.assertIsInstance(item, int) + + self.assertRaises(tkinter.TclError, widget.bbox, 'noindex') + self.assertRaises(tkinter.TclError, widget.bbox, None) + self.assertRaises(TypeError, widget.bbox) + self.assertRaises(TypeError, widget.bbox, 0, 1) + @add_standard_options(StandardOptionsTests) class TextTest(AbstractWidgetTest, unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Library ------- +- Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of + integers instead of a string. Based on patch by Guilherme Polo. + - Issue #19286: Directories in ``package_data`` are no longer added to the filelist, preventing failure outlined in the ticket. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:15:29 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 13:15:29 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=236160=3A_The_bbox=28=29_method_of_tkinter=2ESpin?= =?utf-8?q?box_now_returns_a_tuple_of?= Message-ID: <3dCGLT649Wz7LkL@mail.python.org> http://hg.python.org/cpython/rev/75d8b9136fa6 changeset: 86874:75d8b9136fa6 parent: 86871:6e592d972b86 parent: 86873:5bdbf2258563 user: Serhiy Storchaka date: Sun Nov 03 14:15:00 2013 +0200 summary: Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of integers instead of a string. Based on patch by Guilherme Polo. files: Lib/tkinter/__init__.py | 2 +- Lib/tkinter/test/test_tkinter/test_widgets.py | 12 ++++++++++ Misc/NEWS | 3 ++ 3 files changed, 16 insertions(+), 1 deletions(-) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -3472,7 +3472,7 @@ bounding box may refer to a region outside the visible area of the window. """ - return self.tk.call(self._w, 'bbox', index) + return self._getints(self.tk.call(self._w, 'bbox', index)) or None def delete(self, first, last=None): """Delete one or more elements of the spinbox. diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -457,6 +457,18 @@ widget = self.create() self.checkBooleanParam(widget, 'wrap') + def test_bbox(self): + widget = self.create() + bbox = widget.bbox(0) + self.assertEqual(len(bbox), 4) + for item in bbox: + self.assertIsInstance(item, int) + + self.assertRaises(tkinter.TclError, widget.bbox, 'noindex') + self.assertRaises(tkinter.TclError, widget.bbox, None) + self.assertRaises(TypeError, widget.bbox) + self.assertRaises(TypeError, widget.bbox, 0, 1) + @add_standard_options(StandardOptionsTests) class TextTest(AbstractWidgetTest, unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,9 @@ Library ------- +- Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of + integers instead of a string. Based on patch by Guilherme Polo. + - Issue #19403: contextlib.redirect_stdout is now reentrant - Issue #19286: Directories in ``package_data`` are no longer added to -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:35:15 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 13:35:15 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzYxNTc6?= =?utf-8?q?_Fixed_Tkinter=2EText=2Edebug=28=29=2E__Original_patch_by_Guilh?= =?utf-8?q?erme_Polo=2E?= Message-ID: <3dCGnH0Phlz7LjT@mail.python.org> http://hg.python.org/cpython/rev/b3178d03871b changeset: 86875:b3178d03871b branch: 2.7 parent: 86872:91453ba40b30 user: Serhiy Storchaka date: Sun Nov 03 14:28:29 2013 +0200 summary: Issue #6157: Fixed Tkinter.Text.debug(). Original patch by Guilherme Polo. files: Lib/lib-tk/Tkinter.py | 5 ++- Lib/lib-tk/test/test_tkinter/test_text.py | 11 ++++++++ Lib/lib-tk/test/test_tkinter/test_widgets.py | 13 ++++++++++ Misc/NEWS | 2 + 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Lib/lib-tk/Tkinter.py b/Lib/lib-tk/Tkinter.py --- a/Lib/lib-tk/Tkinter.py +++ b/Lib/lib-tk/Tkinter.py @@ -2908,8 +2908,9 @@ def debug(self, boolean=None): """Turn on the internal consistency checks of the B-Tree inside the text widget according to BOOLEAN.""" - return self.tk.getboolean(self.tk.call( - self._w, 'debug', boolean)) + if boolean is None: + return self.tk.call(self._w, 'debug') + self.tk.call(self._w, 'debug', boolean) def delete(self, index1, index2=None): """Delete the characters between INDEX1 and INDEX2 (not included).""" self.tk.call(self._w, 'delete', index1, index2) diff --git a/Lib/lib-tk/test/test_tkinter/test_text.py b/Lib/lib-tk/test/test_tkinter/test_text.py --- a/Lib/lib-tk/test/test_tkinter/test_text.py +++ b/Lib/lib-tk/test/test_tkinter/test_text.py @@ -14,6 +14,17 @@ def tearDown(self): self.text.destroy() + def test_debug(self): + text = self.text + olddebug = text.debug() + try: + text.debug(0) + self.assertEqual(text.debug(), 0) + text.debug(1) + self.assertEqual(text.debug(), 1) + finally: + text.debug(olddebug) + self.assertEqual(text.debug(), olddebug) def test_search(self): text = self.text diff --git a/Lib/lib-tk/test/test_tkinter/test_widgets.py b/Lib/lib-tk/test/test_tkinter/test_widgets.py --- a/Lib/lib-tk/test/test_tkinter/test_widgets.py +++ b/Lib/lib-tk/test/test_tkinter/test_widgets.py @@ -607,6 +607,19 @@ else: self.checkEnumParam(widget, 'wrap', 'char', 'none', 'word') + def test_bbox(self): + widget = self.create() + bbox = widget.bbox('1.1') + self.assertEqual(len(bbox), 4) + for item in bbox: + self.assertIsInstance(item, int) + + self.assertIsNone(widget.bbox('end')) + self.assertRaises(Tkinter.TclError, widget.bbox, 'noindex') + self.assertRaises(Tkinter.TclError, widget.bbox, None) + self.assertRaises(Tkinter.TclError, widget.bbox) + self.assertRaises(Tkinter.TclError, widget.bbox, '1.1', 'end') + @add_standard_options(PixelSizeTests, StandardOptionsTests) class CanvasTest(AbstractWidgetTest, unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,8 @@ Library ------- +- Issue #6157: Fixed Tkinter.Text.debug(). Original patch by Guilherme Polo. + - Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of integers instead of a string. Based on patch by Guilherme Polo. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:35:16 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 13:35:16 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzYxNTc6?= =?utf-8?q?_Fixed_tkinter=2EText=2Edebug=28=29=2E__Original_patch_by_Guilh?= =?utf-8?q?erme_Polo=2E?= Message-ID: <3dCGnJ2K7Wz7LjT@mail.python.org> http://hg.python.org/cpython/rev/3f5e35b766ac changeset: 86876:3f5e35b766ac branch: 3.3 parent: 86873:5bdbf2258563 user: Serhiy Storchaka date: Sun Nov 03 14:29:35 2013 +0200 summary: Issue #6157: Fixed tkinter.Text.debug(). Original patch by Guilherme Polo. files: Lib/tkinter/__init__.py | 5 ++- Lib/tkinter/test/test_tkinter/test_text.py | 11 ++++++++ Lib/tkinter/test/test_tkinter/test_widgets.py | 13 ++++++++++ Misc/NEWS | 2 + 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -2990,8 +2990,9 @@ def debug(self, boolean=None): """Turn on the internal consistency checks of the B-Tree inside the text widget according to BOOLEAN.""" - return self.tk.getboolean(self.tk.call( - self._w, 'debug', boolean)) + if boolean is None: + return self.tk.call(self._w, 'debug') + self.tk.call(self._w, 'debug', boolean) def delete(self, index1, index2=None): """Delete the characters between INDEX1 and INDEX2 (not included).""" self.tk.call(self._w, 'delete', index1, index2) diff --git a/Lib/tkinter/test/test_tkinter/test_text.py b/Lib/tkinter/test/test_tkinter/test_text.py --- a/Lib/tkinter/test/test_tkinter/test_text.py +++ b/Lib/tkinter/test/test_tkinter/test_text.py @@ -14,6 +14,17 @@ def tearDown(self): self.text.destroy() + def test_debug(self): + text = self.text + olddebug = text.debug() + try: + text.debug(0) + self.assertEqual(text.debug(), 0) + text.debug(1) + self.assertEqual(text.debug(), 1) + finally: + text.debug(olddebug) + self.assertEqual(text.debug(), olddebug) def test_search(self): text = self.text diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -610,6 +610,19 @@ else: self.checkEnumParam(widget, 'wrap', 'char', 'none', 'word') + def test_bbox(self): + widget = self.create() + bbox = widget.bbox('1.1') + self.assertEqual(len(bbox), 4) + for item in bbox: + self.assertIsInstance(item, int) + + self.assertIsNone(widget.bbox('end')) + self.assertRaises(tkinter.TclError, widget.bbox, 'noindex') + self.assertRaises(tkinter.TclError, widget.bbox, None) + self.assertRaises(tkinter.TclError, widget.bbox) + self.assertRaises(tkinter.TclError, widget.bbox, '1.1', 'end') + @add_standard_options(PixelSizeTests, StandardOptionsTests) class CanvasTest(AbstractWidgetTest, unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,8 @@ Library ------- +- Issue #6157: Fixed tkinter.Text.debug(). Original patch by Guilherme Polo. + - Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of integers instead of a string. Based on patch by Guilherme Polo. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:35:17 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 13:35:17 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?b?KTogSXNzdWUgIzYxNTc6IEZpeGVkIHRraW50ZXIuVGV4dC5kZWJ1ZygpLiAg?= =?utf-8?q?tkinter=2EText=2Ebbox=28=29_now_raises?= Message-ID: <3dCGnK5LXjz7Ljl@mail.python.org> http://hg.python.org/cpython/rev/c40b573c9f7a changeset: 86877:c40b573c9f7a parent: 86874:75d8b9136fa6 parent: 86876:3f5e35b766ac user: Serhiy Storchaka date: Sun Nov 03 14:34:25 2013 +0200 summary: Issue #6157: Fixed tkinter.Text.debug(). tkinter.Text.bbox() now raises TypeError instead of TclError on wrong number of arguments. Original patch by Guilherme Polo. files: Lib/tkinter/__init__.py | 11 ++++--- Lib/tkinter/test/test_tkinter/test_text.py | 11 ++++++++ Lib/tkinter/test/test_tkinter/test_widgets.py | 13 ++++++++++ Misc/NEWS | 4 +++ 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -2911,11 +2911,11 @@ """ Widget.__init__(self, master, 'text', cnf, kw) - def bbox(self, *args): + def bbox(self, index): """Return a tuple of (x,y,width,height) which gives the bounding - box of the visible part of the character at the index in ARGS.""" + box of the visible part of the character at the given index.""" return self._getints( - self.tk.call((self._w, 'bbox') + args)) or None + self.tk.call(self._w, 'bbox', index)) or None def tk_textSelectTo(self, index): self.tk.call('tk_textSelectTo', self._w, index) def tk_textBackspace(self): @@ -2951,8 +2951,9 @@ def debug(self, boolean=None): """Turn on the internal consistency checks of the B-Tree inside the text widget according to BOOLEAN.""" - return self.tk.getboolean(self.tk.call( - self._w, 'debug', boolean)) + if boolean is None: + return self.tk.call(self._w, 'debug') + self.tk.call(self._w, 'debug', boolean) def delete(self, index1, index2=None): """Delete the characters between INDEX1 and INDEX2 (not included).""" self.tk.call(self._w, 'delete', index1, index2) diff --git a/Lib/tkinter/test/test_tkinter/test_text.py b/Lib/tkinter/test/test_tkinter/test_text.py --- a/Lib/tkinter/test/test_tkinter/test_text.py +++ b/Lib/tkinter/test/test_tkinter/test_text.py @@ -14,6 +14,17 @@ def tearDown(self): self.text.destroy() + def test_debug(self): + text = self.text + olddebug = text.debug() + try: + text.debug(0) + self.assertEqual(text.debug(), 0) + text.debug(1) + self.assertEqual(text.debug(), 1) + finally: + text.debug(olddebug) + self.assertEqual(text.debug(), olddebug) def test_search(self): text = self.text diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -610,6 +610,19 @@ else: self.checkEnumParam(widget, 'wrap', 'char', 'none', 'word') + def test_bbox(self): + widget = self.create() + bbox = widget.bbox('1.1') + self.assertEqual(len(bbox), 4) + for item in bbox: + self.assertIsInstance(item, int) + + self.assertIsNone(widget.bbox('end')) + self.assertRaises(tkinter.TclError, widget.bbox, 'noindex') + self.assertRaises(tkinter.TclError, widget.bbox, None) + self.assertRaises(TypeError, widget.bbox) + self.assertRaises(TypeError, widget.bbox, '1.1', 'end') + @add_standard_options(PixelSizeTests, StandardOptionsTests) class CanvasTest(AbstractWidgetTest, unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,10 @@ Library ------- +- Issue #6157: Fixed tkinter.Text.debug(). tkinter.Text.bbox() now raises + TypeError instead of TclError on wrong number of arguments. Original patch + by Guilherme Polo. + - Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of integers instead of a string. Based on patch by Guilherme Polo. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 13:51:00 2013 From: python-checkins at python.org (victor.stinner) Date: Sun, 3 Nov 2013 13:51:00 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_PEP_454=3A_remove_enable/disb?= =?utf-8?q?le/is=5Fenabled=28=29_functions?= Message-ID: <3dCH7S5DMfz7LjT@mail.python.org> http://hg.python.org/peps/rev/5a098b7516d9 changeset: 5247:5a098b7516d9 user: Victor Stinner date: Sun Nov 03 13:50:52 2013 +0100 summary: PEP 454: remove enable/disble/is_enabled() functions files: pep-0454.txt | 100 ++++++++++++-------------------------- 1 files changed, 31 insertions(+), 69 deletions(-) diff --git a/pep-0454.txt b/pep-0454.txt --- a/pep-0454.txt +++ b/pep-0454.txt @@ -64,13 +64,12 @@ number: total size, number and average size of allocated memory blocks * Computed differences between two snapshots to detect memory leaks -The API of the tracemalloc module is similar to the API of the -faulthandler module: ``enable()``, ``disable()`` and ``is_enabled()`` -functions, an environment variable (``PYTHONFAULTHANDLER`` and -``PYTHONTRACEMALLOC``), and a ``-X`` command line option (``-X -faulthandler`` and ``-X tracemalloc``). See the -`documentation of the faulthandler module -`_. +The API of the tracemalloc module is similar to the API of the faulthandler +module: ``enable()`` / ``start()``, ``disable()`` / ``stop()`` and +``is_enabled()`` / ``is_tracing()`` functions, an environment variable +(``PYTHONFAULTHANDLER`` and ``PYTHONTRACEMALLOC``), and a ``-X`` command line +option (``-X faulthandler`` and ``-X tracemalloc``). See the `documentation of +the faulthandler module `_. The idea of tracing memory allocations is not new. It was first implemented in the PySizer project in 2005. PySizer was implemented @@ -79,7 +78,7 @@ on CPython adds a overhead on performances and memory footprint, even if the PySizer was not used. tracemalloc attachs a traceback to the underlying layer, to memory blocks, and has no overhead when the module -is disabled. +is not tracing memory allocations. The tracemalloc module has been written for CPython. Other implementations of Python may not be able to provide it. @@ -89,9 +88,9 @@ === To trace most memory blocks allocated by Python, the module should be -enabled as early as possible by setting the ``PYTHONTRACEMALLOC`` +started as early as possible by setting the ``PYTHONTRACEMALLOC`` environment variable to ``1``, or by using ``-X tracemalloc`` command -line option. The ``tracemalloc.enable()`` function can be called at +line option. The ``tracemalloc.start()`` function can be called at runtime to start tracing Python memory allocations. By default, a trace of an allocated memory block only stores the most @@ -112,28 +111,7 @@ Clear traces of memory blocks allocated by Python. - See also ``disable()``. - - -``disable()`` function: - - Disable temporarily tracing new Python memory allocations, - deallocations are still traced. The change is process-wide, tracing - new Python memory allocations is disabled in all threads. Call - ``enable()`` to reenable tracing new Python memory allocations. - - Filters can be used to not trace memory allocations in some files: - use the ``add_filter()`` function. - - See also ``enable()`` and ``is_enabled()`` functions. - - -``enable()`` function: - - Reenable tracing Python memory allocations if was disabled by te - ``disable()`` method. - - See also ``is_enabled()`` functions. + See also ``stop()``. ``get_traced_memory()`` function: @@ -148,26 +126,23 @@ store traces of memory blocks. Return an ``int``. -``is_enabled()`` function: - - ``True`` if the ``tracemalloc`` module is enabled Python memory - allocations, ``False`` if the module is disabled. - - The ``tracemalloc`` module only traces new allocations if - ``is_tracing()`` and ``is_enabled()`` are ``True``. - - See also ``enable()`` and ``disable()`` functions. - - ``is_tracing()`` function: ``True`` if the ``tracemalloc`` module is tracing Python memory allocations, ``False`` otherwise. - The ``tracemalloc`` module only traces new allocations if - ``is_tracing()`` and ``is_enabled()`` are ``True``. + See also ``start()`` and ``stop()`` functions. - See also ``start()`` and ``stop()`` functions. + +``start()`` function: + + Start tracing Python memory allocations. + + The function installs hooks on Python memory allocators. These hooks + have important overhead in term of performances and memory usage: + see `Filter functions`_ to limit the overhead. + + See also ``stop()`` and ``is_tracing()`` functions. ``stop()`` function: @@ -179,21 +154,9 @@ overhead of the module becomes null. Call ``get_traces()`` or ``take_snapshot()`` function to get traces - before clearing them. Use ``disable()`` to disable tracing - temporarily. + before clearing them. - See also ``enable()`` and ``is_enabled()`` functions. - - -``start()`` function: - - Start tracing Python memory allocations. - - The function installs hooks on Python memory allocators. These hooks - have important overhead in term of performances and memory usage: - see `Filter functions`_ to limit the overhead. - - See also ``disable()`` and ``is_tracing()`` functions. + See also ``start()`` and ``is_tracing()`` functions. ``take_snapshot()`` function: @@ -201,8 +164,8 @@ Take a snapshot of traces of memory blocks allocated by Python using the ``get_traces()`` function. Return a new ``Snapshot`` instance. - The ``tracemalloc`` module must be enabled to take a snapshot, see - the the ``enable()`` function. + The ``tracemalloc`` module must be tracing memory allocations to take a + snapshot, see the the ``start()`` function. See also ``get_traces()`` and ``get_object_traceback()`` functions. @@ -235,8 +198,8 @@ Get the traceback where the Python object *obj* was allocated. Return a tuple of ``(filename: str, lineno: int)`` tuples. - Return ``None`` if the ``tracemalloc`` module is disabled or did not - trace the allocation of the object. + Return ``None`` if the ``tracemalloc`` module is not tracing memory + allocations or did not trace the allocation of the object. See also ``gc.get_referrers()`` and ``sys.getsizeof()`` functions. @@ -258,8 +221,8 @@ ``(filename: str, lineno: int)`` tuples. The list of traces do not include memory blocks allocated before the - ``tracemalloc`` module was enabled nor memory blocks ignored by - filters (see ``get_filters()``). + ``tracemalloc`` module started to trace memory allocations nor memory + blocks ignored by filters (see ``get_filters()``). The list is not sorted. Take a snapshot using ``take_snapshot()`` and use the ``Snapshot.statistics()`` method to get a sorted list of @@ -268,7 +231,8 @@ Tracebacks of traces are limited to ``traceback_limit`` frames. Use ``set_traceback_limit()`` to store more frames. - Return an empty list if the ``tracemalloc`` module is disabled. + Return an empty list if the ``tracemalloc`` module is not tracing memory + allocations. See also ``take_snapshot()`` and ``get_object_traceback()`` functions. @@ -308,8 +272,6 @@ By default, there is one exclusive filter to ignore Python memory blocks allocated by the ``tracemalloc`` module. -Tracing can be also be disabled temporarily using the ``disable()`` function. - Use the ``get_tracemalloc_memory()`` function to measure the memory usage. See also the ``set_traceback_limit()`` function to configure how many frames are stored. -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Sun Nov 3 13:56:27 2013 From: python-checkins at python.org (victor.stinner) Date: Sun, 3 Nov 2013 13:56:27 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2319424=3A_Fix_a_co?= =?utf-8?q?mpiler_warning_on_comparing_signed/unsigned_size=5Ft?= Message-ID: <3dCHFl72Phz7Ljw@mail.python.org> http://hg.python.org/cpython/rev/2ed8d500e113 changeset: 86878:2ed8d500e113 user: Victor Stinner date: Sun Nov 03 13:53:12 2013 +0100 summary: Issue #19424: Fix a compiler warning on comparing signed/unsigned size_t Patch written by Zachary Ware. files: Objects/unicodeobject.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -10581,7 +10581,7 @@ kind = PyUnicode_KIND(uni); if (kind == PyUnicode_1BYTE_KIND) { const void *data = PyUnicode_1BYTE_DATA(uni); - Py_ssize_t len1 = PyUnicode_GET_LENGTH(uni); + size_t len1 = (size_t)PyUnicode_GET_LENGTH(uni); size_t len, len2 = strlen(str); int cmp; -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 15:23:15 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 15:23:15 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzEwMTk3?= =?utf-8?q?_Rework_subprocess=2Eget=5Bstatus=5Doutput_to_use_subprocess?= Message-ID: <3dCK9v463hz7LjT@mail.python.org> http://hg.python.org/cpython/rev/c34e163c0086 changeset: 86879:c34e163c0086 branch: 3.3 parent: 86870:dbff708e393f user: Tim Golden date: Sun Nov 03 12:53:17 2013 +0000 summary: Issue #10197 Rework subprocess.get[status]output to use subprocess functionality and thus to work on Windows. Patch by Nick Coghlan. files: Lib/subprocess.py | 24 +++++++++--------------- Lib/test/test_subprocess.py | 11 ++--------- Misc/NEWS | 3 +++ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -681,21 +681,15 @@ >>> subprocess.getstatusoutput('/bin/junk') (256, 'sh: /bin/junk: not found') """ - with os.popen('{ ' + cmd + '; } 2>&1', 'r') as pipe: - try: - text = pipe.read() - sts = pipe.close() - except: - process = pipe._proc - process.kill() - process.wait() - raise - if sts is None: - sts = 0 - if text[-1:] == '\n': - text = text[:-1] - return sts, text - + try: + data = check_output(cmd, shell=True, universal_newlines=True, stderr=STDOUT) + status = 0 + except CalledProcessError as ex: + data = ex.output + status = ex.returncode + if data[-1:] == '\n': + data = data[:-1] + return status, data def getoutput(cmd): """Return output (stdout or stderr) of executing cmd in a shell. diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -2133,13 +2133,6 @@ def test_terminate_dead(self): self._kill_dead_process('terminate') - -# The module says: -# "NB This only works (and is only relevant) for UNIX." -# -# Actually, getoutput should work on any platform with an os.popen, but -# I'll take the comment as given, and skip this suite. - at unittest.skipUnless(os.name == 'posix', "only relevant for UNIX") class CommandTests(unittest.TestCase): def test_getoutput(self): self.assertEqual(subprocess.getoutput('echo xyzzy'), 'xyzzy') @@ -2153,8 +2146,8 @@ try: dir = tempfile.mkdtemp() name = os.path.join(dir, "foo") - - status, output = subprocess.getstatusoutput('cat ' + name) + status, output = subprocess.getstatusoutput( + ("type " if mswindows else "cat ") + name) self.assertNotEqual(status, 0) finally: if dir is not None: diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Library ------- +- Issue #10197: Rework subprocess.get[status]output to use subprocess + functionality and thus to work on Windows. Patch by Nick Coghlan. + - Issue #19286: Directories in ``package_data`` are no longer added to the filelist, preventing failure outlined in the ticket. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 15:23:16 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 15:23:16 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2310197_Rework_subprocess=2Eget=5Bstatus=5Doutput?= =?utf-8?q?_to_use_subprocess?= Message-ID: <3dCK9w6jfsz7Ljr@mail.python.org> http://hg.python.org/cpython/rev/05ce1bd1a4c2 changeset: 86880:05ce1bd1a4c2 parent: 86871:6e592d972b86 parent: 86879:c34e163c0086 user: Tim Golden date: Sun Nov 03 12:55:51 2013 +0000 summary: Issue #10197 Rework subprocess.get[status]output to use subprocess functionality and thus to work on Windows. Patch by Nick Coghlan. files: Lib/subprocess.py | 24 +++++++++--------------- Lib/test/test_subprocess.py | 11 ++--------- Misc/NEWS | 3 +++ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -700,21 +700,15 @@ >>> subprocess.getstatusoutput('/bin/junk') (256, 'sh: /bin/junk: not found') """ - with os.popen('{ ' + cmd + '; } 2>&1', 'r') as pipe: - try: - text = pipe.read() - sts = pipe.close() - except: - process = pipe._proc - process.kill() - process.wait() - raise - if sts is None: - sts = 0 - if text[-1:] == '\n': - text = text[:-1] - return sts, text - + try: + data = check_output(cmd, shell=True, universal_newlines=True, stderr=STDOUT) + status = 0 + except CalledProcessError as ex: + data = ex.output + status = ex.returncode + if data[-1:] == '\n': + data = data[:-1] + return status, data def getoutput(cmd): """Return output (stdout or stderr) of executing cmd in a shell. diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -2158,13 +2158,6 @@ def test_terminate_dead(self): self._kill_dead_process('terminate') - -# The module says: -# "NB This only works (and is only relevant) for UNIX." -# -# Actually, getoutput should work on any platform with an os.popen, but -# I'll take the comment as given, and skip this suite. - at unittest.skipUnless(os.name == 'posix', "only relevant for UNIX") class CommandTests(unittest.TestCase): def test_getoutput(self): self.assertEqual(subprocess.getoutput('echo xyzzy'), 'xyzzy') @@ -2178,8 +2171,8 @@ try: dir = tempfile.mkdtemp() name = os.path.join(dir, "foo") - - status, output = subprocess.getstatusoutput('cat ' + name) + status, output = subprocess.getstatusoutput( + ("type " if mswindows else "cat ") + name) self.assertNotEqual(status, 0) finally: if dir is not None: diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,9 @@ Library ------- +- Issue #10197: Rework subprocess.get[status]output to use subprocess + functionality and thus to work on Windows. Patch by Nick Coghlan + - Issue #19403: contextlib.redirect_stdout is now reentrant - Issue #19286: Directories in ``package_data`` are no longer added to -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 15:23:18 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 15:23:18 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAobWVyZ2UgMy4zIC0+IDMuMyk6?= =?utf-8?q?_Issue_=2310197=3A_merge_heads?= Message-ID: <3dCK9y2HjLz7Lk4@mail.python.org> http://hg.python.org/cpython/rev/b6efaa97ee0e changeset: 86881:b6efaa97ee0e branch: 3.3 parent: 86876:3f5e35b766ac parent: 86879:c34e163c0086 user: Tim Golden date: Sun Nov 03 14:20:23 2013 +0000 summary: Issue #10197: merge heads files: Lib/subprocess.py | 24 +++++++++--------------- Lib/test/test_subprocess.py | 11 ++--------- Misc/NEWS | 3 +++ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -681,21 +681,15 @@ >>> subprocess.getstatusoutput('/bin/junk') (256, 'sh: /bin/junk: not found') """ - with os.popen('{ ' + cmd + '; } 2>&1', 'r') as pipe: - try: - text = pipe.read() - sts = pipe.close() - except: - process = pipe._proc - process.kill() - process.wait() - raise - if sts is None: - sts = 0 - if text[-1:] == '\n': - text = text[:-1] - return sts, text - + try: + data = check_output(cmd, shell=True, universal_newlines=True, stderr=STDOUT) + status = 0 + except CalledProcessError as ex: + data = ex.output + status = ex.returncode + if data[-1:] == '\n': + data = data[:-1] + return status, data def getoutput(cmd): """Return output (stdout or stderr) of executing cmd in a shell. diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -2133,13 +2133,6 @@ def test_terminate_dead(self): self._kill_dead_process('terminate') - -# The module says: -# "NB This only works (and is only relevant) for UNIX." -# -# Actually, getoutput should work on any platform with an os.popen, but -# I'll take the comment as given, and skip this suite. - at unittest.skipUnless(os.name == 'posix', "only relevant for UNIX") class CommandTests(unittest.TestCase): def test_getoutput(self): self.assertEqual(subprocess.getoutput('echo xyzzy'), 'xyzzy') @@ -2153,8 +2146,8 @@ try: dir = tempfile.mkdtemp() name = os.path.join(dir, "foo") - - status, output = subprocess.getstatusoutput('cat ' + name) + status, output = subprocess.getstatusoutput( + ("type " if mswindows else "cat ") + name) self.assertNotEqual(status, 0) finally: if dir is not None: diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -16,6 +16,9 @@ - Issue #6157: Fixed tkinter.Text.debug(). Original patch by Guilherme Polo. - Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of + +- Issue #10197: Rework subprocess.get[status]output to use subprocess + functionality and thus to work on Windows. Patch by Nick Coghlan. integers instead of a string. Based on patch by Guilherme Polo. - Issue #19286: Directories in ``package_data`` are no longer added to -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 15:23:19 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 15:23:19 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2310197=3A_merge_heads?= Message-ID: <3dCK9z4tS1z7Lk3@mail.python.org> http://hg.python.org/cpython/rev/28a0ae3dcb16 changeset: 86882:28a0ae3dcb16 parent: 86878:2ed8d500e113 parent: 86880:05ce1bd1a4c2 user: Tim Golden date: Sun Nov 03 14:21:29 2013 +0000 summary: Issue #10197: merge heads files: Lib/subprocess.py | 24 +++++++++--------------- Lib/test/test_subprocess.py | 11 ++--------- Misc/NEWS | 3 +++ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -700,21 +700,15 @@ >>> subprocess.getstatusoutput('/bin/junk') (256, 'sh: /bin/junk: not found') """ - with os.popen('{ ' + cmd + '; } 2>&1', 'r') as pipe: - try: - text = pipe.read() - sts = pipe.close() - except: - process = pipe._proc - process.kill() - process.wait() - raise - if sts is None: - sts = 0 - if text[-1:] == '\n': - text = text[:-1] - return sts, text - + try: + data = check_output(cmd, shell=True, universal_newlines=True, stderr=STDOUT) + status = 0 + except CalledProcessError as ex: + data = ex.output + status = ex.returncode + if data[-1:] == '\n': + data = data[:-1] + return status, data def getoutput(cmd): """Return output (stdout or stderr) of executing cmd in a shell. diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -2158,13 +2158,6 @@ def test_terminate_dead(self): self._kill_dead_process('terminate') - -# The module says: -# "NB This only works (and is only relevant) for UNIX." -# -# Actually, getoutput should work on any platform with an os.popen, but -# I'll take the comment as given, and skip this suite. - at unittest.skipUnless(os.name == 'posix', "only relevant for UNIX") class CommandTests(unittest.TestCase): def test_getoutput(self): self.assertEqual(subprocess.getoutput('echo xyzzy'), 'xyzzy') @@ -2178,8 +2171,8 @@ try: dir = tempfile.mkdtemp() name = os.path.join(dir, "foo") - - status, output = subprocess.getstatusoutput('cat ' + name) + status, output = subprocess.getstatusoutput( + ("type " if mswindows else "cat ") + name) self.assertNotEqual(status, 0) finally: if dir is not None: diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -35,6 +35,9 @@ TypeError instead of TclError on wrong number of arguments. Original patch by Guilherme Polo. +- Issue #10197: Rework subprocess.get[status]output to use subprocess + functionality and thus to work on Windows. Patch by Nick Coghlan + - Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of integers instead of a string. Based on patch by Guilherme Polo. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 15:23:21 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 15:23:21 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2310197=3A_merge_3=2E3?= Message-ID: <3dCKB10Htcz7LkD@mail.python.org> http://hg.python.org/cpython/rev/fe828884a077 changeset: 86883:fe828884a077 parent: 86882:28a0ae3dcb16 parent: 86881:b6efaa97ee0e user: Tim Golden date: Sun Nov 03 14:22:14 2013 +0000 summary: Issue #10197: merge 3.3 files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 17:26:08 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 17:26:08 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fixed_pixels_rounding_for_last_Tk_patchlevels=2E?= Message-ID: <3dCMvh2RwTz7Ljj@mail.python.org> http://hg.python.org/cpython/rev/a34889a30d52 changeset: 86884:a34889a30d52 branch: 2.7 parent: 86875:b3178d03871b user: Serhiy Storchaka date: Sun Nov 03 18:24:04 2013 +0200 summary: Issue #19085: Fixed pixels rounding for last Tk patchlevels. files: Lib/lib-tk/test/widget_tests.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/lib-tk/test/widget_tests.py b/Lib/lib-tk/test/widget_tests.py --- a/Lib/lib-tk/test/widget_tests.py +++ b/Lib/lib-tk/test/widget_tests.py @@ -14,7 +14,7 @@ _sentinel = object() class AbstractWidgetTest(object): - _conv_pixels = staticmethod(int_round if tcl_version[:2] != (8, 5) else int) + _conv_pixels = staticmethod(int_round) _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 17:26:09 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 17:26:09 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fixed_pixels_rounding_for_last_Tk_patchlevels=2E?= Message-ID: <3dCMvj4DsWz7Ljm@mail.python.org> http://hg.python.org/cpython/rev/dfdf47a9aad4 changeset: 86885:dfdf47a9aad4 branch: 3.3 parent: 86881:b6efaa97ee0e user: Serhiy Storchaka date: Sun Nov 03 18:24:31 2013 +0200 summary: Issue #19085: Fixed pixels rounding for last Tk patchlevels. files: Lib/tkinter/test/widget_tests.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py --- a/Lib/tkinter/test/widget_tests.py +++ b/Lib/tkinter/test/widget_tests.py @@ -11,7 +11,7 @@ _sentinel = object() class AbstractWidgetTest: - _conv_pixels = round if tcl_version[:2] != (8, 5) else int + _conv_pixels = round _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 17:26:10 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 17:26:10 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2319085=3A_Fixed_pixels_rounding_for_last_Tk_patc?= =?utf-8?q?hlevels=2E?= Message-ID: <3dCMvk5w5nz7Lk3@mail.python.org> http://hg.python.org/cpython/rev/e7be7aceab77 changeset: 86886:e7be7aceab77 parent: 86883:fe828884a077 parent: 86885:dfdf47a9aad4 user: Serhiy Storchaka date: Sun Nov 03 18:25:17 2013 +0200 summary: Issue #19085: Fixed pixels rounding for last Tk patchlevels. files: Lib/tkinter/test/widget_tests.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py --- a/Lib/tkinter/test/widget_tests.py +++ b/Lib/tkinter/test/widget_tests.py @@ -11,7 +11,7 @@ _sentinel = object() class AbstractWidgetTest: - _conv_pixels = round if tcl_version[:2] != (8, 5) else int + _conv_pixels = round _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 18:33:42 2013 From: python-checkins at python.org (r.david.murray) Date: Sun, 3 Nov 2013 18:33:42 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogIzE5NDg1OiBjbGFy?= =?utf-8?q?ify_get=5Fparam_example=2E?= Message-ID: <3dCPPf5WgFz7LjW@mail.python.org> http://hg.python.org/cpython/rev/c574951deadd changeset: 86887:c574951deadd branch: 3.3 parent: 86885:dfdf47a9aad4 user: R David Murray date: Sun Nov 03 12:23:23 2013 -0500 summary: #19485: clarify get_param example. Patch by Vajrasky Kok. files: Lib/email/message.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/email/message.py b/Lib/email/message.py --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -636,7 +636,7 @@ If your application doesn't care whether the parameter was RFC 2231 encoded, it can turn the return value into a string as follows: - param = msg.get_param('foo') + rawparam = msg.get_param('foo') param = email.utils.collapse_rfc2231_value(rawparam) """ -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 18:33:44 2013 From: python-checkins at python.org (r.david.murray) Date: Sun, 3 Nov 2013 18:33:44 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Merge_=2319485=3A_clarify_get=5Fparam_example=2E?= Message-ID: <3dCPPh0rqVz7Ljr@mail.python.org> http://hg.python.org/cpython/rev/e3180c58a78c changeset: 86888:e3180c58a78c parent: 86886:e7be7aceab77 parent: 86887:c574951deadd user: R David Murray date: Sun Nov 03 12:23:51 2013 -0500 summary: Merge #19485: clarify get_param example. files: Lib/email/message.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/email/message.py b/Lib/email/message.py --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -662,7 +662,7 @@ If your application doesn't care whether the parameter was RFC 2231 encoded, it can turn the return value into a string as follows: - param = msg.get_param('foo') + rawparam = msg.get_param('foo') param = email.utils.collapse_rfc2231_value(rawparam) """ -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 19:22:44 2013 From: python-checkins at python.org (r.david.murray) Date: Sun, 3 Nov 2013 19:22:44 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogIzE5NDExOiBDbGFy?= =?utf-8?q?ify_that_b2a=5Fhex/hexlify_returns_a_bytes_object=2E?= Message-ID: <3dCQVD73R5z7Ljx@mail.python.org> http://hg.python.org/cpython/rev/25d89a4faede changeset: 86889:25d89a4faede branch: 3.3 parent: 86887:c574951deadd user: R David Murray date: Sun Nov 03 13:21:38 2013 -0500 summary: #19411: Clarify that b2a_hex/hexlify returns a bytes object. Initial patch by Vajrasky Kok. files: Doc/library/binascii.rst | 2 +- Modules/binascii.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/binascii.rst b/Doc/library/binascii.rst --- a/Doc/library/binascii.rst +++ b/Doc/library/binascii.rst @@ -145,7 +145,7 @@ Return the hexadecimal representation of the binary *data*. Every byte of *data* is converted into the corresponding 2-digit hex representation. The - resulting string is therefore twice as long as the length of *data*. + returned bytes object is therefore twice as long as the length of *data*. .. function:: a2b_hex(hexstr) diff --git a/Modules/binascii.c b/Modules/binascii.c --- a/Modules/binascii.c +++ b/Modules/binascii.c @@ -1129,7 +1129,8 @@ PyDoc_STRVAR(doc_hexlify, "b2a_hex(data) -> s; Hexadecimal representation of binary data.\n\ \n\ -This function is also available as \"hexlify()\"."); +The return value is a bytes object. This function is also\n\ +available as \"hexlify()\"."); static int -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 19:22:46 2013 From: python-checkins at python.org (r.david.murray) Date: Sun, 3 Nov 2013 19:22:46 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Merge_=2319411=3A_Clarify_that_b2a=5Fhex/hexlify_returns?= =?utf-8?q?_a_bytes_object=2E?= Message-ID: <3dCQVG29KfzMRc@mail.python.org> http://hg.python.org/cpython/rev/ac190d03aed5 changeset: 86890:ac190d03aed5 parent: 86888:e3180c58a78c parent: 86889:25d89a4faede user: R David Murray date: Sun Nov 03 13:22:17 2013 -0500 summary: Merge #19411: Clarify that b2a_hex/hexlify returns a bytes object. files: Doc/library/binascii.rst | 2 +- Modules/binascii.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/binascii.rst b/Doc/library/binascii.rst --- a/Doc/library/binascii.rst +++ b/Doc/library/binascii.rst @@ -145,7 +145,7 @@ Return the hexadecimal representation of the binary *data*. Every byte of *data* is converted into the corresponding 2-digit hex representation. The - resulting string is therefore twice as long as the length of *data*. + returned bytes object is therefore twice as long as the length of *data*. .. function:: a2b_hex(hexstr) diff --git a/Modules/binascii.c b/Modules/binascii.c --- a/Modules/binascii.c +++ b/Modules/binascii.c @@ -1122,7 +1122,8 @@ PyDoc_STRVAR(doc_hexlify, "b2a_hex(data) -> s; Hexadecimal representation of binary data.\n\ \n\ -This function is also available as \"hexlify()\"."); +The return value is a bytes object. This function is also\n\ +available as \"hexlify()\"."); static int -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 19:28:14 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 19:28:14 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzEwMTk3?= =?utf-8?q?=3A_Indicate_availability_of_subprocess=2Eget=5Bstatus=5Doutput?= =?utf-8?q?_on_Windows?= Message-ID: <3dCQcZ1QwCz7LjR@mail.python.org> http://hg.python.org/cpython/rev/2924a63aab73 changeset: 86891:2924a63aab73 branch: 3.3 parent: 86887:c574951deadd user: Tim Golden date: Sun Nov 03 18:24:50 2013 +0000 summary: Issue #10197: Indicate availability of subprocess.get[status]output on Windows and add a note about the effects of universal newlines files: Doc/library/subprocess.rst | 16 ++++++++++------ 1 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -1050,10 +1050,12 @@ Return ``(status, output)`` of executing *cmd* in a shell. - Execute the string *cmd* in a shell with :func:`os.popen` and return a 2-tuple - ``(status, output)``. *cmd* is actually run as ``{ cmd ; } 2>&1``, so that the - returned output will contain output or error messages. A trailing newline is - stripped from the output. The exit status for the command can be interpreted + Execute the string *cmd* in a shell with :class:`Popen` and return a 2-tuple + ``(status, output)`` via :func:`Popen.communicate`. Universal newlines mode + is used; see the notes on :ref:`frequently-used-arguments` for more details. + + A trailing newline is stripped from the output. + The exit status for the command can be interpreted according to the rules for the C function :c:func:`wait`. Example:: >>> subprocess.getstatusoutput('ls /bin/ls') @@ -1063,7 +1065,8 @@ >>> subprocess.getstatusoutput('/bin/junk') (256, 'sh: /bin/junk: not found') - Availability: UNIX. + .. versionchanged:: 3.3 + Availability: Unix & Windows .. function:: getoutput(cmd) @@ -1076,7 +1079,8 @@ >>> subprocess.getoutput('ls /bin/ls') '/bin/ls' - Availability: UNIX. + .. versionchanged:: 3.3 + Availability: Unix & Windows Notes -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 19:28:15 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 19:28:15 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2310197=3A_Indicate_availability_of_subprocess=2E?= =?utf-8?q?get=5Bstatus=5Doutput_on_Windows?= Message-ID: <3dCQcb3SLHz7Ljg@mail.python.org> http://hg.python.org/cpython/rev/effad2bda4cb changeset: 86892:effad2bda4cb parent: 86888:e3180c58a78c parent: 86891:2924a63aab73 user: Tim Golden date: Sun Nov 03 18:25:51 2013 +0000 summary: Issue #10197: Indicate availability of subprocess.get[status]output on Windows and add a note about the effects of universal newlines files: Doc/library/subprocess.rst | 16 ++++++++++------ 1 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -1058,10 +1058,12 @@ Return ``(status, output)`` of executing *cmd* in a shell. - Execute the string *cmd* in a shell with :func:`os.popen` and return a 2-tuple - ``(status, output)``. *cmd* is actually run as ``{ cmd ; } 2>&1``, so that the - returned output will contain output or error messages. A trailing newline is - stripped from the output. The exit status for the command can be interpreted + Execute the string *cmd* in a shell with :class:`Popen` and return a 2-tuple + ``(status, output)`` via :func:`Popen.communicate`. Universal newlines mode + is used; see the notes on :ref:`frequently-used-arguments` for more details. + + A trailing newline is stripped from the output. + The exit status for the command can be interpreted according to the rules for the C function :c:func:`wait`. Example:: >>> subprocess.getstatusoutput('ls /bin/ls') @@ -1071,7 +1073,8 @@ >>> subprocess.getstatusoutput('/bin/junk') (256, 'sh: /bin/junk: not found') - Availability: UNIX. + .. versionchanged:: 3.3 + Availability: Unix & Windows .. function:: getoutput(cmd) @@ -1084,7 +1087,8 @@ >>> subprocess.getoutput('ls /bin/ls') '/bin/ls' - Availability: UNIX. + .. versionchanged:: 3.3 + Availability: Unix & Windows Notes -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 19:28:16 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 19:28:16 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge?= Message-ID: <3dCQcc58h4z7Ljs@mail.python.org> http://hg.python.org/cpython/rev/fc1f353b8e7e changeset: 86893:fc1f353b8e7e parent: 86892:effad2bda4cb parent: 86890:ac190d03aed5 user: Tim Golden date: Sun Nov 03 18:27:07 2013 +0000 summary: Merge files: Doc/library/binascii.rst | 2 +- Modules/binascii.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/binascii.rst b/Doc/library/binascii.rst --- a/Doc/library/binascii.rst +++ b/Doc/library/binascii.rst @@ -145,7 +145,7 @@ Return the hexadecimal representation of the binary *data*. Every byte of *data* is converted into the corresponding 2-digit hex representation. The - resulting string is therefore twice as long as the length of *data*. + returned bytes object is therefore twice as long as the length of *data*. .. function:: a2b_hex(hexstr) diff --git a/Modules/binascii.c b/Modules/binascii.c --- a/Modules/binascii.c +++ b/Modules/binascii.c @@ -1122,7 +1122,8 @@ PyDoc_STRVAR(doc_hexlify, "b2a_hex(data) -> s; Hexadecimal representation of binary data.\n\ \n\ -This function is also available as \"hexlify()\"."); +The return value is a bytes object. This function is also\n\ +available as \"hexlify()\"."); static int -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 19:28:17 2013 From: python-checkins at python.org (tim.golden) Date: Sun, 3 Nov 2013 19:28:17 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAobWVyZ2UgMy4zIC0+IDMuMyk6?= =?utf-8?q?_Merge?= Message-ID: <3dCQcd74lpz7Ljt@mail.python.org> http://hg.python.org/cpython/rev/b91f4f13f2b0 changeset: 86894:b91f4f13f2b0 branch: 3.3 parent: 86889:25d89a4faede parent: 86891:2924a63aab73 user: Tim Golden date: Sun Nov 03 18:27:40 2013 +0000 summary: Merge files: Doc/library/subprocess.rst | 16 ++++++++++------ 1 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -1050,10 +1050,12 @@ Return ``(status, output)`` of executing *cmd* in a shell. - Execute the string *cmd* in a shell with :func:`os.popen` and return a 2-tuple - ``(status, output)``. *cmd* is actually run as ``{ cmd ; } 2>&1``, so that the - returned output will contain output or error messages. A trailing newline is - stripped from the output. The exit status for the command can be interpreted + Execute the string *cmd* in a shell with :class:`Popen` and return a 2-tuple + ``(status, output)`` via :func:`Popen.communicate`. Universal newlines mode + is used; see the notes on :ref:`frequently-used-arguments` for more details. + + A trailing newline is stripped from the output. + The exit status for the command can be interpreted according to the rules for the C function :c:func:`wait`. Example:: >>> subprocess.getstatusoutput('ls /bin/ls') @@ -1063,7 +1065,8 @@ >>> subprocess.getstatusoutput('/bin/junk') (256, 'sh: /bin/junk: not found') - Availability: UNIX. + .. versionchanged:: 3.3 + Availability: Unix & Windows .. function:: getoutput(cmd) @@ -1076,7 +1079,8 @@ >>> subprocess.getoutput('ls /bin/ls') '/bin/ls' - Availability: UNIX. + .. versionchanged:: 3.3 + Availability: Unix & Windows Notes -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 20:32:21 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 20:32:21 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE4NzAy?= =?utf-8?q?=3A_All_skipped_tests_now_reported_as_skipped=2E?= Message-ID: <3dCS2Y0g86z7LkD@mail.python.org> http://hg.python.org/cpython/rev/1feeeb8992f8 changeset: 86895:1feeeb8992f8 branch: 3.3 user: Serhiy Storchaka date: Sun Nov 03 21:31:18 2013 +0200 summary: Issue #18702: All skipped tests now reported as skipped. files: Lib/test/test_array.py | 17 +- Lib/test/test_compileall.py | 3 +- Lib/test/test_csv.py | 135 +++--- Lib/test/test_dbm_dumb.py | 6 +- Lib/test/test_enumerate.py | 3 +- Lib/test/test_ftplib.py | 14 +- Lib/test/test_mailbox.py | 36 +- Lib/test/test_math.py | 59 +- Lib/test/test_mmap.py | 144 +++--- Lib/test/test_nntplib.py | 74 +- Lib/test/test_os.py | 453 ++++++++++----------- Lib/test/test_poplib.py | 57 +- Lib/test/test_posix.py | 304 +++++++------ Lib/test/test_set.py | 8 +- Lib/test/test_shutil.py | 119 ++-- Lib/test/test_socket.py | 103 ++-- Lib/test/test_socketserver.py | 85 ++- Lib/test/test_sys.py | 17 +- Lib/test/test_warnings.py | 3 +- Lib/test/test_zlib.py | 159 ++++--- Misc/NEWS | 2 + 21 files changed, 913 insertions(+), 888 deletions(-) diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -11,6 +11,7 @@ import io import math import struct +import sys import warnings import array @@ -993,15 +994,15 @@ s = None self.assertRaises(ReferenceError, len, p) + @unittest.skipUnless(hasattr(sys, 'getrefcount'), + 'test needs sys.getrefcount()') def test_bug_782369(self): - import sys - if hasattr(sys, "getrefcount"): - for i in range(10): - b = array.array('B', range(64)) - rc = sys.getrefcount(10) - for i in range(10): - b = array.array('B', range(64)) - self.assertEqual(rc, sys.getrefcount(10)) + for i in range(10): + b = array.array('B', range(64)) + rc = sys.getrefcount(10) + for i in range(10): + b = array.array('B', range(64)) + self.assertEqual(rc, sys.getrefcount(10)) def test_subclass_with_kwargs(self): # SF bug #1486663 -- this used to erroneously raise a TypeError diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -39,11 +39,10 @@ compare = struct.pack('<4sl', imp.get_magic(), mtime) return data, compare + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def recreation_check(self, metadata): """Check that compileall recreates bytecode when the new metadata is used.""" - if not hasattr(os, 'stat'): - return py_compile.compile(self.source_path) self.assertEqual(*self.data()) with open(self.bc_path, 'rb') as file: diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -896,78 +896,77 @@ dialect = sniffer.sniff(self.sample9) self.assertTrue(dialect.doublequote) -if not hasattr(sys, "gettotalrefcount"): - if support.verbose: print("*** skipping leakage tests ***") -else: - class NUL: - def write(s, *args): - pass - writelines = write +class NUL: + def write(s, *args): + pass + writelines = write - class TestLeaks(unittest.TestCase): - def test_create_read(self): - delta = 0 - lastrc = sys.gettotalrefcount() - for i in range(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - delta = rc-lastrc - lastrc = rc - # if csv.reader() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) + at unittest.skipUnless(hasattr(sys, "gettotalrefcount"), + 'requires sys.gettotalrefcount()') +class TestLeaks(unittest.TestCase): + def test_create_read(self): + delta = 0 + lastrc = sys.gettotalrefcount() + for i in range(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + csv.reader(["a,b,c\r\n"]) + csv.reader(["a,b,c\r\n"]) + csv.reader(["a,b,c\r\n"]) + delta = rc-lastrc + lastrc = rc + # if csv.reader() leaks, last delta should be 3 or more + self.assertEqual(delta < 3, True) - def test_create_write(self): - delta = 0 - lastrc = sys.gettotalrefcount() - s = NUL() - for i in range(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.writer(s) - csv.writer(s) - csv.writer(s) - delta = rc-lastrc - lastrc = rc - # if csv.writer() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) + def test_create_write(self): + delta = 0 + lastrc = sys.gettotalrefcount() + s = NUL() + for i in range(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + csv.writer(s) + csv.writer(s) + csv.writer(s) + delta = rc-lastrc + lastrc = rc + # if csv.writer() leaks, last delta should be 3 or more + self.assertEqual(delta < 3, True) - def test_read(self): - delta = 0 - rows = ["a,b,c\r\n"]*5 - lastrc = sys.gettotalrefcount() - for i in range(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - rdr = csv.reader(rows) - for row in rdr: - pass - delta = rc-lastrc - lastrc = rc - # if reader leaks during read, delta should be 5 or more - self.assertEqual(delta < 5, True) + def test_read(self): + delta = 0 + rows = ["a,b,c\r\n"]*5 + lastrc = sys.gettotalrefcount() + for i in range(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + rdr = csv.reader(rows) + for row in rdr: + pass + delta = rc-lastrc + lastrc = rc + # if reader leaks during read, delta should be 5 or more + self.assertEqual(delta < 5, True) - def test_write(self): - delta = 0 - rows = [[1,2,3]]*5 - s = NUL() - lastrc = sys.gettotalrefcount() - for i in range(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - writer = csv.writer(s) - for row in rows: - writer.writerow(row) - delta = rc-lastrc - lastrc = rc - # if writer leaks during write, last delta should be 5 or more - self.assertEqual(delta < 5, True) + def test_write(self): + delta = 0 + rows = [[1,2,3]]*5 + s = NUL() + lastrc = sys.gettotalrefcount() + for i in range(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + writer = csv.writer(s) + for row in rows: + writer.writerow(row) + delta = rc-lastrc + lastrc = rc + # if writer leaks during write, last delta should be 5 or more + self.assertEqual(delta < 5, True) class TestUnicode(unittest.TestCase): diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -37,11 +37,9 @@ self.read_helper(f) f.close() + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'chmod'), 'test needs os.chmod()') def test_dumbdbm_creation_mode(self): - # On platforms without chmod, don't do anything. - if not (hasattr(os, 'chmod') and hasattr(os, 'umask')): - return - try: old_umask = os.umask(0o002) f = dumbdbm.open(_fname, 'c', 0o637) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -204,11 +204,10 @@ self.assertRaises(TypeError, reversed) self.assertRaises(TypeError, reversed, [], 'extra') + @unittest.skipUnless(hasattr(sys, 'getrefcount'), 'test needs sys.getrefcount()') def test_bug1229429(self): # this bug was never in reversed, it was in # PyObject_CallMethod, and reversed_new calls that sometimes. - if not hasattr(sys, "getrefcount"): - return def f(): pass r = f.__reversed__ = object() diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -16,7 +16,7 @@ except ImportError: ssl = None -from unittest import TestCase +from unittest import TestCase, skipUnless from test import support from test.support import HOST, HOSTv6 threading = support.import_module('threading') @@ -779,6 +779,7 @@ self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f) + at skipUnless(support.IPV6_ENABLED, "IPv6 not enabled") class TestIPv6Environment(TestCase): def setUp(self): @@ -819,6 +820,7 @@ retr() + at skipUnless(ssl, "SSL not available") class TestTLS_FTPClassMixin(TestFTPClass): """Repeat TestFTPClass tests starting the TLS layer for both control and data connections first. @@ -834,6 +836,7 @@ self.client.prot_p() + at skipUnless(ssl, "SSL not available") class TestTLS_FTPClass(TestCase): """Specific TLS_FTP class tests.""" @@ -1015,12 +1018,9 @@ def test_main(): - tests = [TestFTPClass, TestTimeouts] - if support.IPV6_ENABLED: - tests.append(TestIPv6Environment) - - if ssl is not None: - tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass]) + tests = [TestFTPClass, TestTimeouts, + TestIPv6Environment, + TestTLS_FTPClassMixin, TestTLS_FTPClass] thread_info = support.threading_setup() try: diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -868,10 +868,10 @@ for msg in self._box: pass + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_file_permissions(self): # Verify that message files are created without execute permissions - if not hasattr(os, "stat") or not hasattr(os, "umask"): - return msg = mailbox.MaildirMessage(self._template % 0) orig_umask = os.umask(0) try: @@ -882,12 +882,11 @@ mode = os.stat(path).st_mode self.assertFalse(mode & 0o111) + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_folder_file_perms(self): # From bug #3228, we want to verify that the file created inside a Maildir # subfolder isn't marked as executable. - if not hasattr(os, "stat") or not hasattr(os, "umask"): - return - orig_umask = os.umask(0) try: subfolder = self._box.add_folder('subfolder') @@ -1097,24 +1096,25 @@ _factory = lambda self, path, factory=None: mailbox.mbox(path, factory) + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_file_perms(self): # From bug #3228, we want to verify that the mailbox file isn't executable, # even if the umask is set to something that would leave executable bits set. # We only run this test on platforms that support umask. - if hasattr(os, 'umask') and hasattr(os, 'stat'): - try: - old_umask = os.umask(0o077) - self._box.close() - os.unlink(self._path) - self._box = mailbox.mbox(self._path, create=True) - self._box.add('') - self._box.close() - finally: - os.umask(old_umask) + try: + old_umask = os.umask(0o077) + self._box.close() + os.unlink(self._path) + self._box = mailbox.mbox(self._path, create=True) + self._box.add('') + self._box.close() + finally: + os.umask(old_umask) - st = os.stat(self._path) - perms = st.st_mode - self.assertFalse((perms & 0o111)) # Execute bits should all be off. + st = os.stat(self._path) + perms = st.st_mode + self.assertFalse((perms & 0o111)) # Execute bits should all be off. def test_terminating_newline(self): message = email.message.Message() diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -980,38 +980,37 @@ # still fails this part of the test on some platforms. For now, we only # *run* test_exceptions() in verbose mode, so that this isn't normally # tested. + @unittest.skipUnless(verbose, 'requires verbose mode') + def test_exceptions(self): + try: + x = math.exp(-1000000000) + except: + # mathmodule.c is failing to weed out underflows from libm, or + # we've got an fp format with huge dynamic range + self.fail("underflowing exp() should not have raised " + "an exception") + if x != 0: + self.fail("underflowing exp() should have returned 0") - if verbose: - def test_exceptions(self): - try: - x = math.exp(-1000000000) - except: - # mathmodule.c is failing to weed out underflows from libm, or - # we've got an fp format with huge dynamic range - self.fail("underflowing exp() should not have raised " - "an exception") - if x != 0: - self.fail("underflowing exp() should have returned 0") + # If this fails, probably using a strict IEEE-754 conforming libm, and x + # is +Inf afterwards. But Python wants overflows detected by default. + try: + x = math.exp(1000000000) + except OverflowError: + pass + else: + self.fail("overflowing exp() didn't trigger OverflowError") - # If this fails, probably using a strict IEEE-754 conforming libm, and x - # is +Inf afterwards. But Python wants overflows detected by default. - try: - x = math.exp(1000000000) - except OverflowError: - pass - else: - self.fail("overflowing exp() didn't trigger OverflowError") - - # If this fails, it could be a puzzle. One odd possibility is that - # mathmodule.c's macros are getting confused while comparing - # Inf (HUGE_VAL) to a NaN, and artificially setting errno to ERANGE - # as a result (and so raising OverflowError instead). - try: - x = math.sqrt(-1.0) - except ValueError: - pass - else: - self.fail("sqrt(-1) didn't raise ValueError") + # If this fails, it could be a puzzle. One odd possibility is that + # mathmodule.c's macros are getting confused while comparing + # Inf (HUGE_VAL) to a NaN, and artificially setting errno to ERANGE + # as a result (and so raising OverflowError instead). + try: + x = math.sqrt(-1.0) + except ValueError: + pass + else: + self.fail("sqrt(-1) didn't raise ValueError") @requires_IEEE_754 def test_testfile(self): diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -314,26 +314,25 @@ mf.close() f.close() + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_entire_file(self): # test mapping of entire file by passing 0 for map length - if hasattr(os, "stat"): - f = open(TESTFN, "wb+") + f = open(TESTFN, "wb+") - f.write(2**16 * b'm') # Arbitrary character - f.close() + f.write(2**16 * b'm') # Arbitrary character + f.close() - f = open(TESTFN, "rb+") - mf = mmap.mmap(f.fileno(), 0) - self.assertEqual(len(mf), 2**16, "Map size should equal file size.") - self.assertEqual(mf.read(2**16), 2**16 * b"m") - mf.close() - f.close() + f = open(TESTFN, "rb+") + mf = mmap.mmap(f.fileno(), 0) + self.assertEqual(len(mf), 2**16, "Map size should equal file size.") + self.assertEqual(mf.read(2**16), 2**16 * b"m") + mf.close() + f.close() + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_length_0_offset(self): # Issue #10916: test mapping of remainder of file by passing 0 for # map length with an offset doesn't cause a segfault. - if not hasattr(os, "stat"): - self.skipTest("needs os.stat") # NOTE: allocation granularity is currently 65536 under Win64, # and therefore the minimum offset alignment. with open(TESTFN, "wb") as f: @@ -343,12 +342,10 @@ with mmap.mmap(f.fileno(), 0, offset=65536, access=mmap.ACCESS_READ) as mf: self.assertRaises(IndexError, mf.__getitem__, 80000) + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_length_0_large_offset(self): # Issue #10959: test mapping of a file by passing 0 for # map length with a large offset doesn't cause a segfault. - if not hasattr(os, "stat"): - self.skipTest("needs os.stat") - with open(TESTFN, "wb") as f: f.write(115699 * b'm') # Arbitrary character @@ -560,9 +557,8 @@ return mmap.mmap.__new__(klass, -1, *args, **kwargs) anon_mmap(PAGESIZE) + @unittest.skipUnless(hasattr(mmap, 'PROT_READ'), "needs mmap.PROT_READ") def test_prot_readonly(self): - if not hasattr(mmap, 'PROT_READ'): - return mapsize = 10 with open(TESTFN, "wb") as fp: fp.write(b"a"*mapsize) @@ -616,67 +612,69 @@ self.assertEqual(m.read_byte(), b) m.close() - if os.name == 'nt': - def test_tagname(self): - data1 = b"0123456789" - data2 = b"abcdefghij" - assert len(data1) == len(data2) + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_tagname(self): + data1 = b"0123456789" + data2 = b"abcdefghij" + assert len(data1) == len(data2) - # Test same tag - m1 = mmap.mmap(-1, len(data1), tagname="foo") - m1[:] = data1 - m2 = mmap.mmap(-1, len(data2), tagname="foo") - m2[:] = data2 - self.assertEqual(m1[:], data2) - self.assertEqual(m2[:], data2) - m2.close() - m1.close() + # Test same tag + m1 = mmap.mmap(-1, len(data1), tagname="foo") + m1[:] = data1 + m2 = mmap.mmap(-1, len(data2), tagname="foo") + m2[:] = data2 + self.assertEqual(m1[:], data2) + self.assertEqual(m2[:], data2) + m2.close() + m1.close() - # Test different tag - m1 = mmap.mmap(-1, len(data1), tagname="foo") - m1[:] = data1 - m2 = mmap.mmap(-1, len(data2), tagname="boo") - m2[:] = data2 - self.assertEqual(m1[:], data1) - self.assertEqual(m2[:], data2) - m2.close() - m1.close() + # Test different tag + m1 = mmap.mmap(-1, len(data1), tagname="foo") + m1[:] = data1 + m2 = mmap.mmap(-1, len(data2), tagname="boo") + m2[:] = data2 + self.assertEqual(m1[:], data1) + self.assertEqual(m2[:], data2) + m2.close() + m1.close() - def test_crasher_on_windows(self): - # Should not crash (Issue 1733986) - m = mmap.mmap(-1, 1000, tagname="foo") - try: - mmap.mmap(-1, 5000, tagname="foo")[:] # same tagname, but larger size - except: - pass - m.close() + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_crasher_on_windows(self): + # Should not crash (Issue 1733986) + m = mmap.mmap(-1, 1000, tagname="foo") + try: + mmap.mmap(-1, 5000, tagname="foo")[:] # same tagname, but larger size + except: + pass + m.close() - # Should not crash (Issue 5385) - with open(TESTFN, "wb") as fp: - fp.write(b"x"*10) - f = open(TESTFN, "r+b") - m = mmap.mmap(f.fileno(), 0) - f.close() - try: - m.resize(0) # will raise WindowsError - except: - pass - try: - m[:] - except: - pass - m.close() + # Should not crash (Issue 5385) + with open(TESTFN, "wb") as fp: + fp.write(b"x"*10) + f = open(TESTFN, "r+b") + m = mmap.mmap(f.fileno(), 0) + f.close() + try: + m.resize(0) # will raise WindowsError + except: + pass + try: + m[:] + except: + pass + m.close() - def test_invalid_descriptor(self): - # socket file descriptors are valid, but out of range - # for _get_osfhandle, causing a crash when validating the - # parameters to _get_osfhandle. - s = socket.socket() - try: - with self.assertRaises(mmap.error): - m = mmap.mmap(s.fileno(), 10) - finally: - s.close() + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_invalid_descriptor(self): + # socket file descriptors are valid, but out of range + # for _get_osfhandle, causing a crash when validating the + # parameters to _get_osfhandle. + s = socket.socket() + try: + with self.assertRaises(mmap.error): + m = mmap.mmap(s.fileno(), 10) + finally: + s.close() def test_context_manager(self): with mmap.mmap(-1, 10) as m: diff --git a/Lib/test/test_nntplib.py b/Lib/test/test_nntplib.py --- a/Lib/test/test_nntplib.py +++ b/Lib/test/test_nntplib.py @@ -6,10 +6,12 @@ import functools import contextlib from test import support -from nntplib import NNTP, GroupInfo, _have_ssl +from nntplib import NNTP, GroupInfo import nntplib -if _have_ssl: +try: import ssl +except ImportError: + ssl = None TIMEOUT = 30 @@ -199,23 +201,23 @@ resp, caps = self.server.capabilities() _check_caps(caps) - if _have_ssl: - def test_starttls(self): - file = self.server.file - sock = self.server.sock - try: - self.server.starttls() - except nntplib.NNTPPermanentError: - self.skipTest("STARTTLS not supported by server.") - else: - # Check that the socket and internal pseudo-file really were - # changed. - self.assertNotEqual(file, self.server.file) - self.assertNotEqual(sock, self.server.sock) - # Check that the new socket really is an SSL one - self.assertIsInstance(self.server.sock, ssl.SSLSocket) - # Check that trying starttls when it's already active fails. - self.assertRaises(ValueError, self.server.starttls) + @unittest.skipUnless(ssl, 'requires SSL support') + def test_starttls(self): + file = self.server.file + sock = self.server.sock + try: + self.server.starttls() + except nntplib.NNTPPermanentError: + self.skipTest("STARTTLS not supported by server.") + else: + # Check that the socket and internal pseudo-file really were + # changed. + self.assertNotEqual(file, self.server.file) + self.assertNotEqual(sock, self.server.sock) + # Check that the new socket really is an SSL one + self.assertIsInstance(self.server.sock, ssl.SSLSocket) + # Check that trying starttls when it's already active fails. + self.assertRaises(ValueError, self.server.starttls) def test_zlogin(self): # This test must be the penultimate because further commands will be @@ -300,25 +302,24 @@ if cls.server is not None: cls.server.quit() + at unittest.skipUnless(ssl, 'requires SSL support') +class NetworkedNNTP_SSLTests(NetworkedNNTPTests): -if _have_ssl: - class NetworkedNNTP_SSLTests(NetworkedNNTPTests): + # Technical limits for this public NNTP server (see http://www.aioe.org): + # "Only two concurrent connections per IP address are allowed and + # 400 connections per day are accepted from each IP address." - # Technical limits for this public NNTP server (see http://www.aioe.org): - # "Only two concurrent connections per IP address are allowed and - # 400 connections per day are accepted from each IP address." + NNTP_HOST = 'nntp.aioe.org' + GROUP_NAME = 'comp.lang.python' + GROUP_PAT = 'comp.lang.*' - NNTP_HOST = 'nntp.aioe.org' - GROUP_NAME = 'comp.lang.python' - GROUP_PAT = 'comp.lang.*' + NNTP_CLASS = getattr(nntplib, 'NNTP_SSL', None) - NNTP_CLASS = nntplib.NNTP_SSL + # Disabled as it produces too much data + test_list = None - # Disabled as it produces too much data - test_list = None - - # Disabled as the connection will already be encrypted. - test_starttls = None + # Disabled as the connection will already be encrypted. + test_starttls = None # @@ -1407,12 +1408,13 @@ gives(2000, 6, 23, "000623", "000000") gives(2010, 6, 5, "100605", "000000") + @unittest.skipUnless(ssl, 'requires SSL support') + def test_ssl_support(self): + self.assertTrue(hasattr(nntplib, 'NNTP_SSL')) def test_main(): tests = [MiscTests, NNTPv1Tests, NNTPv2Tests, CapsAfterLoginNNTPv2Tests, - SendReaderNNTPv2Tests, NetworkedNNTPTests] - if _have_ssl: - tests.append(NetworkedNNTP_SSLTests) + SendReaderNNTPv2Tests, NetworkedNNTPTests, NetworkedNNTP_SSLTests] support.run_unittest(*tests) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -178,10 +178,8 @@ os.unlink(self.fname) os.rmdir(support.TESTFN) + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def check_stat_attributes(self, fname): - if not hasattr(os, "stat"): - return - result = os.stat(fname) # Make sure direct access works @@ -258,10 +256,8 @@ warnings.simplefilter("ignore", DeprecationWarning) self.check_stat_attributes(fname) + @unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()') def test_statvfs_attributes(self): - if not hasattr(os, "statvfs"): - return - try: result = os.statvfs(self.fname) except OSError as e: @@ -450,10 +446,10 @@ os.close(dirfd) self._test_utime_subsecond(set_time) - # Restrict test to Win32, since there is no guarantee other + # Restrict tests to Win32, since there is no guarantee other # systems support centiseconds - if sys.platform == 'win32': - def get_file_system(path): + def get_file_system(path): + if sys.platform == 'win32': root = os.path.splitdrive(os.path.abspath(path))[0] + '\\' import ctypes kernel32 = ctypes.windll.kernel32 @@ -461,38 +457,45 @@ if kernel32.GetVolumeInformationW(root, None, 0, None, None, None, buf, len(buf)): return buf.value - if get_file_system(support.TESTFN) == "NTFS": - def test_1565150(self): - t1 = 1159195039.25 - os.utime(self.fname, (t1, t1)) - self.assertEqual(os.stat(self.fname).st_mtime, t1) + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + @unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS", + "requires NTFS") + def test_1565150(self): + t1 = 1159195039.25 + os.utime(self.fname, (t1, t1)) + self.assertEqual(os.stat(self.fname).st_mtime, t1) - def test_large_time(self): - t1 = 5000000000 # some day in 2128 - os.utime(self.fname, (t1, t1)) - self.assertEqual(os.stat(self.fname).st_mtime, t1) + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + @unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS", + "requires NTFS") + def test_large_time(self): + t1 = 5000000000 # some day in 2128 + os.utime(self.fname, (t1, t1)) + self.assertEqual(os.stat(self.fname).st_mtime, t1) - def test_1686475(self): - # Verify that an open file can be stat'ed - try: - os.stat(r"c:\pagefile.sys") - except WindowsError as e: - if e.errno == 2: # file does not exist; cannot run test - return - self.fail("Could not stat pagefile.sys") + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + def test_1686475(self): + # Verify that an open file can be stat'ed + try: + os.stat(r"c:\pagefile.sys") + except WindowsError as e: + if e.errno == 2: # file does not exist; cannot run test + return + self.fail("Could not stat pagefile.sys") - @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") - def test_15261(self): - # Verify that stat'ing a closed fd does not cause crash - r, w = os.pipe() - try: - os.stat(r) # should not raise error - finally: - os.close(r) - os.close(w) - with self.assertRaises(OSError) as ctx: - os.stat(r) - self.assertEqual(ctx.exception.errno, errno.EBADF) + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + def test_15261(self): + # Verify that stat'ing a closed fd does not cause crash + r, w = os.pipe() + try: + os.stat(r) # should not raise error + finally: + os.close(r) + os.close(w) + with self.assertRaises(OSError) as ctx: + os.stat(r) + self.assertEqual(ctx.exception.errno, errno.EBADF) from test import mapping_tests @@ -1127,6 +1130,7 @@ self._test_internal_execvpe(bytes) + at unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") class Win32ErrorTests(unittest.TestCase): def test_rename(self): self.assertRaises(WindowsError, os.rename, support.TESTFN, support.TESTFN+".bak") @@ -1173,63 +1177,63 @@ self.fail("%r didn't raise a OSError with a bad file descriptor" % f) + @unittest.skipUnless(hasattr(os, 'isatty'), 'test needs os.isatty()') def test_isatty(self): - if hasattr(os, "isatty"): - self.assertEqual(os.isatty(support.make_bad_fd()), False) + self.assertEqual(os.isatty(support.make_bad_fd()), False) + @unittest.skipUnless(hasattr(os, 'closerange'), 'test needs os.closerange()') def test_closerange(self): - if hasattr(os, "closerange"): - fd = support.make_bad_fd() - # Make sure none of the descriptors we are about to close are - # currently valid (issue 6542). - for i in range(10): - try: os.fstat(fd+i) - except OSError: - pass - else: - break - if i < 2: - raise unittest.SkipTest( - "Unable to acquire a range of invalid file descriptors") - self.assertEqual(os.closerange(fd, fd + i-1), None) + fd = support.make_bad_fd() + # Make sure none of the descriptors we are about to close are + # currently valid (issue 6542). + for i in range(10): + try: os.fstat(fd+i) + except OSError: + pass + else: + break + if i < 2: + raise unittest.SkipTest( + "Unable to acquire a range of invalid file descriptors") + self.assertEqual(os.closerange(fd, fd + i-1), None) + @unittest.skipUnless(hasattr(os, 'dup2'), 'test needs os.dup2()') def test_dup2(self): - if hasattr(os, "dup2"): - self.check(os.dup2, 20) + self.check(os.dup2, 20) + @unittest.skipUnless(hasattr(os, 'fchmod'), 'test needs os.fchmod()') def test_fchmod(self): - if hasattr(os, "fchmod"): - self.check(os.fchmod, 0) + self.check(os.fchmod, 0) + @unittest.skipUnless(hasattr(os, 'fchown'), 'test needs os.fchown()') def test_fchown(self): - if hasattr(os, "fchown"): - self.check(os.fchown, -1, -1) + self.check(os.fchown, -1, -1) + @unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') def test_fpathconf(self): - if hasattr(os, "fpathconf"): - self.check(os.pathconf, "PC_NAME_MAX") - self.check(os.fpathconf, "PC_NAME_MAX") + self.check(os.pathconf, "PC_NAME_MAX") + self.check(os.fpathconf, "PC_NAME_MAX") + @unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()') def test_ftruncate(self): - if hasattr(os, "ftruncate"): - self.check(os.truncate, 0) - self.check(os.ftruncate, 0) + self.check(os.truncate, 0) + self.check(os.ftruncate, 0) + @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') def test_lseek(self): - if hasattr(os, "lseek"): - self.check(os.lseek, 0, 0) + self.check(os.lseek, 0, 0) + @unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()') def test_read(self): - if hasattr(os, "read"): - self.check(os.read, 1) + self.check(os.read, 1) + @unittest.skipUnless(hasattr(os, 'tcsetpgrp'), 'test needs os.tcsetpgrp()') def test_tcsetpgrpt(self): - if hasattr(os, "tcsetpgrp"): - self.check(os.tcsetpgrp, 0) + self.check(os.tcsetpgrp, 0) + @unittest.skipUnless(hasattr(os, 'write'), 'test needs os.write()') def test_write(self): - if hasattr(os, "write"): - self.check(os.write, b" ") + self.check(os.write, b" ") class LinkTests(unittest.TestCase): @@ -1269,138 +1273,117 @@ self.file2 = self.file1 + "2" self._test_link(self.file1, self.file2) -if sys.platform != 'win32': - class Win32ErrorTests(unittest.TestCase): - pass + at unittest.skipIf(sys.platform == "win32", "Posix specific tests") +class PosixUidGidTests(unittest.TestCase): + @unittest.skipUnless(hasattr(os, 'setuid'), 'test needs os.setuid()') + def test_setuid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.setuid, 0) + self.assertRaises(OverflowError, os.setuid, 1<<32) - class PosixUidGidTests(unittest.TestCase): - if hasattr(os, 'setuid'): - def test_setuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setuid, 0) - self.assertRaises(OverflowError, os.setuid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setgid'), 'test needs os.setgid()') + def test_setgid(self): + if os.getuid() != 0 and not HAVE_WHEEL_GROUP: + self.assertRaises(os.error, os.setgid, 0) + self.assertRaises(OverflowError, os.setgid, 1<<32) - if hasattr(os, 'setgid'): - def test_setgid(self): - if os.getuid() != 0 and not HAVE_WHEEL_GROUP: - self.assertRaises(os.error, os.setgid, 0) - self.assertRaises(OverflowError, os.setgid, 1<<32) + @unittest.skipUnless(hasattr(os, 'seteuid'), 'test needs os.seteuid()') + def test_seteuid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.seteuid, 0) + self.assertRaises(OverflowError, os.seteuid, 1<<32) - if hasattr(os, 'seteuid'): - def test_seteuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.seteuid, 0) - self.assertRaises(OverflowError, os.seteuid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setegid'), 'test needs os.setegid()') + def test_setegid(self): + if os.getuid() != 0 and not HAVE_WHEEL_GROUP: + self.assertRaises(os.error, os.setegid, 0) + self.assertRaises(OverflowError, os.setegid, 1<<32) - if hasattr(os, 'setegid'): - def test_setegid(self): - if os.getuid() != 0 and not HAVE_WHEEL_GROUP: - self.assertRaises(os.error, os.setegid, 0) - self.assertRaises(OverflowError, os.setegid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()') + def test_setreuid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.setreuid, 0, 0) + self.assertRaises(OverflowError, os.setreuid, 1<<32, 0) + self.assertRaises(OverflowError, os.setreuid, 0, 1<<32) - if hasattr(os, 'setreuid'): - def test_setreuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setreuid, 0, 0) - self.assertRaises(OverflowError, os.setreuid, 1<<32, 0) - self.assertRaises(OverflowError, os.setreuid, 0, 1<<32) + @unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()') + def test_setregid(self): + if os.getuid() != 0 and not HAVE_WHEEL_GROUP: + self.assertRaises(os.error, os.setregid, 0, 0) + self.assertRaises(OverflowError, os.setregid, 1<<32, 0) + self.assertRaises(OverflowError, os.setregid, 0, 1<<32) - def test_setreuid_neg1(self): - # Needs to accept -1. We run this in a subprocess to avoid - # altering the test runner's process state (issue8045). - subprocess.check_call([ - sys.executable, '-c', - 'import os,sys;os.setreuid(-1,-1);sys.exit(0)']) + at unittest.skipIf(sys.platform == "win32", "Posix specific tests") +class Pep383Tests(unittest.TestCase): + def setUp(self): + if support.TESTFN_UNENCODABLE: + self.dir = support.TESTFN_UNENCODABLE + elif support.TESTFN_NONASCII: + self.dir = support.TESTFN_NONASCII + else: + self.dir = support.TESTFN + self.bdir = os.fsencode(self.dir) - if hasattr(os, 'setregid'): - def test_setregid(self): - if os.getuid() != 0 and not HAVE_WHEEL_GROUP: - self.assertRaises(os.error, os.setregid, 0, 0) - self.assertRaises(OverflowError, os.setregid, 1<<32, 0) - self.assertRaises(OverflowError, os.setregid, 0, 1<<32) + bytesfn = [] + def add_filename(fn): + try: + fn = os.fsencode(fn) + except UnicodeEncodeError: + return + bytesfn.append(fn) + add_filename(support.TESTFN_UNICODE) + if support.TESTFN_UNENCODABLE: + add_filename(support.TESTFN_UNENCODABLE) + if support.TESTFN_NONASCII: + add_filename(support.TESTFN_NONASCII) + if not bytesfn: + self.skipTest("couldn't create any non-ascii filename") - def test_setregid_neg1(self): - # Needs to accept -1. We run this in a subprocess to avoid - # altering the test runner's process state (issue8045). - subprocess.check_call([ - sys.executable, '-c', - 'import os,sys;os.setregid(-1,-1);sys.exit(0)']) + self.unicodefn = set() + os.mkdir(self.dir) + try: + for fn in bytesfn: + support.create_empty_file(os.path.join(self.bdir, fn)) + fn = os.fsdecode(fn) + if fn in self.unicodefn: + raise ValueError("duplicate filename") + self.unicodefn.add(fn) + except: + shutil.rmtree(self.dir) + raise - class Pep383Tests(unittest.TestCase): - def setUp(self): - if support.TESTFN_UNENCODABLE: - self.dir = support.TESTFN_UNENCODABLE - elif support.TESTFN_NONASCII: - self.dir = support.TESTFN_NONASCII - else: - self.dir = support.TESTFN - self.bdir = os.fsencode(self.dir) + def tearDown(self): + shutil.rmtree(self.dir) - bytesfn = [] - def add_filename(fn): - try: - fn = os.fsencode(fn) - except UnicodeEncodeError: - return - bytesfn.append(fn) - add_filename(support.TESTFN_UNICODE) - if support.TESTFN_UNENCODABLE: - add_filename(support.TESTFN_UNENCODABLE) - if support.TESTFN_NONASCII: - add_filename(support.TESTFN_NONASCII) - if not bytesfn: - self.skipTest("couldn't create any non-ascii filename") + def test_listdir(self): + expected = self.unicodefn + found = set(os.listdir(self.dir)) + self.assertEqual(found, expected) + # test listdir without arguments + current_directory = os.getcwd() + try: + os.chdir(os.sep) + self.assertEqual(set(os.listdir()), set(os.listdir(os.sep))) + finally: + os.chdir(current_directory) - self.unicodefn = set() - os.mkdir(self.dir) - try: - for fn in bytesfn: - support.create_empty_file(os.path.join(self.bdir, fn)) - fn = os.fsdecode(fn) - if fn in self.unicodefn: - raise ValueError("duplicate filename") - self.unicodefn.add(fn) - except: - shutil.rmtree(self.dir) - raise + def test_open(self): + for fn in self.unicodefn: + f = open(os.path.join(self.dir, fn), 'rb') + f.close() - def tearDown(self): - shutil.rmtree(self.dir) + @unittest.skipUnless(hasattr(os, 'statvfs'), + "need os.statvfs()") + def test_statvfs(self): + # issue #9645 + for fn in self.unicodefn: + # should not fail with file not found error + fullname = os.path.join(self.dir, fn) + os.statvfs(fullname) - def test_listdir(self): - expected = self.unicodefn - found = set(os.listdir(self.dir)) - self.assertEqual(found, expected) - # test listdir without arguments - current_directory = os.getcwd() - try: - os.chdir(os.sep) - self.assertEqual(set(os.listdir()), set(os.listdir(os.sep))) - finally: - os.chdir(current_directory) - - def test_open(self): - for fn in self.unicodefn: - f = open(os.path.join(self.dir, fn), 'rb') - f.close() - - @unittest.skipUnless(hasattr(os, 'statvfs'), - "need os.statvfs()") - def test_statvfs(self): - # issue #9645 - for fn in self.unicodefn: - # should not fail with file not found error - fullname = os.path.join(self.dir, fn) - os.statvfs(fullname) - - def test_stat(self): - for fn in self.unicodefn: - os.stat(os.path.join(self.dir, fn)) -else: - class PosixUidGidTests(unittest.TestCase): - pass - class Pep383Tests(unittest.TestCase): - pass + def test_stat(self): + for fn in self.unicodefn: + os.stat(os.path.join(self.dir, fn)) @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") class Win32KillTests(unittest.TestCase): @@ -1838,6 +1821,8 @@ SUPPORT_HEADERS_TRAILERS = not sys.platform.startswith("linux") and \ not sys.platform.startswith("solaris") and \ not sys.platform.startswith("sunos") + requires_headers_trailers = unittest.skipUnless(SUPPORT_HEADERS_TRAILERS, + 'requires headers and trailers support') @classmethod def setUpClass(cls): @@ -1956,52 +1941,54 @@ # --- headers / trailers tests - if SUPPORT_HEADERS_TRAILERS: + @requires_headers_trailers + def test_headers(self): + total_sent = 0 + sent = os.sendfile(self.sockno, self.fileno, 0, 4096, + headers=[b"x" * 512]) + total_sent += sent + offset = 4096 + nbytes = 4096 + while 1: + sent = self.sendfile_wrapper(self.sockno, self.fileno, + offset, nbytes) + if sent == 0: + break + total_sent += sent + offset += sent - def test_headers(self): - total_sent = 0 - sent = os.sendfile(self.sockno, self.fileno, 0, 4096, - headers=[b"x" * 512]) - total_sent += sent - offset = 4096 - nbytes = 4096 - while 1: - sent = self.sendfile_wrapper(self.sockno, self.fileno, - offset, nbytes) - if sent == 0: - break - total_sent += sent - offset += sent + expected_data = b"x" * 512 + self.DATA + self.assertEqual(total_sent, len(expected_data)) + self.client.close() + self.server.wait() + data = self.server.handler_instance.get_data() + self.assertEqual(hash(data), hash(expected_data)) - expected_data = b"x" * 512 + self.DATA - self.assertEqual(total_sent, len(expected_data)) + @requires_headers_trailers + def test_trailers(self): + TESTFN2 = support.TESTFN + "2" + file_data = b"abcdef" + with open(TESTFN2, 'wb') as f: + f.write(file_data) + with open(TESTFN2, 'rb')as f: + self.addCleanup(os.remove, TESTFN2) + os.sendfile(self.sockno, f.fileno(), 0, len(file_data), + trailers=[b"1234"]) self.client.close() self.server.wait() data = self.server.handler_instance.get_data() - self.assertEqual(hash(data), hash(expected_data)) + self.assertEqual(data, b"abcdef1234") - def test_trailers(self): - TESTFN2 = support.TESTFN + "2" - file_data = b"abcdef" - with open(TESTFN2, 'wb') as f: - f.write(file_data) - with open(TESTFN2, 'rb')as f: - self.addCleanup(os.remove, TESTFN2) - os.sendfile(self.sockno, f.fileno(), 0, len(file_data), - trailers=[b"1234"]) - self.client.close() - self.server.wait() - data = self.server.handler_instance.get_data() - self.assertEqual(data, b"abcdef1234") - - if hasattr(os, "SF_NODISKIO"): - def test_flags(self): - try: - os.sendfile(self.sockno, self.fileno, 0, 4096, - flags=os.SF_NODISKIO) - except OSError as err: - if err.errno not in (errno.EBUSY, errno.EAGAIN): - raise + @requires_headers_trailers + @unittest.skipUnless(hasattr(os, 'SF_NODISKIO'), + 'test needs os.SF_NODISKIO') + def test_flags(self): + try: + os.sendfile(self.sockno, self.fileno, 0, 4096, + flags=os.SF_NODISKIO) + except OSError as err: + if err.errno not in (errno.EBUSY, errno.EAGAIN): + raise def supports_extended_attributes(): diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py --- a/Lib/test/test_poplib.py +++ b/Lib/test/test_poplib.py @@ -11,7 +11,7 @@ import time import errno -from unittest import TestCase +from unittest import TestCase, skipUnless from test import support as test_support threading = test_support.import_module('threading') @@ -288,35 +288,37 @@ else: DummyPOP3Handler.handle_read(self) +requires_ssl = skipUnless(SUPPORTS_SSL, 'SSL not supported') - class TestPOP3_SSLClass(TestPOP3Class): - # repeat previous tests by using poplib.POP3_SSL + at requires_ssl +class TestPOP3_SSLClass(TestPOP3Class): + # repeat previous tests by using poplib.POP3_SSL - def setUp(self): - self.server = DummyPOP3Server((HOST, PORT)) - self.server.handler = DummyPOP3_SSLHandler - self.server.start() - self.client = poplib.POP3_SSL(self.server.host, self.server.port) + def setUp(self): + self.server = DummyPOP3Server((HOST, PORT)) + self.server.handler = DummyPOP3_SSLHandler + self.server.start() + self.client = poplib.POP3_SSL(self.server.host, self.server.port) - def test__all__(self): - self.assertIn('POP3_SSL', poplib.__all__) + def test__all__(self): + self.assertIn('POP3_SSL', poplib.__all__) - def test_context(self): - ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) - self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, - self.server.port, keyfile=CERTFILE, context=ctx) - self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, - self.server.port, certfile=CERTFILE, context=ctx) - self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, - self.server.port, keyfile=CERTFILE, - certfile=CERTFILE, context=ctx) + def test_context(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, + self.server.port, keyfile=CERTFILE, context=ctx) + self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, + self.server.port, certfile=CERTFILE, context=ctx) + self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, + self.server.port, keyfile=CERTFILE, + certfile=CERTFILE, context=ctx) - self.client.quit() - self.client = poplib.POP3_SSL(self.server.host, self.server.port, - context=ctx) - self.assertIsInstance(self.client.sock, ssl.SSLSocket) - self.assertIs(self.client.sock.context, ctx) - self.assertTrue(self.client.noop().startswith(b'+OK')) + self.client.quit() + self.client = poplib.POP3_SSL(self.server.host, self.server.port, + context=ctx) + self.assertIsInstance(self.client.sock, ssl.SSLSocket) + self.assertIs(self.client.sock.context, ctx) + self.assertTrue(self.client.noop().startswith(b'+OK')) class TestTimeouts(TestCase): @@ -374,9 +376,8 @@ def test_main(): - tests = [TestPOP3Class, TestTimeouts] - if SUPPORTS_SSL: - tests.append(TestPOP3_SSLClass) + tests = [TestPOP3Class, TestTimeouts, + TestPOP3_SSLClass] thread_info = test_support.threading_setup() try: test_support.run_unittest(*tests) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -54,47 +54,55 @@ posix_func() self.assertRaises(TypeError, posix_func, 1) - if hasattr(posix, 'getresuid'): - def test_getresuid(self): - user_ids = posix.getresuid() - self.assertEqual(len(user_ids), 3) - for val in user_ids: - self.assertGreaterEqual(val, 0) + @unittest.skipUnless(hasattr(posix, 'getresuid'), + 'test needs posix.getresuid()') + def test_getresuid(self): + user_ids = posix.getresuid() + self.assertEqual(len(user_ids), 3) + for val in user_ids: + self.assertGreaterEqual(val, 0) - if hasattr(posix, 'getresgid'): - def test_getresgid(self): - group_ids = posix.getresgid() - self.assertEqual(len(group_ids), 3) - for val in group_ids: - self.assertGreaterEqual(val, 0) + @unittest.skipUnless(hasattr(posix, 'getresgid'), + 'test needs posix.getresgid()') + def test_getresgid(self): + group_ids = posix.getresgid() + self.assertEqual(len(group_ids), 3) + for val in group_ids: + self.assertGreaterEqual(val, 0) - if hasattr(posix, 'setresuid'): - def test_setresuid(self): - current_user_ids = posix.getresuid() - self.assertIsNone(posix.setresuid(*current_user_ids)) - # -1 means don't change that value. - self.assertIsNone(posix.setresuid(-1, -1, -1)) + @unittest.skipUnless(hasattr(posix, 'setresuid'), + 'test needs posix.setresuid()') + def test_setresuid(self): + current_user_ids = posix.getresuid() + self.assertIsNone(posix.setresuid(*current_user_ids)) + # -1 means don't change that value. + self.assertIsNone(posix.setresuid(-1, -1, -1)) - def test_setresuid_exception(self): - # Don't do this test if someone is silly enough to run us as root. - current_user_ids = posix.getresuid() - if 0 not in current_user_ids: - new_user_ids = (current_user_ids[0]+1, -1, -1) - self.assertRaises(OSError, posix.setresuid, *new_user_ids) + @unittest.skipUnless(hasattr(posix, 'setresuid'), + 'test needs posix.setresuid()') + def test_setresuid_exception(self): + # Don't do this test if someone is silly enough to run us as root. + current_user_ids = posix.getresuid() + if 0 not in current_user_ids: + new_user_ids = (current_user_ids[0]+1, -1, -1) + self.assertRaises(OSError, posix.setresuid, *new_user_ids) - if hasattr(posix, 'setresgid'): - def test_setresgid(self): - current_group_ids = posix.getresgid() - self.assertIsNone(posix.setresgid(*current_group_ids)) - # -1 means don't change that value. - self.assertIsNone(posix.setresgid(-1, -1, -1)) + @unittest.skipUnless(hasattr(posix, 'setresgid'), + 'test needs posix.setresgid()') + def test_setresgid(self): + current_group_ids = posix.getresgid() + self.assertIsNone(posix.setresgid(*current_group_ids)) + # -1 means don't change that value. + self.assertIsNone(posix.setresgid(-1, -1, -1)) - def test_setresgid_exception(self): - # Don't do this test if someone is silly enough to run us as root. - current_group_ids = posix.getresgid() - if 0 not in current_group_ids: - new_group_ids = (current_group_ids[0]+1, -1, -1) - self.assertRaises(OSError, posix.setresgid, *new_group_ids) + @unittest.skipUnless(hasattr(posix, 'setresgid'), + 'test needs posix.setresgid()') + def test_setresgid_exception(self): + # Don't do this test if someone is silly enough to run us as root. + current_group_ids = posix.getresgid() + if 0 not in current_group_ids: + new_group_ids = (current_group_ids[0]+1, -1, -1) + self.assertRaises(OSError, posix.setresgid, *new_group_ids) @unittest.skipUnless(hasattr(posix, 'initgroups'), "test needs os.initgroups()") @@ -121,29 +129,32 @@ else: self.fail("Expected OSError to be raised by initgroups") + @unittest.skipUnless(hasattr(posix, 'statvfs'), + 'test needs posix.statvfs()') def test_statvfs(self): - if hasattr(posix, 'statvfs'): - self.assertTrue(posix.statvfs(os.curdir)) + self.assertTrue(posix.statvfs(os.curdir)) + @unittest.skipUnless(hasattr(posix, 'fstatvfs'), + 'test needs posix.fstatvfs()') def test_fstatvfs(self): - if hasattr(posix, 'fstatvfs'): - fp = open(support.TESTFN) - try: - self.assertTrue(posix.fstatvfs(fp.fileno())) - self.assertTrue(posix.statvfs(fp.fileno())) - finally: - fp.close() + fp = open(support.TESTFN) + try: + self.assertTrue(posix.fstatvfs(fp.fileno())) + self.assertTrue(posix.statvfs(fp.fileno())) + finally: + fp.close() + @unittest.skipUnless(hasattr(posix, 'ftruncate'), + 'test needs posix.ftruncate()') def test_ftruncate(self): - if hasattr(posix, 'ftruncate'): - fp = open(support.TESTFN, 'w+') - try: - # we need to have some data to truncate - fp.write('test') - fp.flush() - posix.ftruncate(fp.fileno(), 0) - finally: - fp.close() + fp = open(support.TESTFN, 'w+') + try: + # we need to have some data to truncate + fp.write('test') + fp.flush() + posix.ftruncate(fp.fileno(), 0) + finally: + fp.close() @unittest.skipUnless(hasattr(posix, 'truncate'), "test needs posix.truncate()") def test_truncate(self): @@ -290,30 +301,33 @@ finally: os.close(fd) + @unittest.skipUnless(hasattr(posix, 'dup'), + 'test needs posix.dup()') def test_dup(self): - if hasattr(posix, 'dup'): - fp = open(support.TESTFN) - try: - fd = posix.dup(fp.fileno()) - self.assertIsInstance(fd, int) - os.close(fd) - finally: - fp.close() + fp = open(support.TESTFN) + try: + fd = posix.dup(fp.fileno()) + self.assertIsInstance(fd, int) + os.close(fd) + finally: + fp.close() + @unittest.skipUnless(hasattr(posix, 'confstr'), + 'test needs posix.confstr()') def test_confstr(self): - if hasattr(posix, 'confstr'): - self.assertRaises(ValueError, posix.confstr, "CS_garbage") - self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) + self.assertRaises(ValueError, posix.confstr, "CS_garbage") + self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) + @unittest.skipUnless(hasattr(posix, 'dup2'), + 'test needs posix.dup2()') def test_dup2(self): - if hasattr(posix, 'dup2'): - fp1 = open(support.TESTFN) - fp2 = open(support.TESTFN) - try: - posix.dup2(fp1.fileno(), fp2.fileno()) - finally: - fp1.close() - fp2.close() + fp1 = open(support.TESTFN) + fp2 = open(support.TESTFN) + try: + posix.dup2(fp1.fileno(), fp2.fileno()) + finally: + fp1.close() + fp2.close() @unittest.skipUnless(hasattr(os, 'O_CLOEXEC'), "needs os.O_CLOEXEC") @support.requires_linux_version(2, 6, 23) @@ -322,65 +336,69 @@ self.addCleanup(os.close, fd) self.assertTrue(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC) + @unittest.skipUnless(hasattr(posix, 'O_EXLOCK'), + 'test needs posix.O_EXLOCK') def test_osexlock(self): - if hasattr(posix, "O_EXLOCK"): + fd = os.open(support.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_CREAT) + self.assertRaises(OSError, os.open, support.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) + + if hasattr(posix, "O_SHLOCK"): fd = os.open(support.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_CREAT) + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) self.assertRaises(OSError, os.open, support.TESTFN, os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) os.close(fd) - if hasattr(posix, "O_SHLOCK"): - fd = os.open(support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, support.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) + @unittest.skipUnless(hasattr(posix, 'O_SHLOCK'), + 'test needs posix.O_SHLOCK') + def test_osshlock(self): + fd1 = os.open(support.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + fd2 = os.open(support.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + os.close(fd2) + os.close(fd1) - def test_osshlock(self): - if hasattr(posix, "O_SHLOCK"): - fd1 = os.open(support.TESTFN, + if hasattr(posix, "O_EXLOCK"): + fd = os.open(support.TESTFN, os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - fd2 = os.open(support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - os.close(fd2) - os.close(fd1) + self.assertRaises(OSError, os.open, support.TESTFN, + os.O_RDONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) - if hasattr(posix, "O_EXLOCK"): - fd = os.open(support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, support.TESTFN, - os.O_RDONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) - + @unittest.skipUnless(hasattr(posix, 'fstat'), + 'test needs posix.fstat()') def test_fstat(self): - if hasattr(posix, 'fstat'): - fp = open(support.TESTFN) - try: - self.assertTrue(posix.fstat(fp.fileno())) - self.assertTrue(posix.stat(fp.fileno())) - - self.assertRaisesRegex(TypeError, - 'should be string, bytes or integer, not', - posix.stat, float(fp.fileno())) - finally: - fp.close() - - def test_stat(self): - if hasattr(posix, 'stat'): - self.assertTrue(posix.stat(support.TESTFN)) - self.assertTrue(posix.stat(os.fsencode(support.TESTFN))) - self.assertTrue(posix.stat(bytearray(os.fsencode(support.TESTFN)))) + fp = open(support.TESTFN) + try: + self.assertTrue(posix.fstat(fp.fileno())) + self.assertTrue(posix.stat(fp.fileno())) self.assertRaisesRegex(TypeError, - 'can\'t specify None for path argument', - posix.stat, None) - self.assertRaisesRegex(TypeError, 'should be string, bytes or integer, not', - posix.stat, list(support.TESTFN)) - self.assertRaisesRegex(TypeError, - 'should be string, bytes or integer, not', - posix.stat, list(os.fsencode(support.TESTFN))) + posix.stat, float(fp.fileno())) + finally: + fp.close() + + @unittest.skipUnless(hasattr(posix, 'stat'), + 'test needs posix.stat()') + def test_stat(self): + self.assertTrue(posix.stat(support.TESTFN)) + self.assertTrue(posix.stat(os.fsencode(support.TESTFN))) + self.assertTrue(posix.stat(bytearray(os.fsencode(support.TESTFN)))) + + self.assertRaisesRegex(TypeError, + 'can\'t specify None for path argument', + posix.stat, None) + self.assertRaisesRegex(TypeError, + 'should be string, bytes or integer, not', + posix.stat, list(support.TESTFN)) + self.assertRaisesRegex(TypeError, + 'should be string, bytes or integer, not', + posix.stat, list(os.fsencode(support.TESTFN))) @unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()") def test_mkfifo(self): @@ -495,10 +513,10 @@ self._test_all_chown_common(posix.lchown, support.TESTFN, getattr(posix, 'lstat', None)) + @unittest.skipUnless(hasattr(posix, 'chdir'), 'test needs posix.chdir()') def test_chdir(self): - if hasattr(posix, 'chdir'): - posix.chdir(os.curdir) - self.assertRaises(OSError, posix.chdir, support.TESTFN) + posix.chdir(os.curdir) + self.assertRaises(OSError, posix.chdir, support.TESTFN) def test_listdir(self): self.assertTrue(support.TESTFN in posix.listdir(os.curdir)) @@ -528,25 +546,26 @@ sorted(posix.listdir(f)) ) + @unittest.skipUnless(hasattr(posix, 'access'), 'test needs posix.access()') def test_access(self): - if hasattr(posix, 'access'): - self.assertTrue(posix.access(support.TESTFN, os.R_OK)) + self.assertTrue(posix.access(support.TESTFN, os.R_OK)) + @unittest.skipUnless(hasattr(posix, 'umask'), 'test needs posix.umask()') def test_umask(self): - if hasattr(posix, 'umask'): - old_mask = posix.umask(0) - self.assertIsInstance(old_mask, int) - posix.umask(old_mask) + old_mask = posix.umask(0) + self.assertIsInstance(old_mask, int) + posix.umask(old_mask) + @unittest.skipUnless(hasattr(posix, 'strerror'), + 'test needs posix.strerror()') def test_strerror(self): - if hasattr(posix, 'strerror'): - self.assertTrue(posix.strerror(0)) + self.assertTrue(posix.strerror(0)) + @unittest.skipUnless(hasattr(posix, 'pipe'), 'test needs posix.pipe()') def test_pipe(self): - if hasattr(posix, 'pipe'): - reader, writer = posix.pipe() - os.close(reader) - os.close(writer) + reader, writer = posix.pipe() + os.close(reader) + os.close(writer) @unittest.skipUnless(hasattr(os, 'pipe2'), "test needs os.pipe2()") @support.requires_linux_version(2, 6, 27) @@ -578,15 +597,15 @@ self.assertRaises(OverflowError, os.pipe2, _testcapi.INT_MAX + 1) self.assertRaises(OverflowError, os.pipe2, _testcapi.UINT_MAX + 1) + @unittest.skipUnless(hasattr(posix, 'utime'), 'test needs posix.utime()') def test_utime(self): - if hasattr(posix, 'utime'): - now = time.time() - posix.utime(support.TESTFN, None) - self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, None)) - self.assertRaises(TypeError, posix.utime, support.TESTFN, (now, None)) - self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, now)) - posix.utime(support.TESTFN, (int(now), int(now))) - posix.utime(support.TESTFN, (now, now)) + now = time.time() + posix.utime(support.TESTFN, None) + self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, None)) + self.assertRaises(TypeError, posix.utime, support.TESTFN, (now, None)) + self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, now)) + posix.utime(support.TESTFN, (int(now), int(now))) + posix.utime(support.TESTFN, (now, now)) def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs): st = os.stat(target_file) @@ -663,6 +682,7 @@ self.assertEqual(type(k), item_type) self.assertEqual(type(v), item_type) + @unittest.skipUnless(hasattr(posix, 'getcwd'), 'test needs posix.getcwd()') def test_getcwd_long_pathnames(self): if hasattr(posix, 'getcwd'): dirname = 'getcwd-test-directory-0123456789abcdef-01234567890abcdef' diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -625,10 +625,10 @@ myset >= myobj self.assertTrue(myobj.le_called) - # C API test only available in a debug build - if hasattr(set, "test_c_api"): - def test_c_api(self): - self.assertEqual(set().test_c_api(), True) + @unittest.skipUnless(hasattr(set, "test_c_api"), + 'C API test only available in a debug build') + def test_c_api(self): + self.assertEqual(set().test_c_api(), True) class SetSubclass(set): pass diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -194,37 +194,37 @@ self.assertIn(errors[1][2][1].filename, possible_args) - # See bug #1071513 for why we don't run this on cygwin - # and bug #1076467 for why we don't run this as root. - if (hasattr(os, 'chmod') and sys.platform[:6] != 'cygwin' - and not (hasattr(os, 'geteuid') and os.geteuid() == 0)): - def test_on_error(self): - self.errorState = 0 - os.mkdir(TESTFN) - self.addCleanup(shutil.rmtree, TESTFN) + @unittest.skipUnless(hasattr(os, 'chmod'), 'requires os.chmod()') + @unittest.skipIf(sys.platform[:6] == 'cygwin', + "This test can't be run on Cygwin (issue #1071513).") + @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, + "This test can't be run reliably as root (issue #1076467).") + def test_on_error(self): + self.errorState = 0 + os.mkdir(TESTFN) + self.addCleanup(shutil.rmtree, TESTFN) - self.child_file_path = os.path.join(TESTFN, 'a') - self.child_dir_path = os.path.join(TESTFN, 'b') - support.create_empty_file(self.child_file_path) - os.mkdir(self.child_dir_path) - old_dir_mode = os.stat(TESTFN).st_mode - old_child_file_mode = os.stat(self.child_file_path).st_mode - old_child_dir_mode = os.stat(self.child_dir_path).st_mode - # Make unwritable. - new_mode = stat.S_IREAD|stat.S_IEXEC - os.chmod(self.child_file_path, new_mode) - os.chmod(self.child_dir_path, new_mode) - os.chmod(TESTFN, new_mode) + self.child_file_path = os.path.join(TESTFN, 'a') + self.child_dir_path = os.path.join(TESTFN, 'b') + support.create_empty_file(self.child_file_path) + os.mkdir(self.child_dir_path) + old_dir_mode = os.stat(TESTFN).st_mode + old_child_file_mode = os.stat(self.child_file_path).st_mode + old_child_dir_mode = os.stat(self.child_dir_path).st_mode + # Make unwritable. + new_mode = stat.S_IREAD|stat.S_IEXEC + os.chmod(self.child_file_path, new_mode) + os.chmod(self.child_dir_path, new_mode) + os.chmod(TESTFN, new_mode) - self.addCleanup(os.chmod, TESTFN, old_dir_mode) - self.addCleanup(os.chmod, self.child_file_path, old_child_file_mode) - self.addCleanup(os.chmod, self.child_dir_path, old_child_dir_mode) + self.addCleanup(os.chmod, TESTFN, old_dir_mode) + self.addCleanup(os.chmod, self.child_file_path, old_child_file_mode) + self.addCleanup(os.chmod, self.child_dir_path, old_child_dir_mode) - shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) - # Test whether onerror has actually been called. - self.assertEqual(self.errorState, 3, - "Expected call to onerror function did not " - "happen.") + shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) + # Test whether onerror has actually been called. + self.assertEqual(self.errorState, 3, + "Expected call to onerror function did not happen.") def check_args_to_onerror(self, func, arg, exc): # test_rmtree_errors deliberately runs rmtree @@ -806,38 +806,39 @@ finally: shutil.rmtree(TESTFN, ignore_errors=True) - if hasattr(os, "mkfifo"): - # Issue #3002: copyfile and copytree block indefinitely on named pipes - def test_copyfile_named_pipe(self): - os.mkfifo(TESTFN) + # Issue #3002: copyfile and copytree block indefinitely on named pipes + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + def test_copyfile_named_pipe(self): + os.mkfifo(TESTFN) + try: + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, TESTFN, TESTFN2) + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, __file__, TESTFN) + finally: + os.remove(TESTFN) + + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + @support.skip_unless_symlink + def test_copytree_named_pipe(self): + os.mkdir(TESTFN) + try: + subdir = os.path.join(TESTFN, "subdir") + os.mkdir(subdir) + pipe = os.path.join(subdir, "mypipe") + os.mkfifo(pipe) try: - self.assertRaises(shutil.SpecialFileError, - shutil.copyfile, TESTFN, TESTFN2) - self.assertRaises(shutil.SpecialFileError, - shutil.copyfile, __file__, TESTFN) - finally: - os.remove(TESTFN) - - @support.skip_unless_symlink - def test_copytree_named_pipe(self): - os.mkdir(TESTFN) - try: - subdir = os.path.join(TESTFN, "subdir") - os.mkdir(subdir) - pipe = os.path.join(subdir, "mypipe") - os.mkfifo(pipe) - try: - shutil.copytree(TESTFN, TESTFN2) - except shutil.Error as e: - errors = e.args[0] - self.assertEqual(len(errors), 1) - src, dst, error_msg = errors[0] - self.assertEqual("`%s` is a named pipe" % pipe, error_msg) - else: - self.fail("shutil.Error should have been raised") - finally: - shutil.rmtree(TESTFN, ignore_errors=True) - shutil.rmtree(TESTFN2, ignore_errors=True) + shutil.copytree(TESTFN, TESTFN2) + except shutil.Error as e: + errors = e.args[0] + self.assertEqual(len(errors), 1) + src, dst, error_msg = errors[0] + self.assertEqual("`%s` is a named pipe" % pipe, error_msg) + else: + self.fail("shutil.Error should have been raised") + finally: + shutil.rmtree(TESTFN, ignore_errors=True) + shutil.rmtree(TESTFN2, ignore_errors=True) def test_copytree_special_func(self): diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -772,16 +772,17 @@ self.assertRaises(TypeError, socket.if_nametoindex, 0) self.assertRaises(TypeError, socket.if_indextoname, '_DEADBEEF') + @unittest.skipUnless(hasattr(sys, 'getrefcount'), + 'test needs sys.getrefcount()') def testRefCountGetNameInfo(self): # Testing reference count for getnameinfo - if hasattr(sys, "getrefcount"): - try: - # On some versions, this loses a reference - orig = sys.getrefcount(__name__) - socket.getnameinfo(__name__,0) - except TypeError: - if sys.getrefcount(__name__) != orig: - self.fail("socket.getnameinfo loses a reference") + try: + # On some versions, this loses a reference + orig = sys.getrefcount(__name__) + socket.getnameinfo(__name__,0) + except TypeError: + if sys.getrefcount(__name__) != orig: + self.fail("socket.getnameinfo loses a reference") def testInterpreterCrash(self): # Making sure getnameinfo doesn't crash the interpreter @@ -886,17 +887,17 @@ # Check that setting it to an invalid type raises TypeError self.assertRaises(TypeError, socket.setdefaulttimeout, "spam") + @unittest.skipUnless(hasattr(socket, 'inet_aton'), + 'test needs socket.inet_aton()') def testIPv4_inet_aton_fourbytes(self): - if not hasattr(socket, 'inet_aton'): - return # No inet_aton, nothing to check # Test that issue1008086 and issue767150 are fixed. # It must return 4 bytes. self.assertEqual(b'\x00'*4, socket.inet_aton('0.0.0.0')) self.assertEqual(b'\xff'*4, socket.inet_aton('255.255.255.255')) + @unittest.skipUnless(hasattr(socket, 'inet_pton'), + 'test needs socket.inet_pton()') def testIPv4toString(self): - if not hasattr(socket, 'inet_pton'): - return # No inet_pton() on this platform from socket import inet_aton as f, inet_pton, AF_INET g = lambda a: inet_pton(AF_INET, a) @@ -925,9 +926,9 @@ assertInvalid(g, '1.2.3.4.5') assertInvalid(g, '::1') + @unittest.skipUnless(hasattr(socket, 'inet_pton'), + 'test needs socket.inet_pton()') def testIPv6toString(self): - if not hasattr(socket, 'inet_pton'): - return # No inet_pton() on this platform try: from socket import inet_pton, AF_INET6, has_ipv6 if not has_ipv6: @@ -979,9 +980,9 @@ assertInvalid('::1.2.3.4:0') assertInvalid('0.100.200.0:3:4:5:6:7:8') + @unittest.skipUnless(hasattr(socket, 'inet_ntop'), + 'test needs socket.inet_ntop()') def testStringToIPv4(self): - if not hasattr(socket, 'inet_ntop'): - return # No inet_ntop() on this platform from socket import inet_ntoa as f, inet_ntop, AF_INET g = lambda a: inet_ntop(AF_INET, a) assertInvalid = lambda func,a: self.assertRaises( @@ -1003,9 +1004,9 @@ assertInvalid(g, b'\x00' * 5) assertInvalid(g, b'\x00' * 16) + @unittest.skipUnless(hasattr(socket, 'inet_ntop'), + 'test needs socket.inet_ntop()') def testStringToIPv6(self): - if not hasattr(socket, 'inet_ntop'): - return # No inet_ntop() on this platform try: from socket import inet_ntop, AF_INET6, has_ipv6 if not has_ipv6: @@ -3531,6 +3532,8 @@ self.cli.connect((HOST, self.port)) time.sleep(1.0) + at unittest.skipUnless(hasattr(socket, 'socketpair'), + 'test needs socket.socketpair()') @unittest.skipUnless(thread, 'Threading required for this test.') class BasicSocketPairTest(SocketPairTest): @@ -3593,26 +3596,27 @@ def _testSetBlocking(self): pass - if hasattr(socket, "SOCK_NONBLOCK"): - @support.requires_linux_version(2, 6, 28) - def testInitNonBlocking(self): - # reinit server socket - self.serv.close() - self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM | - socket.SOCK_NONBLOCK) - self.port = support.bind_port(self.serv) - self.serv.listen(1) - # actual testing - start = time.time() - try: - self.serv.accept() - except socket.error: - pass - end = time.time() - self.assertTrue((end - start) < 1.0, "Error creating with non-blocking mode.") - - def _testInitNonBlocking(self): + @unittest.skipUnless(hasattr(socket, 'SOCK_NONBLOCK'), + 'test needs socket.SOCK_NONBLOCK') + @support.requires_linux_version(2, 6, 28) + def testInitNonBlocking(self): + # reinit server socket + self.serv.close() + self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM | + socket.SOCK_NONBLOCK) + self.port = support.bind_port(self.serv) + self.serv.listen(1) + # actual testing + start = time.time() + try: + self.serv.accept() + except socket.error: pass + end = time.time() + self.assertTrue((end - start) < 1.0, "Error creating with non-blocking mode.") + + def _testInitNonBlocking(self): + pass def testInheritFlags(self): # Issue #7995: when calling accept() on a listening socket with a @@ -4302,12 +4306,12 @@ if not ok: self.fail("accept() returned success when we did not expect it") + @unittest.skipUnless(hasattr(signal, 'alarm'), + 'test needs signal.alarm()') def testInterruptedTimeout(self): # XXX I don't know how to do this test on MSWindows or any other # plaform that doesn't support signal.alarm() or os.kill(), though # the bug should have existed on all platforms. - if not hasattr(signal, "alarm"): - return # can only test on *nix self.serv.settimeout(5.0) # must be longer than alarm class Alarm(Exception): pass @@ -4367,6 +4371,7 @@ self.assertTrue(issubclass(socket.gaierror, socket.error)) self.assertTrue(issubclass(socket.timeout, socket.error)) + at unittest.skipUnless(sys.platform == 'linux', 'Linux specific test') class TestLinuxAbstractNamespace(unittest.TestCase): UNIX_PATH_MAX = 108 @@ -4402,6 +4407,7 @@ finally: s.close() + at unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'test needs socket.AF_UNIX') class TestUnixDomain(unittest.TestCase): def setUp(self): @@ -4551,10 +4557,10 @@ for line in f: if line.startswith("tipc "): return True - if support.verbose: - print("TIPC module is not loaded, please 'sudo modprobe tipc'") return False + at unittest.skipUnless(isTipcAvailable(), + "TIPC module is not loaded, please 'sudo modprobe tipc'") class TIPCTest(unittest.TestCase): def testRDM(self): srv = socket.socket(socket.AF_TIPC, socket.SOCK_RDM) @@ -4577,6 +4583,8 @@ self.assertEqual(msg, MSG) + at unittest.skipUnless(isTipcAvailable(), + "TIPC module is not loaded, please 'sudo modprobe tipc'") class TIPCThreadableTest(unittest.TestCase, ThreadableTest): def __init__(self, methodName = 'runTest'): unittest.TestCase.__init__(self, methodName = methodName) @@ -4842,15 +4850,10 @@ CloexecConstantTest, NonblockConstantTest ]) - if hasattr(socket, "socketpair"): - tests.append(BasicSocketPairTest) - if hasattr(socket, "AF_UNIX"): - tests.append(TestUnixDomain) - if sys.platform == 'linux': - tests.append(TestLinuxAbstractNamespace) - if isTipcAvailable(): - tests.append(TIPCTest) - tests.append(TIPCThreadableTest) + tests.append(BasicSocketPairTest) + tests.append(TestUnixDomain) + tests.append(TestLinuxAbstractNamespace) + tests.extend([TIPCTest, TIPCThreadableTest]) tests.extend([BasicCANTest, CANTest]) tests.extend([BasicRDSTest, RDSTest]) tests.extend([ diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -27,7 +27,10 @@ HOST = test.support.HOST HAVE_UNIX_SOCKETS = hasattr(socket, "AF_UNIX") +requires_unix_sockets = unittest.skipUnless(HAVE_UNIX_SOCKETS, + 'requires Unix sockets') HAVE_FORKING = hasattr(os, "fork") and os.name != "os2" +requires_forking = unittest.skipUnless(HAVE_FORKING, 'requires forking') def signal_alarm(n): """Call signal.alarm when it exists (i.e. not on Windows).""" @@ -189,31 +192,33 @@ socketserver.StreamRequestHandler, self.stream_examine) - if HAVE_FORKING: - def test_ForkingTCPServer(self): - with simple_subprocess(self): - self.run_server(socketserver.ForkingTCPServer, - socketserver.StreamRequestHandler, - self.stream_examine) - - if HAVE_UNIX_SOCKETS: - def test_UnixStreamServer(self): - self.run_server(socketserver.UnixStreamServer, + @requires_forking + def test_ForkingTCPServer(self): + with simple_subprocess(self): + self.run_server(socketserver.ForkingTCPServer, socketserver.StreamRequestHandler, self.stream_examine) - def test_ThreadingUnixStreamServer(self): - self.run_server(socketserver.ThreadingUnixStreamServer, + @requires_unix_sockets + def test_UnixStreamServer(self): + self.run_server(socketserver.UnixStreamServer, + socketserver.StreamRequestHandler, + self.stream_examine) + + @requires_unix_sockets + def test_ThreadingUnixStreamServer(self): + self.run_server(socketserver.ThreadingUnixStreamServer, + socketserver.StreamRequestHandler, + self.stream_examine) + + @requires_unix_sockets + @requires_forking + def test_ForkingUnixStreamServer(self): + with simple_subprocess(self): + self.run_server(ForkingUnixStreamServer, socketserver.StreamRequestHandler, self.stream_examine) - if HAVE_FORKING: - def test_ForkingUnixStreamServer(self): - with simple_subprocess(self): - self.run_server(ForkingUnixStreamServer, - socketserver.StreamRequestHandler, - self.stream_examine) - def test_UDPServer(self): self.run_server(socketserver.UDPServer, socketserver.DatagramRequestHandler, @@ -224,12 +229,12 @@ socketserver.DatagramRequestHandler, self.dgram_examine) - if HAVE_FORKING: - def test_ForkingUDPServer(self): - with simple_subprocess(self): - self.run_server(socketserver.ForkingUDPServer, - socketserver.DatagramRequestHandler, - self.dgram_examine) + @requires_forking + def test_ForkingUDPServer(self): + with simple_subprocess(self): + self.run_server(socketserver.ForkingUDPServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) @contextlib.contextmanager def mocked_select_module(self): @@ -266,22 +271,24 @@ # Alas, on Linux (at least) recvfrom() doesn't return a meaningful # client address so this cannot work: - # if HAVE_UNIX_SOCKETS: - # def test_UnixDatagramServer(self): - # self.run_server(socketserver.UnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # def test_UnixDatagramServer(self): + # self.run_server(socketserver.UnixDatagramServer, + # socketserver.DatagramRequestHandler, + # self.dgram_examine) # - # def test_ThreadingUnixDatagramServer(self): - # self.run_server(socketserver.ThreadingUnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # def test_ThreadingUnixDatagramServer(self): + # self.run_server(socketserver.ThreadingUnixDatagramServer, + # socketserver.DatagramRequestHandler, + # self.dgram_examine) # - # if HAVE_FORKING: - # def test_ForkingUnixDatagramServer(self): - # self.run_server(socketserver.ForkingUnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # @requires_forking + # def test_ForkingUnixDatagramServer(self): + # self.run_server(socketserver.ForkingUnixDatagramServer, + # socketserver.DatagramRequestHandler, + # self.dgram_examine) @reap_threads def test_shutdown(self): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -291,15 +291,16 @@ def test_call_tracing(self): self.assertRaises(TypeError, sys.call_tracing, type, 2) + @unittest.skipUnless(hasattr(sys, "setdlopenflags"), + 'test needs sys.setdlopenflags()') def test_dlopenflags(self): - if hasattr(sys, "setdlopenflags"): - self.assertTrue(hasattr(sys, "getdlopenflags")) - self.assertRaises(TypeError, sys.getdlopenflags, 42) - oldflags = sys.getdlopenflags() - self.assertRaises(TypeError, sys.setdlopenflags) - sys.setdlopenflags(oldflags+1) - self.assertEqual(sys.getdlopenflags(), oldflags+1) - sys.setdlopenflags(oldflags) + self.assertTrue(hasattr(sys, "getdlopenflags")) + self.assertRaises(TypeError, sys.getdlopenflags, 42) + oldflags = sys.getdlopenflags() + self.assertRaises(TypeError, sys.setdlopenflags) + sys.setdlopenflags(oldflags+1) + self.assertEqual(sys.getdlopenflags(), oldflags+1) + sys.setdlopenflags(oldflags) @test.support.refcount_test def test_refcount(self): diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -271,11 +271,10 @@ finally: warning_tests.__file__ = filename + @unittest.skipUnless(hasattr(sys, 'argv'), 'test needs sys.argv') def test_missing_filename_main_with_argv(self): # If __file__ is not specified and the caller is __main__ and sys.argv # exists, then use sys.argv[0] as the file. - if not hasattr(sys, 'argv'): - return filename = warning_tests.__file__ module_name = warning_tests.__name__ try: diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -7,6 +7,13 @@ zlib = support.import_module('zlib') +requires_Compress_copy = unittest.skipUnless( + hasattr(zlib.compressobj(), "copy"), + 'requires Compress.copy()') +requires_Decompress_copy = unittest.skipUnless( + hasattr(zlib.decompressobj(), "copy"), + 'requires Decompress.copy()') + class VersionTestCase(unittest.TestCase): @@ -381,39 +388,39 @@ "mode=%i, level=%i") % (sync, level)) del obj + @unittest.skipUnless(hasattr(zlib, 'Z_SYNC_FLUSH'), + 'requires zlib.Z_SYNC_FLUSH') def test_odd_flush(self): # Test for odd flushing bugs noted in 2.0, and hopefully fixed in 2.1 import random + # Testing on 17K of "random" data - if hasattr(zlib, 'Z_SYNC_FLUSH'): - # Testing on 17K of "random" data + # Create compressor and decompressor objects + co = zlib.compressobj(zlib.Z_BEST_COMPRESSION) + dco = zlib.decompressobj() - # Create compressor and decompressor objects - co = zlib.compressobj(zlib.Z_BEST_COMPRESSION) - dco = zlib.decompressobj() + # Try 17K of data + # generate random data stream + try: + # In 2.3 and later, WichmannHill is the RNG of the bug report + gen = random.WichmannHill() + except AttributeError: + try: + # 2.2 called it Random + gen = random.Random() + except AttributeError: + # others might simply have a single RNG + gen = random + gen.seed(1) + data = genblock(1, 17 * 1024, generator=gen) - # Try 17K of data - # generate random data stream - try: - # In 2.3 and later, WichmannHill is the RNG of the bug report - gen = random.WichmannHill() - except AttributeError: - try: - # 2.2 called it Random - gen = random.Random() - except AttributeError: - # others might simply have a single RNG - gen = random - gen.seed(1) - data = genblock(1, 17 * 1024, generator=gen) + # compress, sync-flush, and decompress + first = co.compress(data) + second = co.flush(zlib.Z_SYNC_FLUSH) + expanded = dco.decompress(first + second) - # compress, sync-flush, and decompress - first = co.compress(data) - second = co.flush(zlib.Z_SYNC_FLUSH) - expanded = dco.decompress(first + second) - - # if decompressed data is different from the input data, choke. - self.assertEqual(expanded, data, "17K random source doesn't match") + # if decompressed data is different from the input data, choke. + self.assertEqual(expanded, data, "17K random source doesn't match") def test_empty_flush(self): # Test that calling .flush() on unused objects works. @@ -525,67 +532,69 @@ data = zlib.compress(input2) self.assertEqual(dco.flush(), input1[1:]) - if hasattr(zlib.compressobj(), "copy"): - def test_compresscopy(self): - # Test copying a compression object - data0 = HAMLET_SCENE - data1 = bytes(str(HAMLET_SCENE, "ascii").swapcase(), "ascii") - c0 = zlib.compressobj(zlib.Z_BEST_COMPRESSION) - bufs0 = [] - bufs0.append(c0.compress(data0)) + @requires_Compress_copy + def test_compresscopy(self): + # Test copying a compression object + data0 = HAMLET_SCENE + data1 = bytes(str(HAMLET_SCENE, "ascii").swapcase(), "ascii") + c0 = zlib.compressobj(zlib.Z_BEST_COMPRESSION) + bufs0 = [] + bufs0.append(c0.compress(data0)) - c1 = c0.copy() - bufs1 = bufs0[:] + c1 = c0.copy() + bufs1 = bufs0[:] - bufs0.append(c0.compress(data0)) - bufs0.append(c0.flush()) - s0 = b''.join(bufs0) + bufs0.append(c0.compress(data0)) + bufs0.append(c0.flush()) + s0 = b''.join(bufs0) - bufs1.append(c1.compress(data1)) - bufs1.append(c1.flush()) - s1 = b''.join(bufs1) + bufs1.append(c1.compress(data1)) + bufs1.append(c1.flush()) + s1 = b''.join(bufs1) - self.assertEqual(zlib.decompress(s0),data0+data0) - self.assertEqual(zlib.decompress(s1),data0+data1) + self.assertEqual(zlib.decompress(s0),data0+data0) + self.assertEqual(zlib.decompress(s1),data0+data1) - def test_badcompresscopy(self): - # Test copying a compression object in an inconsistent state - c = zlib.compressobj() - c.compress(HAMLET_SCENE) - c.flush() - self.assertRaises(ValueError, c.copy) + @requires_Compress_copy + def test_badcompresscopy(self): + # Test copying a compression object in an inconsistent state + c = zlib.compressobj() + c.compress(HAMLET_SCENE) + c.flush() + self.assertRaises(ValueError, c.copy) - if hasattr(zlib.decompressobj(), "copy"): - def test_decompresscopy(self): - # Test copying a decompression object - data = HAMLET_SCENE - comp = zlib.compress(data) - # Test type of return value - self.assertIsInstance(comp, bytes) + @requires_Decompress_copy + def test_decompresscopy(self): + # Test copying a decompression object + data = HAMLET_SCENE + comp = zlib.compress(data) + # Test type of return value + self.assertIsInstance(comp, bytes) - d0 = zlib.decompressobj() - bufs0 = [] - bufs0.append(d0.decompress(comp[:32])) + d0 = zlib.decompressobj() + bufs0 = [] + bufs0.append(d0.decompress(comp[:32])) - d1 = d0.copy() - bufs1 = bufs0[:] + d1 = d0.copy() + bufs1 = bufs0[:] - bufs0.append(d0.decompress(comp[32:])) - s0 = b''.join(bufs0) + bufs0.append(d0.decompress(comp[32:])) + s0 = b''.join(bufs0) - bufs1.append(d1.decompress(comp[32:])) - s1 = b''.join(bufs1) + bufs1.append(d1.decompress(comp[32:])) + s1 = b''.join(bufs1) - self.assertEqual(s0,s1) - self.assertEqual(s0,data) + self.assertEqual(s0,s1) + self.assertEqual(s0,data) - def test_baddecompresscopy(self): - # Test copying a compression object in an inconsistent state - data = zlib.compress(HAMLET_SCENE) - d = zlib.decompressobj() - d.decompress(data) - d.flush() - self.assertRaises(ValueError, d.copy) + @requires_Decompress_copy + def test_baddecompresscopy(self): + # Test copying a compression object in an inconsistent state + data = zlib.compress(HAMLET_SCENE) + d = zlib.decompressobj() + d.decompress(data) + d.flush() + self.assertRaises(ValueError, d.copy) # Memory use of the following functions takes into account overallocation diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -29,6 +29,8 @@ Tests ----- +- Issue #18702: All skipped tests now reported as skipped. + - Issue #19085: Added basic tests for all tkinter widget options. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 20:32:23 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 20:32:23 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2318702=3A_All_skip?= =?utf-8?q?ped_tests_now_reported_as_skipped=2E?= Message-ID: <3dCS2b1sW0z7LkF@mail.python.org> http://hg.python.org/cpython/rev/09105051b9f4 changeset: 86896:09105051b9f4 parent: 86893:fc1f353b8e7e user: Serhiy Storchaka date: Sun Nov 03 21:31:38 2013 +0200 summary: Issue #18702: All skipped tests now reported as skipped. files: Lib/test/test_array.py | 17 +- Lib/test/test_compileall.py | 3 +- Lib/test/test_csv.py | 135 +++--- Lib/test/test_dbm_dumb.py | 6 +- Lib/test/test_enumerate.py | 3 +- Lib/test/test_ftplib.py | 14 +- Lib/test/test_mailbox.py | 36 +- Lib/test/test_math.py | 59 +- Lib/test/test_mmap.py | 144 +++--- Lib/test/test_nntplib.py | 74 +- Lib/test/test_os.py | 465 +++++++++++---------- Lib/test/test_poplib.py | 146 +++--- Lib/test/test_posix.py | 304 +++++++------ Lib/test/test_set.py | 8 +- Lib/test/test_shutil.py | 119 ++-- Lib/test/test_socket.py | 103 ++-- Lib/test/test_socketserver.py | 85 ++- Lib/test/test_sys.py | 17 +- Lib/test/test_warnings.py | 3 +- Lib/test/test_zlib.py | 159 +++--- Misc/NEWS | 2 + 21 files changed, 972 insertions(+), 930 deletions(-) diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -11,6 +11,7 @@ import io import math import struct +import sys import warnings import array @@ -993,15 +994,15 @@ s = None self.assertRaises(ReferenceError, len, p) + @unittest.skipUnless(hasattr(sys, 'getrefcount'), + 'test needs sys.getrefcount()') def test_bug_782369(self): - import sys - if hasattr(sys, "getrefcount"): - for i in range(10): - b = array.array('B', range(64)) - rc = sys.getrefcount(10) - for i in range(10): - b = array.array('B', range(64)) - self.assertEqual(rc, sys.getrefcount(10)) + for i in range(10): + b = array.array('B', range(64)) + rc = sys.getrefcount(10) + for i in range(10): + b = array.array('B', range(64)) + self.assertEqual(rc, sys.getrefcount(10)) def test_subclass_with_kwargs(self): # SF bug #1486663 -- this used to erroneously raise a TypeError diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -40,11 +40,10 @@ compare = struct.pack('<4sl', importlib.util.MAGIC_NUMBER, mtime) return data, compare + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def recreation_check(self, metadata): """Check that compileall recreates bytecode when the new metadata is used.""" - if not hasattr(os, 'stat'): - return py_compile.compile(self.source_path) self.assertEqual(*self.data()) with open(self.bc_path, 'rb') as file: diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -905,78 +905,77 @@ dialect = sniffer.sniff(self.sample9) self.assertTrue(dialect.doublequote) -if not hasattr(sys, "gettotalrefcount"): - if support.verbose: print("*** skipping leakage tests ***") -else: - class NUL: - def write(s, *args): - pass - writelines = write +class NUL: + def write(s, *args): + pass + writelines = write - class TestLeaks(unittest.TestCase): - def test_create_read(self): - delta = 0 - lastrc = sys.gettotalrefcount() - for i in range(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - delta = rc-lastrc - lastrc = rc - # if csv.reader() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) + at unittest.skipUnless(hasattr(sys, "gettotalrefcount"), + 'requires sys.gettotalrefcount()') +class TestLeaks(unittest.TestCase): + def test_create_read(self): + delta = 0 + lastrc = sys.gettotalrefcount() + for i in range(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + csv.reader(["a,b,c\r\n"]) + csv.reader(["a,b,c\r\n"]) + csv.reader(["a,b,c\r\n"]) + delta = rc-lastrc + lastrc = rc + # if csv.reader() leaks, last delta should be 3 or more + self.assertEqual(delta < 3, True) - def test_create_write(self): - delta = 0 - lastrc = sys.gettotalrefcount() - s = NUL() - for i in range(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.writer(s) - csv.writer(s) - csv.writer(s) - delta = rc-lastrc - lastrc = rc - # if csv.writer() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) + def test_create_write(self): + delta = 0 + lastrc = sys.gettotalrefcount() + s = NUL() + for i in range(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + csv.writer(s) + csv.writer(s) + csv.writer(s) + delta = rc-lastrc + lastrc = rc + # if csv.writer() leaks, last delta should be 3 or more + self.assertEqual(delta < 3, True) - def test_read(self): - delta = 0 - rows = ["a,b,c\r\n"]*5 - lastrc = sys.gettotalrefcount() - for i in range(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - rdr = csv.reader(rows) - for row in rdr: - pass - delta = rc-lastrc - lastrc = rc - # if reader leaks during read, delta should be 5 or more - self.assertEqual(delta < 5, True) + def test_read(self): + delta = 0 + rows = ["a,b,c\r\n"]*5 + lastrc = sys.gettotalrefcount() + for i in range(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + rdr = csv.reader(rows) + for row in rdr: + pass + delta = rc-lastrc + lastrc = rc + # if reader leaks during read, delta should be 5 or more + self.assertEqual(delta < 5, True) - def test_write(self): - delta = 0 - rows = [[1,2,3]]*5 - s = NUL() - lastrc = sys.gettotalrefcount() - for i in range(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - writer = csv.writer(s) - for row in rows: - writer.writerow(row) - delta = rc-lastrc - lastrc = rc - # if writer leaks during write, last delta should be 5 or more - self.assertEqual(delta < 5, True) + def test_write(self): + delta = 0 + rows = [[1,2,3]]*5 + s = NUL() + lastrc = sys.gettotalrefcount() + for i in range(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + writer = csv.writer(s) + for row in rows: + writer.writerow(row) + delta = rc-lastrc + lastrc = rc + # if writer leaks during write, last delta should be 5 or more + self.assertEqual(delta < 5, True) class TestUnicode(unittest.TestCase): diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -37,11 +37,9 @@ self.read_helper(f) f.close() + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'chmod'), 'test needs os.chmod()') def test_dumbdbm_creation_mode(self): - # On platforms without chmod, don't do anything. - if not (hasattr(os, 'chmod') and hasattr(os, 'umask')): - return - try: old_umask = os.umask(0o002) f = dumbdbm.open(_fname, 'c', 0o637) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -202,11 +202,10 @@ self.assertRaises(TypeError, reversed) self.assertRaises(TypeError, reversed, [], 'extra') + @unittest.skipUnless(hasattr(sys, 'getrefcount'), 'test needs sys.getrefcount()') def test_bug1229429(self): # this bug was never in reversed, it was in # PyObject_CallMethod, and reversed_new calls that sometimes. - if not hasattr(sys, "getrefcount"): - return def f(): pass r = f.__reversed__ = object() diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -16,7 +16,7 @@ except ImportError: ssl = None -from unittest import TestCase +from unittest import TestCase, skipUnless from test import support from test.support import HOST, HOSTv6 threading = support.import_module('threading') @@ -780,6 +780,7 @@ self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f) + at skipUnless(support.IPV6_ENABLED, "IPv6 not enabled") class TestIPv6Environment(TestCase): def setUp(self): @@ -820,6 +821,7 @@ retr() + at skipUnless(ssl, "SSL not available") class TestTLS_FTPClassMixin(TestFTPClass): """Repeat TestFTPClass tests starting the TLS layer for both control and data connections first. @@ -835,6 +837,7 @@ self.client.prot_p() + at skipUnless(ssl, "SSL not available") class TestTLS_FTPClass(TestCase): """Specific TLS_FTP class tests.""" @@ -1027,12 +1030,9 @@ def test_main(): - tests = [TestFTPClass, TestTimeouts, TestNetrcDeprecation] - if support.IPV6_ENABLED: - tests.append(TestIPv6Environment) - - if ssl is not None: - tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass]) + tests = [TestFTPClass, TestTimeouts, TestNetrcDeprecation, + TestIPv6Environment, + TestTLS_FTPClassMixin, TestTLS_FTPClass] thread_info = support.threading_setup() try: diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -868,10 +868,10 @@ for msg in self._box: pass + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_file_permissions(self): # Verify that message files are created without execute permissions - if not hasattr(os, "stat") or not hasattr(os, "umask"): - return msg = mailbox.MaildirMessage(self._template % 0) orig_umask = os.umask(0) try: @@ -882,12 +882,11 @@ mode = os.stat(path).st_mode self.assertFalse(mode & 0o111) + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_folder_file_perms(self): # From bug #3228, we want to verify that the file created inside a Maildir # subfolder isn't marked as executable. - if not hasattr(os, "stat") or not hasattr(os, "umask"): - return - orig_umask = os.umask(0) try: subfolder = self._box.add_folder('subfolder') @@ -1097,24 +1096,25 @@ _factory = lambda self, path, factory=None: mailbox.mbox(path, factory) + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_file_perms(self): # From bug #3228, we want to verify that the mailbox file isn't executable, # even if the umask is set to something that would leave executable bits set. # We only run this test on platforms that support umask. - if hasattr(os, 'umask') and hasattr(os, 'stat'): - try: - old_umask = os.umask(0o077) - self._box.close() - os.unlink(self._path) - self._box = mailbox.mbox(self._path, create=True) - self._box.add('') - self._box.close() - finally: - os.umask(old_umask) + try: + old_umask = os.umask(0o077) + self._box.close() + os.unlink(self._path) + self._box = mailbox.mbox(self._path, create=True) + self._box.add('') + self._box.close() + finally: + os.umask(old_umask) - st = os.stat(self._path) - perms = st.st_mode - self.assertFalse((perms & 0o111)) # Execute bits should all be off. + st = os.stat(self._path) + perms = st.st_mode + self.assertFalse((perms & 0o111)) # Execute bits should all be off. def test_terminating_newline(self): message = email.message.Message() diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -980,38 +980,37 @@ # still fails this part of the test on some platforms. For now, we only # *run* test_exceptions() in verbose mode, so that this isn't normally # tested. + @unittest.skipUnless(verbose, 'requires verbose mode') + def test_exceptions(self): + try: + x = math.exp(-1000000000) + except: + # mathmodule.c is failing to weed out underflows from libm, or + # we've got an fp format with huge dynamic range + self.fail("underflowing exp() should not have raised " + "an exception") + if x != 0: + self.fail("underflowing exp() should have returned 0") - if verbose: - def test_exceptions(self): - try: - x = math.exp(-1000000000) - except: - # mathmodule.c is failing to weed out underflows from libm, or - # we've got an fp format with huge dynamic range - self.fail("underflowing exp() should not have raised " - "an exception") - if x != 0: - self.fail("underflowing exp() should have returned 0") + # If this fails, probably using a strict IEEE-754 conforming libm, and x + # is +Inf afterwards. But Python wants overflows detected by default. + try: + x = math.exp(1000000000) + except OverflowError: + pass + else: + self.fail("overflowing exp() didn't trigger OverflowError") - # If this fails, probably using a strict IEEE-754 conforming libm, and x - # is +Inf afterwards. But Python wants overflows detected by default. - try: - x = math.exp(1000000000) - except OverflowError: - pass - else: - self.fail("overflowing exp() didn't trigger OverflowError") - - # If this fails, it could be a puzzle. One odd possibility is that - # mathmodule.c's macros are getting confused while comparing - # Inf (HUGE_VAL) to a NaN, and artificially setting errno to ERANGE - # as a result (and so raising OverflowError instead). - try: - x = math.sqrt(-1.0) - except ValueError: - pass - else: - self.fail("sqrt(-1) didn't raise ValueError") + # If this fails, it could be a puzzle. One odd possibility is that + # mathmodule.c's macros are getting confused while comparing + # Inf (HUGE_VAL) to a NaN, and artificially setting errno to ERANGE + # as a result (and so raising OverflowError instead). + try: + x = math.sqrt(-1.0) + except ValueError: + pass + else: + self.fail("sqrt(-1) didn't raise ValueError") @requires_IEEE_754 def test_testfile(self): diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -315,26 +315,25 @@ mf.close() f.close() + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_entire_file(self): # test mapping of entire file by passing 0 for map length - if hasattr(os, "stat"): - f = open(TESTFN, "wb+") + f = open(TESTFN, "wb+") - f.write(2**16 * b'm') # Arbitrary character - f.close() + f.write(2**16 * b'm') # Arbitrary character + f.close() - f = open(TESTFN, "rb+") - mf = mmap.mmap(f.fileno(), 0) - self.assertEqual(len(mf), 2**16, "Map size should equal file size.") - self.assertEqual(mf.read(2**16), 2**16 * b"m") - mf.close() - f.close() + f = open(TESTFN, "rb+") + mf = mmap.mmap(f.fileno(), 0) + self.assertEqual(len(mf), 2**16, "Map size should equal file size.") + self.assertEqual(mf.read(2**16), 2**16 * b"m") + mf.close() + f.close() + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_length_0_offset(self): # Issue #10916: test mapping of remainder of file by passing 0 for # map length with an offset doesn't cause a segfault. - if not hasattr(os, "stat"): - self.skipTest("needs os.stat") # NOTE: allocation granularity is currently 65536 under Win64, # and therefore the minimum offset alignment. with open(TESTFN, "wb") as f: @@ -344,12 +343,10 @@ with mmap.mmap(f.fileno(), 0, offset=65536, access=mmap.ACCESS_READ) as mf: self.assertRaises(IndexError, mf.__getitem__, 80000) + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_length_0_large_offset(self): # Issue #10959: test mapping of a file by passing 0 for # map length with a large offset doesn't cause a segfault. - if not hasattr(os, "stat"): - self.skipTest("needs os.stat") - with open(TESTFN, "wb") as f: f.write(115699 * b'm') # Arbitrary character @@ -561,9 +558,8 @@ return mmap.mmap.__new__(klass, -1, *args, **kwargs) anon_mmap(PAGESIZE) + @unittest.skipUnless(hasattr(mmap, 'PROT_READ'), "needs mmap.PROT_READ") def test_prot_readonly(self): - if not hasattr(mmap, 'PROT_READ'): - return mapsize = 10 with open(TESTFN, "wb") as fp: fp.write(b"a"*mapsize) @@ -617,67 +613,69 @@ self.assertEqual(m.read_byte(), b) m.close() - if os.name == 'nt': - def test_tagname(self): - data1 = b"0123456789" - data2 = b"abcdefghij" - assert len(data1) == len(data2) + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_tagname(self): + data1 = b"0123456789" + data2 = b"abcdefghij" + assert len(data1) == len(data2) - # Test same tag - m1 = mmap.mmap(-1, len(data1), tagname="foo") - m1[:] = data1 - m2 = mmap.mmap(-1, len(data2), tagname="foo") - m2[:] = data2 - self.assertEqual(m1[:], data2) - self.assertEqual(m2[:], data2) - m2.close() - m1.close() + # Test same tag + m1 = mmap.mmap(-1, len(data1), tagname="foo") + m1[:] = data1 + m2 = mmap.mmap(-1, len(data2), tagname="foo") + m2[:] = data2 + self.assertEqual(m1[:], data2) + self.assertEqual(m2[:], data2) + m2.close() + m1.close() - # Test different tag - m1 = mmap.mmap(-1, len(data1), tagname="foo") - m1[:] = data1 - m2 = mmap.mmap(-1, len(data2), tagname="boo") - m2[:] = data2 - self.assertEqual(m1[:], data1) - self.assertEqual(m2[:], data2) - m2.close() - m1.close() + # Test different tag + m1 = mmap.mmap(-1, len(data1), tagname="foo") + m1[:] = data1 + m2 = mmap.mmap(-1, len(data2), tagname="boo") + m2[:] = data2 + self.assertEqual(m1[:], data1) + self.assertEqual(m2[:], data2) + m2.close() + m1.close() - def test_crasher_on_windows(self): - # Should not crash (Issue 1733986) - m = mmap.mmap(-1, 1000, tagname="foo") - try: - mmap.mmap(-1, 5000, tagname="foo")[:] # same tagname, but larger size - except: - pass - m.close() + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_crasher_on_windows(self): + # Should not crash (Issue 1733986) + m = mmap.mmap(-1, 1000, tagname="foo") + try: + mmap.mmap(-1, 5000, tagname="foo")[:] # same tagname, but larger size + except: + pass + m.close() - # Should not crash (Issue 5385) - with open(TESTFN, "wb") as fp: - fp.write(b"x"*10) - f = open(TESTFN, "r+b") - m = mmap.mmap(f.fileno(), 0) - f.close() - try: - m.resize(0) # will raise OSError - except: - pass - try: - m[:] - except: - pass - m.close() + # Should not crash (Issue 5385) + with open(TESTFN, "wb") as fp: + fp.write(b"x"*10) + f = open(TESTFN, "r+b") + m = mmap.mmap(f.fileno(), 0) + f.close() + try: + m.resize(0) # will raise OSError + except: + pass + try: + m[:] + except: + pass + m.close() - def test_invalid_descriptor(self): - # socket file descriptors are valid, but out of range - # for _get_osfhandle, causing a crash when validating the - # parameters to _get_osfhandle. - s = socket.socket() - try: - with self.assertRaises(OSError): - m = mmap.mmap(s.fileno(), 10) - finally: - s.close() + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_invalid_descriptor(self): + # socket file descriptors are valid, but out of range + # for _get_osfhandle, causing a crash when validating the + # parameters to _get_osfhandle. + s = socket.socket() + try: + with self.assertRaises(OSError): + m = mmap.mmap(s.fileno(), 10) + finally: + s.close() def test_context_manager(self): with mmap.mmap(-1, 10) as m: diff --git a/Lib/test/test_nntplib.py b/Lib/test/test_nntplib.py --- a/Lib/test/test_nntplib.py +++ b/Lib/test/test_nntplib.py @@ -6,10 +6,12 @@ import functools import contextlib from test import support -from nntplib import NNTP, GroupInfo, _have_ssl +from nntplib import NNTP, GroupInfo import nntplib -if _have_ssl: +try: import ssl +except ImportError: + ssl = None TIMEOUT = 30 @@ -199,23 +201,23 @@ resp, caps = self.server.capabilities() _check_caps(caps) - if _have_ssl: - def test_starttls(self): - file = self.server.file - sock = self.server.sock - try: - self.server.starttls() - except nntplib.NNTPPermanentError: - self.skipTest("STARTTLS not supported by server.") - else: - # Check that the socket and internal pseudo-file really were - # changed. - self.assertNotEqual(file, self.server.file) - self.assertNotEqual(sock, self.server.sock) - # Check that the new socket really is an SSL one - self.assertIsInstance(self.server.sock, ssl.SSLSocket) - # Check that trying starttls when it's already active fails. - self.assertRaises(ValueError, self.server.starttls) + @unittest.skipUnless(ssl, 'requires SSL support') + def test_starttls(self): + file = self.server.file + sock = self.server.sock + try: + self.server.starttls() + except nntplib.NNTPPermanentError: + self.skipTest("STARTTLS not supported by server.") + else: + # Check that the socket and internal pseudo-file really were + # changed. + self.assertNotEqual(file, self.server.file) + self.assertNotEqual(sock, self.server.sock) + # Check that the new socket really is an SSL one + self.assertIsInstance(self.server.sock, ssl.SSLSocket) + # Check that trying starttls when it's already active fails. + self.assertRaises(ValueError, self.server.starttls) def test_zlogin(self): # This test must be the penultimate because further commands will be @@ -300,25 +302,24 @@ if cls.server is not None: cls.server.quit() + at unittest.skipUnless(ssl, 'requires SSL support') +class NetworkedNNTP_SSLTests(NetworkedNNTPTests): -if _have_ssl: - class NetworkedNNTP_SSLTests(NetworkedNNTPTests): + # Technical limits for this public NNTP server (see http://www.aioe.org): + # "Only two concurrent connections per IP address are allowed and + # 400 connections per day are accepted from each IP address." - # Technical limits for this public NNTP server (see http://www.aioe.org): - # "Only two concurrent connections per IP address are allowed and - # 400 connections per day are accepted from each IP address." + NNTP_HOST = 'nntp.aioe.org' + GROUP_NAME = 'comp.lang.python' + GROUP_PAT = 'comp.lang.*' - NNTP_HOST = 'nntp.aioe.org' - GROUP_NAME = 'comp.lang.python' - GROUP_PAT = 'comp.lang.*' + NNTP_CLASS = getattr(nntplib, 'NNTP_SSL', None) - NNTP_CLASS = nntplib.NNTP_SSL + # Disabled as it produces too much data + test_list = None - # Disabled as it produces too much data - test_list = None - - # Disabled as the connection will already be encrypted. - test_starttls = None + # Disabled as the connection will already be encrypted. + test_starttls = None # @@ -1407,12 +1408,13 @@ gives(2000, 6, 23, "000623", "000000") gives(2010, 6, 5, "100605", "000000") + @unittest.skipUnless(ssl, 'requires SSL support') + def test_ssl_support(self): + self.assertTrue(hasattr(nntplib, 'NNTP_SSL')) def test_main(): tests = [MiscTests, NNTPv1Tests, NNTPv2Tests, CapsAfterLoginNNTPv2Tests, - SendReaderNNTPv2Tests, NetworkedNNTPTests] - if _have_ssl: - tests.append(NetworkedNNTP_SSLTests) + SendReaderNNTPv2Tests, NetworkedNNTPTests, NetworkedNNTP_SSLTests] support.run_unittest(*tests) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -185,10 +185,8 @@ os.unlink(self.fname) os.rmdir(support.TESTFN) + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def check_stat_attributes(self, fname): - if not hasattr(os, "stat"): - return - result = os.stat(fname) # Make sure direct access works @@ -272,10 +270,8 @@ unpickled = pickle.loads(p) self.assertEqual(result, unpickled) + @unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()') def test_statvfs_attributes(self): - if not hasattr(os, "statvfs"): - return - try: result = os.statvfs(self.fname) except OSError as e: @@ -479,10 +475,10 @@ os.close(dirfd) self._test_utime_subsecond(set_time) - # Restrict test to Win32, since there is no guarantee other + # Restrict tests to Win32, since there is no guarantee other # systems support centiseconds - if sys.platform == 'win32': - def get_file_system(path): + def get_file_system(path): + if sys.platform == 'win32': root = os.path.splitdrive(os.path.abspath(path))[0] + '\\' import ctypes kernel32 = ctypes.windll.kernel32 @@ -490,38 +486,45 @@ if kernel32.GetVolumeInformationW(root, None, 0, None, None, None, buf, len(buf)): return buf.value - if get_file_system(support.TESTFN) == "NTFS": - def test_1565150(self): - t1 = 1159195039.25 - os.utime(self.fname, (t1, t1)) - self.assertEqual(os.stat(self.fname).st_mtime, t1) + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + @unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS", + "requires NTFS") + def test_1565150(self): + t1 = 1159195039.25 + os.utime(self.fname, (t1, t1)) + self.assertEqual(os.stat(self.fname).st_mtime, t1) - def test_large_time(self): - t1 = 5000000000 # some day in 2128 - os.utime(self.fname, (t1, t1)) - self.assertEqual(os.stat(self.fname).st_mtime, t1) + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + @unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS", + "requires NTFS") + def test_large_time(self): + t1 = 5000000000 # some day in 2128 + os.utime(self.fname, (t1, t1)) + self.assertEqual(os.stat(self.fname).st_mtime, t1) - def test_1686475(self): - # Verify that an open file can be stat'ed - try: - os.stat(r"c:\pagefile.sys") - except FileNotFoundError: - pass # file does not exist; cannot run test - except OSError as e: - self.fail("Could not stat pagefile.sys") + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + def test_1686475(self): + # Verify that an open file can be stat'ed + try: + os.stat(r"c:\pagefile.sys") + except FileNotFoundError: + pass # file does not exist; cannot run test + except OSError as e: + self.fail("Could not stat pagefile.sys") - @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") - def test_15261(self): - # Verify that stat'ing a closed fd does not cause crash - r, w = os.pipe() - try: - os.stat(r) # should not raise error - finally: - os.close(r) - os.close(w) - with self.assertRaises(OSError) as ctx: - os.stat(r) - self.assertEqual(ctx.exception.errno, errno.EBADF) + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + def test_15261(self): + # Verify that stat'ing a closed fd does not cause crash + r, w = os.pipe() + try: + os.stat(r) # should not raise error + finally: + os.close(r) + os.close(w) + with self.assertRaises(OSError) as ctx: + os.stat(r) + self.assertEqual(ctx.exception.errno, errno.EBADF) from test import mapping_tests @@ -1167,6 +1170,7 @@ self._test_internal_execvpe(bytes) + at unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") class Win32ErrorTests(unittest.TestCase): def test_rename(self): self.assertRaises(OSError, os.rename, support.TESTFN, support.TESTFN+".bak") @@ -1213,63 +1217,63 @@ self.fail("%r didn't raise a OSError with a bad file descriptor" % f) + @unittest.skipUnless(hasattr(os, 'isatty'), 'test needs os.isatty()') def test_isatty(self): - if hasattr(os, "isatty"): - self.assertEqual(os.isatty(support.make_bad_fd()), False) + self.assertEqual(os.isatty(support.make_bad_fd()), False) + @unittest.skipUnless(hasattr(os, 'closerange'), 'test needs os.closerange()') def test_closerange(self): - if hasattr(os, "closerange"): - fd = support.make_bad_fd() - # Make sure none of the descriptors we are about to close are - # currently valid (issue 6542). - for i in range(10): - try: os.fstat(fd+i) - except OSError: - pass - else: - break - if i < 2: - raise unittest.SkipTest( - "Unable to acquire a range of invalid file descriptors") - self.assertEqual(os.closerange(fd, fd + i-1), None) + fd = support.make_bad_fd() + # Make sure none of the descriptors we are about to close are + # currently valid (issue 6542). + for i in range(10): + try: os.fstat(fd+i) + except OSError: + pass + else: + break + if i < 2: + raise unittest.SkipTest( + "Unable to acquire a range of invalid file descriptors") + self.assertEqual(os.closerange(fd, fd + i-1), None) + @unittest.skipUnless(hasattr(os, 'dup2'), 'test needs os.dup2()') def test_dup2(self): - if hasattr(os, "dup2"): - self.check(os.dup2, 20) + self.check(os.dup2, 20) + @unittest.skipUnless(hasattr(os, 'fchmod'), 'test needs os.fchmod()') def test_fchmod(self): - if hasattr(os, "fchmod"): - self.check(os.fchmod, 0) + self.check(os.fchmod, 0) + @unittest.skipUnless(hasattr(os, 'fchown'), 'test needs os.fchown()') def test_fchown(self): - if hasattr(os, "fchown"): - self.check(os.fchown, -1, -1) + self.check(os.fchown, -1, -1) + @unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') def test_fpathconf(self): - if hasattr(os, "fpathconf"): - self.check(os.pathconf, "PC_NAME_MAX") - self.check(os.fpathconf, "PC_NAME_MAX") + self.check(os.pathconf, "PC_NAME_MAX") + self.check(os.fpathconf, "PC_NAME_MAX") + @unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()') def test_ftruncate(self): - if hasattr(os, "ftruncate"): - self.check(os.truncate, 0) - self.check(os.ftruncate, 0) + self.check(os.truncate, 0) + self.check(os.ftruncate, 0) + @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') def test_lseek(self): - if hasattr(os, "lseek"): - self.check(os.lseek, 0, 0) + self.check(os.lseek, 0, 0) + @unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()') def test_read(self): - if hasattr(os, "read"): - self.check(os.read, 1) + self.check(os.read, 1) + @unittest.skipUnless(hasattr(os, 'tcsetpgrp'), 'test needs os.tcsetpgrp()') def test_tcsetpgrpt(self): - if hasattr(os, "tcsetpgrp"): - self.check(os.tcsetpgrp, 0) + self.check(os.tcsetpgrp, 0) + @unittest.skipUnless(hasattr(os, 'write'), 'test needs os.write()') def test_write(self): - if hasattr(os, "write"): - self.check(os.write, b" ") + self.check(os.write, b" ") class LinkTests(unittest.TestCase): @@ -1309,138 +1313,133 @@ self.file2 = self.file1 + "2" self._test_link(self.file1, self.file2) -if sys.platform != 'win32': - class Win32ErrorTests(unittest.TestCase): - pass + at unittest.skipIf(sys.platform == "win32", "Posix specific tests") +class PosixUidGidTests(unittest.TestCase): + @unittest.skipUnless(hasattr(os, 'setuid'), 'test needs os.setuid()') + def test_setuid(self): + if os.getuid() != 0: + self.assertRaises(OSError, os.setuid, 0) + self.assertRaises(OverflowError, os.setuid, 1<<32) - class PosixUidGidTests(unittest.TestCase): - if hasattr(os, 'setuid'): - def test_setuid(self): - if os.getuid() != 0: - self.assertRaises(OSError, os.setuid, 0) - self.assertRaises(OverflowError, os.setuid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setgid'), 'test needs os.setgid()') + def test_setgid(self): + if os.getuid() != 0 and not HAVE_WHEEL_GROUP: + self.assertRaises(OSError, os.setgid, 0) + self.assertRaises(OverflowError, os.setgid, 1<<32) - if hasattr(os, 'setgid'): - def test_setgid(self): - if os.getuid() != 0 and not HAVE_WHEEL_GROUP: - self.assertRaises(OSError, os.setgid, 0) - self.assertRaises(OverflowError, os.setgid, 1<<32) + @unittest.skipUnless(hasattr(os, 'seteuid'), 'test needs os.seteuid()') + def test_seteuid(self): + if os.getuid() != 0: + self.assertRaises(OSError, os.seteuid, 0) + self.assertRaises(OverflowError, os.seteuid, 1<<32) - if hasattr(os, 'seteuid'): - def test_seteuid(self): - if os.getuid() != 0: - self.assertRaises(OSError, os.seteuid, 0) - self.assertRaises(OverflowError, os.seteuid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setegid'), 'test needs os.setegid()') + def test_setegid(self): + if os.getuid() != 0 and not HAVE_WHEEL_GROUP: + self.assertRaises(OSError, os.setegid, 0) + self.assertRaises(OverflowError, os.setegid, 1<<32) - if hasattr(os, 'setegid'): - def test_setegid(self): - if os.getuid() != 0 and not HAVE_WHEEL_GROUP: - self.assertRaises(OSError, os.setegid, 0) - self.assertRaises(OverflowError, os.setegid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()') + def test_setreuid(self): + if os.getuid() != 0: + self.assertRaises(OSError, os.setreuid, 0, 0) + self.assertRaises(OverflowError, os.setreuid, 1<<32, 0) + self.assertRaises(OverflowError, os.setreuid, 0, 1<<32) - if hasattr(os, 'setreuid'): - def test_setreuid(self): - if os.getuid() != 0: - self.assertRaises(OSError, os.setreuid, 0, 0) - self.assertRaises(OverflowError, os.setreuid, 1<<32, 0) - self.assertRaises(OverflowError, os.setreuid, 0, 1<<32) + @unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()') + def test_setreuid_neg1(self): + # Needs to accept -1. We run this in a subprocess to avoid + # altering the test runner's process state (issue8045). + subprocess.check_call([ + sys.executable, '-c', + 'import os,sys;os.setreuid(-1,-1);sys.exit(0)']) - def test_setreuid_neg1(self): - # Needs to accept -1. We run this in a subprocess to avoid - # altering the test runner's process state (issue8045). - subprocess.check_call([ - sys.executable, '-c', - 'import os,sys;os.setreuid(-1,-1);sys.exit(0)']) + @unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()') + def test_setregid(self): + if os.getuid() != 0 and not HAVE_WHEEL_GROUP: + self.assertRaises(OSError, os.setregid, 0, 0) + self.assertRaises(OverflowError, os.setregid, 1<<32, 0) + self.assertRaises(OverflowError, os.setregid, 0, 1<<32) - if hasattr(os, 'setregid'): - def test_setregid(self): - if os.getuid() != 0 and not HAVE_WHEEL_GROUP: - self.assertRaises(OSError, os.setregid, 0, 0) - self.assertRaises(OverflowError, os.setregid, 1<<32, 0) - self.assertRaises(OverflowError, os.setregid, 0, 1<<32) + @unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()') + def test_setregid_neg1(self): + # Needs to accept -1. We run this in a subprocess to avoid + # altering the test runner's process state (issue8045). + subprocess.check_call([ + sys.executable, '-c', + 'import os,sys;os.setregid(-1,-1);sys.exit(0)']) - def test_setregid_neg1(self): - # Needs to accept -1. We run this in a subprocess to avoid - # altering the test runner's process state (issue8045). - subprocess.check_call([ - sys.executable, '-c', - 'import os,sys;os.setregid(-1,-1);sys.exit(0)']) + at unittest.skipIf(sys.platform == "win32", "Posix specific tests") +class Pep383Tests(unittest.TestCase): + def setUp(self): + if support.TESTFN_UNENCODABLE: + self.dir = support.TESTFN_UNENCODABLE + elif support.TESTFN_NONASCII: + self.dir = support.TESTFN_NONASCII + else: + self.dir = support.TESTFN + self.bdir = os.fsencode(self.dir) - class Pep383Tests(unittest.TestCase): - def setUp(self): - if support.TESTFN_UNENCODABLE: - self.dir = support.TESTFN_UNENCODABLE - elif support.TESTFN_NONASCII: - self.dir = support.TESTFN_NONASCII - else: - self.dir = support.TESTFN - self.bdir = os.fsencode(self.dir) + bytesfn = [] + def add_filename(fn): + try: + fn = os.fsencode(fn) + except UnicodeEncodeError: + return + bytesfn.append(fn) + add_filename(support.TESTFN_UNICODE) + if support.TESTFN_UNENCODABLE: + add_filename(support.TESTFN_UNENCODABLE) + if support.TESTFN_NONASCII: + add_filename(support.TESTFN_NONASCII) + if not bytesfn: + self.skipTest("couldn't create any non-ascii filename") - bytesfn = [] - def add_filename(fn): - try: - fn = os.fsencode(fn) - except UnicodeEncodeError: - return - bytesfn.append(fn) - add_filename(support.TESTFN_UNICODE) - if support.TESTFN_UNENCODABLE: - add_filename(support.TESTFN_UNENCODABLE) - if support.TESTFN_NONASCII: - add_filename(support.TESTFN_NONASCII) - if not bytesfn: - self.skipTest("couldn't create any non-ascii filename") + self.unicodefn = set() + os.mkdir(self.dir) + try: + for fn in bytesfn: + support.create_empty_file(os.path.join(self.bdir, fn)) + fn = os.fsdecode(fn) + if fn in self.unicodefn: + raise ValueError("duplicate filename") + self.unicodefn.add(fn) + except: + shutil.rmtree(self.dir) + raise - self.unicodefn = set() - os.mkdir(self.dir) - try: - for fn in bytesfn: - support.create_empty_file(os.path.join(self.bdir, fn)) - fn = os.fsdecode(fn) - if fn in self.unicodefn: - raise ValueError("duplicate filename") - self.unicodefn.add(fn) - except: - shutil.rmtree(self.dir) - raise + def tearDown(self): + shutil.rmtree(self.dir) - def tearDown(self): - shutil.rmtree(self.dir) + def test_listdir(self): + expected = self.unicodefn + found = set(os.listdir(self.dir)) + self.assertEqual(found, expected) + # test listdir without arguments + current_directory = os.getcwd() + try: + os.chdir(os.sep) + self.assertEqual(set(os.listdir()), set(os.listdir(os.sep))) + finally: + os.chdir(current_directory) - def test_listdir(self): - expected = self.unicodefn - found = set(os.listdir(self.dir)) - self.assertEqual(found, expected) - # test listdir without arguments - current_directory = os.getcwd() - try: - os.chdir(os.sep) - self.assertEqual(set(os.listdir()), set(os.listdir(os.sep))) - finally: - os.chdir(current_directory) + def test_open(self): + for fn in self.unicodefn: + f = open(os.path.join(self.dir, fn), 'rb') + f.close() - def test_open(self): - for fn in self.unicodefn: - f = open(os.path.join(self.dir, fn), 'rb') - f.close() + @unittest.skipUnless(hasattr(os, 'statvfs'), + "need os.statvfs()") + def test_statvfs(self): + # issue #9645 + for fn in self.unicodefn: + # should not fail with file not found error + fullname = os.path.join(self.dir, fn) + os.statvfs(fullname) - @unittest.skipUnless(hasattr(os, 'statvfs'), - "need os.statvfs()") - def test_statvfs(self): - # issue #9645 - for fn in self.unicodefn: - # should not fail with file not found error - fullname = os.path.join(self.dir, fn) - os.statvfs(fullname) - - def test_stat(self): - for fn in self.unicodefn: - os.stat(os.path.join(self.dir, fn)) -else: - class PosixUidGidTests(unittest.TestCase): - pass - class Pep383Tests(unittest.TestCase): - pass + def test_stat(self): + for fn in self.unicodefn: + os.stat(os.path.join(self.dir, fn)) @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") class Win32KillTests(unittest.TestCase): @@ -1924,6 +1923,8 @@ SUPPORT_HEADERS_TRAILERS = not sys.platform.startswith("linux") and \ not sys.platform.startswith("solaris") and \ not sys.platform.startswith("sunos") + requires_headers_trailers = unittest.skipUnless(SUPPORT_HEADERS_TRAILERS, + 'requires headers and trailers support') @classmethod def setUpClass(cls): @@ -2042,52 +2043,54 @@ # --- headers / trailers tests - if SUPPORT_HEADERS_TRAILERS: + @requires_headers_trailers + def test_headers(self): + total_sent = 0 + sent = os.sendfile(self.sockno, self.fileno, 0, 4096, + headers=[b"x" * 512]) + total_sent += sent + offset = 4096 + nbytes = 4096 + while 1: + sent = self.sendfile_wrapper(self.sockno, self.fileno, + offset, nbytes) + if sent == 0: + break + total_sent += sent + offset += sent - def test_headers(self): - total_sent = 0 - sent = os.sendfile(self.sockno, self.fileno, 0, 4096, - headers=[b"x" * 512]) - total_sent += sent - offset = 4096 - nbytes = 4096 - while 1: - sent = self.sendfile_wrapper(self.sockno, self.fileno, - offset, nbytes) - if sent == 0: - break - total_sent += sent - offset += sent + expected_data = b"x" * 512 + self.DATA + self.assertEqual(total_sent, len(expected_data)) + self.client.close() + self.server.wait() + data = self.server.handler_instance.get_data() + self.assertEqual(hash(data), hash(expected_data)) - expected_data = b"x" * 512 + self.DATA - self.assertEqual(total_sent, len(expected_data)) + @requires_headers_trailers + def test_trailers(self): + TESTFN2 = support.TESTFN + "2" + file_data = b"abcdef" + with open(TESTFN2, 'wb') as f: + f.write(file_data) + with open(TESTFN2, 'rb')as f: + self.addCleanup(os.remove, TESTFN2) + os.sendfile(self.sockno, f.fileno(), 0, len(file_data), + trailers=[b"1234"]) self.client.close() self.server.wait() data = self.server.handler_instance.get_data() - self.assertEqual(hash(data), hash(expected_data)) + self.assertEqual(data, b"abcdef1234") - def test_trailers(self): - TESTFN2 = support.TESTFN + "2" - file_data = b"abcdef" - with open(TESTFN2, 'wb') as f: - f.write(file_data) - with open(TESTFN2, 'rb')as f: - self.addCleanup(os.remove, TESTFN2) - os.sendfile(self.sockno, f.fileno(), 0, len(file_data), - trailers=[b"1234"]) - self.client.close() - self.server.wait() - data = self.server.handler_instance.get_data() - self.assertEqual(data, b"abcdef1234") - - if hasattr(os, "SF_NODISKIO"): - def test_flags(self): - try: - os.sendfile(self.sockno, self.fileno, 0, 4096, - flags=os.SF_NODISKIO) - except OSError as err: - if err.errno not in (errno.EBUSY, errno.EAGAIN): - raise + @requires_headers_trailers + @unittest.skipUnless(hasattr(os, 'SF_NODISKIO'), + 'test needs os.SF_NODISKIO') + def test_flags(self): + try: + os.sendfile(self.sockno, self.fileno, 0, 4096, + flags=os.SF_NODISKIO) + except OSError as err: + if err.errno not in (errno.EBUSY, errno.EAGAIN): + raise def supports_extended_attributes(): diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py --- a/Lib/test/test_poplib.py +++ b/Lib/test/test_poplib.py @@ -11,7 +11,7 @@ import time import errno -from unittest import TestCase +from unittest import TestCase, skipUnless from test import support as test_support threading = test_support.import_module('threading') @@ -24,6 +24,7 @@ SUPPORTS_SSL = True CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "keycert.pem") +requires_ssl = skipUnless(SUPPORTS_SSL, 'SSL not supported') # the dummy data returned by server when LIST and RETR commands are issued LIST_RESP = b'1 1\r\n2 2\r\n3 3\r\n4 4\r\n5 5\r\n.\r\n' @@ -316,22 +317,23 @@ self.assertIsNone(self.client.sock) self.assertIsNone(self.client.file) - if SUPPORTS_SSL: + @requires_ssl + def test_stls_capa(self): + capa = self.client.capa() + self.assertTrue('STLS' in capa.keys()) - def test_stls_capa(self): - capa = self.client.capa() - self.assertTrue('STLS' in capa.keys()) + @requires_ssl + def test_stls(self): + expected = b'+OK Begin TLS negotiation' + resp = self.client.stls() + self.assertEqual(resp, expected) - def test_stls(self): - expected = b'+OK Begin TLS negotiation' - resp = self.client.stls() - self.assertEqual(resp, expected) - - def test_stls_context(self): - expected = b'+OK Begin TLS negotiation' - ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) - resp = self.client.stls(context=ctx) - self.assertEqual(resp, expected) + @requires_ssl + def test_stls_context(self): + expected = b'+OK Begin TLS negotiation' + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + resp = self.client.stls(context=ctx) + self.assertEqual(resp, expected) if SUPPORTS_SSL: @@ -354,73 +356,75 @@ self.push('+OK dummy pop3 server ready. ') - class TestPOP3_SSLClass(TestPOP3Class): - # repeat previous tests by using poplib.POP3_SSL + at requires_ssl +class TestPOP3_SSLClass(TestPOP3Class): + # repeat previous tests by using poplib.POP3_SSL - def setUp(self): - self.server = DummyPOP3Server((HOST, PORT)) - self.server.handler = DummyPOP3_SSLHandler - self.server.start() - self.client = poplib.POP3_SSL(self.server.host, self.server.port) + def setUp(self): + self.server = DummyPOP3Server((HOST, PORT)) + self.server.handler = DummyPOP3_SSLHandler + self.server.start() + self.client = poplib.POP3_SSL(self.server.host, self.server.port) - def test__all__(self): - self.assertIn('POP3_SSL', poplib.__all__) + def test__all__(self): + self.assertIn('POP3_SSL', poplib.__all__) - def test_context(self): - ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) - self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, - self.server.port, keyfile=CERTFILE, context=ctx) - self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, - self.server.port, certfile=CERTFILE, context=ctx) - self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, - self.server.port, keyfile=CERTFILE, - certfile=CERTFILE, context=ctx) + def test_context(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, + self.server.port, keyfile=CERTFILE, context=ctx) + self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, + self.server.port, certfile=CERTFILE, context=ctx) + self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host, + self.server.port, keyfile=CERTFILE, + certfile=CERTFILE, context=ctx) - self.client.quit() - self.client = poplib.POP3_SSL(self.server.host, self.server.port, - context=ctx) - self.assertIsInstance(self.client.sock, ssl.SSLSocket) - self.assertIs(self.client.sock.context, ctx) - self.assertTrue(self.client.noop().startswith(b'+OK')) + self.client.quit() + self.client = poplib.POP3_SSL(self.server.host, self.server.port, + context=ctx) + self.assertIsInstance(self.client.sock, ssl.SSLSocket) + self.assertIs(self.client.sock.context, ctx) + self.assertTrue(self.client.noop().startswith(b'+OK')) - def test_stls(self): - self.assertRaises(poplib.error_proto, self.client.stls) + def test_stls(self): + self.assertRaises(poplib.error_proto, self.client.stls) - test_stls_context = test_stls + test_stls_context = test_stls - def test_stls_capa(self): - capa = self.client.capa() - self.assertFalse('STLS' in capa.keys()) + def test_stls_capa(self): + capa = self.client.capa() + self.assertFalse('STLS' in capa.keys()) - class TestPOP3_TLSClass(TestPOP3Class): - # repeat previous tests by using poplib.POP3.stls() + at requires_ssl +class TestPOP3_TLSClass(TestPOP3Class): + # repeat previous tests by using poplib.POP3.stls() - def setUp(self): - self.server = DummyPOP3Server((HOST, PORT)) - self.server.start() - self.client = poplib.POP3(self.server.host, self.server.port, timeout=3) - self.client.stls() + def setUp(self): + self.server = DummyPOP3Server((HOST, PORT)) + self.server.start() + self.client = poplib.POP3(self.server.host, self.server.port, timeout=3) + self.client.stls() - def tearDown(self): - if self.client.file is not None and self.client.sock is not None: - try: - self.client.quit() - except poplib.error_proto: - # happens in the test_too_long_lines case; the overlong - # response will be treated as response to QUIT and raise - # this exception - pass - self.server.stop() + def tearDown(self): + if self.client.file is not None and self.client.sock is not None: + try: + self.client.quit() + except poplib.error_proto: + # happens in the test_too_long_lines case; the overlong + # response will be treated as response to QUIT and raise + # this exception + pass + self.server.stop() - def test_stls(self): - self.assertRaises(poplib.error_proto, self.client.stls) + def test_stls(self): + self.assertRaises(poplib.error_proto, self.client.stls) - test_stls_context = test_stls + test_stls_context = test_stls - def test_stls_capa(self): - capa = self.client.capa() - self.assertFalse(b'STLS' in capa.keys()) + def test_stls_capa(self): + capa = self.client.capa() + self.assertFalse(b'STLS' in capa.keys()) class TestTimeouts(TestCase): @@ -478,10 +482,8 @@ def test_main(): - tests = [TestPOP3Class, TestTimeouts] - if SUPPORTS_SSL: - tests.append(TestPOP3_SSLClass) - tests.append(TestPOP3_TLSClass) + tests = [TestPOP3Class, TestTimeouts, + TestPOP3_SSLClass, TestPOP3_TLSClass] thread_info = test_support.threading_setup() try: test_support.run_unittest(*tests) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -54,47 +54,55 @@ posix_func() self.assertRaises(TypeError, posix_func, 1) - if hasattr(posix, 'getresuid'): - def test_getresuid(self): - user_ids = posix.getresuid() - self.assertEqual(len(user_ids), 3) - for val in user_ids: - self.assertGreaterEqual(val, 0) + @unittest.skipUnless(hasattr(posix, 'getresuid'), + 'test needs posix.getresuid()') + def test_getresuid(self): + user_ids = posix.getresuid() + self.assertEqual(len(user_ids), 3) + for val in user_ids: + self.assertGreaterEqual(val, 0) - if hasattr(posix, 'getresgid'): - def test_getresgid(self): - group_ids = posix.getresgid() - self.assertEqual(len(group_ids), 3) - for val in group_ids: - self.assertGreaterEqual(val, 0) + @unittest.skipUnless(hasattr(posix, 'getresgid'), + 'test needs posix.getresgid()') + def test_getresgid(self): + group_ids = posix.getresgid() + self.assertEqual(len(group_ids), 3) + for val in group_ids: + self.assertGreaterEqual(val, 0) - if hasattr(posix, 'setresuid'): - def test_setresuid(self): - current_user_ids = posix.getresuid() - self.assertIsNone(posix.setresuid(*current_user_ids)) - # -1 means don't change that value. - self.assertIsNone(posix.setresuid(-1, -1, -1)) + @unittest.skipUnless(hasattr(posix, 'setresuid'), + 'test needs posix.setresuid()') + def test_setresuid(self): + current_user_ids = posix.getresuid() + self.assertIsNone(posix.setresuid(*current_user_ids)) + # -1 means don't change that value. + self.assertIsNone(posix.setresuid(-1, -1, -1)) - def test_setresuid_exception(self): - # Don't do this test if someone is silly enough to run us as root. - current_user_ids = posix.getresuid() - if 0 not in current_user_ids: - new_user_ids = (current_user_ids[0]+1, -1, -1) - self.assertRaises(OSError, posix.setresuid, *new_user_ids) + @unittest.skipUnless(hasattr(posix, 'setresuid'), + 'test needs posix.setresuid()') + def test_setresuid_exception(self): + # Don't do this test if someone is silly enough to run us as root. + current_user_ids = posix.getresuid() + if 0 not in current_user_ids: + new_user_ids = (current_user_ids[0]+1, -1, -1) + self.assertRaises(OSError, posix.setresuid, *new_user_ids) - if hasattr(posix, 'setresgid'): - def test_setresgid(self): - current_group_ids = posix.getresgid() - self.assertIsNone(posix.setresgid(*current_group_ids)) - # -1 means don't change that value. - self.assertIsNone(posix.setresgid(-1, -1, -1)) + @unittest.skipUnless(hasattr(posix, 'setresgid'), + 'test needs posix.setresgid()') + def test_setresgid(self): + current_group_ids = posix.getresgid() + self.assertIsNone(posix.setresgid(*current_group_ids)) + # -1 means don't change that value. + self.assertIsNone(posix.setresgid(-1, -1, -1)) - def test_setresgid_exception(self): - # Don't do this test if someone is silly enough to run us as root. - current_group_ids = posix.getresgid() - if 0 not in current_group_ids: - new_group_ids = (current_group_ids[0]+1, -1, -1) - self.assertRaises(OSError, posix.setresgid, *new_group_ids) + @unittest.skipUnless(hasattr(posix, 'setresgid'), + 'test needs posix.setresgid()') + def test_setresgid_exception(self): + # Don't do this test if someone is silly enough to run us as root. + current_group_ids = posix.getresgid() + if 0 not in current_group_ids: + new_group_ids = (current_group_ids[0]+1, -1, -1) + self.assertRaises(OSError, posix.setresgid, *new_group_ids) @unittest.skipUnless(hasattr(posix, 'initgroups'), "test needs os.initgroups()") @@ -121,29 +129,32 @@ else: self.fail("Expected OSError to be raised by initgroups") + @unittest.skipUnless(hasattr(posix, 'statvfs'), + 'test needs posix.statvfs()') def test_statvfs(self): - if hasattr(posix, 'statvfs'): - self.assertTrue(posix.statvfs(os.curdir)) + self.assertTrue(posix.statvfs(os.curdir)) + @unittest.skipUnless(hasattr(posix, 'fstatvfs'), + 'test needs posix.fstatvfs()') def test_fstatvfs(self): - if hasattr(posix, 'fstatvfs'): - fp = open(support.TESTFN) - try: - self.assertTrue(posix.fstatvfs(fp.fileno())) - self.assertTrue(posix.statvfs(fp.fileno())) - finally: - fp.close() + fp = open(support.TESTFN) + try: + self.assertTrue(posix.fstatvfs(fp.fileno())) + self.assertTrue(posix.statvfs(fp.fileno())) + finally: + fp.close() + @unittest.skipUnless(hasattr(posix, 'ftruncate'), + 'test needs posix.ftruncate()') def test_ftruncate(self): - if hasattr(posix, 'ftruncate'): - fp = open(support.TESTFN, 'w+') - try: - # we need to have some data to truncate - fp.write('test') - fp.flush() - posix.ftruncate(fp.fileno(), 0) - finally: - fp.close() + fp = open(support.TESTFN, 'w+') + try: + # we need to have some data to truncate + fp.write('test') + fp.flush() + posix.ftruncate(fp.fileno(), 0) + finally: + fp.close() @unittest.skipUnless(hasattr(posix, 'truncate'), "test needs posix.truncate()") def test_truncate(self): @@ -290,30 +301,33 @@ finally: os.close(fd) + @unittest.skipUnless(hasattr(posix, 'dup'), + 'test needs posix.dup()') def test_dup(self): - if hasattr(posix, 'dup'): - fp = open(support.TESTFN) - try: - fd = posix.dup(fp.fileno()) - self.assertIsInstance(fd, int) - os.close(fd) - finally: - fp.close() + fp = open(support.TESTFN) + try: + fd = posix.dup(fp.fileno()) + self.assertIsInstance(fd, int) + os.close(fd) + finally: + fp.close() + @unittest.skipUnless(hasattr(posix, 'confstr'), + 'test needs posix.confstr()') def test_confstr(self): - if hasattr(posix, 'confstr'): - self.assertRaises(ValueError, posix.confstr, "CS_garbage") - self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) + self.assertRaises(ValueError, posix.confstr, "CS_garbage") + self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) + @unittest.skipUnless(hasattr(posix, 'dup2'), + 'test needs posix.dup2()') def test_dup2(self): - if hasattr(posix, 'dup2'): - fp1 = open(support.TESTFN) - fp2 = open(support.TESTFN) - try: - posix.dup2(fp1.fileno(), fp2.fileno()) - finally: - fp1.close() - fp2.close() + fp1 = open(support.TESTFN) + fp2 = open(support.TESTFN) + try: + posix.dup2(fp1.fileno(), fp2.fileno()) + finally: + fp1.close() + fp2.close() @unittest.skipUnless(hasattr(os, 'O_CLOEXEC'), "needs os.O_CLOEXEC") @support.requires_linux_version(2, 6, 23) @@ -322,65 +336,69 @@ self.addCleanup(os.close, fd) self.assertTrue(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC) + @unittest.skipUnless(hasattr(posix, 'O_EXLOCK'), + 'test needs posix.O_EXLOCK') def test_osexlock(self): - if hasattr(posix, "O_EXLOCK"): + fd = os.open(support.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_CREAT) + self.assertRaises(OSError, os.open, support.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) + + if hasattr(posix, "O_SHLOCK"): fd = os.open(support.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_CREAT) + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) self.assertRaises(OSError, os.open, support.TESTFN, os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) os.close(fd) - if hasattr(posix, "O_SHLOCK"): - fd = os.open(support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, support.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) + @unittest.skipUnless(hasattr(posix, 'O_SHLOCK'), + 'test needs posix.O_SHLOCK') + def test_osshlock(self): + fd1 = os.open(support.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + fd2 = os.open(support.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + os.close(fd2) + os.close(fd1) - def test_osshlock(self): - if hasattr(posix, "O_SHLOCK"): - fd1 = os.open(support.TESTFN, + if hasattr(posix, "O_EXLOCK"): + fd = os.open(support.TESTFN, os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - fd2 = os.open(support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - os.close(fd2) - os.close(fd1) + self.assertRaises(OSError, os.open, support.TESTFN, + os.O_RDONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) - if hasattr(posix, "O_EXLOCK"): - fd = os.open(support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, support.TESTFN, - os.O_RDONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) - + @unittest.skipUnless(hasattr(posix, 'fstat'), + 'test needs posix.fstat()') def test_fstat(self): - if hasattr(posix, 'fstat'): - fp = open(support.TESTFN) - try: - self.assertTrue(posix.fstat(fp.fileno())) - self.assertTrue(posix.stat(fp.fileno())) - - self.assertRaisesRegex(TypeError, - 'should be string, bytes or integer, not', - posix.stat, float(fp.fileno())) - finally: - fp.close() - - def test_stat(self): - if hasattr(posix, 'stat'): - self.assertTrue(posix.stat(support.TESTFN)) - self.assertTrue(posix.stat(os.fsencode(support.TESTFN))) - self.assertTrue(posix.stat(bytearray(os.fsencode(support.TESTFN)))) + fp = open(support.TESTFN) + try: + self.assertTrue(posix.fstat(fp.fileno())) + self.assertTrue(posix.stat(fp.fileno())) self.assertRaisesRegex(TypeError, - 'can\'t specify None for path argument', - posix.stat, None) - self.assertRaisesRegex(TypeError, 'should be string, bytes or integer, not', - posix.stat, list(support.TESTFN)) - self.assertRaisesRegex(TypeError, - 'should be string, bytes or integer, not', - posix.stat, list(os.fsencode(support.TESTFN))) + posix.stat, float(fp.fileno())) + finally: + fp.close() + + @unittest.skipUnless(hasattr(posix, 'stat'), + 'test needs posix.stat()') + def test_stat(self): + self.assertTrue(posix.stat(support.TESTFN)) + self.assertTrue(posix.stat(os.fsencode(support.TESTFN))) + self.assertTrue(posix.stat(bytearray(os.fsencode(support.TESTFN)))) + + self.assertRaisesRegex(TypeError, + 'can\'t specify None for path argument', + posix.stat, None) + self.assertRaisesRegex(TypeError, + 'should be string, bytes or integer, not', + posix.stat, list(support.TESTFN)) + self.assertRaisesRegex(TypeError, + 'should be string, bytes or integer, not', + posix.stat, list(os.fsencode(support.TESTFN))) @unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()") def test_mkfifo(self): @@ -495,10 +513,10 @@ self._test_all_chown_common(posix.lchown, support.TESTFN, getattr(posix, 'lstat', None)) + @unittest.skipUnless(hasattr(posix, 'chdir'), 'test needs posix.chdir()') def test_chdir(self): - if hasattr(posix, 'chdir'): - posix.chdir(os.curdir) - self.assertRaises(OSError, posix.chdir, support.TESTFN) + posix.chdir(os.curdir) + self.assertRaises(OSError, posix.chdir, support.TESTFN) def test_listdir(self): self.assertTrue(support.TESTFN in posix.listdir(os.curdir)) @@ -528,25 +546,26 @@ sorted(posix.listdir(f)) ) + @unittest.skipUnless(hasattr(posix, 'access'), 'test needs posix.access()') def test_access(self): - if hasattr(posix, 'access'): - self.assertTrue(posix.access(support.TESTFN, os.R_OK)) + self.assertTrue(posix.access(support.TESTFN, os.R_OK)) + @unittest.skipUnless(hasattr(posix, 'umask'), 'test needs posix.umask()') def test_umask(self): - if hasattr(posix, 'umask'): - old_mask = posix.umask(0) - self.assertIsInstance(old_mask, int) - posix.umask(old_mask) + old_mask = posix.umask(0) + self.assertIsInstance(old_mask, int) + posix.umask(old_mask) + @unittest.skipUnless(hasattr(posix, 'strerror'), + 'test needs posix.strerror()') def test_strerror(self): - if hasattr(posix, 'strerror'): - self.assertTrue(posix.strerror(0)) + self.assertTrue(posix.strerror(0)) + @unittest.skipUnless(hasattr(posix, 'pipe'), 'test needs posix.pipe()') def test_pipe(self): - if hasattr(posix, 'pipe'): - reader, writer = posix.pipe() - os.close(reader) - os.close(writer) + reader, writer = posix.pipe() + os.close(reader) + os.close(writer) @unittest.skipUnless(hasattr(os, 'pipe2'), "test needs os.pipe2()") @support.requires_linux_version(2, 6, 27) @@ -580,15 +599,15 @@ self.assertRaises(OverflowError, os.pipe2, _testcapi.INT_MAX + 1) self.assertRaises(OverflowError, os.pipe2, _testcapi.UINT_MAX + 1) + @unittest.skipUnless(hasattr(posix, 'utime'), 'test needs posix.utime()') def test_utime(self): - if hasattr(posix, 'utime'): - now = time.time() - posix.utime(support.TESTFN, None) - self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, None)) - self.assertRaises(TypeError, posix.utime, support.TESTFN, (now, None)) - self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, now)) - posix.utime(support.TESTFN, (int(now), int(now))) - posix.utime(support.TESTFN, (now, now)) + now = time.time() + posix.utime(support.TESTFN, None) + self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, None)) + self.assertRaises(TypeError, posix.utime, support.TESTFN, (now, None)) + self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, now)) + posix.utime(support.TESTFN, (int(now), int(now))) + posix.utime(support.TESTFN, (now, now)) def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs): st = os.stat(target_file) @@ -665,6 +684,7 @@ self.assertEqual(type(k), item_type) self.assertEqual(type(v), item_type) + @unittest.skipUnless(hasattr(posix, 'getcwd'), 'test needs posix.getcwd()') def test_getcwd_long_pathnames(self): dirname = 'getcwd-test-directory-0123456789abcdef-01234567890abcdef' curdir = os.getcwd() diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -625,10 +625,10 @@ myset >= myobj self.assertTrue(myobj.le_called) - # C API test only available in a debug build - if hasattr(set, "test_c_api"): - def test_c_api(self): - self.assertEqual(set().test_c_api(), True) + @unittest.skipUnless(hasattr(set, "test_c_api"), + 'C API test only available in a debug build') + def test_c_api(self): + self.assertEqual(set().test_c_api(), True) class SetSubclass(set): pass diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -195,37 +195,37 @@ self.assertIn(errors[1][2][1].filename, possible_args) - # See bug #1071513 for why we don't run this on cygwin - # and bug #1076467 for why we don't run this as root. - if (hasattr(os, 'chmod') and sys.platform[:6] != 'cygwin' - and not (hasattr(os, 'geteuid') and os.geteuid() == 0)): - def test_on_error(self): - self.errorState = 0 - os.mkdir(TESTFN) - self.addCleanup(shutil.rmtree, TESTFN) + @unittest.skipUnless(hasattr(os, 'chmod'), 'requires os.chmod()') + @unittest.skipIf(sys.platform[:6] == 'cygwin', + "This test can't be run on Cygwin (issue #1071513).") + @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, + "This test can't be run reliably as root (issue #1076467).") + def test_on_error(self): + self.errorState = 0 + os.mkdir(TESTFN) + self.addCleanup(shutil.rmtree, TESTFN) - self.child_file_path = os.path.join(TESTFN, 'a') - self.child_dir_path = os.path.join(TESTFN, 'b') - support.create_empty_file(self.child_file_path) - os.mkdir(self.child_dir_path) - old_dir_mode = os.stat(TESTFN).st_mode - old_child_file_mode = os.stat(self.child_file_path).st_mode - old_child_dir_mode = os.stat(self.child_dir_path).st_mode - # Make unwritable. - new_mode = stat.S_IREAD|stat.S_IEXEC - os.chmod(self.child_file_path, new_mode) - os.chmod(self.child_dir_path, new_mode) - os.chmod(TESTFN, new_mode) + self.child_file_path = os.path.join(TESTFN, 'a') + self.child_dir_path = os.path.join(TESTFN, 'b') + support.create_empty_file(self.child_file_path) + os.mkdir(self.child_dir_path) + old_dir_mode = os.stat(TESTFN).st_mode + old_child_file_mode = os.stat(self.child_file_path).st_mode + old_child_dir_mode = os.stat(self.child_dir_path).st_mode + # Make unwritable. + new_mode = stat.S_IREAD|stat.S_IEXEC + os.chmod(self.child_file_path, new_mode) + os.chmod(self.child_dir_path, new_mode) + os.chmod(TESTFN, new_mode) - self.addCleanup(os.chmod, TESTFN, old_dir_mode) - self.addCleanup(os.chmod, self.child_file_path, old_child_file_mode) - self.addCleanup(os.chmod, self.child_dir_path, old_child_dir_mode) + self.addCleanup(os.chmod, TESTFN, old_dir_mode) + self.addCleanup(os.chmod, self.child_file_path, old_child_file_mode) + self.addCleanup(os.chmod, self.child_dir_path, old_child_dir_mode) - shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) - # Test whether onerror has actually been called. - self.assertEqual(self.errorState, 3, - "Expected call to onerror function did not " - "happen.") + shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) + # Test whether onerror has actually been called. + self.assertEqual(self.errorState, 3, + "Expected call to onerror function did not happen.") def check_args_to_onerror(self, func, arg, exc): # test_rmtree_errors deliberately runs rmtree @@ -807,38 +807,39 @@ finally: shutil.rmtree(TESTFN, ignore_errors=True) - if hasattr(os, "mkfifo"): - # Issue #3002: copyfile and copytree block indefinitely on named pipes - def test_copyfile_named_pipe(self): - os.mkfifo(TESTFN) + # Issue #3002: copyfile and copytree block indefinitely on named pipes + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + def test_copyfile_named_pipe(self): + os.mkfifo(TESTFN) + try: + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, TESTFN, TESTFN2) + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, __file__, TESTFN) + finally: + os.remove(TESTFN) + + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + @support.skip_unless_symlink + def test_copytree_named_pipe(self): + os.mkdir(TESTFN) + try: + subdir = os.path.join(TESTFN, "subdir") + os.mkdir(subdir) + pipe = os.path.join(subdir, "mypipe") + os.mkfifo(pipe) try: - self.assertRaises(shutil.SpecialFileError, - shutil.copyfile, TESTFN, TESTFN2) - self.assertRaises(shutil.SpecialFileError, - shutil.copyfile, __file__, TESTFN) - finally: - os.remove(TESTFN) - - @support.skip_unless_symlink - def test_copytree_named_pipe(self): - os.mkdir(TESTFN) - try: - subdir = os.path.join(TESTFN, "subdir") - os.mkdir(subdir) - pipe = os.path.join(subdir, "mypipe") - os.mkfifo(pipe) - try: - shutil.copytree(TESTFN, TESTFN2) - except shutil.Error as e: - errors = e.args[0] - self.assertEqual(len(errors), 1) - src, dst, error_msg = errors[0] - self.assertEqual("`%s` is a named pipe" % pipe, error_msg) - else: - self.fail("shutil.Error should have been raised") - finally: - shutil.rmtree(TESTFN, ignore_errors=True) - shutil.rmtree(TESTFN2, ignore_errors=True) + shutil.copytree(TESTFN, TESTFN2) + except shutil.Error as e: + errors = e.args[0] + self.assertEqual(len(errors), 1) + src, dst, error_msg = errors[0] + self.assertEqual("`%s` is a named pipe" % pipe, error_msg) + else: + self.fail("shutil.Error should have been raised") + finally: + shutil.rmtree(TESTFN, ignore_errors=True) + shutil.rmtree(TESTFN2, ignore_errors=True) def test_copytree_special_func(self): diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -817,16 +817,17 @@ self.assertRaises(TypeError, socket.if_nametoindex, 0) self.assertRaises(TypeError, socket.if_indextoname, '_DEADBEEF') + @unittest.skipUnless(hasattr(sys, 'getrefcount'), + 'test needs sys.getrefcount()') def testRefCountGetNameInfo(self): # Testing reference count for getnameinfo - if hasattr(sys, "getrefcount"): - try: - # On some versions, this loses a reference - orig = sys.getrefcount(__name__) - socket.getnameinfo(__name__,0) - except TypeError: - if sys.getrefcount(__name__) != orig: - self.fail("socket.getnameinfo loses a reference") + try: + # On some versions, this loses a reference + orig = sys.getrefcount(__name__) + socket.getnameinfo(__name__,0) + except TypeError: + if sys.getrefcount(__name__) != orig: + self.fail("socket.getnameinfo loses a reference") def testInterpreterCrash(self): # Making sure getnameinfo doesn't crash the interpreter @@ -931,17 +932,17 @@ # Check that setting it to an invalid type raises TypeError self.assertRaises(TypeError, socket.setdefaulttimeout, "spam") + @unittest.skipUnless(hasattr(socket, 'inet_aton'), + 'test needs socket.inet_aton()') def testIPv4_inet_aton_fourbytes(self): - if not hasattr(socket, 'inet_aton'): - return # No inet_aton, nothing to check # Test that issue1008086 and issue767150 are fixed. # It must return 4 bytes. self.assertEqual(b'\x00'*4, socket.inet_aton('0.0.0.0')) self.assertEqual(b'\xff'*4, socket.inet_aton('255.255.255.255')) + @unittest.skipUnless(hasattr(socket, 'inet_pton'), + 'test needs socket.inet_pton()') def testIPv4toString(self): - if not hasattr(socket, 'inet_pton'): - return # No inet_pton() on this platform from socket import inet_aton as f, inet_pton, AF_INET g = lambda a: inet_pton(AF_INET, a) @@ -970,9 +971,9 @@ assertInvalid(g, '1.2.3.4.5') assertInvalid(g, '::1') + @unittest.skipUnless(hasattr(socket, 'inet_pton'), + 'test needs socket.inet_pton()') def testIPv6toString(self): - if not hasattr(socket, 'inet_pton'): - return # No inet_pton() on this platform try: from socket import inet_pton, AF_INET6, has_ipv6 if not has_ipv6: @@ -1024,9 +1025,9 @@ assertInvalid('::1.2.3.4:0') assertInvalid('0.100.200.0:3:4:5:6:7:8') + @unittest.skipUnless(hasattr(socket, 'inet_ntop'), + 'test needs socket.inet_ntop()') def testStringToIPv4(self): - if not hasattr(socket, 'inet_ntop'): - return # No inet_ntop() on this platform from socket import inet_ntoa as f, inet_ntop, AF_INET g = lambda a: inet_ntop(AF_INET, a) assertInvalid = lambda func,a: self.assertRaises( @@ -1048,9 +1049,9 @@ assertInvalid(g, b'\x00' * 5) assertInvalid(g, b'\x00' * 16) + @unittest.skipUnless(hasattr(socket, 'inet_ntop'), + 'test needs socket.inet_ntop()') def testStringToIPv6(self): - if not hasattr(socket, 'inet_ntop'): - return # No inet_ntop() on this platform try: from socket import inet_ntop, AF_INET6, has_ipv6 if not has_ipv6: @@ -3660,6 +3661,8 @@ self.cli.connect((HOST, self.port)) time.sleep(1.0) + at unittest.skipUnless(hasattr(socket, 'socketpair'), + 'test needs socket.socketpair()') @unittest.skipUnless(thread, 'Threading required for this test.') class BasicSocketPairTest(SocketPairTest): @@ -3722,26 +3725,27 @@ def _testSetBlocking(self): pass - if hasattr(socket, "SOCK_NONBLOCK"): - @support.requires_linux_version(2, 6, 28) - def testInitNonBlocking(self): - # reinit server socket - self.serv.close() - self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM | - socket.SOCK_NONBLOCK) - self.port = support.bind_port(self.serv) - self.serv.listen(1) - # actual testing - start = time.time() - try: - self.serv.accept() - except OSError: - pass - end = time.time() - self.assertTrue((end - start) < 1.0, "Error creating with non-blocking mode.") - - def _testInitNonBlocking(self): + @unittest.skipUnless(hasattr(socket, 'SOCK_NONBLOCK'), + 'test needs socket.SOCK_NONBLOCK') + @support.requires_linux_version(2, 6, 28) + def testInitNonBlocking(self): + # reinit server socket + self.serv.close() + self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM | + socket.SOCK_NONBLOCK) + self.port = support.bind_port(self.serv) + self.serv.listen(1) + # actual testing + start = time.time() + try: + self.serv.accept() + except OSError: pass + end = time.time() + self.assertTrue((end - start) < 1.0, "Error creating with non-blocking mode.") + + def _testInitNonBlocking(self): + pass def testInheritFlags(self): # Issue #7995: when calling accept() on a listening socket with a @@ -4431,12 +4435,12 @@ if not ok: self.fail("accept() returned success when we did not expect it") + @unittest.skipUnless(hasattr(signal, 'alarm'), + 'test needs signal.alarm()') def testInterruptedTimeout(self): # XXX I don't know how to do this test on MSWindows or any other # plaform that doesn't support signal.alarm() or os.kill(), though # the bug should have existed on all platforms. - if not hasattr(signal, "alarm"): - return # can only test on *nix self.serv.settimeout(5.0) # must be longer than alarm class Alarm(Exception): pass @@ -4496,6 +4500,7 @@ self.assertTrue(issubclass(socket.gaierror, OSError)) self.assertTrue(issubclass(socket.timeout, OSError)) + at unittest.skipUnless(sys.platform == 'linux', 'Linux specific test') class TestLinuxAbstractNamespace(unittest.TestCase): UNIX_PATH_MAX = 108 @@ -4531,6 +4536,7 @@ finally: s.close() + at unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'test needs socket.AF_UNIX') class TestUnixDomain(unittest.TestCase): def setUp(self): @@ -4680,10 +4686,10 @@ for line in f: if line.startswith("tipc "): return True - if support.verbose: - print("TIPC module is not loaded, please 'sudo modprobe tipc'") return False + at unittest.skipUnless(isTipcAvailable(), + "TIPC module is not loaded, please 'sudo modprobe tipc'") class TIPCTest(unittest.TestCase): def testRDM(self): srv = socket.socket(socket.AF_TIPC, socket.SOCK_RDM) @@ -4706,6 +4712,8 @@ self.assertEqual(msg, MSG) + at unittest.skipUnless(isTipcAvailable(), + "TIPC module is not loaded, please 'sudo modprobe tipc'") class TIPCThreadableTest(unittest.TestCase, ThreadableTest): def __init__(self, methodName = 'runTest'): unittest.TestCase.__init__(self, methodName = methodName) @@ -5028,15 +5036,10 @@ InheritanceTest, NonblockConstantTest ]) - if hasattr(socket, "socketpair"): - tests.append(BasicSocketPairTest) - if hasattr(socket, "AF_UNIX"): - tests.append(TestUnixDomain) - if sys.platform == 'linux': - tests.append(TestLinuxAbstractNamespace) - if isTipcAvailable(): - tests.append(TIPCTest) - tests.append(TIPCThreadableTest) + tests.append(BasicSocketPairTest) + tests.append(TestUnixDomain) + tests.append(TestLinuxAbstractNamespace) + tests.extend([TIPCTest, TIPCThreadableTest]) tests.extend([BasicCANTest, CANTest]) tests.extend([BasicRDSTest, RDSTest]) tests.extend([ diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -27,7 +27,10 @@ HOST = test.support.HOST HAVE_UNIX_SOCKETS = hasattr(socket, "AF_UNIX") +requires_unix_sockets = unittest.skipUnless(HAVE_UNIX_SOCKETS, + 'requires Unix sockets') HAVE_FORKING = hasattr(os, "fork") +requires_forking = unittest.skipUnless(HAVE_FORKING, 'requires forking') def signal_alarm(n): """Call signal.alarm when it exists (i.e. not on Windows).""" @@ -175,31 +178,33 @@ socketserver.StreamRequestHandler, self.stream_examine) - if HAVE_FORKING: - def test_ForkingTCPServer(self): - with simple_subprocess(self): - self.run_server(socketserver.ForkingTCPServer, - socketserver.StreamRequestHandler, - self.stream_examine) - - if HAVE_UNIX_SOCKETS: - def test_UnixStreamServer(self): - self.run_server(socketserver.UnixStreamServer, + @requires_forking + def test_ForkingTCPServer(self): + with simple_subprocess(self): + self.run_server(socketserver.ForkingTCPServer, socketserver.StreamRequestHandler, self.stream_examine) - def test_ThreadingUnixStreamServer(self): - self.run_server(socketserver.ThreadingUnixStreamServer, + @requires_unix_sockets + def test_UnixStreamServer(self): + self.run_server(socketserver.UnixStreamServer, + socketserver.StreamRequestHandler, + self.stream_examine) + + @requires_unix_sockets + def test_ThreadingUnixStreamServer(self): + self.run_server(socketserver.ThreadingUnixStreamServer, + socketserver.StreamRequestHandler, + self.stream_examine) + + @requires_unix_sockets + @requires_forking + def test_ForkingUnixStreamServer(self): + with simple_subprocess(self): + self.run_server(ForkingUnixStreamServer, socketserver.StreamRequestHandler, self.stream_examine) - if HAVE_FORKING: - def test_ForkingUnixStreamServer(self): - with simple_subprocess(self): - self.run_server(ForkingUnixStreamServer, - socketserver.StreamRequestHandler, - self.stream_examine) - def test_UDPServer(self): self.run_server(socketserver.UDPServer, socketserver.DatagramRequestHandler, @@ -210,12 +215,12 @@ socketserver.DatagramRequestHandler, self.dgram_examine) - if HAVE_FORKING: - def test_ForkingUDPServer(self): - with simple_subprocess(self): - self.run_server(socketserver.ForkingUDPServer, - socketserver.DatagramRequestHandler, - self.dgram_examine) + @requires_forking + def test_ForkingUDPServer(self): + with simple_subprocess(self): + self.run_server(socketserver.ForkingUDPServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) @contextlib.contextmanager def mocked_select_module(self): @@ -252,22 +257,24 @@ # Alas, on Linux (at least) recvfrom() doesn't return a meaningful # client address so this cannot work: - # if HAVE_UNIX_SOCKETS: - # def test_UnixDatagramServer(self): - # self.run_server(socketserver.UnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # def test_UnixDatagramServer(self): + # self.run_server(socketserver.UnixDatagramServer, + # socketserver.DatagramRequestHandler, + # self.dgram_examine) # - # def test_ThreadingUnixDatagramServer(self): - # self.run_server(socketserver.ThreadingUnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # def test_ThreadingUnixDatagramServer(self): + # self.run_server(socketserver.ThreadingUnixDatagramServer, + # socketserver.DatagramRequestHandler, + # self.dgram_examine) # - # if HAVE_FORKING: - # def test_ForkingUnixDatagramServer(self): - # self.run_server(socketserver.ForkingUnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # @requires_forking + # def test_ForkingUnixDatagramServer(self): + # self.run_server(socketserver.ForkingUnixDatagramServer, + # socketserver.DatagramRequestHandler, + # self.dgram_examine) @reap_threads def test_shutdown(self): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -293,15 +293,16 @@ def test_call_tracing(self): self.assertRaises(TypeError, sys.call_tracing, type, 2) + @unittest.skipUnless(hasattr(sys, "setdlopenflags"), + 'test needs sys.setdlopenflags()') def test_dlopenflags(self): - if hasattr(sys, "setdlopenflags"): - self.assertTrue(hasattr(sys, "getdlopenflags")) - self.assertRaises(TypeError, sys.getdlopenflags, 42) - oldflags = sys.getdlopenflags() - self.assertRaises(TypeError, sys.setdlopenflags) - sys.setdlopenflags(oldflags+1) - self.assertEqual(sys.getdlopenflags(), oldflags+1) - sys.setdlopenflags(oldflags) + self.assertTrue(hasattr(sys, "getdlopenflags")) + self.assertRaises(TypeError, sys.getdlopenflags, 42) + oldflags = sys.getdlopenflags() + self.assertRaises(TypeError, sys.setdlopenflags) + sys.setdlopenflags(oldflags+1) + self.assertEqual(sys.getdlopenflags(), oldflags+1) + sys.setdlopenflags(oldflags) @test.support.refcount_test def test_refcount(self): diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -271,11 +271,10 @@ finally: warning_tests.__file__ = filename + @unittest.skipUnless(hasattr(sys, 'argv'), 'test needs sys.argv') def test_missing_filename_main_with_argv(self): # If __file__ is not specified and the caller is __main__ and sys.argv # exists, then use sys.argv[0] as the file. - if not hasattr(sys, 'argv'): - return filename = warning_tests.__file__ module_name = warning_tests.__name__ try: diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -7,6 +7,13 @@ zlib = support.import_module('zlib') +requires_Compress_copy = unittest.skipUnless( + hasattr(zlib.compressobj(), "copy"), + 'requires Compress.copy()') +requires_Decompress_copy = unittest.skipUnless( + hasattr(zlib.decompressobj(), "copy"), + 'requires Decompress.copy()') + class VersionTestCase(unittest.TestCase): @@ -381,39 +388,39 @@ "mode=%i, level=%i") % (sync, level)) del obj + @unittest.skipUnless(hasattr(zlib, 'Z_SYNC_FLUSH'), + 'requires zlib.Z_SYNC_FLUSH') def test_odd_flush(self): # Test for odd flushing bugs noted in 2.0, and hopefully fixed in 2.1 import random + # Testing on 17K of "random" data - if hasattr(zlib, 'Z_SYNC_FLUSH'): - # Testing on 17K of "random" data + # Create compressor and decompressor objects + co = zlib.compressobj(zlib.Z_BEST_COMPRESSION) + dco = zlib.decompressobj() - # Create compressor and decompressor objects - co = zlib.compressobj(zlib.Z_BEST_COMPRESSION) - dco = zlib.decompressobj() + # Try 17K of data + # generate random data stream + try: + # In 2.3 and later, WichmannHill is the RNG of the bug report + gen = random.WichmannHill() + except AttributeError: + try: + # 2.2 called it Random + gen = random.Random() + except AttributeError: + # others might simply have a single RNG + gen = random + gen.seed(1) + data = genblock(1, 17 * 1024, generator=gen) - # Try 17K of data - # generate random data stream - try: - # In 2.3 and later, WichmannHill is the RNG of the bug report - gen = random.WichmannHill() - except AttributeError: - try: - # 2.2 called it Random - gen = random.Random() - except AttributeError: - # others might simply have a single RNG - gen = random - gen.seed(1) - data = genblock(1, 17 * 1024, generator=gen) + # compress, sync-flush, and decompress + first = co.compress(data) + second = co.flush(zlib.Z_SYNC_FLUSH) + expanded = dco.decompress(first + second) - # compress, sync-flush, and decompress - first = co.compress(data) - second = co.flush(zlib.Z_SYNC_FLUSH) - expanded = dco.decompress(first + second) - - # if decompressed data is different from the input data, choke. - self.assertEqual(expanded, data, "17K random source doesn't match") + # if decompressed data is different from the input data, choke. + self.assertEqual(expanded, data, "17K random source doesn't match") def test_empty_flush(self): # Test that calling .flush() on unused objects works. @@ -525,67 +532,69 @@ data = zlib.compress(input2) self.assertEqual(dco.flush(), input1[1:]) - if hasattr(zlib.compressobj(), "copy"): - def test_compresscopy(self): - # Test copying a compression object - data0 = HAMLET_SCENE - data1 = bytes(str(HAMLET_SCENE, "ascii").swapcase(), "ascii") - c0 = zlib.compressobj(zlib.Z_BEST_COMPRESSION) - bufs0 = [] - bufs0.append(c0.compress(data0)) + @requires_Compress_copy + def test_compresscopy(self): + # Test copying a compression object + data0 = HAMLET_SCENE + data1 = bytes(str(HAMLET_SCENE, "ascii").swapcase(), "ascii") + c0 = zlib.compressobj(zlib.Z_BEST_COMPRESSION) + bufs0 = [] + bufs0.append(c0.compress(data0)) - c1 = c0.copy() - bufs1 = bufs0[:] + c1 = c0.copy() + bufs1 = bufs0[:] - bufs0.append(c0.compress(data0)) - bufs0.append(c0.flush()) - s0 = b''.join(bufs0) + bufs0.append(c0.compress(data0)) + bufs0.append(c0.flush()) + s0 = b''.join(bufs0) - bufs1.append(c1.compress(data1)) - bufs1.append(c1.flush()) - s1 = b''.join(bufs1) + bufs1.append(c1.compress(data1)) + bufs1.append(c1.flush()) + s1 = b''.join(bufs1) - self.assertEqual(zlib.decompress(s0),data0+data0) - self.assertEqual(zlib.decompress(s1),data0+data1) + self.assertEqual(zlib.decompress(s0),data0+data0) + self.assertEqual(zlib.decompress(s1),data0+data1) - def test_badcompresscopy(self): - # Test copying a compression object in an inconsistent state - c = zlib.compressobj() - c.compress(HAMLET_SCENE) - c.flush() - self.assertRaises(ValueError, c.copy) + @requires_Compress_copy + def test_badcompresscopy(self): + # Test copying a compression object in an inconsistent state + c = zlib.compressobj() + c.compress(HAMLET_SCENE) + c.flush() + self.assertRaises(ValueError, c.copy) - if hasattr(zlib.decompressobj(), "copy"): - def test_decompresscopy(self): - # Test copying a decompression object - data = HAMLET_SCENE - comp = zlib.compress(data) - # Test type of return value - self.assertIsInstance(comp, bytes) + @requires_Decompress_copy + def test_decompresscopy(self): + # Test copying a decompression object + data = HAMLET_SCENE + comp = zlib.compress(data) + # Test type of return value + self.assertIsInstance(comp, bytes) - d0 = zlib.decompressobj() - bufs0 = [] - bufs0.append(d0.decompress(comp[:32])) + d0 = zlib.decompressobj() + bufs0 = [] + bufs0.append(d0.decompress(comp[:32])) - d1 = d0.copy() - bufs1 = bufs0[:] + d1 = d0.copy() + bufs1 = bufs0[:] - bufs0.append(d0.decompress(comp[32:])) - s0 = b''.join(bufs0) + bufs0.append(d0.decompress(comp[32:])) + s0 = b''.join(bufs0) - bufs1.append(d1.decompress(comp[32:])) - s1 = b''.join(bufs1) + bufs1.append(d1.decompress(comp[32:])) + s1 = b''.join(bufs1) - self.assertEqual(s0,s1) - self.assertEqual(s0,data) + self.assertEqual(s0,s1) + self.assertEqual(s0,data) - def test_baddecompresscopy(self): - # Test copying a compression object in an inconsistent state - data = zlib.compress(HAMLET_SCENE) - d = zlib.decompressobj() - d.decompress(data) - d.flush() - self.assertRaises(ValueError, d.copy) + @requires_Decompress_copy + def test_baddecompresscopy(self): + # Test copying a compression object in an inconsistent state + data = zlib.compress(HAMLET_SCENE) + d = zlib.decompressobj() + d.decompress(data) + d.flush() + self.assertRaises(ValueError, d.copy) # Memory use of the following functions takes into account overallocation diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -165,6 +165,8 @@ Tests ----- +- Issue #18702: All skipped tests now reported as skipped. + - Issue #19439: interpreter embedding tests are now executed on Windows (Patch by Zachary Ware) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 22:16:13 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 22:16:13 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE4NzAy?= =?utf-8?q?=3A_All_skipped_tests_now_reported_as_skipped=2E?= Message-ID: <3dCVLP5jCzz7LjT@mail.python.org> http://hg.python.org/cpython/rev/2d330f7908e7 changeset: 86897:2d330f7908e7 branch: 2.7 parent: 86884:a34889a30d52 user: Serhiy Storchaka date: Sun Nov 03 23:15:46 2013 +0200 summary: Issue #18702: All skipped tests now reported as skipped. files: Lib/test/test_array.py | 17 +- Lib/test/test_compileall.py | 3 +- Lib/test/test_csv.py | 135 +++--- Lib/test/test_enumerate.py | 3 +- Lib/test/test_ftplib.py | 26 +- Lib/test/test_mailbox.py | 36 +- Lib/test/test_math.py | 59 +- Lib/test/test_mmap.py | 142 +++--- Lib/test/test_os.py | 231 ++++++------ Lib/test/test_poplib.py | 28 +- Lib/test/test_posix.py | 401 +++++++++++---------- Lib/test/test_set.py | 8 +- Lib/test/test_shutil.py | 110 +++-- Lib/test/test_socket.py | 64 +- Lib/test/test_socketserver.py | 85 ++-- Lib/test/test_sys.py | 17 +- Lib/test/test_warnings.py | 3 +- Lib/test/test_zlib.py | 155 ++++---- Misc/NEWS | 2 + 19 files changed, 783 insertions(+), 742 deletions(-) diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -9,6 +9,7 @@ from weakref import proxy import array, cStringIO from cPickle import loads, dumps, HIGHEST_PROTOCOL +import sys class ArraySubclass(array.array): pass @@ -772,15 +773,15 @@ s = None self.assertRaises(ReferenceError, len, p) + @unittest.skipUnless(hasattr(sys, 'getrefcount'), + 'test needs sys.getrefcount()') def test_bug_782369(self): - import sys - if hasattr(sys, "getrefcount"): - for i in range(10): - b = array.array('B', range(64)) - rc = sys.getrefcount(10) - for i in range(10): - b = array.array('B', range(64)) - self.assertEqual(rc, sys.getrefcount(10)) + for i in range(10): + b = array.array('B', range(64)) + rc = sys.getrefcount(10) + for i in range(10): + b = array.array('B', range(64)) + self.assertEqual(rc, sys.getrefcount(10)) def test_subclass_with_kwargs(self): # SF bug #1486663 -- this used to erroneously raise a TypeError diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -31,11 +31,10 @@ compare = struct.pack('<4sl', imp.get_magic(), mtime) return data, compare + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def recreation_check(self, metadata): """Check that compileall recreates bytecode when the new metadata is used.""" - if not hasattr(os, 'stat'): - return py_compile.compile(self.source_path) self.assertEqual(*self.data()) with open(self.bc_path, 'rb') as file: diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -1014,78 +1014,77 @@ dialect = sniffer.sniff(self.sample9) self.assertTrue(dialect.doublequote) -if not hasattr(sys, "gettotalrefcount"): - if test_support.verbose: print "*** skipping leakage tests ***" -else: - class NUL: - def write(s, *args): - pass - writelines = write +class NUL: + def write(s, *args): + pass + writelines = write - class TestLeaks(unittest.TestCase): - def test_create_read(self): - delta = 0 - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - delta = rc-lastrc - lastrc = rc - # if csv.reader() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) + at unittest.skipUnless(hasattr(sys, "gettotalrefcount"), + 'requires sys.gettotalrefcount()') +class TestLeaks(unittest.TestCase): + def test_create_read(self): + delta = 0 + lastrc = sys.gettotalrefcount() + for i in xrange(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + csv.reader(["a,b,c\r\n"]) + csv.reader(["a,b,c\r\n"]) + csv.reader(["a,b,c\r\n"]) + delta = rc-lastrc + lastrc = rc + # if csv.reader() leaks, last delta should be 3 or more + self.assertEqual(delta < 3, True) - def test_create_write(self): - delta = 0 - lastrc = sys.gettotalrefcount() - s = NUL() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.writer(s) - csv.writer(s) - csv.writer(s) - delta = rc-lastrc - lastrc = rc - # if csv.writer() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) + def test_create_write(self): + delta = 0 + lastrc = sys.gettotalrefcount() + s = NUL() + for i in xrange(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + csv.writer(s) + csv.writer(s) + csv.writer(s) + delta = rc-lastrc + lastrc = rc + # if csv.writer() leaks, last delta should be 3 or more + self.assertEqual(delta < 3, True) - def test_read(self): - delta = 0 - rows = ["a,b,c\r\n"]*5 - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - rdr = csv.reader(rows) - for row in rdr: - pass - delta = rc-lastrc - lastrc = rc - # if reader leaks during read, delta should be 5 or more - self.assertEqual(delta < 5, True) + def test_read(self): + delta = 0 + rows = ["a,b,c\r\n"]*5 + lastrc = sys.gettotalrefcount() + for i in xrange(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + rdr = csv.reader(rows) + for row in rdr: + pass + delta = rc-lastrc + lastrc = rc + # if reader leaks during read, delta should be 5 or more + self.assertEqual(delta < 5, True) - def test_write(self): - delta = 0 - rows = [[1,2,3]]*5 - s = NUL() - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - writer = csv.writer(s) - for row in rows: - writer.writerow(row) - delta = rc-lastrc - lastrc = rc - # if writer leaks during write, last delta should be 5 or more - self.assertEqual(delta < 5, True) + def test_write(self): + delta = 0 + rows = [[1,2,3]]*5 + s = NUL() + lastrc = sys.gettotalrefcount() + for i in xrange(20): + gc.collect() + self.assertEqual(gc.garbage, []) + rc = sys.gettotalrefcount() + writer = csv.writer(s) + for row in rows: + writer.writerow(row) + delta = rc-lastrc + lastrc = rc + # if writer leaks during write, last delta should be 5 or more + self.assertEqual(delta < 5, True) # commented out for now - csv module doesn't yet support Unicode ## class TestUnicode(unittest.TestCase): diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -188,11 +188,10 @@ self.assertRaises(TypeError, reversed) self.assertRaises(TypeError, reversed, [], 'extra') + @unittest.skipUnless(hasattr(sys, 'getrefcount'), 'test needs sys.getrefcount()') def test_bug1229429(self): # this bug was never in reversed, it was in # PyObject_CallMethod, and reversed_new calls that sometimes. - if not hasattr(sys, "getrefcount"): - return def f(): pass r = f.__reversed__ = object() diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -15,7 +15,7 @@ except ImportError: ssl = None -from unittest import TestCase +from unittest import TestCase, SkipTest, skipUnless from test import test_support from test.test_support import HOST, HOSTv6 threading = test_support.import_module('threading') @@ -579,8 +579,16 @@ self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f) + at skipUnless(socket.has_ipv6, "IPv6 not enabled") class TestIPv6Environment(TestCase): + @classmethod + def setUpClass(cls): + try: + DummyFTPServer((HOST, 0), af=socket.AF_INET6) + except socket.error: + raise SkipTest("IPv6 not enabled") + def setUp(self): self.server = DummyFTPServer((HOSTv6, 0), af=socket.AF_INET6) self.server.start() @@ -615,6 +623,7 @@ retr() + at skipUnless(ssl, "SSL not available") class TestTLS_FTPClassMixin(TestFTPClass): """Repeat TestFTPClass tests starting the TLS layer for both control and data connections first. @@ -630,6 +639,7 @@ self.client.prot_p() + at skipUnless(ssl, "SSL not available") class TestTLS_FTPClass(TestCase): """Specific TLS_FTP class tests.""" @@ -783,17 +793,9 @@ def test_main(): - tests = [TestFTPClass, TestTimeouts] - if socket.has_ipv6: - try: - DummyFTPServer((HOST, 0), af=socket.AF_INET6) - except socket.error: - pass - else: - tests.append(TestIPv6Environment) - - if ssl is not None: - tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass]) + tests = [TestFTPClass, TestTimeouts, + TestIPv6Environment, + TestTLS_FTPClassMixin, TestTLS_FTPClass] thread_info = test_support.threading_setup() try: diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -772,10 +772,10 @@ for msg in self._box: pass + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_file_permissions(self): # Verify that message files are created without execute permissions - if not hasattr(os, "stat") or not hasattr(os, "umask"): - return msg = mailbox.MaildirMessage(self._template % 0) orig_umask = os.umask(0) try: @@ -786,12 +786,11 @@ mode = os.stat(path).st_mode self.assertEqual(mode & 0111, 0) + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_folder_file_perms(self): # From bug #3228, we want to verify that the file created inside a Maildir # subfolder isn't marked as executable. - if not hasattr(os, "stat") or not hasattr(os, "umask"): - return - orig_umask = os.umask(0) try: subfolder = self._box.add_folder('subfolder') @@ -991,24 +990,25 @@ _factory = lambda self, path, factory=None: mailbox.mbox(path, factory) + @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_file_perms(self): # From bug #3228, we want to verify that the mailbox file isn't executable, # even if the umask is set to something that would leave executable bits set. # We only run this test on platforms that support umask. - if hasattr(os, 'umask') and hasattr(os, 'stat'): - try: - old_umask = os.umask(0077) - self._box.close() - os.unlink(self._path) - self._box = mailbox.mbox(self._path, create=True) - self._box.add('') - self._box.close() - finally: - os.umask(old_umask) + try: + old_umask = os.umask(0077) + self._box.close() + os.unlink(self._path) + self._box = mailbox.mbox(self._path, create=True) + self._box.add('') + self._box.close() + finally: + os.umask(old_umask) - st = os.stat(self._path) - perms = st.st_mode - self.assertFalse((perms & 0111)) # Execute bits should all be off. + st = os.stat(self._path) + perms = st.st_mode + self.assertFalse((perms & 0111)) # Execute bits should all be off. def test_terminating_newline(self): message = email.message.Message() diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -906,38 +906,37 @@ # still fails this part of the test on some platforms. For now, we only # *run* test_exceptions() in verbose mode, so that this isn't normally # tested. + @unittest.skipUnless(verbose, 'requires verbose mode') + def test_exceptions(self): + try: + x = math.exp(-1000000000) + except: + # mathmodule.c is failing to weed out underflows from libm, or + # we've got an fp format with huge dynamic range + self.fail("underflowing exp() should not have raised " + "an exception") + if x != 0: + self.fail("underflowing exp() should have returned 0") - if verbose: - def test_exceptions(self): - try: - x = math.exp(-1000000000) - except: - # mathmodule.c is failing to weed out underflows from libm, or - # we've got an fp format with huge dynamic range - self.fail("underflowing exp() should not have raised " - "an exception") - if x != 0: - self.fail("underflowing exp() should have returned 0") + # If this fails, probably using a strict IEEE-754 conforming libm, and x + # is +Inf afterwards. But Python wants overflows detected by default. + try: + x = math.exp(1000000000) + except OverflowError: + pass + else: + self.fail("overflowing exp() didn't trigger OverflowError") - # If this fails, probably using a strict IEEE-754 conforming libm, and x - # is +Inf afterwards. But Python wants overflows detected by default. - try: - x = math.exp(1000000000) - except OverflowError: - pass - else: - self.fail("overflowing exp() didn't trigger OverflowError") - - # If this fails, it could be a puzzle. One odd possibility is that - # mathmodule.c's macros are getting confused while comparing - # Inf (HUGE_VAL) to a NaN, and artificially setting errno to ERANGE - # as a result (and so raising OverflowError instead). - try: - x = math.sqrt(-1.0) - except ValueError: - pass - else: - self.fail("sqrt(-1) didn't raise ValueError") + # If this fails, it could be a puzzle. One odd possibility is that + # mathmodule.c's macros are getting confused while comparing + # Inf (HUGE_VAL) to a NaN, and artificially setting errno to ERANGE + # as a result (and so raising OverflowError instead). + try: + x = math.sqrt(-1.0) + except ValueError: + pass + else: + self.fail("sqrt(-1) didn't raise ValueError") @requires_IEEE_754 def test_testfile(self): diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -320,26 +320,25 @@ mf.close() f.close() + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_entire_file(self): # test mapping of entire file by passing 0 for map length - if hasattr(os, "stat"): - f = open(TESTFN, "w+") + f = open(TESTFN, "w+") - f.write(2**16 * 'm') # Arbitrary character - f.close() + f.write(2**16 * 'm') # Arbitrary character + f.close() - f = open(TESTFN, "rb+") - mf = mmap.mmap(f.fileno(), 0) - self.assertEqual(len(mf), 2**16, "Map size should equal file size.") - self.assertEqual(mf.read(2**16), 2**16 * "m") - mf.close() - f.close() + f = open(TESTFN, "rb+") + mf = mmap.mmap(f.fileno(), 0) + self.assertEqual(len(mf), 2**16, "Map size should equal file size.") + self.assertEqual(mf.read(2**16), 2**16 * "m") + mf.close() + f.close() + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_length_0_offset(self): # Issue #10916: test mapping of remainder of file by passing 0 for # map length with an offset doesn't cause a segfault. - if not hasattr(os, "stat"): - self.skipTest("needs os.stat") # NOTE: allocation granularity is currently 65536 under Win64, # and therefore the minimum offset alignment. with open(TESTFN, "wb") as f: @@ -352,12 +351,10 @@ finally: mf.close() + @unittest.skipUnless(hasattr(os, "stat"), "needs os.stat()") def test_length_0_large_offset(self): # Issue #10959: test mapping of a file by passing 0 for # map length with a large offset doesn't cause a segfault. - if not hasattr(os, "stat"): - self.skipTest("needs os.stat") - with open(TESTFN, "wb") as f: f.write(115699 * b'm') # Arbitrary character @@ -538,9 +535,8 @@ return mmap.mmap.__new__(klass, -1, *args, **kwargs) anon_mmap(PAGESIZE) + @unittest.skipUnless(hasattr(mmap, 'PROT_READ'), "needs mmap.PROT_READ") def test_prot_readonly(self): - if not hasattr(mmap, 'PROT_READ'): - return mapsize = 10 open(TESTFN, "wb").write("a"*mapsize) f = open(TESTFN, "rb") @@ -584,66 +580,68 @@ m.seek(8) self.assertRaises(ValueError, m.write, "bar") - if os.name == 'nt': - def test_tagname(self): - data1 = "0123456789" - data2 = "abcdefghij" - assert len(data1) == len(data2) + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_tagname(self): + data1 = "0123456789" + data2 = "abcdefghij" + assert len(data1) == len(data2) - # Test same tag - m1 = mmap.mmap(-1, len(data1), tagname="foo") - m1[:] = data1 - m2 = mmap.mmap(-1, len(data2), tagname="foo") - m2[:] = data2 - self.assertEqual(m1[:], data2) - self.assertEqual(m2[:], data2) - m2.close() - m1.close() + # Test same tag + m1 = mmap.mmap(-1, len(data1), tagname="foo") + m1[:] = data1 + m2 = mmap.mmap(-1, len(data2), tagname="foo") + m2[:] = data2 + self.assertEqual(m1[:], data2) + self.assertEqual(m2[:], data2) + m2.close() + m1.close() - # Test different tag - m1 = mmap.mmap(-1, len(data1), tagname="foo") - m1[:] = data1 - m2 = mmap.mmap(-1, len(data2), tagname="boo") - m2[:] = data2 - self.assertEqual(m1[:], data1) - self.assertEqual(m2[:], data2) - m2.close() - m1.close() + # Test different tag + m1 = mmap.mmap(-1, len(data1), tagname="foo") + m1[:] = data1 + m2 = mmap.mmap(-1, len(data2), tagname="boo") + m2[:] = data2 + self.assertEqual(m1[:], data1) + self.assertEqual(m2[:], data2) + m2.close() + m1.close() - def test_crasher_on_windows(self): - # Should not crash (Issue 1733986) - m = mmap.mmap(-1, 1000, tagname="foo") - try: - mmap.mmap(-1, 5000, tagname="foo")[:] # same tagname, but larger size - except: - pass - m.close() + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_crasher_on_windows(self): + # Should not crash (Issue 1733986) + m = mmap.mmap(-1, 1000, tagname="foo") + try: + mmap.mmap(-1, 5000, tagname="foo")[:] # same tagname, but larger size + except: + pass + m.close() - # Should not crash (Issue 5385) - open(TESTFN, "wb").write("x"*10) - f = open(TESTFN, "r+b") - m = mmap.mmap(f.fileno(), 0) - f.close() - try: - m.resize(0) # will raise WindowsError - except: - pass - try: - m[:] - except: - pass - m.close() + # Should not crash (Issue 5385) + open(TESTFN, "wb").write("x"*10) + f = open(TESTFN, "r+b") + m = mmap.mmap(f.fileno(), 0) + f.close() + try: + m.resize(0) # will raise WindowsError + except: + pass + try: + m[:] + except: + pass + m.close() - def test_invalid_descriptor(self): - # socket file descriptors are valid, but out of range - # for _get_osfhandle, causing a crash when validating the - # parameters to _get_osfhandle. - s = socket.socket() - try: - with self.assertRaises(mmap.error): - m = mmap.mmap(s.fileno(), 10) - finally: - s.close() + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_invalid_descriptor(self): + # socket file descriptors are valid, but out of range + # for _get_osfhandle, causing a crash when validating the + # parameters to _get_osfhandle. + s = socket.socket() + try: + with self.assertRaises(mmap.error): + m = mmap.mmap(s.fileno(), 10) + finally: + s.close() class LargeMmapTests(unittest.TestCase): diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -83,9 +83,8 @@ open(name, "w") self.files.append(name) + @unittest.skipUnless(hasattr(os, 'tempnam'), 'test needs os.tempnam()') def test_tempnam(self): - if not hasattr(os, "tempnam"): - return with warnings.catch_warnings(): warnings.filterwarnings("ignore", "tempnam", RuntimeWarning, r"test_os$") @@ -99,9 +98,8 @@ self.assertTrue(os.path.basename(name)[:3] == "pfx") self.check_tempfile(name) + @unittest.skipUnless(hasattr(os, 'tmpfile'), 'test needs os.tmpfile()') def test_tmpfile(self): - if not hasattr(os, "tmpfile"): - return # As with test_tmpnam() below, the Windows implementation of tmpfile() # attempts to create a file in the root directory of the current drive. # On Vista and Server 2008, this test will always fail for normal users @@ -150,9 +148,8 @@ fp.close() self.assertTrue(s == "foobar") + @unittest.skipUnless(hasattr(os, 'tmpnam'), 'test needs os.tmpnam()') def test_tmpnam(self): - if not hasattr(os, "tmpnam"): - return with warnings.catch_warnings(): warnings.filterwarnings("ignore", "tmpnam", RuntimeWarning, r"test_os$") @@ -193,10 +190,8 @@ os.unlink(self.fname) os.rmdir(test_support.TESTFN) + @unittest.skipUnless(hasattr(os, 'stat'), 'test needs os.stat()') def test_stat_attributes(self): - if not hasattr(os, "stat"): - return - import stat result = os.stat(self.fname) @@ -256,10 +251,8 @@ pass + @unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()') def test_statvfs_attributes(self): - if not hasattr(os, "statvfs"): - return - try: result = os.statvfs(self.fname) except OSError, e: @@ -311,10 +304,10 @@ st2 = os.stat(test_support.TESTFN) self.assertEqual(st2.st_mtime, int(st.st_mtime-delta)) - # Restrict test to Win32, since there is no guarantee other + # Restrict tests to Win32, since there is no guarantee other # systems support centiseconds - if sys.platform == 'win32': - def get_file_system(path): + def get_file_system(path): + if sys.platform == 'win32': root = os.path.splitdrive(os.path.abspath(path))[0] + '\\' import ctypes kernel32 = ctypes.windll.kernel32 @@ -322,25 +315,31 @@ if kernel32.GetVolumeInformationA(root, None, 0, None, None, None, buf, len(buf)): return buf.value - if get_file_system(test_support.TESTFN) == "NTFS": - def test_1565150(self): - t1 = 1159195039.25 - os.utime(self.fname, (t1, t1)) - self.assertEqual(os.stat(self.fname).st_mtime, t1) + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + @unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS", + "requires NTFS") + def test_1565150(self): + t1 = 1159195039.25 + os.utime(self.fname, (t1, t1)) + self.assertEqual(os.stat(self.fname).st_mtime, t1) - def test_large_time(self): - t1 = 5000000000 # some day in 2128 - os.utime(self.fname, (t1, t1)) - self.assertEqual(os.stat(self.fname).st_mtime, t1) + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + @unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS", + "requires NTFS") + def test_large_time(self): + t1 = 5000000000 # some day in 2128 + os.utime(self.fname, (t1, t1)) + self.assertEqual(os.stat(self.fname).st_mtime, t1) - def test_1686475(self): - # Verify that an open file can be stat'ed - try: - os.stat(r"c:\pagefile.sys") - except WindowsError, e: - if e.errno == 2: # file does not exist; cannot run test - return - self.fail("Could not stat pagefile.sys") + @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") + def test_1686475(self): + # Verify that an open file can be stat'ed + try: + os.stat(r"c:\pagefile.sys") + except WindowsError, e: + if e.errno == 2: # file does not exist; cannot run test + return + self.fail("Could not stat pagefile.sys") from test import mapping_tests @@ -598,6 +597,7 @@ self.assertRaises(ValueError, os.execvpe, 'notepad', [], None) + at unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") class Win32ErrorTests(unittest.TestCase): def test_rename(self): self.assertRaises(WindowsError, os.rename, test_support.TESTFN, test_support.TESTFN+".bak") @@ -644,121 +644,118 @@ self.fail("%r didn't raise a OSError with a bad file descriptor" % f) + @unittest.skipUnless(hasattr(os, 'isatty'), 'test needs os.isatty()') def test_isatty(self): - if hasattr(os, "isatty"): - self.assertEqual(os.isatty(test_support.make_bad_fd()), False) + self.assertEqual(os.isatty(test_support.make_bad_fd()), False) + @unittest.skipUnless(hasattr(os, 'closerange'), 'test needs os.closerange()') def test_closerange(self): - if hasattr(os, "closerange"): - fd = test_support.make_bad_fd() - # Make sure none of the descriptors we are about to close are - # currently valid (issue 6542). - for i in range(10): - try: os.fstat(fd+i) - except OSError: - pass - else: - break - if i < 2: - raise unittest.SkipTest( - "Unable to acquire a range of invalid file descriptors") - self.assertEqual(os.closerange(fd, fd + i-1), None) + fd = test_support.make_bad_fd() + # Make sure none of the descriptors we are about to close are + # currently valid (issue 6542). + for i in range(10): + try: os.fstat(fd+i) + except OSError: + pass + else: + break + if i < 2: + raise unittest.SkipTest( + "Unable to acquire a range of invalid file descriptors") + self.assertEqual(os.closerange(fd, fd + i-1), None) + @unittest.skipUnless(hasattr(os, 'dup2'), 'test needs os.dup2()') def test_dup2(self): - if hasattr(os, "dup2"): - self.check(os.dup2, 20) + self.check(os.dup2, 20) + @unittest.skipUnless(hasattr(os, 'fchmod'), 'test needs os.fchmod()') def test_fchmod(self): - if hasattr(os, "fchmod"): - self.check(os.fchmod, 0) + self.check(os.fchmod, 0) + @unittest.skipUnless(hasattr(os, 'fchown'), 'test needs os.fchown()') def test_fchown(self): - if hasattr(os, "fchown"): - self.check(os.fchown, -1, -1) + self.check(os.fchown, -1, -1) + @unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') def test_fpathconf(self): - if hasattr(os, "fpathconf"): - self.check(os.fpathconf, "PC_NAME_MAX") + self.check(os.fpathconf, "PC_NAME_MAX") + @unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()') def test_ftruncate(self): - if hasattr(os, "ftruncate"): - self.check(os.ftruncate, 0) + self.check(os.ftruncate, 0) + @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') def test_lseek(self): - if hasattr(os, "lseek"): - self.check(os.lseek, 0, 0) + self.check(os.lseek, 0, 0) + @unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()') def test_read(self): - if hasattr(os, "read"): - self.check(os.read, 1) + self.check(os.read, 1) + @unittest.skipUnless(hasattr(os, 'tcsetpgrp'), 'test needs os.tcsetpgrp()') def test_tcsetpgrpt(self): - if hasattr(os, "tcsetpgrp"): - self.check(os.tcsetpgrp, 0) + self.check(os.tcsetpgrp, 0) + @unittest.skipUnless(hasattr(os, 'write'), 'test needs os.write()') def test_write(self): - if hasattr(os, "write"): - self.check(os.write, " ") + self.check(os.write, " ") -if sys.platform != 'win32': - class Win32ErrorTests(unittest.TestCase): - pass + at unittest.skipIf(sys.platform == "win32", "Posix specific tests") +class PosixUidGidTests(unittest.TestCase): + @unittest.skipUnless(hasattr(os, 'setuid'), 'test needs os.setuid()') + def test_setuid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.setuid, 0) + self.assertRaises(OverflowError, os.setuid, 1<<32) - class PosixUidGidTests(unittest.TestCase): - if hasattr(os, 'setuid'): - def test_setuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setuid, 0) - self.assertRaises(OverflowError, os.setuid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setgid'), 'test needs os.setgid()') + def test_setgid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.setgid, 0) + self.assertRaises(OverflowError, os.setgid, 1<<32) - if hasattr(os, 'setgid'): - def test_setgid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setgid, 0) - self.assertRaises(OverflowError, os.setgid, 1<<32) + @unittest.skipUnless(hasattr(os, 'seteuid'), 'test needs os.seteuid()') + def test_seteuid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.seteuid, 0) + self.assertRaises(OverflowError, os.seteuid, 1<<32) - if hasattr(os, 'seteuid'): - def test_seteuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.seteuid, 0) - self.assertRaises(OverflowError, os.seteuid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setegid'), 'test needs os.setegid()') + def test_setegid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.setegid, 0) + self.assertRaises(OverflowError, os.setegid, 1<<32) - if hasattr(os, 'setegid'): - def test_setegid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setegid, 0) - self.assertRaises(OverflowError, os.setegid, 1<<32) + @unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()') + def test_setreuid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.setreuid, 0, 0) + self.assertRaises(OverflowError, os.setreuid, 1<<32, 0) + self.assertRaises(OverflowError, os.setreuid, 0, 1<<32) - if hasattr(os, 'setreuid'): - def test_setreuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setreuid, 0, 0) - self.assertRaises(OverflowError, os.setreuid, 1<<32, 0) - self.assertRaises(OverflowError, os.setreuid, 0, 1<<32) + @unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()') + def test_setreuid_neg1(self): + # Needs to accept -1. We run this in a subprocess to avoid + # altering the test runner's process state (issue8045). + subprocess.check_call([ + sys.executable, '-c', + 'import os,sys;os.setreuid(-1,-1);sys.exit(0)']) - def test_setreuid_neg1(self): - # Needs to accept -1. We run this in a subprocess to avoid - # altering the test runner's process state (issue8045). - subprocess.check_call([ - sys.executable, '-c', - 'import os,sys;os.setreuid(-1,-1);sys.exit(0)']) + @unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()') + def test_setregid(self): + if os.getuid() != 0: + self.assertRaises(os.error, os.setregid, 0, 0) + self.assertRaises(OverflowError, os.setregid, 1<<32, 0) + self.assertRaises(OverflowError, os.setregid, 0, 1<<32) - if hasattr(os, 'setregid'): - def test_setregid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setregid, 0, 0) - self.assertRaises(OverflowError, os.setregid, 1<<32, 0) - self.assertRaises(OverflowError, os.setregid, 0, 1<<32) + @unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()') + def test_setregid_neg1(self): + # Needs to accept -1. We run this in a subprocess to avoid + # altering the test runner's process state (issue8045). + subprocess.check_call([ + sys.executable, '-c', + 'import os,sys;os.setregid(-1,-1);sys.exit(0)']) - def test_setregid_neg1(self): - # Needs to accept -1. We run this in a subprocess to avoid - # altering the test runner's process state (issue8045). - subprocess.check_call([ - sys.executable, '-c', - 'import os,sys;os.setregid(-1,-1);sys.exit(0)']) -else: - class PosixUidGidTests(unittest.TestCase): - pass @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") class Win32KillTests(unittest.TestCase): diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py --- a/Lib/test/test_poplib.py +++ b/Lib/test/test_poplib.py @@ -11,7 +11,7 @@ import time import errno -from unittest import TestCase +from unittest import TestCase, skipUnless from test import test_support from test.test_support import HOST threading = test_support.import_module('threading') @@ -263,17 +263,20 @@ else: DummyPOP3Handler.handle_read(self) - class TestPOP3_SSLClass(TestPOP3Class): - # repeat previous tests by using poplib.POP3_SSL +requires_ssl = skipUnless(SUPPORTS_SSL, 'SSL not supported') - def setUp(self): - self.server = DummyPOP3Server((HOST, 0)) - self.server.handler = DummyPOP3_SSLHandler - self.server.start() - self.client = poplib.POP3_SSL(self.server.host, self.server.port) + at requires_ssl +class TestPOP3_SSLClass(TestPOP3Class): + # repeat previous tests by using poplib.POP3_SSL - def test__all__(self): - self.assertIn('POP3_SSL', poplib.__all__) + def setUp(self): + self.server = DummyPOP3Server((HOST, 0)) + self.server.handler = DummyPOP3_SSLHandler + self.server.start() + self.client = poplib.POP3_SSL(self.server.host, self.server.port) + + def test__all__(self): + self.assertIn('POP3_SSL', poplib.__all__) class TestTimeouts(TestCase): @@ -331,9 +334,8 @@ def test_main(): - tests = [TestPOP3Class, TestTimeouts] - if SUPPORTS_SSL: - tests.append(TestPOP3_SSLClass) + tests = [TestPOP3Class, TestTimeouts, + TestPOP3_SSLClass] thread_info = test_support.threading_setup() try: test_support.run_unittest(*tests) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -53,47 +53,55 @@ posix_func() self.assertRaises(TypeError, posix_func, 1) - if hasattr(posix, 'getresuid'): - def test_getresuid(self): - user_ids = posix.getresuid() - self.assertEqual(len(user_ids), 3) - for val in user_ids: - self.assertGreaterEqual(val, 0) + @unittest.skipUnless(hasattr(posix, 'getresuid'), + 'test needs posix.getresuid()') + def test_getresuid(self): + user_ids = posix.getresuid() + self.assertEqual(len(user_ids), 3) + for val in user_ids: + self.assertGreaterEqual(val, 0) - if hasattr(posix, 'getresgid'): - def test_getresgid(self): - group_ids = posix.getresgid() - self.assertEqual(len(group_ids), 3) - for val in group_ids: - self.assertGreaterEqual(val, 0) + @unittest.skipUnless(hasattr(posix, 'getresgid'), + 'test needs posix.getresgid()') + def test_getresgid(self): + group_ids = posix.getresgid() + self.assertEqual(len(group_ids), 3) + for val in group_ids: + self.assertGreaterEqual(val, 0) - if hasattr(posix, 'setresuid'): - def test_setresuid(self): - current_user_ids = posix.getresuid() - self.assertIsNone(posix.setresuid(*current_user_ids)) - # -1 means don't change that value. - self.assertIsNone(posix.setresuid(-1, -1, -1)) + @unittest.skipUnless(hasattr(posix, 'setresuid'), + 'test needs posix.setresuid()') + def test_setresuid(self): + current_user_ids = posix.getresuid() + self.assertIsNone(posix.setresuid(*current_user_ids)) + # -1 means don't change that value. + self.assertIsNone(posix.setresuid(-1, -1, -1)) - def test_setresuid_exception(self): - # Don't do this test if someone is silly enough to run us as root. - current_user_ids = posix.getresuid() - if 0 not in current_user_ids: - new_user_ids = (current_user_ids[0]+1, -1, -1) - self.assertRaises(OSError, posix.setresuid, *new_user_ids) + @unittest.skipUnless(hasattr(posix, 'setresuid'), + 'test needs posix.setresuid()') + def test_setresuid_exception(self): + # Don't do this test if someone is silly enough to run us as root. + current_user_ids = posix.getresuid() + if 0 not in current_user_ids: + new_user_ids = (current_user_ids[0]+1, -1, -1) + self.assertRaises(OSError, posix.setresuid, *new_user_ids) - if hasattr(posix, 'setresgid'): - def test_setresgid(self): - current_group_ids = posix.getresgid() - self.assertIsNone(posix.setresgid(*current_group_ids)) - # -1 means don't change that value. - self.assertIsNone(posix.setresgid(-1, -1, -1)) + @unittest.skipUnless(hasattr(posix, 'setresgid'), + 'test needs posix.setresgid()') + def test_setresgid(self): + current_group_ids = posix.getresgid() + self.assertIsNone(posix.setresgid(*current_group_ids)) + # -1 means don't change that value. + self.assertIsNone(posix.setresgid(-1, -1, -1)) - def test_setresgid_exception(self): - # Don't do this test if someone is silly enough to run us as root. - current_group_ids = posix.getresgid() - if 0 not in current_group_ids: - new_group_ids = (current_group_ids[0]+1, -1, -1) - self.assertRaises(OSError, posix.setresgid, *new_group_ids) + @unittest.skipUnless(hasattr(posix, 'setresgid'), + 'test needs posix.setresgid()') + def test_setresgid_exception(self): + # Don't do this test if someone is silly enough to run us as root. + current_group_ids = posix.getresgid() + if 0 not in current_group_ids: + new_group_ids = (current_group_ids[0]+1, -1, -1) + self.assertRaises(OSError, posix.setresgid, *new_group_ids) @unittest.skipUnless(hasattr(posix, 'initgroups'), "test needs os.initgroups()") @@ -120,107 +128,118 @@ else: self.fail("Expected OSError to be raised by initgroups") + @unittest.skipUnless(hasattr(posix, 'statvfs'), + 'test needs posix.statvfs()') def test_statvfs(self): - if hasattr(posix, 'statvfs'): - self.assertTrue(posix.statvfs(os.curdir)) + self.assertTrue(posix.statvfs(os.curdir)) + @unittest.skipUnless(hasattr(posix, 'fstatvfs'), + 'test needs posix.fstatvfs()') def test_fstatvfs(self): - if hasattr(posix, 'fstatvfs'): - fp = open(test_support.TESTFN) - try: - self.assertTrue(posix.fstatvfs(fp.fileno())) - finally: - fp.close() + fp = open(test_support.TESTFN) + try: + self.assertTrue(posix.fstatvfs(fp.fileno())) + finally: + fp.close() + @unittest.skipUnless(hasattr(posix, 'ftruncate'), + 'test needs posix.ftruncate()') def test_ftruncate(self): - if hasattr(posix, 'ftruncate'): - fp = open(test_support.TESTFN, 'w+') - try: - # we need to have some data to truncate - fp.write('test') - fp.flush() - posix.ftruncate(fp.fileno(), 0) - finally: - fp.close() + fp = open(test_support.TESTFN, 'w+') + try: + # we need to have some data to truncate + fp.write('test') + fp.flush() + posix.ftruncate(fp.fileno(), 0) + finally: + fp.close() + @unittest.skipUnless(hasattr(posix, 'dup'), + 'test needs posix.dup()') def test_dup(self): - if hasattr(posix, 'dup'): - fp = open(test_support.TESTFN) - try: - fd = posix.dup(fp.fileno()) - self.assertIsInstance(fd, int) - os.close(fd) - finally: - fp.close() + fp = open(test_support.TESTFN) + try: + fd = posix.dup(fp.fileno()) + self.assertIsInstance(fd, int) + os.close(fd) + finally: + fp.close() + @unittest.skipUnless(hasattr(posix, 'confstr'), + 'test needs posix.confstr()') def test_confstr(self): - if hasattr(posix, 'confstr'): - self.assertRaises(ValueError, posix.confstr, "CS_garbage") - self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) + self.assertRaises(ValueError, posix.confstr, "CS_garbage") + self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) + @unittest.skipUnless(hasattr(posix, 'dup2'), + 'test needs posix.dup2()') def test_dup2(self): - if hasattr(posix, 'dup2'): - fp1 = open(test_support.TESTFN) - fp2 = open(test_support.TESTFN) - try: - posix.dup2(fp1.fileno(), fp2.fileno()) - finally: - fp1.close() - fp2.close() + fp1 = open(test_support.TESTFN) + fp2 = open(test_support.TESTFN) + try: + posix.dup2(fp1.fileno(), fp2.fileno()) + finally: + fp1.close() + fp2.close() def fdopen_helper(self, *args): fd = os.open(test_support.TESTFN, os.O_RDONLY) fp2 = posix.fdopen(fd, *args) fp2.close() + @unittest.skipUnless(hasattr(posix, 'fdopen'), + 'test needs posix.fdopen()') def test_fdopen(self): - if hasattr(posix, 'fdopen'): - self.fdopen_helper() - self.fdopen_helper('r') - self.fdopen_helper('r', 100) + self.fdopen_helper() + self.fdopen_helper('r') + self.fdopen_helper('r', 100) + @unittest.skipUnless(hasattr(posix, 'O_EXLOCK'), + 'test needs posix.O_EXLOCK') def test_osexlock(self): - if hasattr(posix, "O_EXLOCK"): + fd = os.open(test_support.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_CREAT) + self.assertRaises(OSError, os.open, test_support.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) + + if hasattr(posix, "O_SHLOCK"): fd = os.open(test_support.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_CREAT) + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) self.assertRaises(OSError, os.open, test_support.TESTFN, os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) os.close(fd) - if hasattr(posix, "O_SHLOCK"): - fd = os.open(test_support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, test_support.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) + @unittest.skipUnless(hasattr(posix, 'O_SHLOCK'), + 'test needs posix.O_SHLOCK') + def test_osshlock(self): + fd1 = os.open(test_support.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + fd2 = os.open(test_support.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + os.close(fd2) + os.close(fd1) - def test_osshlock(self): - if hasattr(posix, "O_SHLOCK"): - fd1 = os.open(test_support.TESTFN, + if hasattr(posix, "O_EXLOCK"): + fd = os.open(test_support.TESTFN, os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - fd2 = os.open(test_support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - os.close(fd2) - os.close(fd1) + self.assertRaises(OSError, os.open, test_support.TESTFN, + os.O_RDONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) - if hasattr(posix, "O_EXLOCK"): - fd = os.open(test_support.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, test_support.TESTFN, - os.O_RDONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) + @unittest.skipUnless(hasattr(posix, 'fstat'), + 'test needs posix.fstat()') + def test_fstat(self): + fp = open(test_support.TESTFN) + try: + self.assertTrue(posix.fstat(fp.fileno())) + finally: + fp.close() - def test_fstat(self): - if hasattr(posix, 'fstat'): - fp = open(test_support.TESTFN) - try: - self.assertTrue(posix.fstat(fp.fileno())) - finally: - fp.close() - + @unittest.skipUnless(hasattr(posix, 'stat'), + 'test needs posix.stat()') def test_stat(self): - if hasattr(posix, 'stat'): - self.assertTrue(posix.stat(test_support.TESTFN)) + self.assertTrue(posix.stat(test_support.TESTFN)) def _test_all_chown_common(self, chown_func, first_param, stat_func): """Common code for chown, fchown and lchown tests.""" @@ -313,59 +332,62 @@ self._test_all_chown_common(posix.lchown, test_support.TESTFN, getattr(posix, 'lstat', None)) + @unittest.skipUnless(hasattr(posix, 'chdir'), 'test needs posix.chdir()') def test_chdir(self): - if hasattr(posix, 'chdir'): - posix.chdir(os.curdir) - self.assertRaises(OSError, posix.chdir, test_support.TESTFN) + posix.chdir(os.curdir) + self.assertRaises(OSError, posix.chdir, test_support.TESTFN) + @unittest.skipUnless(hasattr(posix, 'lsdir'), 'test needs posix.lsdir()') def test_lsdir(self): - if hasattr(posix, 'lsdir'): - self.assertIn(test_support.TESTFN, posix.lsdir(os.curdir)) + self.assertIn(test_support.TESTFN, posix.lsdir(os.curdir)) + @unittest.skipUnless(hasattr(posix, 'access'), 'test needs posix.access()') def test_access(self): - if hasattr(posix, 'access'): - self.assertTrue(posix.access(test_support.TESTFN, os.R_OK)) + self.assertTrue(posix.access(test_support.TESTFN, os.R_OK)) + @unittest.skipUnless(hasattr(posix, 'umask'), 'test needs posix.umask()') def test_umask(self): - if hasattr(posix, 'umask'): - old_mask = posix.umask(0) - self.assertIsInstance(old_mask, int) - posix.umask(old_mask) + old_mask = posix.umask(0) + self.assertIsInstance(old_mask, int) + posix.umask(old_mask) + @unittest.skipUnless(hasattr(posix, 'strerror'), + 'test needs posix.strerror()') def test_strerror(self): - if hasattr(posix, 'strerror'): - self.assertTrue(posix.strerror(0)) + self.assertTrue(posix.strerror(0)) + @unittest.skipUnless(hasattr(posix, 'pipe'), 'test needs posix.pipe()') def test_pipe(self): - if hasattr(posix, 'pipe'): - reader, writer = posix.pipe() - os.close(reader) - os.close(writer) + reader, writer = posix.pipe() + os.close(reader) + os.close(writer) + @unittest.skipUnless(hasattr(posix, 'tempnam'), + 'test needs posix.tempnam()') def test_tempnam(self): - if hasattr(posix, 'tempnam'): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "tempnam", DeprecationWarning) - self.assertTrue(posix.tempnam()) - self.assertTrue(posix.tempnam(os.curdir)) - self.assertTrue(posix.tempnam(os.curdir, 'blah')) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "tempnam", DeprecationWarning) + self.assertTrue(posix.tempnam()) + self.assertTrue(posix.tempnam(os.curdir)) + self.assertTrue(posix.tempnam(os.curdir, 'blah')) + @unittest.skipUnless(hasattr(posix, 'tmpfile'), + 'test needs posix.tmpfile()') def test_tmpfile(self): - if hasattr(posix, 'tmpfile'): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "tmpfile", DeprecationWarning) - fp = posix.tmpfile() - fp.close() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "tmpfile", DeprecationWarning) + fp = posix.tmpfile() + fp.close() + @unittest.skipUnless(hasattr(posix, 'utime'), 'test needs posix.utime()') def test_utime(self): - if hasattr(posix, 'utime'): - now = time.time() - posix.utime(test_support.TESTFN, None) - self.assertRaises(TypeError, posix.utime, test_support.TESTFN, (None, None)) - self.assertRaises(TypeError, posix.utime, test_support.TESTFN, (now, None)) - self.assertRaises(TypeError, posix.utime, test_support.TESTFN, (None, now)) - posix.utime(test_support.TESTFN, (int(now), int(now))) - posix.utime(test_support.TESTFN, (now, now)) + now = time.time() + posix.utime(test_support.TESTFN, None) + self.assertRaises(TypeError, posix.utime, test_support.TESTFN, (None, None)) + self.assertRaises(TypeError, posix.utime, test_support.TESTFN, (now, None)) + self.assertRaises(TypeError, posix.utime, test_support.TESTFN, (None, now)) + posix.utime(test_support.TESTFN, (int(now), int(now))) + posix.utime(test_support.TESTFN, (now, now)) def _test_chflags_regular_file(self, chflags_func, target_file): st = os.stat(target_file) @@ -428,56 +450,57 @@ finally: posix.lchflags(_DUMMY_SYMLINK, dummy_symlink_st.st_flags) + @unittest.skipUnless(hasattr(posix, 'getcwd'), + 'test needs posix.getcwd()') def test_getcwd_long_pathnames(self): - if hasattr(posix, 'getcwd'): - dirname = 'getcwd-test-directory-0123456789abcdef-01234567890abcdef' - curdir = os.getcwd() - base_path = os.path.abspath(test_support.TESTFN) + '.getcwd' + dirname = 'getcwd-test-directory-0123456789abcdef-01234567890abcdef' + curdir = os.getcwd() + base_path = os.path.abspath(test_support.TESTFN) + '.getcwd' - try: - os.mkdir(base_path) - os.chdir(base_path) - except: -# Just returning nothing instead of the SkipTest exception, -# because the test results in Error in that case. -# Is that ok? -# raise unittest.SkipTest, "cannot create directory for testing" - return + try: + os.mkdir(base_path) + os.chdir(base_path) + except: +# Just returning nothing instead of the SkipTest exception, +# because the test results in Error in that case. +# Is that ok? +# raise unittest.SkipTest, "cannot create directory for testing" + return - try: - def _create_and_do_getcwd(dirname, current_path_length = 0): - try: - os.mkdir(dirname) - except: - raise unittest.SkipTest, "mkdir cannot create directory sufficiently deep for getcwd test" + try: + def _create_and_do_getcwd(dirname, current_path_length = 0): + try: + os.mkdir(dirname) + except: + raise unittest.SkipTest, "mkdir cannot create directory sufficiently deep for getcwd test" - os.chdir(dirname) - try: - os.getcwd() - if current_path_length < 4099: - _create_and_do_getcwd(dirname, current_path_length + len(dirname) + 1) - except OSError as e: - expected_errno = errno.ENAMETOOLONG - # The following platforms have quirky getcwd() - # behaviour -- see issue 9185 and 15765 for - # more information. - quirky_platform = ( - 'sunos' in sys.platform or - 'netbsd' in sys.platform or - 'openbsd' in sys.platform - ) - if quirky_platform: - expected_errno = errno.ERANGE - self.assertEqual(e.errno, expected_errno) - finally: - os.chdir('..') - os.rmdir(dirname) + os.chdir(dirname) + try: + os.getcwd() + if current_path_length < 4099: + _create_and_do_getcwd(dirname, current_path_length + len(dirname) + 1) + except OSError as e: + expected_errno = errno.ENAMETOOLONG + # The following platforms have quirky getcwd() + # behaviour -- see issue 9185 and 15765 for + # more information. + quirky_platform = ( + 'sunos' in sys.platform or + 'netbsd' in sys.platform or + 'openbsd' in sys.platform + ) + if quirky_platform: + expected_errno = errno.ERANGE + self.assertEqual(e.errno, expected_errno) + finally: + os.chdir('..') + os.rmdir(dirname) - _create_and_do_getcwd(dirname) + _create_and_do_getcwd(dirname) - finally: - os.chdir(curdir) - shutil.rmtree(base_path) + finally: + os.chdir(curdir) + shutil.rmtree(base_path) @unittest.skipUnless(hasattr(os, 'getegid'), "test needs os.getegid()") def test_getgroups(self): @@ -522,7 +545,7 @@ posix.initgroups(name, self.saved_groups[0]) @unittest.skipUnless(hasattr(posix, 'initgroups'), - "test needs posix.initgroups()") + 'test needs posix.initgroups()') def test_initgroups(self): # find missing group @@ -532,7 +555,7 @@ self.assertIn(g, posix.getgroups()) @unittest.skipUnless(hasattr(posix, 'setgroups'), - "test needs posix.setgroups()") + 'test needs posix.setgroups()') def test_setgroups(self): for groups in [[0], range(16)]: posix.setgroups(groups) diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -561,10 +561,10 @@ s = None self.assertRaises(ReferenceError, str, p) - # C API test only available in a debug build - if hasattr(set, "test_c_api"): - def test_c_api(self): - self.assertEqual(set().test_c_api(), True) + @unittest.skipUnless(hasattr(set, "test_c_api"), + 'C API test only available in a debug build') + def test_c_api(self): + self.assertEqual(set().test_c_api(), True) class SetSubclass(set): pass diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -78,33 +78,34 @@ filename = tempfile.mktemp() self.assertRaises(OSError, shutil.rmtree, filename) - # See bug #1071513 for why we don't run this on cygwin - # and bug #1076467 for why we don't run this as root. - if (hasattr(os, 'chmod') and sys.platform[:6] != 'cygwin' - and not (hasattr(os, 'geteuid') and os.geteuid() == 0)): - def test_on_error(self): - self.errorState = 0 - os.mkdir(TESTFN) - self.childpath = os.path.join(TESTFN, 'a') - f = open(self.childpath, 'w') - f.close() - old_dir_mode = os.stat(TESTFN).st_mode - old_child_mode = os.stat(self.childpath).st_mode - # Make unwritable. - os.chmod(self.childpath, stat.S_IREAD) - os.chmod(TESTFN, stat.S_IREAD) + @unittest.skipUnless(hasattr(os, 'chmod'), 'requires os.chmod()') + @unittest.skipIf(sys.platform[:6] == 'cygwin', + "This test can't be run on Cygwin (issue #1071513).") + @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, + "This test can't be run reliably as root (issue #1076467).") + def test_on_error(self): + self.errorState = 0 + os.mkdir(TESTFN) + self.childpath = os.path.join(TESTFN, 'a') + f = open(self.childpath, 'w') + f.close() + old_dir_mode = os.stat(TESTFN).st_mode + old_child_mode = os.stat(self.childpath).st_mode + # Make unwritable. + os.chmod(self.childpath, stat.S_IREAD) + os.chmod(TESTFN, stat.S_IREAD) - shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) - # Test whether onerror has actually been called. - self.assertEqual(self.errorState, 2, - "Expected call to onerror function did not happen.") + shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) + # Test whether onerror has actually been called. + self.assertEqual(self.errorState, 2, + "Expected call to onerror function did not happen.") - # Make writable again. - os.chmod(TESTFN, old_dir_mode) - os.chmod(self.childpath, old_child_mode) + # Make writable again. + os.chmod(TESTFN, old_dir_mode) + os.chmod(self.childpath, old_child_mode) - # Clean up. - shutil.rmtree(TESTFN) + # Clean up. + shutil.rmtree(TESTFN) def check_args_to_onerror(self, func, arg, exc): # test_rmtree_errors deliberately runs rmtree @@ -308,37 +309,38 @@ finally: shutil.rmtree(TESTFN, ignore_errors=True) - if hasattr(os, "mkfifo"): - # Issue #3002: copyfile and copytree block indefinitely on named pipes - def test_copyfile_named_pipe(self): - os.mkfifo(TESTFN) + # Issue #3002: copyfile and copytree block indefinitely on named pipes + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + def test_copyfile_named_pipe(self): + os.mkfifo(TESTFN) + try: + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, TESTFN, TESTFN2) + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, __file__, TESTFN) + finally: + os.remove(TESTFN) + + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + def test_copytree_named_pipe(self): + os.mkdir(TESTFN) + try: + subdir = os.path.join(TESTFN, "subdir") + os.mkdir(subdir) + pipe = os.path.join(subdir, "mypipe") + os.mkfifo(pipe) try: - self.assertRaises(shutil.SpecialFileError, - shutil.copyfile, TESTFN, TESTFN2) - self.assertRaises(shutil.SpecialFileError, - shutil.copyfile, __file__, TESTFN) - finally: - os.remove(TESTFN) - - def test_copytree_named_pipe(self): - os.mkdir(TESTFN) - try: - subdir = os.path.join(TESTFN, "subdir") - os.mkdir(subdir) - pipe = os.path.join(subdir, "mypipe") - os.mkfifo(pipe) - try: - shutil.copytree(TESTFN, TESTFN2) - except shutil.Error as e: - errors = e.args[0] - self.assertEqual(len(errors), 1) - src, dst, error_msg = errors[0] - self.assertEqual("`%s` is a named pipe" % pipe, error_msg) - else: - self.fail("shutil.Error should have been raised") - finally: - shutil.rmtree(TESTFN, ignore_errors=True) - shutil.rmtree(TESTFN2, ignore_errors=True) + shutil.copytree(TESTFN, TESTFN2) + except shutil.Error as e: + errors = e.args[0] + self.assertEqual(len(errors), 1) + src, dst, error_msg = errors[0] + self.assertEqual("`%s` is a named pipe" % pipe, error_msg) + else: + self.fail("shutil.Error should have been raised") + finally: + shutil.rmtree(TESTFN, ignore_errors=True) + shutil.rmtree(TESTFN2, ignore_errors=True) @unittest.skipUnless(hasattr(os, 'chflags') and hasattr(errno, 'EOPNOTSUPP') and diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -343,16 +343,17 @@ if not fqhn in all_host_names: self.fail("Error testing host resolution mechanisms. (fqdn: %s, all: %s)" % (fqhn, repr(all_host_names))) + @unittest.skipUnless(hasattr(sys, 'getrefcount'), + 'test needs sys.getrefcount()') def testRefCountGetNameInfo(self): # Testing reference count for getnameinfo - if hasattr(sys, "getrefcount"): - try: - # On some versions, this loses a reference - orig = sys.getrefcount(__name__) - socket.getnameinfo(__name__,0) - except TypeError: - self.assertEqual(sys.getrefcount(__name__), orig, - "socket.getnameinfo loses a reference") + try: + # On some versions, this loses a reference + orig = sys.getrefcount(__name__) + socket.getnameinfo(__name__,0) + except TypeError: + self.assertEqual(sys.getrefcount(__name__), orig, + "socket.getnameinfo loses a reference") def testInterpreterCrash(self): # Making sure getnameinfo doesn't crash the interpreter @@ -459,17 +460,17 @@ # Check that setting it to an invalid type raises TypeError self.assertRaises(TypeError, socket.setdefaulttimeout, "spam") + @unittest.skipUnless(hasattr(socket, 'inet_aton'), + 'test needs socket.inet_aton()') def testIPv4_inet_aton_fourbytes(self): - if not hasattr(socket, 'inet_aton'): - return # No inet_aton, nothing to check # Test that issue1008086 and issue767150 are fixed. # It must return 4 bytes. self.assertEqual('\x00'*4, socket.inet_aton('0.0.0.0')) self.assertEqual('\xff'*4, socket.inet_aton('255.255.255.255')) + @unittest.skipUnless(hasattr(socket, 'inet_pton'), + 'test needs socket.inet_pton()') def testIPv4toString(self): - if not hasattr(socket, 'inet_pton'): - return # No inet_pton() on this platform from socket import inet_aton as f, inet_pton, AF_INET g = lambda a: inet_pton(AF_INET, a) @@ -484,9 +485,9 @@ self.assertEqual('\xaa\xaa\xaa\xaa', g('170.170.170.170')) self.assertEqual('\xff\xff\xff\xff', g('255.255.255.255')) + @unittest.skipUnless(hasattr(socket, 'inet_pton'), + 'test needs socket.inet_pton()') def testIPv6toString(self): - if not hasattr(socket, 'inet_pton'): - return # No inet_pton() on this platform try: from socket import inet_pton, AF_INET6, has_ipv6 if not has_ipv6: @@ -503,9 +504,9 @@ f('45ef:76cb:1a:56ef:afeb:bac:1924:aeae') ) + @unittest.skipUnless(hasattr(socket, 'inet_ntop'), + 'test needs socket.inet_ntop()') def testStringToIPv4(self): - if not hasattr(socket, 'inet_ntop'): - return # No inet_ntop() on this platform from socket import inet_ntoa as f, inet_ntop, AF_INET g = lambda a: inet_ntop(AF_INET, a) @@ -518,9 +519,9 @@ self.assertEqual('170.85.170.85', g('\xaa\x55\xaa\x55')) self.assertEqual('255.255.255.255', g('\xff\xff\xff\xff')) + @unittest.skipUnless(hasattr(socket, 'inet_ntop'), + 'test needs socket.inet_ntop()') def testStringToIPv6(self): - if not hasattr(socket, 'inet_ntop'): - return # No inet_ntop() on this platform try: from socket import inet_ntop, AF_INET6, has_ipv6 if not has_ipv6: @@ -871,6 +872,8 @@ self.cli.connect((HOST, self.port)) time.sleep(1.0) + at unittest.skipUnless(hasattr(socket, 'socketpair'), + 'test needs socket.socketpair()') @unittest.skipUnless(thread, 'Threading required for this test.') class BasicSocketPairTest(SocketPairTest): @@ -1456,12 +1459,12 @@ if not ok: self.fail("accept() returned success when we did not expect it") + @unittest.skipUnless(hasattr(signal, 'alarm'), + 'test needs signal.alarm()') def testInterruptedTimeout(self): # XXX I don't know how to do this test on MSWindows or any other # plaform that doesn't support signal.alarm() or os.kill(), though # the bug should have existed on all platforms. - if not hasattr(signal, "alarm"): - return # can only test on *nix self.serv.settimeout(5.0) # must be longer than alarm class Alarm(Exception): pass @@ -1521,6 +1524,7 @@ self.assertTrue(issubclass(socket.gaierror, socket.error)) self.assertTrue(issubclass(socket.timeout, socket.error)) + at unittest.skipUnless(sys.platform == 'linux', 'Linux specific test') class TestLinuxAbstractNamespace(unittest.TestCase): UNIX_PATH_MAX = 108 @@ -1635,11 +1639,11 @@ for line in f: if line.startswith("tipc "): return True - if test_support.verbose: - print "TIPC module is not loaded, please 'sudo modprobe tipc'" return False -class TIPCTest (unittest.TestCase): + at unittest.skipUnless(isTipcAvailable(), + "TIPC module is not loaded, please 'sudo modprobe tipc'") +class TIPCTest(unittest.TestCase): def testRDM(self): srv = socket.socket(socket.AF_TIPC, socket.SOCK_RDM) cli = socket.socket(socket.AF_TIPC, socket.SOCK_RDM) @@ -1659,7 +1663,9 @@ self.assertEqual(msg, MSG) -class TIPCThreadableTest (unittest.TestCase, ThreadableTest): + at unittest.skipUnless(isTipcAvailable(), + "TIPC module is not loaded, please 'sudo modprobe tipc'") +class TIPCThreadableTest(unittest.TestCase, ThreadableTest): def __init__(self, methodName = 'runTest'): unittest.TestCase.__init__(self, methodName = methodName) ThreadableTest.__init__(self) @@ -1712,13 +1718,9 @@ NetworkConnectionAttributesTest, NetworkConnectionBehaviourTest, ]) - if hasattr(socket, "socketpair"): - tests.append(BasicSocketPairTest) - if sys.platform == 'linux2': - tests.append(TestLinuxAbstractNamespace) - if isTipcAvailable(): - tests.append(TIPCTest) - tests.append(TIPCThreadableTest) + tests.append(BasicSocketPairTest) + tests.append(TestLinuxAbstractNamespace) + tests.extend([TIPCTest, TIPCThreadableTest]) thread_info = test_support.threading_setup() test_support.run_unittest(*tests) diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -27,7 +27,10 @@ HOST = test.test_support.HOST HAVE_UNIX_SOCKETS = hasattr(socket, "AF_UNIX") +requires_unix_sockets = unittest.skipUnless(HAVE_UNIX_SOCKETS, + 'requires Unix sockets') HAVE_FORKING = hasattr(os, "fork") and os.name != "os2" +requires_forking = unittest.skipUnless(HAVE_FORKING, 'requires forking') def signal_alarm(n): """Call signal.alarm when it exists (i.e. not on Windows).""" @@ -188,31 +191,33 @@ SocketServer.StreamRequestHandler, self.stream_examine) - if HAVE_FORKING: - def test_ForkingTCPServer(self): - with simple_subprocess(self): - self.run_server(SocketServer.ForkingTCPServer, - SocketServer.StreamRequestHandler, - self.stream_examine) - - if HAVE_UNIX_SOCKETS: - def test_UnixStreamServer(self): - self.run_server(SocketServer.UnixStreamServer, + @requires_forking + def test_ForkingTCPServer(self): + with simple_subprocess(self): + self.run_server(SocketServer.ForkingTCPServer, SocketServer.StreamRequestHandler, self.stream_examine) - def test_ThreadingUnixStreamServer(self): - self.run_server(SocketServer.ThreadingUnixStreamServer, + @requires_unix_sockets + def test_UnixStreamServer(self): + self.run_server(SocketServer.UnixStreamServer, + SocketServer.StreamRequestHandler, + self.stream_examine) + + @requires_unix_sockets + def test_ThreadingUnixStreamServer(self): + self.run_server(SocketServer.ThreadingUnixStreamServer, + SocketServer.StreamRequestHandler, + self.stream_examine) + + @requires_unix_sockets + @requires_forking + def test_ForkingUnixStreamServer(self): + with simple_subprocess(self): + self.run_server(ForkingUnixStreamServer, SocketServer.StreamRequestHandler, self.stream_examine) - if HAVE_FORKING: - def test_ForkingUnixStreamServer(self): - with simple_subprocess(self): - self.run_server(ForkingUnixStreamServer, - SocketServer.StreamRequestHandler, - self.stream_examine) - def test_UDPServer(self): self.run_server(SocketServer.UDPServer, SocketServer.DatagramRequestHandler, @@ -223,12 +228,12 @@ SocketServer.DatagramRequestHandler, self.dgram_examine) - if HAVE_FORKING: - def test_ForkingUDPServer(self): - with simple_subprocess(self): - self.run_server(SocketServer.ForkingUDPServer, - SocketServer.DatagramRequestHandler, - self.dgram_examine) + @requires_forking + def test_ForkingUDPServer(self): + with simple_subprocess(self): + self.run_server(SocketServer.ForkingUDPServer, + SocketServer.DatagramRequestHandler, + self.dgram_examine) @contextlib.contextmanager def mocked_select_module(self): @@ -265,22 +270,24 @@ # Alas, on Linux (at least) recvfrom() doesn't return a meaningful # client address so this cannot work: - # if HAVE_UNIX_SOCKETS: - # def test_UnixDatagramServer(self): - # self.run_server(SocketServer.UnixDatagramServer, - # SocketServer.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # def test_UnixDatagramServer(self): + # self.run_server(SocketServer.UnixDatagramServer, + # SocketServer.DatagramRequestHandler, + # self.dgram_examine) # - # def test_ThreadingUnixDatagramServer(self): - # self.run_server(SocketServer.ThreadingUnixDatagramServer, - # SocketServer.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # def test_ThreadingUnixDatagramServer(self): + # self.run_server(SocketServer.ThreadingUnixDatagramServer, + # SocketServer.DatagramRequestHandler, + # self.dgram_examine) # - # if HAVE_FORKING: - # def test_ForkingUnixDatagramServer(self): - # self.run_server(SocketServer.ForkingUnixDatagramServer, - # SocketServer.DatagramRequestHandler, - # self.dgram_examine) + # @requires_unix_sockets + # @requires_forking + # def test_ForkingUnixDatagramServer(self): + # self.run_server(SocketServer.ForkingUnixDatagramServer, + # SocketServer.DatagramRequestHandler, + # self.dgram_examine) @reap_threads def test_shutdown(self): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -266,15 +266,16 @@ # still has 5 elements maj, min, buildno, plat, csd = sys.getwindowsversion() + @unittest.skipUnless(hasattr(sys, "setdlopenflags"), + 'test needs sys.setdlopenflags()') def test_dlopenflags(self): - if hasattr(sys, "setdlopenflags"): - self.assertTrue(hasattr(sys, "getdlopenflags")) - self.assertRaises(TypeError, sys.getdlopenflags, 42) - oldflags = sys.getdlopenflags() - self.assertRaises(TypeError, sys.setdlopenflags) - sys.setdlopenflags(oldflags+1) - self.assertEqual(sys.getdlopenflags(), oldflags+1) - sys.setdlopenflags(oldflags) + self.assertTrue(hasattr(sys, "getdlopenflags")) + self.assertRaises(TypeError, sys.getdlopenflags, 42) + oldflags = sys.getdlopenflags() + self.assertRaises(TypeError, sys.setdlopenflags) + sys.setdlopenflags(oldflags+1) + self.assertEqual(sys.getdlopenflags(), oldflags+1) + sys.setdlopenflags(oldflags) def test_refcount(self): # n here must be a global in order for this test to pass while diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -259,11 +259,10 @@ finally: warning_tests.__file__ = filename + @unittest.skipUnless(hasattr(sys, 'argv'), 'test needs sys.argv') def test_missing_filename_main_with_argv(self): # If __file__ is not specified and the caller is __main__ and sys.argv # exists, then use sys.argv[0] as the file. - if not hasattr(sys, 'argv'): - return filename = warning_tests.__file__ module_name = warning_tests.__name__ try: diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -12,6 +12,13 @@ zlib = import_module('zlib') +requires_Compress_copy = unittest.skipUnless( + hasattr(zlib.compressobj(), "copy"), + 'requires Compress.copy()') +requires_Decompress_copy = unittest.skipUnless( + hasattr(zlib.decompressobj(), "copy"), + 'requires Decompress.copy()') + class ChecksumTestCase(unittest.TestCase): # checksum test cases @@ -339,39 +346,39 @@ "mode=%i, level=%i") % (sync, level)) del obj + @unittest.skipUnless(hasattr(zlib, 'Z_SYNC_FLUSH'), + 'requires zlib.Z_SYNC_FLUSH') def test_odd_flush(self): # Test for odd flushing bugs noted in 2.0, and hopefully fixed in 2.1 import random + # Testing on 17K of "random" data - if hasattr(zlib, 'Z_SYNC_FLUSH'): - # Testing on 17K of "random" data + # Create compressor and decompressor objects + co = zlib.compressobj(zlib.Z_BEST_COMPRESSION) + dco = zlib.decompressobj() - # Create compressor and decompressor objects - co = zlib.compressobj(zlib.Z_BEST_COMPRESSION) - dco = zlib.decompressobj() + # Try 17K of data + # generate random data stream + try: + # In 2.3 and later, WichmannHill is the RNG of the bug report + gen = random.WichmannHill() + except AttributeError: + try: + # 2.2 called it Random + gen = random.Random() + except AttributeError: + # others might simply have a single RNG + gen = random + gen.seed(1) + data = genblock(1, 17 * 1024, generator=gen) - # Try 17K of data - # generate random data stream - try: - # In 2.3 and later, WichmannHill is the RNG of the bug report - gen = random.WichmannHill() - except AttributeError: - try: - # 2.2 called it Random - gen = random.Random() - except AttributeError: - # others might simply have a single RNG - gen = random - gen.seed(1) - data = genblock(1, 17 * 1024, generator=gen) + # compress, sync-flush, and decompress + first = co.compress(data) + second = co.flush(zlib.Z_SYNC_FLUSH) + expanded = dco.decompress(first + second) - # compress, sync-flush, and decompress - first = co.compress(data) - second = co.flush(zlib.Z_SYNC_FLUSH) - expanded = dco.decompress(first + second) - - # if decompressed data is different from the input data, choke. - self.assertEqual(expanded, data, "17K random source doesn't match") + # if decompressed data is different from the input data, choke. + self.assertEqual(expanded, data, "17K random source doesn't match") def test_empty_flush(self): # Test that calling .flush() on unused objects works. @@ -408,35 +415,36 @@ data = zlib.compress(input2) self.assertEqual(dco.flush(), input1[1:]) - if hasattr(zlib.compressobj(), "copy"): - def test_compresscopy(self): - # Test copying a compression object - data0 = HAMLET_SCENE - data1 = HAMLET_SCENE.swapcase() - c0 = zlib.compressobj(zlib.Z_BEST_COMPRESSION) - bufs0 = [] - bufs0.append(c0.compress(data0)) + @requires_Compress_copy + def test_compresscopy(self): + # Test copying a compression object + data0 = HAMLET_SCENE + data1 = HAMLET_SCENE.swapcase() + c0 = zlib.compressobj(zlib.Z_BEST_COMPRESSION) + bufs0 = [] + bufs0.append(c0.compress(data0)) - c1 = c0.copy() - bufs1 = bufs0[:] + c1 = c0.copy() + bufs1 = bufs0[:] - bufs0.append(c0.compress(data0)) - bufs0.append(c0.flush()) - s0 = ''.join(bufs0) + bufs0.append(c0.compress(data0)) + bufs0.append(c0.flush()) + s0 = ''.join(bufs0) - bufs1.append(c1.compress(data1)) - bufs1.append(c1.flush()) - s1 = ''.join(bufs1) + bufs1.append(c1.compress(data1)) + bufs1.append(c1.flush()) + s1 = ''.join(bufs1) - self.assertEqual(zlib.decompress(s0),data0+data0) - self.assertEqual(zlib.decompress(s1),data0+data1) + self.assertEqual(zlib.decompress(s0),data0+data0) + self.assertEqual(zlib.decompress(s1),data0+data1) - def test_badcompresscopy(self): - # Test copying a compression object in an inconsistent state - c = zlib.compressobj() - c.compress(HAMLET_SCENE) - c.flush() - self.assertRaises(ValueError, c.copy) + @requires_Compress_copy + def test_badcompresscopy(self): + # Test copying a compression object in an inconsistent state + c = zlib.compressobj() + c.compress(HAMLET_SCENE) + c.flush() + self.assertRaises(ValueError, c.copy) def test_decompress_unused_data(self): # Repeated calls to decompress() after EOF should accumulate data in @@ -463,35 +471,36 @@ self.assertEqual(dco.unconsumed_tail, b'') self.assertEqual(dco.unused_data, remainder) - if hasattr(zlib.decompressobj(), "copy"): - def test_decompresscopy(self): - # Test copying a decompression object - data = HAMLET_SCENE - comp = zlib.compress(data) + @requires_Decompress_copy + def test_decompresscopy(self): + # Test copying a decompression object + data = HAMLET_SCENE + comp = zlib.compress(data) - d0 = zlib.decompressobj() - bufs0 = [] - bufs0.append(d0.decompress(comp[:32])) + d0 = zlib.decompressobj() + bufs0 = [] + bufs0.append(d0.decompress(comp[:32])) - d1 = d0.copy() - bufs1 = bufs0[:] + d1 = d0.copy() + bufs1 = bufs0[:] - bufs0.append(d0.decompress(comp[32:])) - s0 = ''.join(bufs0) + bufs0.append(d0.decompress(comp[32:])) + s0 = ''.join(bufs0) - bufs1.append(d1.decompress(comp[32:])) - s1 = ''.join(bufs1) + bufs1.append(d1.decompress(comp[32:])) + s1 = ''.join(bufs1) - self.assertEqual(s0,s1) - self.assertEqual(s0,data) + self.assertEqual(s0,s1) + self.assertEqual(s0,data) - def test_baddecompresscopy(self): - # Test copying a compression object in an inconsistent state - data = zlib.compress(HAMLET_SCENE) - d = zlib.decompressobj() - d.decompress(data) - d.flush() - self.assertRaises(ValueError, d.copy) + @requires_Decompress_copy + def test_baddecompresscopy(self): + # Test copying a compression object in an inconsistent state + data = zlib.compress(HAMLET_SCENE) + d = zlib.decompressobj() + d.decompress(data) + d.flush() + self.assertRaises(ValueError, d.copy) # Memory use of the following functions takes into account overallocation diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -23,6 +23,8 @@ Tests ----- +- Issue #18702: All skipped tests now reported as skipped. + - Issue #19085: Added basic tests for all tkinter widget options. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Sun Nov 3 22:26:33 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Sun, 3 Nov 2013 22:26:33 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogRml4IHRlc3Rfb3Mg?= =?utf-8?b?KGlzc3VlICMxODcwMiku?= Message-ID: <3dCVZK6kZRz7Ljy@mail.python.org> http://hg.python.org/cpython/rev/0d8f0526813f changeset: 86898:0d8f0526813f branch: 2.7 user: Serhiy Storchaka date: Sun Nov 03 23:25:42 2013 +0200 summary: Fix test_os (issue #18702). files: Lib/test/test_os.py | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -316,7 +316,7 @@ return buf.value @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") - @unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS", + @unittest.skipUnless(get_file_system(test_support.TESTFN) == "NTFS", "requires NTFS") def test_1565150(self): t1 = 1159195039.25 @@ -324,7 +324,7 @@ self.assertEqual(os.stat(self.fname).st_mtime, t1) @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") - @unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS", + @unittest.skipUnless(get_file_system(test_support.TESTFN) == "NTFS", "requires NTFS") def test_large_time(self): t1 = 5000000000 # some day in 2128 -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 00:35:47 2013 From: python-checkins at python.org (ned.deily) Date: Mon, 4 Nov 2013 00:35:47 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2318702_null_merge?= Message-ID: <3dCYRR5GXGzSqw@mail.python.org> http://hg.python.org/cpython/rev/a699550bc73b changeset: 86899:a699550bc73b parent: 86896:09105051b9f4 parent: 86895:1feeeb8992f8 user: Ned Deily date: Sun Nov 03 15:34:37 2013 -0800 summary: Issue #18702 null merge files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 01:54:27 2013 From: python-checkins at python.org (r.david.murray) Date: Mon, 4 Nov 2013 01:54:27 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_=2318678=3A_Correct_names_?= =?utf-8?q?of_spwd_struct_members=2E?= Message-ID: <3dCbBC62qrz7LjR@mail.python.org> http://hg.python.org/cpython/rev/1b0ca1a7a3ca changeset: 86900:1b0ca1a7a3ca user: R David Murray date: Sun Nov 03 19:54:05 2013 -0500 summary: #18678: Correct names of spwd struct members. The old names (sp_nam and sp_pwd) are kept for backward compatibility. Since this is a long standing bug that hasn't caused any real-world problems, I'm not backporting it. However, it is worth fixing because the corrected names match the documentation, and more importantly now match the C struct, just like the other struct members. Patch by Vajrasky Kok. files: Doc/library/spwd.rst | 10 +++++----- Misc/NEWS | 4 ++++ Modules/spwdmodule.c | 14 +++++++++----- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Doc/library/spwd.rst b/Doc/library/spwd.rst --- a/Doc/library/spwd.rst +++ b/Doc/library/spwd.rst @@ -19,9 +19,9 @@ +-------+---------------+---------------------------------+ | Index | Attribute | Meaning | +=======+===============+=================================+ -| 0 | ``sp_nam`` | Login name | +| 0 | ``sp_namp`` | Login name | +-------+---------------+---------------------------------+ -| 1 | ``sp_pwd`` | Encrypted password | +| 1 | ``sp_pwdp`` | Encrypted password | +-------+---------------+---------------------------------+ | 2 | ``sp_lstchg`` | Date of last change | +-------+---------------+---------------------------------+ @@ -36,15 +36,15 @@ +-------+---------------+---------------------------------+ | 6 | ``sp_inact`` | Number of days after password | | | | expires until account is | -| | | blocked | +| | | disabled | +-------+---------------+---------------------------------+ | 7 | ``sp_expire`` | Number of days since 1970-01-01 | -| | | until account is disabled | +| | | when account expires | +-------+---------------+---------------------------------+ | 8 | ``sp_flag`` | Reserved | +-------+---------------+---------------------------------+ -The sp_nam and sp_pwd items are strings, all others are integers. +The sp_namp and sp_pwdp items are strings, all others are integers. :exc:`KeyError` is raised if the entry asked for cannot be found. The following functions are defined: diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,10 @@ Library ------- +- Issue #18678: Corrected spwd struct member names in spwd module: + sp_nam->sp_namp, and sp_pwd->sp_pwdp. The old names are kept as extra + structseq members, for backward compatibility. + - Issue #6157: Fixed tkinter.Text.debug(). tkinter.Text.bbox() now raises TypeError instead of TclError on wrong number of arguments. Original patch by Guilherme Polo. diff --git a/Modules/spwdmodule.c b/Modules/spwdmodule.c --- a/Modules/spwdmodule.c +++ b/Modules/spwdmodule.c @@ -26,22 +26,24 @@ #if defined(HAVE_GETSPNAM) || defined(HAVE_GETSPENT) static PyStructSequence_Field struct_spwd_type_fields[] = { - {"sp_nam", "login name"}, - {"sp_pwd", "encrypted password"}, + {"sp_namp", "login name"}, + {"sp_pwdp", "encrypted password"}, {"sp_lstchg", "date of last change"}, {"sp_min", "min #days between changes"}, {"sp_max", "max #days between changes"}, {"sp_warn", "#days before pw expires to warn user about it"}, - {"sp_inact", "#days after pw expires until account is blocked"}, - {"sp_expire", "#days since 1970-01-01 until account is disabled"}, + {"sp_inact", "#days after pw expires until account is disabled"}, + {"sp_expire", "#days since 1970-01-01 when account expires"}, {"sp_flag", "reserved"}, + {"sp_nam", "login name; deprecated"}, /* Backward compatibility */ + {"sp_pwd", "encrypted password; deprecated"}, /* Backward compatibility */ {0} }; PyDoc_STRVAR(struct_spwd__doc__, "spwd.struct_spwd: Results from getsp*() routines.\n\n\ This object may be accessed either as a 9-tuple of\n\ - (sp_nam,sp_pwd,sp_lstchg,sp_min,sp_max,sp_warn,sp_inact,sp_expire,sp_flag)\n\ + (sp_namp,sp_pwdp,sp_lstchg,sp_min,sp_max,sp_warn,sp_inact,sp_expire,sp_flag)\n\ or via the object attributes as named in the above tuple."); static PyStructSequence_Desc struct_spwd_type_desc = { @@ -86,6 +88,8 @@ SETI(setIndex++, p->sp_inact); SETI(setIndex++, p->sp_expire); SETI(setIndex++, p->sp_flag); + SETS(setIndex++, p->sp_namp); /* Backward compatibility for sp_nam */ + SETS(setIndex++, p->sp_pwdp); /* Backward compatibility for sp_pwd */ #undef SETS #undef SETI -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 04:53:10 2013 From: python-checkins at python.org (zach.ware) Date: Mon, 4 Nov 2013 04:53:10 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5Mzkx?= =?utf-8?q?=3A_Clean_up_PCbuild/readme=2Etxt?= Message-ID: <3dCg8Q6VdWz7LjN@mail.python.org> http://hg.python.org/cpython/rev/7b6ac858bb17 changeset: 86901:7b6ac858bb17 branch: 2.7 parent: 86898:0d8f0526813f user: Zachary Ware date: Sun Nov 03 21:43:33 2013 -0600 summary: Issue #19391: Clean up PCbuild/readme.txt files: PCbuild/readme.txt | 62 ++++++++++----------------------- 1 files changed, 20 insertions(+), 42 deletions(-) diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -1,7 +1,7 @@ Building Python using VC++ 9.0 ------------------------------ -This directory is used to build Python for Win32 and x64 platforms, e.g. +This directory is used to build Python for Win32 and x64 platforms, e.g. Windows 2000, XP, Vista and Windows Server 2008. In order to build 32-bit debug and release executables, Microsoft Visual C++ 2008 Express Edition is required at the very least. In order to build 64-bit debug and release @@ -27,7 +27,7 @@ The solution is configured to build the projects in the correct order. "Build Solution" or F7 takes care of dependencies except for x64 builds. To make -cross compiling x64 builds on a 32bit OS possible the x64 builds require a +cross compiling x64 builds on a 32bit OS possible the x64 builds require a 32bit version of Python. NOTE: @@ -37,7 +37,7 @@ running a Python core buildbot test slave; see SUBPROJECTS below) When using the Debug setting, the output files have a _d added to -their name: python30_d.dll, python_d.exe, parser_d.pyd, and so on. Both +their name: python27_d.dll, python_d.exe, parser_d.pyd, and so on. Both the build and rt batch files accept a -d option for debug builds. The 32bit builds end up in the solution folder PCbuild while the x64 builds @@ -47,7 +47,7 @@ Legacy support -------------- -You can find build directories for older versions of Visual Studio and +You can find build directories for older versions of Visual Studio and Visual C++ in the PC directory. The legacy build directories are no longer actively maintained and may not work out of the box. @@ -64,7 +64,7 @@ Visual Studio 2008 uses version 9 of the C runtime (MSVCRT9). The executables are linked to a CRT "side by side" assembly which must be present on the target -machine. This is avalible under the VC/Redist folder of your visual studio +machine. This is available under the VC/Redist folder of your visual studio distribution. On XP and later operating systems that support side-by-side assemblies it is not enough to have the msvcrt90.dll present, it has to be there as a whole assembly, that is, a folder with the .dll @@ -105,16 +105,16 @@ Python-controlled subprojects that wrap external projects: _bsddb Wraps Berkeley DB 4.7.25, which is currently built by _bsddb.vcproj. - project (see below). + project. _sqlite3 - Wraps SQLite 3.6.21, which is currently built by sqlite3.vcproj (see below). + Wraps SQLite 3.6.21, which is currently built by sqlite3.vcproj. _tkinter Wraps the Tk windowing system. Unlike _bsddb and _sqlite3, there's no corresponding tcltk.vcproj-type project that builds Tcl/Tk from vcproj's within our pcbuild.sln, which means this module expects to find a pre-built Tcl/Tk in either ..\..\tcltk for 32-bit or ..\..\tcltk64 for 64-bit (relative to this directory). See below for instructions to build - Tcl/Tk. + Tcl/Tk. bz2 Python wrapper for the libbz2 compression library. Homepage http://sources.redhat.com/bzip2/ @@ -127,16 +127,6 @@ obtaining external sources then you don't need to manually get the source above via subversion. ** - A custom pre-link step in the bz2 project settings should manage to - build bzip2-1.0.6\libbz2.lib by magic before bz2.pyd (or bz2_d.pyd) is - linked in PCbuild\. - However, the bz2 project is not smart enough to remove anything under - bzip2-1.0.6\ when you do a clean, so if you want to rebuild bzip2.lib - you need to clean up bzip2-1.0.6\ by hand. - - All of this managed to build libbz2.lib in - bzip2-1.0.6\$platform-$configuration\, which the Python project links in. - _ssl Python wrapper for the secure sockets library. @@ -154,18 +144,16 @@ You must install the NASM assembler from http://nasm.sf.net - for x86 builds. Put nasmw.exe anywhere in your PATH. - Note: recent releases of nasm only have nasm.exe. Just rename it to - nasmw.exe. + for x86 builds. Put nasm.exe anywhere in your PATH. You can also install ActivePerl from http://www.activestate.com/activeperl/ - if you like to use the official sources instead of the files from + if you like to use the official sources instead of the files from python's subversion repository. The svn version contains pre-build makefiles and assembly files. The build process makes sure that no patented algorithms are included. - For now RC5, MDC2 and IDEA are excluded from the build. You may have + For now RC5, MDC2 and IDEA are excluded from the build. You may have to manually remove $(OBJ_D)\i_*.obj from ms\nt.mak if the build process complains about missing files or forbidden IDEA. Again the files provided in the subversion repository are already fixed. @@ -186,16 +174,16 @@ this by hand. The subprojects above wrap external projects Python doesn't control, and as -such, a little more work is required in order to download the relevant source +such, a little more work is required in order to download the relevant source files for each project before they can be built. The buildbots do this each -time they're built, so the easiest approach is to run either external.bat or +time they're built, so the easiest approach is to run either external.bat or external-amd64.bat in the ..\Tools\buildbot directory from ..\, i.e.: C:\..\svn.python.org\projects\python\trunk\PCbuild>cd .. C:\..\svn.python.org\projects\python\trunk>Tools\buildbot\external.bat This extracts all the external subprojects from http://svn.python.org/external -via Subversion (so you'll need an svn.exe on your PATH) and places them in +via Subversion (so you'll need an svn.exe on your PATH) and places them in ..\.. (relative to this directory). The external(-amd64).bat scripts will also build a debug build of Tcl/Tk; there aren't any equivalent batch files for building release versions of Tcl/Tk lying around in the Tools\buildbot @@ -238,7 +226,7 @@ junction as follows (using the directory structure above as an example): C:\..\python\trunk\external <- already exists and has built versions - of the external subprojects + of the external subprojects C:\..\python\branches\py3k>linkd.exe external ..\..\trunk\external Link created at: external @@ -251,19 +239,9 @@ Building for Itanium -------------------- -NOTE: Official support for Itanium builds have been dropped from the build. Please contact us and provide patches if you are interested in Itanium builds. -The project files support a ReleaseItanium configuration which creates -Win64/Itanium binaries. For this to work, you need to install the Platform -SDK, in particular the 64-bit support. This includes an Itanium compiler -(future releases of the SDK likely include an AMD64 compiler as well). -In addition, you need the Visual Studio plugin for external C compilers, -from http://sf.net/projects/vsextcomp. The plugin will wrap cl.exe, to -locate the proper target compiler, and convert compiler options -accordingly. The project files require at least version 0.9. - Building for AMD64 ------------------ @@ -283,7 +261,7 @@ The solution has two configurations for PGO. The PGInstrument configuration must be build first. The PGInstrument binaries are -lniked against a profiling library and contain extra debug +linked against a profiling library and contain extra debug information. The PGUpdate configuration takes the profiling data and generates optimized binaries. @@ -291,22 +269,22 @@ creates the PGI files, runs the unit test suite or PyBench with the PGI python and finally creates the optimized files. -http://msdn2.microsoft.com/en-us/library/e7k32f4k(VS.90).aspx +http://msdn.microsoft.com/en-us/library/e7k32f4k(VS.90).aspx Static library -------------- The solution has no configuration for static libraries. However it is easy -it build a static library instead of a DLL. You simply have to set the +it build a static library instead of a DLL. You simply have to set the "Configuration Type" to "Static Library (.lib)" and alter the preprocessor macro "Py_ENABLE_SHARED" to "Py_NO_ENABLE_SHARED". You may also have to -change the "Runtime Library" from "Multi-threaded DLL (/MD)" to +change the "Runtime Library" from "Multi-threaded DLL (/MD)" to "Multi-threaded (/MT)". Visual Studio properties ------------------------ -The PCbuild solution makes heavy use of Visual Studio property files +The PCbuild solution makes heavy use of Visual Studio property files (*.vsprops). The properties can be viewed and altered in the Property Manager (View -> Other Windows -> Property Manager). -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 04:53:12 2013 From: python-checkins at python.org (zach.ware) Date: Mon, 4 Nov 2013 04:53:12 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE5Mzkx?= =?utf-8?q?=3A_Clean_up_PCbuild/readme=2Etxt?= Message-ID: <3dCg8S2KkTz7LjT@mail.python.org> http://hg.python.org/cpython/rev/f28a2d072767 changeset: 86902:f28a2d072767 branch: 3.3 parent: 86895:1feeeb8992f8 user: Zachary Ware date: Sun Nov 03 21:48:54 2013 -0600 summary: Issue #19391: Clean up PCbuild/readme.txt files: PCbuild/readme.txt | 74 +++++++++++---------------------- 1 files changed, 26 insertions(+), 48 deletions(-) diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -1,8 +1,8 @@ Building Python using VC++ 10.0 ------------------------------- -This directory is used to build Python for Win32 and x64 platforms, e.g. -Windows 2000, XP, Vista and Windows Server 2008. In order to build 32-bit +This directory is used to build Python for Win32 and x64 platforms, e.g. +Windows XP, Vista and Windows Server 2008. In order to build 32-bit debug and release executables, Microsoft Visual C++ 2010 Express Edition is required at the very least. In order to build 64-bit debug and release executables, Visual Studio 2010 Standard Edition is required at the very @@ -27,7 +27,7 @@ The solution is configured to build the projects in the correct order. "Build Solution" or F7 takes care of dependencies except for x64 builds. To make -cross compiling x64 builds on a 32bit OS possible the x64 builds require a +cross compiling x64 builds on a 32bit OS possible the x64 builds require a 32bit version of Python. NOTE: @@ -47,7 +47,7 @@ Legacy support -------------- -You can find build directories for older versions of Visual Studio and +You can find build directories for older versions of Visual Studio and Visual C++ in the PC directory. The legacy build directories are no longer actively maintained and may not work out of the box. @@ -64,10 +64,10 @@ C RUNTIME --------- -Visual Studio 2010 uses version 10 of the C runtime (MSVCRT9). The executables +Visual Studio 2010 uses version 10 of the C runtime (MSVCRT10). The executables no longer use the "Side by Side" assemblies used in previous versions of the compiler. This simplifies distribution of applications. -The run time libraries are avalible under the VC/Redist folder of your visual studio +The run time libraries are available under the VC/Redist folder of your visual studio distribution. For more info, see the Readme in the VC/Redist folder. SUBPROJECTS @@ -103,14 +103,14 @@ Python-controlled subprojects that wrap external projects: _sqlite3 - Wraps SQLite 3.7.4, which is currently built by sqlite3.vcproj (see below). + Wraps SQLite 3.7.12, which is currently built by sqlite3.vcxproj. _tkinter Wraps the Tk windowing system. Unlike _sqlite3, there's no - corresponding tcltk.vcproj-type project that builds Tcl/Tk from vcproj's + corresponding tcltk.vcxproj-type project that builds Tcl/Tk from vcxproj's within our pcbuild.sln, which means this module expects to find a pre-built Tcl/Tk in either ..\..\tcltk for 32-bit or ..\..\tcltk64 for 64-bit (relative to this directory). See below for instructions to build - Tcl/Tk. + Tcl/Tk. _bz2 Python wrapper for the libbzip2 compression library. Homepage http://www.bzip.org/ @@ -122,16 +122,6 @@ ** NOTE: if you use the Tools\buildbot\external(-amd64).bat approach for obtaining external sources then you don't need to manually get the source above via subversion. ** - - A custom pre-link step in the bz2 project settings should manage to - build bzip2-1.0.6\libbz2.lib by magic before bz2.pyd (or bz2_d.pyd) is - linked in PCbuild\. - However, the bz2 project is not smart enough to remove anything under - bzip2-1.0.6\ when you do a clean, so if you want to rebuild bzip2.lib - you need to clean up bzip2-1.0.6\ by hand. - - All of this managed to build libbz2.lib in - bzip2-1.0.6\$platform-$configuration\, which the Python project links in. _lzma Python wrapper for the liblzma compression library. @@ -156,21 +146,19 @@ You must install the NASM assembler 2.10 or newer from http://nasm.sf.net - for x86 builds. Put nasmw.exe anywhere in your PATH. More recent + for x86 builds. Put nasm.exe anywhere in your PATH. More recent versions of OpenSSL may need a later version of NASM. If OpenSSL's self tests don't pass, you should first try to update NASM and do a full rebuild of OpenSSL. - Note: recent releases of nasm only have nasm.exe. Just rename it to - nasmw.exe. You can also install ActivePerl from http://www.activestate.com/activeperl/ - if you like to use the official sources instead of the files from + if you like to use the official sources instead of the files from python's subversion repository. The svn version contains pre-build makefiles and assembly files. The build process makes sure that no patented algorithms are included. - For now RC5, MDC2 and IDEA are excluded from the build. You may have + For now RC5, MDC2 and IDEA are excluded from the build. You may have to manually remove $(OBJ_D)\i_*.obj from ms\nt.mak if the build process complains about missing files or forbidden IDEA. Again the files provided in the subversion repository are already fixed. @@ -191,16 +179,16 @@ this by hand. The subprojects above wrap external projects Python doesn't control, and as -such, a little more work is required in order to download the relevant source +such, a little more work is required in order to download the relevant source files for each project before they can be built. The buildbots do this each -time they're built, so the easiest approach is to run either external.bat or +time they're built, so the easiest approach is to run either external.bat or external-amd64.bat in the ..\Tools\buildbot directory from ..\, i.e.: C:\..\svn.python.org\projects\python\trunk\PCbuild>cd .. C:\..\svn.python.org\projects\python\trunk>Tools\buildbot\external.bat This extracts all the external subprojects from http://svn.python.org/external -via Subversion (so you'll need an svn.exe on your PATH) and places them in +via Subversion (so you'll need an svn.exe on your PATH) and places them in ..\.. (relative to this directory). The external(-amd64).bat scripts will also build a debug build of Tcl/Tk; there aren't any equivalent batch files for building release versions of Tcl/Tk lying around in the Tools\buildbot @@ -209,18 +197,18 @@ two nmake lines, then call each one without the 'DEBUG=1' parameter, i.e.: The external-amd64.bat file contains this for tcl: - nmake -f makefile.vc COMPILERFLAGS=-DWINVER=0x0500 DEBUG=1 MACHINE=AMD64 INSTALLDIR=..\..\tcltk64 clean all install + nmake -f makefile.vc DEBUG=1 MACHINE=AMD64 INSTALLDIR=..\..\tcltk64 clean all install So for a release build, you'd call it as: - nmake -f makefile.vc COMPILERFLAGS=-DWINVER=0x0500 MACHINE=AMD64 INSTALLDIR=..\..\tcltk64 clean all install + nmake -f makefile.vc MACHINE=AMD64 INSTALLDIR=..\..\tcltk64 clean all install XXX Should we compile with OPTS=threads? XXX Our installer copies a lot of stuff out of the Tcl/Tk install XXX directory. Is all of that really needed for Python use of Tcl/Tk? This will be cleaned up in the future; ideally Tcl/Tk will be brought into our -pcbuild.sln as custom .vcproj files, just as we've recently done with the -sqlite3.vcproj file, which will remove the need for Tcl/Tk to be built +pcbuild.sln as custom .vcxproj files, just as we've recently done with the +sqlite3.vcxproj file, which will remove the need for Tcl/Tk to be built separately via a batch file. XXX trent.nelson 02-Apr-08: @@ -243,7 +231,7 @@ junction as follows (using the directory structure above as an example): C:\..\python\trunk\external <- already exists and has built versions - of the external subprojects + of the external subprojects C:\..\python\branches\py3k>linkd.exe external ..\..\trunk\external Link created at: external @@ -256,19 +244,9 @@ Building for Itanium -------------------- -NOTE: Official support for Itanium builds have been dropped from the build. Please contact us and provide patches if you are interested in Itanium builds. -The project files support a ReleaseItanium configuration which creates -Win64/Itanium binaries. For this to work, you need to install the Platform -SDK, in particular the 64-bit support. This includes an Itanium compiler -(future releases of the SDK likely include an AMD64 compiler as well). -In addition, you need the Visual Studio plugin for external C compilers, -from http://sf.net/projects/vsextcomp. The plugin will wrap cl.exe, to -locate the proper target compiler, and convert compiler options -accordingly. The project files require at least version 0.9. - Building for AMD64 ------------------ @@ -288,7 +266,7 @@ The solution has two configurations for PGO. The PGInstrument configuration must be build first. The PGInstrument binaries are -lniked against a profiling library and contain extra debug +linked against a profiling library and contain extra debug information. The PGUpdate configuration takes the profiling data and generates optimized binaries. @@ -296,23 +274,23 @@ creates the PGI files, runs the unit test suite or PyBench with the PGI python and finally creates the optimized files. -http://msdn2.microsoft.com/en-us/library/e7k32f4k(VS.90).aspx +http://msdn.microsoft.com/en-us/library/e7k32f4k(VS.100).aspx Static library -------------- The solution has no configuration for static libraries. However it is easy -it build a static library instead of a DLL. You simply have to set the +it build a static library instead of a DLL. You simply have to set the "Configuration Type" to "Static Library (.lib)" and alter the preprocessor macro "Py_ENABLE_SHARED" to "Py_NO_ENABLE_SHARED". You may also have to -change the "Runtime Library" from "Multi-threaded DLL (/MD)" to +change the "Runtime Library" from "Multi-threaded DLL (/MD)" to "Multi-threaded (/MT)". Visual Studio properties ------------------------ -The PCbuild solution makes heavy use of Visual Studio property files -(*.vsprops). The properties can be viewed and altered in the Property +The PCbuild solution makes heavy use of Visual Studio property files +(*.props). The properties can be viewed and altered in the Property Manager (View -> Other Windows -> Property Manager). * debug (debug macro: _DEBUG) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 04:53:13 2013 From: python-checkins at python.org (zach.ware) Date: Mon, 4 Nov 2013 04:53:13 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Null_merge_3=2E3?= Message-ID: <3dCg8T3y84z7Lk3@mail.python.org> http://hg.python.org/cpython/rev/74a118d260a7 changeset: 86903:74a118d260a7 parent: 86900:1b0ca1a7a3ca parent: 86902:f28a2d072767 user: Zachary Ware date: Sun Nov 03 21:51:42 2013 -0600 summary: Null merge 3.3 files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 05:10:21 2013 From: python-checkins at python.org (ned.deily) Date: Mon, 4 Nov 2013 05:10:21 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE1Mzky?= =?utf-8?q?=3A_Install_idlelib/idle=5Ftest=2E?= Message-ID: <3dCgXF5KWjz7Ljc@mail.python.org> http://hg.python.org/cpython/rev/dac6aea39814 changeset: 86904:dac6aea39814 branch: 2.7 parent: 86901:7b6ac858bb17 user: Ned Deily date: Sun Nov 03 20:08:17 2013 -0800 summary: Issue #15392: Install idlelib/idle_test. files: Makefile.pre.in | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -939,7 +939,8 @@ logging bsddb bsddb/test csv importlib wsgiref \ lib2to3 lib2to3/fixes lib2to3/pgen2 lib2to3/tests \ lib2to3/tests/data lib2to3/tests/data/fixers lib2to3/tests/data/fixers/myfixes \ - ctypes ctypes/test ctypes/macholib idlelib idlelib/Icons \ + ctypes ctypes/test ctypes/macholib \ + idlelib idlelib/Icons idlelib/idle_test \ distutils distutils/command distutils/tests $(XMLLIBSUBDIRS) \ multiprocessing multiprocessing/dummy \ unittest unittest/test \ -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 05:10:23 2013 From: python-checkins at python.org (ned.deily) Date: Mon, 4 Nov 2013 05:10:23 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE1Mzky?= =?utf-8?q?=3A_Install_idlelib/idle=5Ftest=2E?= Message-ID: <3dCgXH02VFz7Ljx@mail.python.org> http://hg.python.org/cpython/rev/e52dad892521 changeset: 86905:e52dad892521 branch: 3.3 parent: 86902:f28a2d072767 user: Ned Deily date: Sun Nov 03 20:08:53 2013 -0800 summary: Issue #15392: Install idlelib/idle_test. files: Makefile.pre.in | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1048,7 +1048,8 @@ lib2to3 lib2to3/fixes lib2to3/pgen2 lib2to3/tests \ lib2to3/tests/data lib2to3/tests/data/fixers \ lib2to3/tests/data/fixers/myfixes \ - ctypes ctypes/test ctypes/macholib idlelib idlelib/Icons \ + ctypes ctypes/test ctypes/macholib \ + idlelib idlelib/Icons idlelib/idle_test \ distutils distutils/command distutils/tests $(XMLLIBSUBDIRS) \ importlib test/test_importlib test/test_importlib/builtin \ test/test_importlib/extension test/test_importlib/frozen \ -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 05:10:24 2013 From: python-checkins at python.org (ned.deily) Date: Mon, 4 Nov 2013 05:10:24 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2315392=3A_merge_from_3=2E3?= Message-ID: <3dCgXJ1pT5z7Lkg@mail.python.org> http://hg.python.org/cpython/rev/06239fe781fe changeset: 86906:06239fe781fe parent: 86903:74a118d260a7 parent: 86905:e52dad892521 user: Ned Deily date: Sun Nov 03 20:09:51 2013 -0800 summary: Issue #15392: merge from 3.3 files: Makefile.pre.in | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1097,7 +1097,8 @@ lib2to3 lib2to3/fixes lib2to3/pgen2 lib2to3/tests \ lib2to3/tests/data lib2to3/tests/data/fixers \ lib2to3/tests/data/fixers/myfixes \ - ctypes ctypes/test ctypes/macholib idlelib idlelib/Icons \ + ctypes ctypes/test ctypes/macholib \ + idlelib idlelib/Icons idlelib/idle_test \ distutils distutils/command distutils/tests $(XMLLIBSUBDIRS) \ importlib test/test_importlib test/test_importlib/builtin \ test/test_importlib/extension test/test_importlib/frozen \ -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 05:27:51 2013 From: python-checkins at python.org (zach.ware) Date: Mon, 4 Nov 2013 05:27:51 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE3ODgz?= =?utf-8?q?=3A_Backport_test=2Etest=5Fsupport=2E=5Fis=5Fgui=5Favailable=28?= =?utf-8?q?=29?= Message-ID: <3dCgwR2k66z7LjN@mail.python.org> http://hg.python.org/cpython/rev/358496e67a89 changeset: 86907:358496e67a89 branch: 2.7 parent: 86904:dac6aea39814 user: Zachary Ware date: Sun Nov 03 22:27:04 2013 -0600 summary: Issue #17883: Backport test.test_support._is_gui_available() This should stop the Windows buildbots from hanging on test_ttk_guionly. files: Lib/test/test_support.py | 34 ++++++++++++++++++++++++++++ Misc/NEWS | 3 ++ 2 files changed, 37 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -271,6 +271,36 @@ # is exited) but there is a .pyo file. unlink(os.path.join(dirname, modname + os.extsep + 'pyo')) +# On some platforms, should not run gui test even if it is allowed +# in `use_resources'. +if sys.platform.startswith('win'): + import ctypes + import ctypes.wintypes + def _is_gui_available(): + UOI_FLAGS = 1 + WSF_VISIBLE = 0x0001 + class USEROBJECTFLAGS(ctypes.Structure): + _fields_ = [("fInherit", ctypes.wintypes.BOOL), + ("fReserved", ctypes.wintypes.BOOL), + ("dwFlags", ctypes.wintypes.DWORD)] + dll = ctypes.windll.user32 + h = dll.GetProcessWindowStation() + if not h: + raise ctypes.WinError() + uof = USEROBJECTFLAGS() + needed = ctypes.wintypes.DWORD() + res = dll.GetUserObjectInformationW(h, + UOI_FLAGS, + ctypes.byref(uof), + ctypes.sizeof(uof), + ctypes.byref(needed)) + if not res: + raise ctypes.WinError() + return bool(uof.dwFlags & WSF_VISIBLE) +else: + def _is_gui_available(): + return True + def is_resource_enabled(resource): """Test whether a resource is enabled. Known resources are set by regrtest.py.""" @@ -281,6 +311,8 @@ If the caller's module is __main__ then automatically return True. The possibility of False being returned occurs when regrtest.py is executing.""" + if resource == 'gui' and not _is_gui_available(): + raise unittest.SkipTest("Cannot use the 'gui' resource") # see if the caller's module is __main__ - if so, treat as if # the resource was set if sys._getframe(1).f_globals.get("__name__") == "__main__": @@ -1128,6 +1160,8 @@ return obj def requires_resource(resource): + if resource == 'gui' and not _is_gui_available(): + return unittest.skip("resource 'gui' is not available") if is_resource_enabled(resource): return _id else: diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -23,6 +23,9 @@ Tests ----- +- Issue #17883: Backported _is_gui_available() in test.test_support to + avoid hanging Windows buildbots on test_ttk_guionly. + - Issue #18702: All skipped tests now reported as skipped. - Issue #19085: Added basic tests for all tkinter widget options. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 05:48:04 2013 From: python-checkins at python.org (terry.reedy) Date: Mon, 4 Nov 2013 05:48:04 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgI1hYWFhY?= =?utf-8?q?=3A_Fix_test=5Fidle_so_that_idlelib_test_cases_are_actually_run?= Message-ID: <3dChMm1bw5z7LjR@mail.python.org> http://hg.python.org/cpython/rev/cced7981ec4d changeset: 86908:cced7981ec4d branch: 2.7 user: Terry Jan Reedy date: Sun Nov 03 23:37:54 2013 -0500 summary: Issue #XXXXX: Fix test_idle so that idlelib test cases are actually run under test.regrtest on 2.7. files: Lib/test/test_idle.py | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-) diff --git a/Lib/test/test_idle.py b/Lib/test/test_idle.py --- a/Lib/test/test_idle.py +++ b/Lib/test/test_idle.py @@ -23,6 +23,10 @@ # load_tests() if it finds it. (Unittest.main does the same.) load_tests = idletest.load_tests +# pre-3.3 regrtest does not support the load_tests protocol. use test_main +def test_main(): + support.run_unittest(unittest.TestLoader().loadTestsFromModule(idletest)) + if __name__ == '__main__': # Until unittest supports resources, we emulate regrtest's -ugui # so loaded tests run the same as if textually present here. -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 05:52:47 2013 From: python-checkins at python.org (zach.ware) Date: Mon, 4 Nov 2013 05:52:47 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE3ODgz?= =?utf-8?q?=3A_Tweak_test=5Ftcl_testLoadWithUNC_to_skip_the_test_in_the?= Message-ID: <3dChTC6Ycyz7LjS@mail.python.org> http://hg.python.org/cpython/rev/72c3ca3ed22a changeset: 86909:72c3ca3ed22a branch: 2.7 user: Zachary Ware date: Sun Nov 03 22:51:25 2013 -0600 summary: Issue #17883: Tweak test_tcl testLoadWithUNC to skip the test in the event of a permission error on Windows and to properly report other skip conditions. files: Lib/test/test_tcl.py | 16 ++++++++++------ Misc/NEWS | 4 ++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -138,18 +138,15 @@ tcl = self.interp self.assertRaises(TclError,tcl.eval,'package require DNE') + @unittest.skipUnless(sys.platform == 'win32', "only applies to Windows") def testLoadWithUNC(self): - import sys - if sys.platform != 'win32': - return - # Build a UNC path from the regular path. # Something like # \\%COMPUTERNAME%\c$\python27\python.exe fullname = os.path.abspath(sys.executable) if fullname[1] != ':': - return + self.skipTest('unusable path: %r' % fullname) unc_name = r'\\%s\%s$\%s' % (os.environ['COMPUTERNAME'], fullname[0], fullname[3:]) @@ -158,7 +155,14 @@ env.unset("TCL_LIBRARY") cmd = '%s -c "import Tkinter; print Tkinter"' % (unc_name,) - p = Popen(cmd, stdout=PIPE, stderr=PIPE) + try: + p = Popen(cmd, stdout=PIPE, stderr=PIPE) + except WindowsError as e: + if e.winerror == 5: + self.skipTest('Not permitted to start the child process') + else: + raise + out_data, err_data = p.communicate() msg = '\n\n'.join(['"Tkinter.py" not in output', diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -23,6 +23,10 @@ Tests ----- +- Issue #17883: Tweak test_tcl testLoadWithUNC to skip the test in the + event of a permission error on Windows and to properly report other + skip conditions. + - Issue #17883: Backported _is_gui_available() in test.test_support to avoid hanging Windows buildbots on test_ttk_guionly. -- Repository URL: http://hg.python.org/cpython From tjreedy at udel.edu Mon Nov 4 05:54:23 2013 From: tjreedy at udel.edu (Terry Reedy) Date: Sun, 03 Nov 2013 23:54:23 -0500 Subject: [Python-checkins] cpython (2.7): Issue #XXXXX: Fix test_idle so that idlelib test cases are actually run In-Reply-To: <3dChMm1bw5z7LjR@mail.python.org> References: <3dChMm1bw5z7LjR@mail.python.org> Message-ID: <5277287F.8060607@udel.edu> On 11/3/2013 11:48 PM, terry.reedy wrote: > http://hg.python.org/cpython/rev/cced7981ec4d > changeset: 86908:cced7981ec4d > branch: 2.7 > user: Terry Jan Reedy > date: Sun Nov 03 23:37:54 2013 -0500 > summary: > Issue #XXXXX: Fix test_idle so that idlelib test cases are actually run > under test.regrtest on 2.7. This message is the one included with the patch by Ned Daily. Because a message *was* included (not normal), hg import committed the patch immediately, without giving me a chance to edit the patch or message. As far as I know, there is no way I could have edited the message after the commit. If there was, let me know. From python-checkins at python.org Mon Nov 4 07:26:56 2013 From: python-checkins at python.org (georg.brandl) Date: Mon, 4 Nov 2013 07:26:56 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_update_for_3=2E3=2E3?= Message-ID: <3dCkYr1kxHz7LjS@mail.python.org> http://hg.python.org/peps/rev/f6470cb739db changeset: 5248:f6470cb739db user: Georg Brandl date: Mon Nov 04 07:28:07 2013 +0100 summary: update for 3.3.3 files: pep-0398.txt | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-) diff --git a/pep-0398.txt b/pep-0398.txt --- a/pep-0398.txt +++ b/pep-0398.txt @@ -78,6 +78,13 @@ - 3.3.2 final: May 13, 2013 +3.3.3 schedule +-------------- + +- 3.3.3 candidate 1: October 27, 2013 +- 3.3.3 candidate 2: November 9, 2013 +- 3.3.3 final: November 16, 2013 + Features for 3.3 ================ -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Mon Nov 4 07:29:42 2013 From: python-checkins at python.org (georg.brandl) Date: Mon, 4 Nov 2013 07:29:42 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Note_that_examples_are_sor?= =?utf-8?q?ted_only_for_convenience=2E?= Message-ID: <3dCkd23hxTz7LjS@mail.python.org> http://hg.python.org/cpython/rev/ed8ad769fb1f changeset: 86910:ed8ad769fb1f parent: 86906:06239fe781fe user: Georg Brandl date: Mon Nov 04 07:30:50 2013 +0100 summary: Note that examples are sorted only for convenience. files: Doc/library/statistics.rst | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/Doc/library/statistics.rst b/Doc/library/statistics.rst --- a/Doc/library/statistics.rst +++ b/Doc/library/statistics.rst @@ -52,6 +52,9 @@ Function details ---------------- +Note: The functions do not require the data given to them to be sorted. +However, for reading convenience, most of the examples show sorted sequences. + .. function:: mean(data) Return the sample arithmetic mean of *data*, a sequence or iterator of -- Repository URL: http://hg.python.org/cpython From brian at python.org Mon Nov 4 07:30:56 2013 From: brian at python.org (Brian Curtin) Date: Sun, 3 Nov 2013 22:30:56 -0800 Subject: [Python-checkins] cpython (2.7): Issue #XXXXX: Fix test_idle so that idlelib test cases are actually run In-Reply-To: <5277287F.8060607@udel.edu> References: <3dChMm1bw5z7LjR@mail.python.org> <5277287F.8060607@udel.edu> Message-ID: On Sun, Nov 3, 2013 at 8:54 PM, Terry Reedy wrote: > On 11/3/2013 11:48 PM, terry.reedy wrote: >> >> http://hg.python.org/cpython/rev/cced7981ec4d >> changeset: 86908:cced7981ec4d >> branch: 2.7 >> user: Terry Jan Reedy >> date: Sun Nov 03 23:37:54 2013 -0500 >> summary: >> Issue #XXXXX: Fix test_idle so that idlelib test cases are actually run >> under test.regrtest on 2.7. > > > This message is the one included with the patch by Ned Daily. Because a > message *was* included (not normal), hg import committed the patch > immediately, without giving me a chance to edit the patch or message. As far > as I know, there is no way I could have edited the message after the commit. > If there was, let me know. Besides what Zach mentions, most of the time you probably want to "hg import --no-commit ", run it, test it, then commit it with whatever message you want. From solipsis at pitrou.net Mon Nov 4 07:33:44 2013 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Mon, 04 Nov 2013 07:33:44 +0100 Subject: [Python-checkins] Daily reference leaks (1b0ca1a7a3ca): sum=0 Message-ID: results for 1b0ca1a7a3ca on branch "default" -------------------------------------------- test_site leaked [2, 0, -2] references, sum=0 test_site leaked [2, 0, -2] memory blocks, sum=0 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/antoine/cpython/refleaks/reflogbBtchS', '-x'] From python-checkins at python.org Mon Nov 4 07:43:56 2013 From: python-checkins at python.org (georg.brandl) Date: Mon, 4 Nov 2013 07:43:56 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=283=2E2=29=3A_Backout_d80207?= =?utf-8?q?d15294=2E?= Message-ID: <3dCkxS4VV3zRJ8@mail.python.org> http://hg.python.org/cpython/rev/f27394782ed7 changeset: 86911:f27394782ed7 branch: 3.2 parent: 86861:7d399099334d user: Georg Brandl date: Mon Nov 04 07:43:32 2013 +0100 summary: Backout d80207d15294. files: Lib/distutils/tests/test_build_py.py | 31 ---------------- 1 files changed, 0 insertions(+), 31 deletions(-) diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py --- a/Lib/distutils/tests/test_build_py.py +++ b/Lib/distutils/tests/test_build_py.py @@ -121,37 +121,6 @@ found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) self.assertEqual(sorted(found), ['boiledeggs.%s.pyo' % imp.get_tag()]) - def test_dir_in_package_data(self): - """ - A directory in package_data should not be added to the filelist. - """ - # See bug 19286 - sources = self.mkdtemp() - pkg_dir = os.path.join(sources, "pkg") - - os.mkdir(pkg_dir) - open(os.path.join(pkg_dir, "__init__.py"), "w").close() - - docdir = os.path.join(pkg_dir, "doc") - os.mkdir(docdir) - open(os.path.join(docdir, "testfile"), "w").close() - - # create the directory that could be incorrectly detected as a file - os.mkdir(os.path.join(docdir, 'otherdir')) - - os.chdir(sources) - dist = Distribution({"packages": ["pkg"], - "package_data": {"pkg": ["doc/*"]}}) - # script_name need not exist, it just need to be initialized - dist.script_name = os.path.join(sources, "setup.py") - dist.script_args = ["build"] - dist.parse_command_line() - - try: - dist.run_commands() - except DistutilsFileError: - self.fail("failed package_data when data dir includes a dir") - def test_dont_write_bytecode(self): # makes sure byte_compile is not used dist = self.create_dist()[1] -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 07:43:57 2013 From: python-checkins at python.org (georg.brandl) Date: Mon, 4 Nov 2013 07:43:57 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=283=2E2=29=3A_Backout_265d36?= =?utf-8?q?9ad3b9=2E?= Message-ID: <3dCkxT6FsKz7Ljc@mail.python.org> http://hg.python.org/cpython/rev/6432d6ce3d6b changeset: 86912:6432d6ce3d6b branch: 3.2 user: Georg Brandl date: Mon Nov 04 07:43:41 2013 +0100 summary: Backout 265d369ad3b9. files: Lib/distutils/command/build_py.py | 3 +-- 1 files changed, 1 insertions(+), 2 deletions(-) diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -127,8 +127,7 @@ # Each pattern has to be converted to a platform-specific path filelist = glob(os.path.join(src_dir, convert_path(pattern))) # Files that match more than one pattern are only added once - files.extend([fn for fn in filelist if fn not in files - and os.path.isfile(fn)]) + files.extend([fn for fn in filelist if fn not in files]) return files def build_package_data(self): -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 07:43:59 2013 From: python-checkins at python.org (georg.brandl) Date: Mon, 4 Nov 2013 07:43:59 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=283=2E2=29=3A_Backout_7d3990?= =?utf-8?q?99334d=2E?= Message-ID: <3dCkxW0wGHz7Ljw@mail.python.org> http://hg.python.org/cpython/rev/8c9769b17171 changeset: 86913:8c9769b17171 branch: 3.2 user: Georg Brandl date: Mon Nov 04 07:44:29 2013 +0100 summary: Backout 7d399099334d. files: Misc/NEWS | 3 --- 1 files changed, 0 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,9 +10,6 @@ Library ------- -- Issue #19286: Directories in ``package_data`` are no longer added to - the filelist, preventing failure outlined in the ticket. - - Issue #19435: Fix directory traversal attack on CGIHttpRequestHandler. - Issue #14984: On POSIX systems, when netrc is called without a filename -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 07:45:19 2013 From: python-checkins at python.org (georg.brandl) Date: Mon, 4 Nov 2013 07:45:19 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAobWVyZ2UgMy4yIC0+IDMuMyk6?= =?utf-8?q?_null-merge_3=2E2_backouts?= Message-ID: <3dCkz34J0Yz7LjS@mail.python.org> http://hg.python.org/cpython/rev/00ad499704c3 changeset: 86914:00ad499704c3 branch: 3.3 parent: 86905:e52dad892521 parent: 86913:8c9769b17171 user: Georg Brandl date: Mon Nov 04 07:46:02 2013 +0100 summary: null-merge 3.2 backouts files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 07:45:20 2013 From: python-checkins at python.org (georg.brandl) Date: Mon, 4 Nov 2013 07:45:20 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_merge_with_3=2E3?= Message-ID: <3dCkz466kBz7Ljv@mail.python.org> http://hg.python.org/cpython/rev/627e024fc17c changeset: 86915:627e024fc17c parent: 86910:ed8ad769fb1f parent: 86914:00ad499704c3 user: Georg Brandl date: Mon Nov 04 07:46:23 2013 +0100 summary: merge with 3.3 files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 08:51:55 2013 From: python-checkins at python.org (victor.stinner) Date: Mon, 4 Nov 2013 08:51:55 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_PEP_454=3A_cleanup?= Message-ID: <3dCmRv3tlnz7LjR@mail.python.org> http://hg.python.org/peps/rev/a5d89a8b66fe changeset: 5249:a5d89a8b66fe user: Victor Stinner date: Mon Nov 04 08:50:35 2013 +0100 summary: PEP 454: cleanup files: pep-0454.txt | 50 ++++++++++++++++++++-------------------- 1 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pep-0454.txt b/pep-0454.txt --- a/pep-0454.txt +++ b/pep-0454.txt @@ -134,17 +134,6 @@ See also ``start()`` and ``stop()`` functions. -``start()`` function: - - Start tracing Python memory allocations. - - The function installs hooks on Python memory allocators. These hooks - have important overhead in term of performances and memory usage: - see `Filter functions`_ to limit the overhead. - - See also ``stop()`` and ``is_tracing()`` functions. - - ``stop()`` function: Stop tracing Python memory allocations and clear traces of memory @@ -159,13 +148,24 @@ See also ``start()`` and ``is_tracing()`` functions. +``start()`` function: + + Start tracing Python memory allocations. + + The function installs hooks on Python memory allocators. These hooks + have important overhead in term of performances and memory usage: + see `Filter functions`_ to limit the overhead. + + See also ``stop()`` and ``is_tracing()`` functions. + + ``take_snapshot()`` function: Take a snapshot of traces of memory blocks allocated by Python using the ``get_traces()`` function. Return a new ``Snapshot`` instance. - The ``tracemalloc`` module must be tracing memory allocations to take a - snapshot, see the the ``start()`` function. + The ``tracemalloc`` module must be tracing memory allocations to + take a snapshot, see the the ``start()`` function. See also ``get_traces()`` and ``get_object_traceback()`` functions. @@ -175,7 +175,7 @@ When Python allocates a memory block, ``tracemalloc`` attachs a "trace" to the memory block to store its size in bytes and the traceback where the -allocation occured. +allocation occurred. The following functions give access to these traces. A trace is a ``(size: int, traceback)`` tuple. *size* is the size of the memory block in bytes. @@ -208,8 +208,8 @@ Get the maximum number of frames stored in the traceback of a trace. - By default, a trace of an allocated memory block only stores the - most recent frame: the limit is ``1``. + By default, a trace of a memory block only stores the most recent + frame: the limit is ``1``. Use the ``set_traceback_limit()`` function to change the limit. @@ -220,19 +220,19 @@ ``(size: int, traceback: tuple)`` tuples. *traceback* is a tuple of ``(filename: str, lineno: int)`` tuples. - The list of traces do not include memory blocks allocated before the - ``tracemalloc`` module started to trace memory allocations nor memory - blocks ignored by filters (see ``get_filters()``). + The list of traces does not include memory blocks allocated before + the ``tracemalloc`` module started to trace memory allocations nor + memory blocks ignored by filters (see ``get_filters()``). - The list is not sorted. Take a snapshot using ``take_snapshot()`` - and use the ``Snapshot.statistics()`` method to get a sorted list of - statistics. + The list has an undefined order. Take a snapshot using + ``take_snapshot()`` and use the ``Snapshot.statistics()`` method to + get a sorted list of statistics. Tracebacks of traces are limited to ``traceback_limit`` frames. Use ``set_traceback_limit()`` to store more frames. - Return an empty list if the ``tracemalloc`` module is not tracing memory - allocations. + Return an empty list if the ``tracemalloc`` module is not tracing + memory allocations. See also ``take_snapshot()`` and ``get_object_traceback()`` functions. @@ -266,7 +266,7 @@ To limit the overhead, some files can be excluded or tracing can be restricted to a set of files using filters. Examples: ``add_filter(Filter(True, subprocess.__file__))`` only traces memory allocations in the ``subprocess`` -module, and ``add_filter(Filter(False, tracemalloc.__file__))`` do not trace +module, and ``add_filter(Filter(False, tracemalloc.__file__))`` ignores memory allocations in the ``tracemalloc`` module By default, there is one exclusive filter to ignore Python memory blocks -- Repository URL: http://hg.python.org/peps From tjreedy at udel.edu Mon Nov 4 09:18:05 2013 From: tjreedy at udel.edu (Terry Reedy) Date: Mon, 04 Nov 2013 03:18:05 -0500 Subject: [Python-checkins] cpython (2.7): Issue #XXXXX: Fix test_idle so that idlelib test cases are actually run In-Reply-To: References: <3dChMm1bw5z7LjR@mail.python.org> <5277287F.8060607@udel.edu> Message-ID: <5277583D.7090203@udel.edu> On 11/4/2013 1:30 AM, Brian Curtin wrote: > On Sun, Nov 3, 2013 at 8:54 PM, Terry Reedy wrote: >> On 11/3/2013 11:48 PM, terry.reedy wrote: >>> >>> http://hg.python.org/cpython/rev/cced7981ec4d >>> changeset: 86908:cced7981ec4d >>> branch: 2.7 >>> user: Terry Jan Reedy >>> date: Sun Nov 03 23:37:54 2013 -0500 >>> summary: >>> Issue #XXXXX: Fix test_idle so that idlelib test cases are actually run >>> under test.regrtest on 2.7. >> >> >> This message is the one included with the patch by Ned Daily. Because a >> message *was* included (not normal), hg import committed the patch >> immediately, without giving me a chance to edit the patch or message. As far >> as I know, there is no way I could have edited the message after the commit. >> If there was, let me know. > > Besides what Zach mentions, I have only seen commit messages here. > most of the time you probably want to "hg > import --no-commit ", run it, test it, then commit it with > whatever message you want. That is what I normally do, in effect, along with pulling just before committing. To be more precise. I use TortoiseHg Workbench -Repository/Import which normally brings up an editor window for a commit message after doing the patch. Leaving the window blank, as I always do, has the effect of --no-commit. A 'changeset patch' containing a commit message bypasses the option of either editing the message or making it blank. There does not seem to be a 'setting' to change this. The issue is http://bugs.python.org/issue19488 From python-checkins at python.org Mon Nov 4 11:08:47 2013 From: python-checkins at python.org (nick.coghlan) Date: Mon, 4 Nov 2013 11:08:47 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogQ2xvc2UgIzE3ODI3?= =?utf-8?q?=3A_Document_codecs=2Eencode_=26_codecs=2Edecode?= Message-ID: <3dCqTq1QnYz7Ljy@mail.python.org> http://hg.python.org/cpython/rev/bdb30bdf60a5 changeset: 86916:bdb30bdf60a5 branch: 2.7 parent: 86909:72c3ca3ed22a user: Nick Coghlan date: Mon Nov 04 20:05:16 2013 +1000 summary: Close #17827: Document codecs.encode & codecs.decode files: Doc/library/codecs.rst | 23 +++++++++++++++++++++++ Misc/NEWS | 4 ++++ 2 files changed, 27 insertions(+), 0 deletions(-) diff --git a/Doc/library/codecs.rst b/Doc/library/codecs.rst --- a/Doc/library/codecs.rst +++ b/Doc/library/codecs.rst @@ -23,6 +23,29 @@ It defines the following functions: +.. function:: encode(obj, encoding='ascii', errors='strict') + + Encodes *obj* using the codec registered for *encoding*. + + *Errors* may be given to set the desired error handling scheme. The + default error handler is ``strict`` meaning that encoding errors raise + :exc:`ValueError` (or a more codec specific subclass, such as + :exc:`UnicodeEncodeError`). Refer to :ref:`codec-base-classes` for more + information on codec error handling. + + .. versionadded:: 2.4 + +.. function:: decode(obj, encoding='ascii', errors='strict') + + Decodes *obj* using the codec registered for *encoding*. + + *Errors* may be given to set the desired error handling scheme. The + default error handler is ``strict`` meaning that decoding errors raise + :exc:`ValueError` (or a more codec specific subclass, such as + :exc:`UnicodeDecodeError`). Refer to :ref:`codec-base-classes` for more + information on codec error handling. + + .. versionadded:: 2.4 .. function:: register(search_function) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,9 +12,13 @@ Library ------- +- Issue #17827: Add the missing documentation for ``codecs.encode`` and + ``codecs.decode``. + - Issue #6157: Fixed Tkinter.Text.debug(). Original patch by Guilherme Polo. - Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of + integers instead of a string. Based on patch by Guilherme Polo. - Issue #19286: Directories in ``package_data`` are no longer added to -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 11:11:12 2013 From: python-checkins at python.org (victor.stinner) Date: Mon, 4 Nov 2013 11:11:12 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2316286=3A_write_a_?= =?utf-8?q?new_subfunction_bytes=5Fcompare=5Feq=28=29?= Message-ID: <3dCqXc0KZsz7Ljv@mail.python.org> http://hg.python.org/cpython/rev/536a7c09c7fd changeset: 86917:536a7c09c7fd parent: 86915:627e024fc17c user: Victor Stinner date: Mon Nov 04 11:08:10 2013 +0100 summary: Issue #16286: write a new subfunction bytes_compare_eq() * cleanup bytes_richcompare() * PyUnicode_RichCompare(): replace a test with a XOR files: Objects/bytesobject.c | 84 ++++++++++++++++------------ Objects/unicodeobject.c | 8 +- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -802,6 +802,23 @@ return PyLong_FromLong((unsigned char)a->ob_sval[i]); } +Py_LOCAL(int) +bytes_compare_eq(PyBytesObject *a, PyBytesObject *b) +{ + int cmp; + Py_ssize_t len; + + len = Py_SIZE(a); + if (Py_SIZE(b) != len) + return 0; + + if (a->ob_sval[0] != b->ob_sval[0]) + return 0; + + cmp = memcmp(a->ob_sval, b->ob_sval, len); + return (cmp == 0); +} + static PyObject* bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op) { @@ -822,53 +839,46 @@ return NULL; } result = Py_NotImplemented; - goto out; } - if (a == b) { + else if (a == b) { switch (op) { case Py_EQ:case Py_LE:case Py_GE: result = Py_True; - goto out; + break; case Py_NE:case Py_LT:case Py_GT: result = Py_False; - goto out; + break; } } - if (op == Py_EQ) { - /* Supporting Py_NE here as well does not save - much time, since Py_NE is rarely used. */ - if (Py_SIZE(a) == Py_SIZE(b) - && (a->ob_sval[0] == b->ob_sval[0] - && memcmp(a->ob_sval, b->ob_sval, Py_SIZE(a)) == 0)) { - result = Py_True; - } else { - result = Py_False; + else if (op == Py_EQ || op == Py_NE) { + int eq = bytes_compare_eq(a, b); + eq ^= (op == Py_NE); + result = eq ? Py_True : Py_False; + } + else { + len_a = Py_SIZE(a); len_b = Py_SIZE(b); + min_len = (len_a < len_b) ? len_a : len_b; + if (min_len > 0) { + c = Py_CHARMASK(*a->ob_sval) - Py_CHARMASK(*b->ob_sval); + if (c==0) + c = memcmp(a->ob_sval, b->ob_sval, min_len); } - goto out; + else + c = 0; + if (c == 0) + c = (len_a < len_b) ? -1 : (len_a > len_b) ? 1 : 0; + switch (op) { + case Py_LT: c = c < 0; break; + case Py_LE: c = c <= 0; break; + case Py_GT: c = c > 0; break; + case Py_GE: c = c >= 0; break; + default: + assert(op != Py_EQ && op != Py_NE); + Py_RETURN_NOTIMPLEMENTED; + } + result = c ? Py_True : Py_False; } - len_a = Py_SIZE(a); len_b = Py_SIZE(b); - min_len = (len_a < len_b) ? len_a : len_b; - if (min_len > 0) { - c = Py_CHARMASK(*a->ob_sval) - Py_CHARMASK(*b->ob_sval); - if (c==0) - c = memcmp(a->ob_sval, b->ob_sval, min_len); - } else - c = 0; - if (c == 0) - c = (len_a < len_b) ? -1 : (len_a > len_b) ? 1 : 0; - switch (op) { - case Py_LT: c = c < 0; break; - case Py_LE: c = c <= 0; break; - case Py_EQ: assert(0); break; /* unreachable */ - case Py_NE: c = c != 0; break; - case Py_GT: c = c > 0; break; - case Py_GE: c = c >= 0; break; - default: - result = Py_NotImplemented; - goto out; - } - result = c ? Py_True : Py_False; - out: + Py_INCREF(result); return result; } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -10526,7 +10526,7 @@ #undef COMPARE } -static int +Py_LOCAL(int) unicode_compare_eq(PyObject *str1, PyObject *str2) { int kind; @@ -10630,10 +10630,8 @@ if (op == Py_EQ || op == Py_NE) { result = unicode_compare_eq(left, right); - if (op == Py_EQ) - v = TEST_COND(result); - else - v = TEST_COND(!result); + result ^= (op == Py_NE); + v = TEST_COND(result); } else { result = unicode_compare(left, right); -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 11:24:53 2013 From: python-checkins at python.org (victor.stinner) Date: Mon, 4 Nov 2013 11:24:53 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2316286=3A_optimize?= =?utf-8?q?_PyUnicode=5FRichCompare=28=29_for_identical_strings_=28same?= Message-ID: <3dCqrP3SCSz7LkF@mail.python.org> http://hg.python.org/cpython/rev/5fa291435740 changeset: 86918:5fa291435740 user: Victor Stinner date: Mon Nov 04 11:23:05 2013 +0100 summary: Issue #16286: optimize PyUnicode_RichCompare() for identical strings (same pointer) for any operator, not only Py_EQ and Py_NE. Code of bytes_richcompare() and PyUnicode_RichCompare() is now closer. files: Objects/bytesobject.c | 23 ++++++++++++++++------- Objects/unicodeobject.c | 24 +++++++++++++++++++----- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -842,12 +842,20 @@ } else if (a == b) { switch (op) { - case Py_EQ:case Py_LE:case Py_GE: + case Py_EQ: + case Py_LE: + case Py_GE: + /* a string is equal to itself */ result = Py_True; break; - case Py_NE:case Py_LT:case Py_GT: + case Py_NE: + case Py_LT: + case Py_GT: result = Py_False; break; + default: + PyErr_BadArgument(); + return NULL; } } else if (op == Py_EQ || op == Py_NE) { @@ -856,11 +864,12 @@ result = eq ? Py_True : Py_False; } else { - len_a = Py_SIZE(a); len_b = Py_SIZE(b); - min_len = (len_a < len_b) ? len_a : len_b; + len_a = Py_SIZE(a); + len_b = Py_SIZE(b); + min_len = Py_MIN(len_a, len_b); if (min_len > 0) { c = Py_CHARMASK(*a->ob_sval) - Py_CHARMASK(*b->ob_sval); - if (c==0) + if (c == 0) c = memcmp(a->ob_sval, b->ob_sval, min_len); } else @@ -873,8 +882,8 @@ case Py_GT: c = c > 0; break; case Py_GE: c = c >= 0; break; default: - assert(op != Py_EQ && op != Py_NE); - Py_RETURN_NOTIMPLEMENTED; + PyErr_BadArgument(); + return NULL; } result = c ? Py_True : Py_False; } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -10534,10 +10534,6 @@ Py_ssize_t len; int cmp; - /* a string is equal to itself */ - if (str1 == str2) - return 1; - len = PyUnicode_GET_LENGTH(str1); if (PyUnicode_GET_LENGTH(str2) != len) return 0; @@ -10628,7 +10624,25 @@ PyUnicode_READY(right) == -1) return NULL; - if (op == Py_EQ || op == Py_NE) { + if (left == right) { + switch (op) { + case Py_EQ: + case Py_LE: + case Py_GE: + /* a string is equal to itself */ + v = Py_True; + break; + case Py_NE: + case Py_LT: + case Py_GT: + v = Py_False; + break; + default: + PyErr_BadArgument(); + return NULL; + } + } + else if (op == Py_EQ || op == Py_NE) { result = unicode_compare_eq(left, right); result ^= (op == Py_NE); v = TEST_COND(result); -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 11:29:11 2013 From: python-checkins at python.org (victor.stinner) Date: Mon, 4 Nov 2013 11:29:11 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2316286=3A_remove_d?= =?utf-8?q?uplicated_identity_check_from_unicode=5Fcompare=28=29?= Message-ID: <3dCqxM2XJfz7LjW@mail.python.org> http://hg.python.org/cpython/rev/da9c6e4ef301 changeset: 86919:da9c6e4ef301 user: Victor Stinner date: Mon Nov 04 11:27:14 2013 +0100 summary: Issue #16286: remove duplicated identity check from unicode_compare() Move the test to PyUnicode_Compare() files: Objects/unicodeobject.c | 9 +++++---- 1 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -10428,10 +10428,6 @@ void *data1, *data2; Py_ssize_t len1, len2, len; - /* a string is equal to itself */ - if (str1 == str2) - return 0; - kind1 = PyUnicode_KIND(str1); kind2 = PyUnicode_KIND(str2); data1 = PyUnicode_DATA(str1); @@ -10555,6 +10551,11 @@ if (PyUnicode_READY(left) == -1 || PyUnicode_READY(right) == -1) return -1; + + /* a string is equal to itself */ + if (left == right) + return 0; + return unicode_compare(left, right); } PyErr_Format(PyExc_TypeError, -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 11:29:12 2013 From: python-checkins at python.org (victor.stinner) Date: Mon, 4 Nov 2013 11:29:12 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Issue_=2319424=3A_PyUnicod?= =?utf-8?q?e=5FCompareWithASCIIString=28=29_normalizes_memcmp=28=29_result?= Message-ID: <3dCqxN4P6Qz7LjP@mail.python.org> http://hg.python.org/cpython/rev/494f736f5945 changeset: 86920:494f736f5945 user: Victor Stinner date: Mon Nov 04 11:28:26 2013 +0100 summary: Issue #19424: PyUnicode_CompareWithASCIIString() normalizes memcmp() result to -1, 0, 1 files: Objects/unicodeobject.c | 8 ++++++-- 1 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -10584,8 +10584,12 @@ len = Py_MIN(len1, len2); cmp = memcmp(data, str, len); - if (cmp != 0) - return cmp; + if (cmp != 0) { + if (cmp < 0) + return -1; + else + return 1; + } if (len1 > len2) return 1; /* uni is longer */ if (len2 > len1) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 14:32:35 2013 From: python-checkins at python.org (nick.coghlan) Date: Mon, 4 Nov 2013 14:32:35 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_Fix_functools=2Epartialmet?= =?utf-8?q?hod_docs_and_=5F=5Fall=5F=5F?= Message-ID: <3dCw0z6Ksdz7Lkr@mail.python.org> http://hg.python.org/cpython/rev/ac1685661b07 changeset: 86921:ac1685661b07 user: Nick Coghlan date: Mon Nov 04 23:32:16 2013 +1000 summary: Fix functools.partialmethod docs and __all__ files: Doc/library/functools.rst | 4 ++-- Lib/functools.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -223,8 +223,8 @@ ... return self._alive ... def set_state(self, state): ... self._alive = bool(state) - ... set_alive = partialmethod(set_alive, True) - ... set_dead = partialmethod(set_alive, False) + ... set_alive = partialmethod(set_state, True) + ... set_dead = partialmethod(set_state, False) ... >>> c = Cell() >>> c.alive diff --git a/Lib/functools.py b/Lib/functools.py --- a/Lib/functools.py +++ b/Lib/functools.py @@ -11,7 +11,7 @@ __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial', - 'singledispatch'] + 'partialmethod', 'singledispatch'] try: from _functools import reduce -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 15:24:19 2013 From: python-checkins at python.org (nick.coghlan) Date: Mon, 4 Nov 2013 15:24:19 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=282=2E7=29=3A_Remove_merge_a?= =?utf-8?q?rtifact_from_Misc/NEWS?= Message-ID: <3dCx8g3cblz7LjY@mail.python.org> http://hg.python.org/cpython/rev/7268838063e1 changeset: 86922:7268838063e1 branch: 2.7 parent: 86916:bdb30bdf60a5 user: Nick Coghlan date: Tue Nov 05 00:24:05 2013 +1000 summary: Remove merge artifact from Misc/NEWS files: Misc/NEWS | 1 - 1 files changed, 0 insertions(+), 1 deletions(-) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -18,7 +18,6 @@ - Issue #6157: Fixed Tkinter.Text.debug(). Original patch by Guilherme Polo. - Issue #6160: The bbox() method of tkinter.Spinbox now returns a tuple of - integers instead of a string. Based on patch by Guilherme Polo. - Issue #19286: Directories in ``package_data`` are no longer added to -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 16:19:27 2013 From: python-checkins at python.org (antoine.pitrou) Date: Mon, 4 Nov 2013 16:19:27 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_relative=28=29_becomes_relati?= =?utf-8?b?dmVfdG8oKQ==?= Message-ID: <3dCyNH5Jh1z7LjQ@mail.python.org> http://hg.python.org/peps/rev/857f13a5c8c1 changeset: 5250:857f13a5c8c1 user: Antoine Pitrou date: Mon Nov 04 16:19:23 2013 +0100 summary: relative() becomes relative_to() files: pep-0428.txt | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pep-0428.txt b/pep-0428.txt --- a/pep-0428.txt +++ b/pep-0428.txt @@ -445,18 +445,18 @@ Making the path relative ^^^^^^^^^^^^^^^^^^^^^^^^ -The ``relative()`` method computes the relative difference of a path to +The ``relative_to()`` method computes the relative difference of a path to another:: - >>> PurePosixPath('/usr/bin/python').relative('/usr') + >>> PurePosixPath('/usr/bin/python').relative_to('/usr') PurePosixPath('bin/python') ValueError is raised if the method cannot return a meaningful value:: - >>> PurePosixPath('/usr/bin/python').relative('/etc') + >>> PurePosixPath('/usr/bin/python').relative_to('/etc') Traceback (most recent call last): File "", line 1, in - File "pathlib.py", line 926, in relative + File "pathlib.py", line 926, in relative_to .format(str(self), str(formatted))) ValueError: '/usr/bin/python' does not start with '/etc' -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Mon Nov 4 16:20:16 2013 From: python-checkins at python.org (antoine.pitrou) Date: Mon, 4 Nov 2013 16:20:16 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_Remove_the_st=5F*_shortcuts?= Message-ID: <3dCyPD23Smz7Llw@mail.python.org> http://hg.python.org/peps/rev/7ab9323febf9 changeset: 5251:7ab9323febf9 user: Antoine Pitrou date: Mon Nov 04 16:20:12 2013 +0100 summary: Remove the st_* shortcuts files: pep-0428.txt | 10 ---------- 1 files changed, 0 insertions(+), 10 deletions(-) diff --git a/pep-0428.txt b/pep-0428.txt --- a/pep-0428.txt +++ b/pep-0428.txt @@ -221,8 +221,6 @@ PosixPath('pathlib/setup.py') >>> p.exists() True - >>> p.st_size - 928 Pure paths API @@ -560,14 +558,6 @@ >>> p.stat() posix.stat_result(st_mode=33277, st_ino=7483155, st_dev=2053, st_nlink=1, st_uid=500, st_gid=500, st_size=928, st_atime=1343597970, st_mtime=1328287308, st_ctime=1343597964) -For ease of use, direct attribute access to the fields of the stat structure -is provided over the path object itself:: - - >>> p.st_size - 928 - >>> p.st_mtime - 1328287308.889562 - Higher-level methods help examine the kind of the file:: >>> p.exists() -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Mon Nov 4 16:22:07 2013 From: python-checkins at python.org (antoine.pitrou) Date: Mon, 4 Nov 2013 16:22:07 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?peps=3A_Remove_stat_caching?= Message-ID: <3dCyRM6CDfz7LlN@mail.python.org> http://hg.python.org/peps/rev/101c459e5570 changeset: 5252:101c459e5570 user: Antoine Pitrou date: Mon Nov 04 16:22:03 2013 +0100 summary: Remove stat caching files: pep-0428.txt | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pep-0428.txt b/pep-0428.txt --- a/pep-0428.txt +++ b/pep-0428.txt @@ -551,9 +551,9 @@ File metadata ------------- -The ``stat()`` method caches and returns the file's stat() result; -``restat()`` forces refreshing of the cache. ``lstat()`` is also provided, -but doesn't have any caching behaviour:: +The ``stat()`` returns the file's stat() result; similarly, ``lstat()`` +returns the file's lstat() result (which is different iff the file is a +symbolic link):: >>> p.stat() posix.stat_result(st_mode=33277, st_ino=7483155, st_dev=2053, st_nlink=1, st_uid=500, st_gid=500, st_size=928, st_atime=1343597970, st_mtime=1328287308, st_ctime=1343597964) -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Mon Nov 4 21:12:52 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Mon, 4 Nov 2013 21:12:52 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fix_Tkinter_tests_on_Tk_8=2E5_with_patchlevel_=3C_8=2E5=2E1?= =?utf-8?q?2=2E?= Message-ID: <3dD4tr5fzlz7LjP@mail.python.org> http://hg.python.org/cpython/rev/c3fa22d04fb2 changeset: 86923:c3fa22d04fb2 branch: 2.7 user: Serhiy Storchaka date: Mon Nov 04 22:10:35 2013 +0200 summary: Issue #19085: Fix Tkinter tests on Tk 8.5 with patchlevel < 8.5.12. files: Lib/lib-tk/test/widget_tests.py | 14 +++++++++++++- 1 files changed, 13 insertions(+), 1 deletions(-) diff --git a/Lib/lib-tk/test/widget_tests.py b/Lib/lib-tk/test/widget_tests.py --- a/Lib/lib-tk/test/widget_tests.py +++ b/Lib/lib-tk/test/widget_tests.py @@ -11,10 +11,22 @@ def int_round(x): return int(round(x)) +pixels_round = int_round +if tcl_version[:2] == (8, 5): + # Issue #19085: Workaround a bug in Tk + # http://core.tcl.tk/tk/info/3497848 + root = setup_master() + patchlevel = root.call('info', 'patchlevel') + patchlevel = tuple(map(int, patchlevel.split('.'))) + if patchlevel < (8, 5, 12): + pixels_round = int + del root + + _sentinel = object() class AbstractWidgetTest(object): - _conv_pixels = staticmethod(int_round) + _conv_pixels = staticmethod(pixels_round) _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 21:12:54 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Mon, 4 Nov 2013 21:12:54 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fix_Tkinter_tests_on_Tk_8=2E5_with_patchlevel_=3C_8=2E5=2E1?= =?utf-8?q?2=2E?= Message-ID: <3dD4tt15Cjz7Ljv@mail.python.org> http://hg.python.org/cpython/rev/583347b79aa0 changeset: 86924:583347b79aa0 branch: 3.3 parent: 86914:00ad499704c3 user: Serhiy Storchaka date: Mon Nov 04 22:11:12 2013 +0200 summary: Issue #19085: Fix Tkinter tests on Tk 8.5 with patchlevel < 8.5.12. files: Lib/tkinter/test/widget_tests.py | 14 +++++++++++++- 1 files changed, 13 insertions(+), 1 deletions(-) diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py --- a/Lib/tkinter/test/widget_tests.py +++ b/Lib/tkinter/test/widget_tests.py @@ -8,10 +8,22 @@ noconv = str if tcl_version < (8, 5) else False +pixels_round = round +if tcl_version[:2] == (8, 5): + # Issue #19085: Workaround a bug in Tk + # http://core.tcl.tk/tk/info/3497848 + root = setup_master() + patchlevel = root.call('info', 'patchlevel') + patchlevel = tuple(map(int, patchlevel.split('.'))) + if patchlevel < (8, 5, 12): + pixels_round = int + del root + + _sentinel = object() class AbstractWidgetTest: - _conv_pixels = round + _conv_pixels = pixels_round _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 21:12:55 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Mon, 4 Nov 2013 21:12:55 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2319085=3A_Fix_Tkinter_tests_on_Tk_8=2E5_with_pat?= =?utf-8?b?Y2hsZXZlbCA8IDguNS4xMi4=?= Message-ID: <3dD4tv3Tk4z7Lld@mail.python.org> http://hg.python.org/cpython/rev/fe5a829bd645 changeset: 86925:fe5a829bd645 parent: 86921:ac1685661b07 parent: 86924:583347b79aa0 user: Serhiy Storchaka date: Mon Nov 04 22:11:43 2013 +0200 summary: Issue #19085: Fix Tkinter tests on Tk 8.5 with patchlevel < 8.5.12. files: Lib/tkinter/test/widget_tests.py | 14 +++++++++++++- 1 files changed, 13 insertions(+), 1 deletions(-) diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py --- a/Lib/tkinter/test/widget_tests.py +++ b/Lib/tkinter/test/widget_tests.py @@ -8,10 +8,22 @@ noconv = str if tcl_version < (8, 5) else False +pixels_round = round +if tcl_version[:2] == (8, 5): + # Issue #19085: Workaround a bug in Tk + # http://core.tcl.tk/tk/info/3497848 + root = setup_master() + patchlevel = root.call('info', 'patchlevel') + patchlevel = tuple(map(int, patchlevel.split('.'))) + if patchlevel < (8, 5, 12): + pixels_round = int + del root + + _sentinel = object() class AbstractWidgetTest: - _conv_pixels = round + _conv_pixels = pixels_round _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 22:07:26 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Mon, 4 Nov 2013 22:07:26 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMi43KTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fix_running_test=5Fttk=5Ftextonly_on_displayless_host=2E?= Message-ID: <3dD65p0SVsz7LjT@mail.python.org> http://hg.python.org/cpython/rev/fe7aaf14b129 changeset: 86926:fe7aaf14b129 branch: 2.7 parent: 86923:c3fa22d04fb2 user: Serhiy Storchaka date: Mon Nov 04 23:05:23 2013 +0200 summary: Issue #19085: Fix running test_ttk_textonly on displayless host. files: Lib/lib-tk/test/test_tkinter/test_widgets.py | 5 +- Lib/lib-tk/test/widget_tests.py | 18 ++++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Lib/lib-tk/test/test_tkinter/test_widgets.py b/Lib/lib-tk/test/test_tkinter/test_widgets.py --- a/Lib/lib-tk/test/test_tkinter/test_widgets.py +++ b/Lib/lib-tk/test/test_tkinter/test_widgets.py @@ -4,7 +4,8 @@ from test.test_support import requires, run_unittest from test_ttk.support import tcl_version, requires_tcl, widget_eq -from widget_tests import (add_standard_options, noconv, noconv_meth, int_round, +from widget_tests import ( + add_standard_options, noconv, noconv_meth, int_round, pixels_round, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) @@ -240,7 +241,7 @@ 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength', ) - _conv_pixels = staticmethod(AbstractWidgetTest._conv_pixels) + _conv_pixels = staticmethod(pixels_round) def _create(self, **kwargs): return Tkinter.Menubutton(self.root, **kwargs) diff --git a/Lib/lib-tk/test/widget_tests.py b/Lib/lib-tk/test/widget_tests.py --- a/Lib/lib-tk/test/widget_tests.py +++ b/Lib/lib-tk/test/widget_tests.py @@ -15,12 +15,18 @@ if tcl_version[:2] == (8, 5): # Issue #19085: Workaround a bug in Tk # http://core.tcl.tk/tk/info/3497848 - root = setup_master() - patchlevel = root.call('info', 'patchlevel') - patchlevel = tuple(map(int, patchlevel.split('.'))) - if patchlevel < (8, 5, 12): - pixels_round = int - del root + _pixels_round = None + def pixels_round(x): + global _pixels_round + if _pixels_round is None: + root = setup_master() + patchlevel = root.call('info', 'patchlevel') + patchlevel = tuple(map(int, patchlevel.split('.'))) + if patchlevel < (8, 5, 12): + _pixels_round = int + else: + _pixels_round = int_round + return _pixels_round(x) _sentinel = object() -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 22:07:27 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Mon, 4 Nov 2013 22:07:27 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAoMy4zKTogSXNzdWUgIzE5MDg1?= =?utf-8?q?=3A_Fix_running_test=5Fttk=5Ftextonly_on_displayless_host=2E?= Message-ID: <3dD65q4B4Jz7Ll9@mail.python.org> http://hg.python.org/cpython/rev/47d3714dcb33 changeset: 86927:47d3714dcb33 branch: 3.3 parent: 86924:583347b79aa0 user: Serhiy Storchaka date: Mon Nov 04 23:05:37 2013 +0200 summary: Issue #19085: Fix running test_ttk_textonly on displayless host. files: Lib/tkinter/test/test_tkinter/test_widgets.py | 5 +- Lib/tkinter/test/widget_tests.py | 20 ++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -4,7 +4,8 @@ from test.support import requires from tkinter.test.support import tcl_version, requires_tcl, widget_eq -from tkinter.test.widget_tests import (add_standard_options, noconv, +from tkinter.test.widget_tests import ( + add_standard_options, noconv, pixels_round, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) requires('gui') @@ -243,7 +244,7 @@ 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength', ) - _conv_pixels = AbstractWidgetTest._conv_pixels + _conv_pixels = staticmethod(pixels_round) def _create(self, **kwargs): return tkinter.Menubutton(self.root, **kwargs) diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py --- a/Lib/tkinter/test/widget_tests.py +++ b/Lib/tkinter/test/widget_tests.py @@ -12,18 +12,24 @@ if tcl_version[:2] == (8, 5): # Issue #19085: Workaround a bug in Tk # http://core.tcl.tk/tk/info/3497848 - root = setup_master() - patchlevel = root.call('info', 'patchlevel') - patchlevel = tuple(map(int, patchlevel.split('.'))) - if patchlevel < (8, 5, 12): - pixels_round = int - del root + _pixels_round = None + def pixels_round(x): + global _pixels_round + if _pixels_round is None: + root = setup_master() + patchlevel = root.call('info', 'patchlevel') + patchlevel = tuple(map(int, patchlevel.split('.'))) + if patchlevel < (8, 5, 12): + _pixels_round = int + else: + _pixels_round = int_round + return _pixels_round(x) _sentinel = object() class AbstractWidgetTest: - _conv_pixels = pixels_round + _conv_pixels = staticmethod(pixels_round) _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 22:07:29 2013 From: python-checkins at python.org (serhiy.storchaka) Date: Mon, 4 Nov 2013 22:07:29 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=28merge_3=2E3_-=3E_default?= =?utf-8?q?=29=3A_Issue_=2319085=3A_Fix_running_test=5Fttk=5Ftextonly_on_d?= =?utf-8?q?isplayless_host=2E?= Message-ID: <3dD65s0ZtDz7Llh@mail.python.org> http://hg.python.org/cpython/rev/713cc4908a96 changeset: 86928:713cc4908a96 parent: 86925:fe5a829bd645 parent: 86927:47d3714dcb33 user: Serhiy Storchaka date: Mon Nov 04 23:06:51 2013 +0200 summary: Issue #19085: Fix running test_ttk_textonly on displayless host. files: Lib/tkinter/test/test_tkinter/test_widgets.py | 5 +- Lib/tkinter/test/widget_tests.py | 20 ++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -4,7 +4,8 @@ from test.support import requires from tkinter.test.support import tcl_version, requires_tcl, widget_eq -from tkinter.test.widget_tests import (add_standard_options, noconv, +from tkinter.test.widget_tests import ( + add_standard_options, noconv, pixels_round, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) requires('gui') @@ -243,7 +244,7 @@ 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength', ) - _conv_pixels = AbstractWidgetTest._conv_pixels + _conv_pixels = staticmethod(pixels_round) def _create(self, **kwargs): return tkinter.Menubutton(self.root, **kwargs) diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py --- a/Lib/tkinter/test/widget_tests.py +++ b/Lib/tkinter/test/widget_tests.py @@ -12,18 +12,24 @@ if tcl_version[:2] == (8, 5): # Issue #19085: Workaround a bug in Tk # http://core.tcl.tk/tk/info/3497848 - root = setup_master() - patchlevel = root.call('info', 'patchlevel') - patchlevel = tuple(map(int, patchlevel.split('.'))) - if patchlevel < (8, 5, 12): - pixels_round = int - del root + _pixels_round = None + def pixels_round(x): + global _pixels_round + if _pixels_round is None: + root = setup_master() + patchlevel = root.call('info', 'patchlevel') + patchlevel = tuple(map(int, patchlevel.split('.'))) + if patchlevel < (8, 5, 12): + _pixels_round = int + else: + _pixels_round = int_round + return _pixels_round(x) _sentinel = object() class AbstractWidgetTest: - _conv_pixels = pixels_round + _conv_pixels = staticmethod(pixels_round) _conv_pad_pixels = None wantobjects = True -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 22:18:27 2013 From: python-checkins at python.org (guido.van.rossum) Date: Mon, 4 Nov 2013 22:18:27 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Locks_improveme?= =?utf-8?q?nts_by_Arnaud_Faure=3A_better_repr=28=29=2C_change_Conditio=5C?= Message-ID: <3dD6LW2Gyjz7LjP@mail.python.org> http://hg.python.org/cpython/rev/268259370a01 changeset: 86929:268259370a01 user: Guido van Rossum date: Mon Nov 04 13:18:19 2013 -0800 summary: asyncio: Locks improvements by Arnaud Faure: better repr(), change Conditio\ n structure. files: Lib/asyncio/locks.py | 78 +++++++++++----- Lib/test/test_asyncio/test_locks.py | 71 +++++++++++++++- 2 files changed, 124 insertions(+), 25 deletions(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -155,9 +155,11 @@ self._loop = events.get_event_loop() def __repr__(self): - # TODO: add waiters:N if > 0. res = super().__repr__() - return '<{} [{}]>'.format(res[1:-1], 'set' if self._value else 'unset') + extra = 'set' if self._value else 'unset' + if self._waiters: + extra = '{},waiters:{}'.format(extra, len(self._waiters)) + return '<{} [{}]>'.format(res[1:-1], extra) def is_set(self): """Return true if and only if the internal flag is true.""" @@ -201,20 +203,38 @@ self._waiters.remove(fut) -# TODO: Why is this a Lock subclass? threading.Condition *has* a lock. -class Condition(Lock): - """A Condition implementation. +class Condition: + """A Condition implementation, our equivalent to threading.Condition. This class implements condition variable objects. A condition variable allows one or more coroutines to wait until they are notified by another coroutine. + + A new Lock object is created and used as the underlying lock. """ def __init__(self, *, loop=None): - super().__init__(loop=loop) - self._condition_waiters = collections.deque() + if loop is not None: + self._loop = loop + else: + self._loop = events.get_event_loop() - # TODO: Add __repr__() with len(_condition_waiters). + # Lock as an attribute as in threading.Condition. + lock = Lock(loop=self._loop) + self._lock = lock + # Export the lock's locked(), acquire() and release() methods. + self.locked = lock.locked + self.acquire = lock.acquire + self.release = lock.release + + self._waiters = collections.deque() + + def __repr__(self): + res = super().__repr__() + extra = 'locked' if self.locked() else 'unlocked' + if self._waiters: + extra = '{},waiters:{}'.format(extra, len(self._waiters)) + return '<{} [{}]>'.format(res[1:-1], extra) @tasks.coroutine def wait(self): @@ -228,19 +248,19 @@ the same condition variable in another coroutine. Once awakened, it re-acquires the lock and returns True. """ - if not self._locked: + if not self.locked(): raise RuntimeError('cannot wait on un-acquired lock') keep_lock = True self.release() try: fut = futures.Future(loop=self._loop) - self._condition_waiters.append(fut) + self._waiters.append(fut) try: yield from fut return True finally: - self._condition_waiters.remove(fut) + self._waiters.remove(fut) except GeneratorExit: keep_lock = False # Prevent yield in finally clause. @@ -275,11 +295,11 @@ wait() call until it can reacquire the lock. Since notify() does not release the lock, its caller should. """ - if not self._locked: + if not self.locked(): raise RuntimeError('cannot notify on un-acquired lock') idx = 0 - for fut in self._condition_waiters: + for fut in self._waiters: if idx >= n: break @@ -293,7 +313,17 @@ calling thread has not acquired the lock when this method is called, a RuntimeError is raised. """ - self.notify(len(self._condition_waiters)) + self.notify(len(self._waiters)) + + def __enter__(self): + return self._lock.__enter__() + + def __exit__(self, *args): + return self._lock.__exit__(*args) + + def __iter__(self): + yield from self.acquire() + return self class Semaphore: @@ -310,10 +340,10 @@ counter; it defaults to 1. If the value given is less than 0, ValueError is raised. - The second optional argument determins can semophore be released more than - initial internal counter value; it defaults to False. If the value given - is True and number of release() is more than number of successfull - acquire() calls ValueError is raised. + The second optional argument determines if the semaphore can be released + more than initial internal counter value; it defaults to False. If the + value given is True and number of release() is more than number of + successful acquire() calls ValueError is raised. """ def __init__(self, value=1, bound=False, *, loop=None): @@ -330,12 +360,12 @@ self._loop = events.get_event_loop() def __repr__(self): - # TODO: add waiters:N if > 0. res = super().__repr__() - return '<{} [{}]>'.format( - res[1:-1], - 'locked' if self._locked else 'unlocked,value:{}'.format( - self._value)) + extra = 'locked' if self._locked else 'unlocked,value:{}'.format( + self._value) + if self._waiters: + extra = '{},waiters:{}'.format(extra, len(self._waiters)) + return '<{} [{}]>'.format(res[1:-1], extra) def locked(self): """Returns True if semaphore can not be acquired immediately.""" @@ -373,7 +403,7 @@ When it was zero on entry and another coroutine is waiting for it to become larger than zero again, wake up that coroutine. - If Semaphore is create with "bound" paramter equals true, then + If Semaphore is created with "bound" parameter equals true, then release() method checks to make sure its current value doesn't exceed its initial value. If it does, ValueError is raised. """ diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -2,6 +2,7 @@ import unittest import unittest.mock +import re from asyncio import events from asyncio import futures @@ -10,6 +11,15 @@ from asyncio import test_utils +STR_RGX_REPR = ( + r'^<(?P.*?) object at (?P
.*?)' + r'\[(?P' + r'(set|unset|locked|unlocked)(,value:\d)?(,waiters:\d+)?' + r')\]>\Z' +) +RGX_REPR = re.compile(STR_RGX_REPR) + + class LockTests(unittest.TestCase): def setUp(self): @@ -38,6 +48,7 @@ def test_repr(self): lock = locks.Lock(loop=self.loop) self.assertTrue(repr(lock).endswith('[unlocked]>')) + self.assertTrue(RGX_REPR.match(repr(lock))) @tasks.coroutine def acquire_lock(): @@ -45,6 +56,7 @@ self.loop.run_until_complete(acquire_lock()) self.assertTrue(repr(lock).endswith('[locked]>')) + self.assertTrue(RGX_REPR.match(repr(lock))) def test_lock(self): lock = locks.Lock(loop=self.loop) @@ -239,9 +251,16 @@ def test_repr(self): ev = locks.Event(loop=self.loop) self.assertTrue(repr(ev).endswith('[unset]>')) + match = RGX_REPR.match(repr(ev)) + self.assertEqual(match.group('extras'), 'unset') ev.set() self.assertTrue(repr(ev).endswith('[set]>')) + self.assertTrue(RGX_REPR.match(repr(ev))) + + ev._waiters.append(unittest.mock.Mock()) + self.assertTrue('waiters:1' in repr(ev)) + self.assertTrue(RGX_REPR.match(repr(ev))) def test_wait(self): ev = locks.Event(loop=self.loop) @@ -440,7 +459,7 @@ self.assertRaises( futures.CancelledError, self.loop.run_until_complete, wait) - self.assertFalse(cond._condition_waiters) + self.assertFalse(cond._waiters) self.assertTrue(cond.locked()) def test_wait_unacquired(self): @@ -600,6 +619,45 @@ cond = locks.Condition(loop=self.loop) self.assertRaises(RuntimeError, cond.notify_all) + def test_repr(self): + cond = locks.Condition(loop=self.loop) + self.assertTrue('unlocked' in repr(cond)) + self.assertTrue(RGX_REPR.match(repr(cond))) + + self.loop.run_until_complete(cond.acquire()) + self.assertTrue('locked' in repr(cond)) + + cond._waiters.append(unittest.mock.Mock()) + self.assertTrue('waiters:1' in repr(cond)) + self.assertTrue(RGX_REPR.match(repr(cond))) + + cond._waiters.append(unittest.mock.Mock()) + self.assertTrue('waiters:2' in repr(cond)) + self.assertTrue(RGX_REPR.match(repr(cond))) + + def test_context_manager(self): + cond = locks.Condition(loop=self.loop) + + @tasks.coroutine + def acquire_cond(): + return (yield from cond) + + with self.loop.run_until_complete(acquire_cond()): + self.assertTrue(cond.locked()) + + self.assertFalse(cond.locked()) + + def test_context_manager_no_yield(self): + cond = locks.Condition(loop=self.loop) + + try: + with cond: + self.fail('RuntimeError is not raised in with expression') + except RuntimeError as err: + self.assertEqual( + str(err), + '"yield from" should be used as context manager expression') + class SemaphoreTests(unittest.TestCase): @@ -629,9 +687,20 @@ def test_repr(self): sem = locks.Semaphore(loop=self.loop) self.assertTrue(repr(sem).endswith('[unlocked,value:1]>')) + self.assertTrue(RGX_REPR.match(repr(sem))) self.loop.run_until_complete(sem.acquire()) self.assertTrue(repr(sem).endswith('[locked]>')) + self.assertTrue('waiters' not in repr(sem)) + self.assertTrue(RGX_REPR.match(repr(sem))) + + sem._waiters.append(unittest.mock.Mock()) + self.assertTrue('waiters:1' in repr(sem)) + self.assertTrue(RGX_REPR.match(repr(sem))) + + sem._waiters.append(unittest.mock.Mock()) + self.assertTrue('waiters:2' in repr(sem)) + self.assertTrue(RGX_REPR.match(repr(sem))) def test_semaphore(self): sem = locks.Semaphore(loop=self.loop) -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 22:35:40 2013 From: python-checkins at python.org (benjamin.peterson) Date: Mon, 4 Nov 2013 22:35:40 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython_=282=2E7=29=3A_Fix_unintended?= =?utf-8?q?_switch_from_a_constant_to_a_global_in_56a3c0bc4634?= Message-ID: <3dD6kN1FgBz7Lq4@mail.python.org> http://hg.python.org/cpython/rev/d9790d8526fa changeset: 86930:d9790d8526fa branch: 2.7 parent: 86817:2d02b7a97e0b user: Raymond Hettinger date: Mon Oct 28 02:39:04 2013 -0600 summary: Fix unintended switch from a constant to a global in 56a3c0bc4634 files: Lib/heapq.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/heapq.py b/Lib/heapq.py --- a/Lib/heapq.py +++ b/Lib/heapq.py @@ -380,7 +380,7 @@ while _len(h) > 1: try: - while True: + while 1: v, itnum, next = s = h[0] yield v s[0] = next() # raises StopIteration when exhausted -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Mon Nov 4 22:35:41 2013 From: python-checkins at python.org (benjamin.peterson) Date: Mon, 4 Nov 2013 22:35:41 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?Y3B5dGhvbiAobWVyZ2UgMi43IC0+IDIuNyk6?= =?utf-8?q?_merge_2=2E7=2E6_release_branch?= Message-ID: <3dD6kP3zd8z7Lnl@mail.python.org> http://hg.python.org/cpython/rev/c7c90330eea1 changeset: 86931:c7c90330eea1 branch: 2.7 parent: 86926:fe7aaf14b129 parent: 86930:d9790d8526fa user: Benjamin Peterson date: Mon Nov 04 16:35:33 2013 -0500 summary: merge 2.7.6 release branch files: -- Repository URL: http://hg.python.org/cpython From python-checkins at python.org Tue Nov 5 00:45:08 2013 From: python-checkins at python.org (eric.snow) Date: Tue, 5 Nov 2013 00:45:08 +0100 (CET) Subject: [Python-checkins] =?utf-8?b?cGVwczogW1BFUCA0NTFdICJ0YXJnZXQiIGFy?= =?utf-8?q?g_of_find=5Fspec=28=29_is_not_exclusive_to_reloading=2E?= Message-ID: <3dD9bm1LMnz7LpC@mail.python.org> http://hg.python.org/peps/rev/b8c8299914c5 changeset: 5253:b8c8299914c5 user: Eric Snow date: Mon Nov 04 16:40:45 2013 -0700 summary: [PEP 451] "target" arg of find_spec() is not exclusive to reloading. files: pep-0451.txt | 33 +++++++++++++++++++-------------- 1 files changed, 19 insertions(+), 14 deletions(-) diff --git a/pep-0451.txt b/pep-0451.txt --- a/pep-0451.txt +++ b/pep-0451.txt @@ -717,22 +717,27 @@ The "target" parameter of find_spec() ------------------------------------- -A module object with the same name as the "name" argument (or None, the -default) should be passed in to "exising". This argument allows the -finder to build the module spec with more information than is otherwise -available. This is particularly relevant in identifying the loader to -use. +A call to find_spec() may optionally include a "target" argument. This +is the module object that will be used subsequently as the target of +loading. During normal import (and by default) "target" is None, +meaning the target module has yet to be created. During reloading the +module passed in to reload() is passed through to find_spec() as the +target. This argument allows the finder to build the module spec with +more information than is otherwise available. Doing so is particularly +relevant in identifying the loader to use. Through find_spec() the finder will always identify the loader it -will return in the spec. In the case of reload, at this point the -finder should also decide whether or not the loader supports loading -into the module-to-be-reloaded (which was passed in to find_spec() as -"target"). This decision may entail consulting with the loader. If -the finder determines that the loader does not support reloading that -module, it should either find another loader or raise ImportError -(completely stopping import of the module). This reload decision is -important since, as noted in `How Reloading Will Work`_, loaders will -no longer be able to trivially identify a reload situation on their own. +will return in the spec (or return None). At the point the loader is +identified, the finder should also decide whether or not the loader +supports loading into the target module, in the case that "target" is +passed in. This decision may entail consulting with the loader. + +If the finder determines that the loader does not support loading into +the target module, it should either find another loader or raise +ImportError (completely stopping import of the module). This +determination is especially important during reload since, as noted in +`How Reloading Will Work`_, loaders will no longer be able to trivially +identify a reload situation on their own. Two alternatives were presented to the "target" parameter: Loader.supports_reload() and adding "target" to Loader.exec_module() -- Repository URL: http://hg.python.org/peps From python-checkins at python.org Tue Nov 5 00:50:55 2013 From: python-checkins at python.org (guido.van.rossum) Date: Tue, 5 Nov 2013 00:50:55 +0100 (CET) Subject: [Python-checkins] =?utf-8?q?cpython=3A_asyncio=3A_Refactor_SIGCHL?= =?utf-8?q?D_handling=2E_By_Anthony_Baire=2E?= Message-ID: <3dD9kR3cS1z7Lnt@mail.python.org> http://hg.python.org/cpython/rev/8d93ad260714 changeset: 86932:8d93ad260714 parent: 86929:268259370a01 user: Guido van Rossum date: Mon Nov 04 15:50:46 2013 -0800 summary: asyncio: Refactor SIGCHLD handling. By Anthony Baire. files: Lib/asyncio/events.py | 70 +- Lib/asyncio/unix_events.py | 396 +++- Lib/asyncio/windows_events.py | 17 +- Lib/test/test_asyncio/test_events.py | 44 +- Lib/test/test_asyncio/test_unix_events.py | 987 ++++++++- 5 files changed, 1315 insertions(+), 199 deletions(-) diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -1,10 +1,11 @@ """Event loop and event loop policy.""" -__all__ = ['AbstractEventLoopPolicy', 'DefaultEventLoopPolicy', +__all__ = ['AbstractEventLoopPolicy', 'AbstractEventLoop', 'AbstractServer', 'Handle', 'TimerHandle', 'get_event_loop_policy', 'set_event_loop_policy', 'get_event_loop', 'set_event_loop', 'new_event_loop', + 'get_child_watcher', 'set_child_watcher', ] import subprocess @@ -318,8 +319,18 @@ """XXX""" raise NotImplementedError + # Child processes handling (Unix only). -class DefaultEventLoopPolicy(threading.local, AbstractEventLoopPolicy): + def get_child_watcher(self): + """XXX""" + raise NotImplementedError + + def set_child_watcher(self, watcher): + """XXX""" + raise NotImplementedError + + +class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy): """Default policy implementation for accessing the event loop. In this policy, each thread has its own event loop. However, we @@ -332,28 +343,34 @@ associated). """ - _loop = None - _set_called = False + _loop_factory = None + + class _Local(threading.local): + _loop = None + _set_called = False + + def __init__(self): + self._local = self._Local() def get_event_loop(self): """Get the event loop. This may be None or an instance of EventLoop. """ - if (self._loop is None and - not self._set_called and + if (self._local._loop is None and + not self._local._set_called and isinstance(threading.current_thread(), threading._MainThread)): - self._loop = self.new_event_loop() - assert self._loop is not None, \ + self._local._loop = self.new_event_loop() + assert self._local._loop is not None, \ ('There is no current event loop in thread %r.' % threading.current_thread().name) - return self._loop + return self._local._loop def set_event_loop(self, loop): """Set the event loop.""" - self._set_called = True + self._local._set_called = True assert loop is None or isinstance(loop, AbstractEventLoop) - self._loop = loop + self._local._loop = loop def new_event_loop(self): """Create a new event loop. @@ -361,12 +378,7 @@ You must call set_event_loop() to make this the current event loop. """ - if sys.platform == 'win32': # pragma: no cover - from . import windows_events - return windows_events.SelectorEventLoop() - else: # pragma: no cover - from . import unix_events - return unix_events.SelectorEventLoop() + return self._loop_factory() # Event loop policy. The policy itself is always global, even if the @@ -375,12 +387,22 @@ # call to get_event_loop_policy(). _event_loop_policy = None +# Lock for protecting the on-the-fly creation of the event loop policy. +_lock = threading.Lock() + + +def _init_event_loop_policy(): + global _event_loop_policy + with _lock: + if _event_loop_policy is None: # pragma: no branch + from . import DefaultEventLoopPolicy + _event_loop_policy = DefaultEventLoopPolicy() + def get_event_loop_policy(): """XXX""" - global _event_loop_policy if _event_loop_policy is None: - _event_loop_policy = DefaultEventLoopPolicy() + _init_event_loop_policy() return _event_loop_policy @@ -404,3 +426,13 @@ def new_event_loop(): """XXX""" return get_event_loop_policy().new_event_loop() + + +def get_child_watcher(): + """XXX""" + return get_event_loop_policy().get_child_watcher() + + +def set_child_watcher(watcher): + """XXX""" + return get_event_loop_policy().set_child_watcher(watcher) diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py --- a/Lib/asyncio/unix_events.py +++ b/Lib/asyncio/unix_events.py @@ -8,6 +8,7 @@ import stat import subprocess import sys +import threading from . import base_subprocess @@ -20,7 +21,10 @@ from .log import logger -__all__ = ['SelectorEventLoop', 'STDIN', 'STDOUT', 'STDERR'] +__all__ = ['SelectorEventLoop', 'STDIN', 'STDOUT', 'STDERR', + 'AbstractChildWatcher', 'SafeChildWatcher', + 'FastChildWatcher', 'DefaultEventLoopPolicy', + ] STDIN = 0 STDOUT = 1 @@ -31,7 +35,7 @@ raise ImportError('Signals are not really supported on Windows') -class SelectorEventLoop(selector_events.BaseSelectorEventLoop): +class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): """Unix event loop Adds signal handling to SelectorEventLoop @@ -40,17 +44,10 @@ def __init__(self, selector=None): super().__init__(selector) self._signal_handlers = {} - self._subprocesses = {} def _socketpair(self): return socket.socketpair() - def close(self): - handler = self._signal_handlers.get(signal.SIGCHLD) - if handler is not None: - self.remove_signal_handler(signal.SIGCHLD) - super().close() - def add_signal_handler(self, sig, callback, *args): """Add a handler for a signal. UNIX only. @@ -152,49 +149,20 @@ def _make_subprocess_transport(self, protocol, args, shell, stdin, stdout, stderr, bufsize, extra=None, **kwargs): - self._reg_sigchld() - transp = _UnixSubprocessTransport(self, protocol, args, shell, - stdin, stdout, stderr, bufsize, - extra=None, **kwargs) - self._subprocesses[transp.get_pid()] = transp + with events.get_child_watcher() as watcher: + transp = _UnixSubprocessTransport(self, protocol, args, shell, + stdin, stdout, stderr, bufsize, + extra=None, **kwargs) + watcher.add_child_handler(transp.get_pid(), + self._child_watcher_callback, transp) yield from transp._post_init() return transp - def _reg_sigchld(self): - if signal.SIGCHLD not in self._signal_handlers: - self.add_signal_handler(signal.SIGCHLD, self._sig_chld) + def _child_watcher_callback(self, pid, returncode, transp): + self.call_soon_threadsafe(transp._process_exited, returncode) - def _sig_chld(self): - try: - # Because of signal coalescing, we must keep calling waitpid() as - # long as we're able to reap a child. - while True: - try: - pid, status = os.waitpid(-1, os.WNOHANG) - except ChildProcessError: - break # No more child processes exist. - if pid == 0: - break # All remaining child processes are still alive. - elif os.WIFSIGNALED(status): - # A child process died because of a signal. - returncode = -os.WTERMSIG(status) - elif os.WIFEXITED(status): - # A child process exited (e.g. sys.exit()). - returncode = os.WEXITSTATUS(status) - else: - # A child exited, but we don't understand its status. - # This shouldn't happen, but if it does, let's just - # return that status; perhaps that helps debug it. - returncode = status - transp = self._subprocesses.get(pid) - if transp is not None: - transp._process_exited(returncode) - except Exception: - logger.exception('Unknown exception in SIGCHLD handler') - - def _subprocess_closed(self, transport): - pid = transport.get_pid() - self._subprocesses.pop(pid, None) + def _subprocess_closed(self, transp): + pass def _set_nonblocking(fd): @@ -423,3 +391,335 @@ if stdin_w is not None: stdin.close() self._proc.stdin = open(stdin_w.detach(), 'rb', buffering=bufsize) + + +class AbstractChildWatcher: + """Abstract base class for monitoring child processes. + + Objects derived from this class monitor a collection of subprocesses and + report their termination or interruption by a signal. + + New callbacks are registered with .add_child_handler(). Starting a new + process must be done within a 'with' block to allow the watcher to suspend + its activity until the new process if fully registered (this is needed to + prevent a race condition in some implementations). + + Example: + with watcher: + proc = subprocess.Popen("sleep 1") + watcher.add_child_handler(proc.pid, callback) + + Notes: + Implementations of this class must be thread-safe. + + Since child watcher objects may catch the SIGCHLD signal and call + waitpid(-1), there should be only one active object per process. + """ + + def add_child_handler(self, pid, callback, *args): + """Register a new child handler. + + Arrange for callback(pid, returncode, *args) to be called when + process 'pid' terminates. Specifying another callback for the same + process replaces the previous handler. + + Note: callback() must be thread-safe + """ + raise NotImplementedError() + + def remove_child_handler(self, pid): + """Removes the handler for process 'pid'. + + The function returns True if the handler was successfully removed, + False if there was nothing to remove.""" + + raise NotImplementedError() + + def set_loop(self, loop): + """Reattach the watcher to another event loop. + + Note: loop may be None + """ + raise NotImplementedError() + + def close(self): + """Close the watcher. + + This must be called to make sure that any underlying resource is freed. + """ + raise NotImplementedError() + + def __enter__(self): + """Enter the watcher's context and allow starting new processes + + This function must return self""" + raise NotImplementedError() + + def __exit__(self, a, b, c): + """Exit the watcher's context""" + raise NotImplementedError() + + +class BaseChildWatcher(AbstractChildWatcher): + + def __init__(self, loop): + self._loop = None + self._callbacks = {} + + self.set_loop(loop) + + def close(self): + self.set_loop(None) + self._callbacks.clear() + + def _do_waitpid(self, expected_pid): + raise NotImplementedError() + + def _do_waitpid_all(self): + raise NotImplementedError() + + def set_loop(self, loop): + assert loop is None or isinstance(loop, events.AbstractEventLoop) + + if self._loop is not None: + self._loop.remove_signal_handler(signal.SIGCHLD) + + self._loop = loop + if loop is not None: + loop.add_signal_handler(signal.SIGCHLD, self._sig_chld) + + # Prevent a race condition in case a child terminated + # during the switch. + self._do_waitpid_all() + + def remove_child_handler(self, pid): + try: + del self._callbacks[pid] + return True + except KeyError: + return False + + def _sig_chld(self): + try: + self._do_waitpid_all() + except Exception: + logger.exception('Unknown exception in SIGCHLD handler') + + def _compute_returncode(self, status): + if os.WIFSIGNALED(status): + # The child process died because of a signal. + return -os.WTERMSIG(status) + elif os.WIFEXITED(status): + # The child process exited (e.g sys.exit()). + return os.WEXITSTATUS(status) + else: + # The child exited, but we don't understand its status. + # This shouldn't happen, but if it does, let's just + # return that status; perhaps that helps debug it. + return status + + +class SafeChildWatcher(BaseChildWatcher): + """'Safe' child watcher implementation. + + This implementation avoids disrupting other code spawning processes by + polling explicitly each process in the SIGCHLD handler instead of calling + os.waitpid(-1). + + This is a safe solution but it has a significant overhead when handling a + big number of children (O(n) each time SIGCHLD is raised) + """ + + def __enter__(self): + return self + + def __exit__(self, a, b, c): + pass + + def add_child_handler(self, pid, callback, *args): + self._callbacks[pid] = callback, args + + # Prevent a race condition in case the child is already terminated. + self._do_waitpid(pid) + + def _do_waitpid_all(self): + + for pid in list(self._callbacks): + self._do_waitpid(pid) + + def _do_waitpid(self, expected_pid): + assert expected_pid > 0 + + try: + pid, status = os.waitpid(expected_pid, os.WNOHANG) + except ChildProcessError: + # The child process is already reaped + # (may happen if waitpid() is called elsewhere). + pid = expected_pid + returncode = 255 + logger.warning( + "Unknown child process pid %d, will report returncode 255", + pid) + else: + if pid == 0: + # The child process is still alive. + return + + returncode = self._compute_returncode(status) + + try: + callback, args = self._callbacks.pop(pid) + except KeyError: # pragma: no cover + # May happen if .remove_child_handler() is called + # after os.waitpid() returns. + pass + else: + callback(pid, returncode, *args) + + +class FastChildWatcher(BaseChildWatcher): + """'Fast' child watcher implementation. + + This implementation reaps every terminated processes by calling + os.waitpid(-1) directly, possibly breaking other code spawning processes + and waiting for their termination. + + There is no noticeable overhead when handling a big number of children + (O(1) each time a child terminates). + """ + def __init__(self, loop): + super().__init__(loop) + + self._lock = threading.Lock() + self._zombies = {} + self._forks = 0 + + def close(self): + super().close() + self._zombies.clear() + + def __enter__(self): + with self._lock: + self._forks += 1 + + return self + + def __exit__(self, a, b, c): + with self._lock: + self._forks -= 1 + + if self._forks or not self._zombies: + return + + collateral_victims = str(self._zombies) + self._zombies.clear() + + logger.warning( + "Caught subprocesses termination from unknown pids: %s", + collateral_victims) + + def add_child_handler(self, pid, callback, *args): + assert self._forks, "Must use the context manager" + + self._callbacks[pid] = callback, args + + try: + # Ensure that the child is not already terminated. + # (raise KeyError if still alive) + returncode = self._zombies.pop(pid) + + # Child is dead, therefore we can fire the callback immediately. + # First we remove it from the dict. + # (raise KeyError if .remove_child_handler() was called in-between) + del self._callbacks[pid] + except KeyError: + pass + else: + callback(pid, returncode, *args) + + def _do_waitpid_all(self): + # Because of signal coalescing, we must keep calling waitpid() as + # long as we're able to reap a child. + while True: + try: + pid, status = os.waitpid(-1, os.WNOHANG) + except ChildProcessError: + # No more child processes exist. + return + else: + if pid == 0: + # A child process is still alive. + return + + returncode = self._compute_returncode(status) + + try: + callback, args = self._callbacks.pop(pid) + except KeyError: + # unknown child + with self._lock: + if self._forks: + # It may not be registered yet. + self._zombies[pid] = returncode + continue + + logger.warning( + "Caught subprocess termination from unknown pid: " + "%d -> %d", pid, returncode) + else: + callback(pid, returncode, *args) + + +class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy): + """XXX""" + _loop_factory = _UnixSelectorEventLoop + + def __init__(self): + super().__init__() + self._watcher = None + + def _init_watcher(self): + with events._lock: + if self._watcher is None: # pragma: no branch + if isinstance(threading.current_thread(), + threading._MainThread): + self._watcher = SafeChildWatcher(self._local._loop) + else: + self._watcher = SafeChildWatcher(None) + + def set_event_loop(self, loop): + """Set the event loop. + + As a side effect, if a child watcher was set before, then calling + .set_event_loop() from the main thread will call .set_loop(loop) on the + child watcher. + """ + + super().set_event_loop(loop) + + if self._watcher is not None and \ + isinstance(threading.current_thread(), threading._MainThread): + self._watcher.set_loop(loop) + + def get_child_watcher(self): + """Get the child watcher + + If not yet set, a SafeChildWatcher object is automatically created. + """ + if self._watcher is None: + self._init_watcher() + + return self._watcher + + def set_child_watcher(self, watcher): + """Set the child watcher""" + + assert watcher is None or isinstance(watcher, AbstractChildWatcher) + + if self._watcher is not None: + self._watcher.close() + + self._watcher = watcher + +SelectorEventLoop = _UnixSelectorEventLoop +DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -7,6 +7,7 @@ import struct import _winapi +from . import events from . import base_subprocess from . import futures from . import proactor_events @@ -17,7 +18,9 @@ from . import _overlapped -__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor'] +__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor', + 'DefaultEventLoopPolicy', + ] NULL = 0 @@ -108,7 +111,7 @@ __del__ = close -class SelectorEventLoop(selector_events.BaseSelectorEventLoop): +class _WindowsSelectorEventLoop(selector_events.BaseSelectorEventLoop): """Windows version of selector event loop.""" def _socketpair(self): @@ -453,3 +456,13 @@ f = self._loop._proactor.wait_for_handle(int(self._proc._handle)) f.add_done_callback(callback) + + +SelectorEventLoop = _WindowsSelectorEventLoop + + +class _WindowsDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy): + _loop_factory = SelectorEventLoop + + +DefaultEventLoopPolicy = _WindowsDefaultEventLoopPolicy diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1308,8 +1308,17 @@ from asyncio import selectors from asyncio import unix_events + class UnixEventLoopTestsMixin(EventLoopTestsMixin): + def setUp(self): + super().setUp() + events.set_child_watcher(unix_events.SafeChildWatcher(self.loop)) + + def tearDown(self): + events.set_child_watcher(None) + super().tearDown() + if hasattr(selectors, 'KqueueSelector'): - class KqueueEventLoopTests(EventLoopTestsMixin, + class KqueueEventLoopTests(UnixEventLoopTestsMixin, SubprocessTestsMixin, unittest.TestCase): @@ -1318,7 +1327,7 @@ selectors.KqueueSelector()) if hasattr(selectors, 'EpollSelector'): - class EPollEventLoopTests(EventLoopTestsMixin, + class EPollEventLoopTests(UnixEventLoopTestsMixin, SubprocessTestsMixin, unittest.TestCase): @@ -1326,7 +1335,7 @@ return unix_events.SelectorEventLoop(selectors.EpollSelector()) if hasattr(selectors, 'PollSelector'): - class PollEventLoopTests(EventLoopTestsMixin, + class PollEventLoopTests(UnixEventLoopTestsMixin, SubprocessTestsMixin, unittest.TestCase): @@ -1334,7 +1343,7 @@ return unix_events.SelectorEventLoop(selectors.PollSelector()) # Should always exist. - class SelectEventLoopTests(EventLoopTestsMixin, + class SelectEventLoopTests(UnixEventLoopTestsMixin, SubprocessTestsMixin, unittest.TestCase): @@ -1557,25 +1566,36 @@ class PolicyTests(unittest.TestCase): + def create_policy(self): + if sys.platform == "win32": + from asyncio import windows_events + return windows_events.DefaultEventLoopPolicy() + else: + from asyncio import unix_events + return unix_events.DefaultEventLoopPolicy() + def test_event_loop_policy(self): policy = events.AbstractEventLoopPolicy() self.assertRaises(NotImplementedError, policy.get_event_loop) self.assertRaises(NotImplementedError, policy.set_event_loop, object()) self.assertRaises(NotImplementedError, policy.new_event_loop) + self.assertRaises(NotImplementedError, policy.get_child_watcher) + self.assertRaises(NotImplementedError, policy.set_child_watcher, + object()) def test_get_event_loop(self): - policy = events.DefaultEventLoopPolicy() - self.assertIsNone(policy._loop) + policy = self.create_policy() + self.assertIsNone(policy._local._loop) loop = policy.get_event_loop() self.assertIsInstance(loop, events.AbstractEventLoop) - self.assertIs(policy._loop, loop) + self.assertIs(policy._local._loop, loop) self.assertIs(loop, policy.get_event_loop()) loop.close() def test_get_event_loop_after_set_none(self): - policy = events.DefaultEventLoopPolicy() + policy = self.create_policy() policy.set_event_loop(None) self.assertRaises(AssertionError, policy.get_event_loop) @@ -1583,7 +1603,7 @@ def test_get_event_loop_thread(self, m_current_thread): def f(): - policy = events.DefaultEventLoopPolicy() + policy = self.create_policy() self.assertRaises(AssertionError, policy.get_event_loop) th = threading.Thread(target=f) @@ -1591,14 +1611,14 @@ th.join() def test_new_event_loop(self): - policy = events.DefaultEventLoopPolicy() + policy = self.create_policy() loop = policy.new_event_loop() self.assertIsInstance(loop, events.AbstractEventLoop) loop.close() def test_set_event_loop(self): - policy = events.DefaultEventLoopPolicy() + policy = self.create_policy() old_loop = policy.get_event_loop() self.assertRaises(AssertionError, policy.set_event_loop, object()) @@ -1621,7 +1641,7 @@ old_policy = events.get_event_loop_policy() - policy = events.DefaultEventLoopPolicy() + policy = self.create_policy() events.set_event_loop_policy(policy) self.assertIs(policy, events.get_event_loop_policy()) self.assertIsNot(policy, old_policy) diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -3,10 +3,12 @@ import gc import errno import io +import os import pprint import signal import stat import sys +import threading import unittest import unittest.mock @@ -181,124 +183,6 @@ self.assertRaises( RuntimeError, self.loop.remove_signal_handler, signal.SIGHUP) - @unittest.mock.patch('os.WTERMSIG') - @unittest.mock.patch('os.WEXITSTATUS') - @unittest.mock.patch('os.WIFSIGNALED') - @unittest.mock.patch('os.WIFEXITED') - @unittest.mock.patch('os.waitpid') - def test__sig_chld(self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, - m_WEXITSTATUS, m_WTERMSIG): - m_waitpid.side_effect = [(7, object()), ChildProcessError] - m_WIFEXITED.return_value = True - m_WIFSIGNALED.return_value = False - m_WEXITSTATUS.return_value = 3 - transp = unittest.mock.Mock() - self.loop._subprocesses[7] = transp - - self.loop._sig_chld() - transp._process_exited.assert_called_with(3) - self.assertFalse(m_WTERMSIG.called) - - @unittest.mock.patch('os.WTERMSIG') - @unittest.mock.patch('os.WEXITSTATUS') - @unittest.mock.patch('os.WIFSIGNALED') - @unittest.mock.patch('os.WIFEXITED') - @unittest.mock.patch('os.waitpid') - def test__sig_chld_signal(self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, - m_WEXITSTATUS, m_WTERMSIG): - m_waitpid.side_effect = [(7, object()), ChildProcessError] - m_WIFEXITED.return_value = False - m_WIFSIGNALED.return_value = True - m_WTERMSIG.return_value = 1 - transp = unittest.mock.Mock() - self.loop._subprocesses[7] = transp - - self.loop._sig_chld() - transp._process_exited.assert_called_with(-1) - self.assertFalse(m_WEXITSTATUS.called) - - @unittest.mock.patch('os.WTERMSIG') - @unittest.mock.patch('os.WEXITSTATUS') - @unittest.mock.patch('os.WIFSIGNALED') - @unittest.mock.patch('os.WIFEXITED') - @unittest.mock.patch('os.waitpid') - def test__sig_chld_zero_pid(self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, - m_WEXITSTATUS, m_WTERMSIG): - m_waitpid.side_effect = [(0, object()), ChildProcessError] - transp = unittest.mock.Mock() - self.loop._subprocesses[7] = transp - - self.loop._sig_chld() - self.assertFalse(transp._process_exited.called) - self.assertFalse(m_WIFSIGNALED.called) - self.assertFalse(m_WIFEXITED.called) - self.assertFalse(m_WTERMSIG.called) - self.assertFalse(m_WEXITSTATUS.called) - - @unittest.mock.patch('os.WTERMSIG') - @unittest.mock.patch('os.WEXITSTATUS') - @unittest.mock.patch('os.WIFSIGNALED') - @unittest.mock.patch('os.WIFEXITED') - @unittest.mock.patch('os.waitpid') - def test__sig_chld_not_registered_subprocess(self, m_waitpid, - m_WIFEXITED, m_WIFSIGNALED, - m_WEXITSTATUS, m_WTERMSIG): - m_waitpid.side_effect = [(7, object()), ChildProcessError] - m_WIFEXITED.return_value = True - m_WIFSIGNALED.return_value = False - m_WEXITSTATUS.return_value = 3 - - self.loop._sig_chld() - self.assertFalse(m_WTERMSIG.called) - - @unittest.mock.patch('os.WTERMSIG') - @unittest.mock.patch('os.WEXITSTATUS') - @unittest.mock.patch('os.WIFSIGNALED') - @unittest.mock.patch('os.WIFEXITED') - @unittest.mock.patch('os.waitpid') - def test__sig_chld_unknown_status(self, m_waitpid, - m_WIFEXITED, m_WIFSIGNALED, - m_WEXITSTATUS, m_WTERMSIG): - m_waitpid.side_effect = [(7, object()), ChildProcessError] - m_WIFEXITED.return_value = False - m_WIFSIGNALED.return_value = False - transp = unittest.mock.Mock() - self.loop._subprocesses[7] = transp - - self.loop._sig_chld() - self.assertTrue(transp._process_exited.called) - self.assertFalse(m_WEXITSTATUS.called) - self.assertFalse(m_WTERMSIG.called) - - @unittest.mock.patch('asyncio.unix_events.logger') - @unittest.mock.patch('os.WTERMSIG') - @unittest.mock.patch('os.WEXITSTATUS') - @unittest.mock.patch('os.WIFSIGNALED') - @unittest.mock.patch('os.WIFEXITED') - @unittest.mock.patch('os.waitpid') - def test__sig_chld_unknown_status_in_handler(self, m_waitpid, - m_WIFEXITED, m_WIFSIGNALED, - m_WEXITSTATUS, m_WTERMSIG, - m_log): - m_waitpid.side_effect = Exception - transp = unittest.mock.Mock() - self.loop._subprocesses[7] = transp - - self.loop._sig_chld() - self.assertFalse(transp._process_exited.called) - self.assertFalse(m_WIFSIGNALED.called) - self.assertFalse(m_WIFEXITED.called) - self.assertFalse(m_WTERMSIG.called) - self.assertFalse(m_WEXITSTATUS.called) - m_log.exception.assert_called_with( - 'Unknown exception in SIGCHLD handler') - - @unittest.mock.patch('os.waitpid') - def test__sig_chld_process_error(self, m_waitpid): - m_waitpid.side_effect = ChildProcessError - self.loop._sig_chld() - self.assertTrue(m_waitpid.called) - class UnixReadPipeTransportTests(unittest.TestCase): @@ -777,5 +661,872 @@ self.assertFalse(self.protocol.connection_lost.called) +class AbstractChildWatcherTests(unittest.TestCase): + + def test_not_implemented(self): + f = unittest.mock.Mock() + watcher = unix_events.AbstractChildWatcher() + self.assertRaises( + NotImplementedError, watcher.add_child_handler, f, f) + self.assertRaises( + NotImplementedError, watcher.remove_child_handler, f) + self.assertRaises( + NotImplementedError, watcher.set_loop, f) + self.assertRaises( + NotImplementedError, watcher.close) + self.assertRaises( + NotImplementedError, watcher.__enter__) + self.assertRaises( + NotImplementedError, watcher.__exit__, f, f, f) + + +class BaseChildWatcherTests(unittest.TestCase): + + def test_not_implemented(self): + f = unittest.mock.Mock() + watcher = unix_events.BaseChildWatcher(None) + self.assertRaises( + NotImplementedError, watcher._do_waitpid, f) + + +class ChildWatcherTestsMixin: + instance = None + + ignore_warnings = unittest.mock.patch.object(unix_events.logger, "warning") + + def setUp(self): + self.loop = test_utils.TestLoop() + self.running = False + self.zombies = {} + + assert ChildWatcherTestsMixin.instance is None + ChildWatcherTestsMixin.instance = self + + with unittest.mock.patch.object( + self.loop, "add_signal_handler") as self.m_add_signal_handler: + self.watcher = self.create_watcher(self.loop) + + def tearDown(self): + ChildWatcherTestsMixin.instance = None + + def waitpid(pid, flags): + self = ChildWatcherTestsMixin.instance + if isinstance(self.watcher, unix_events.SafeChildWatcher) or pid != -1: + self.assertGreater(pid, 0) + try: + if pid < 0: + return self.zombies.popitem() + else: + return pid, self.zombies.pop(pid) + except KeyError: + pass + if self.running: + return 0, 0 + else: + raise ChildProcessError() + + def add_zombie(self, pid, returncode): + self.zombies[pid] = returncode + 32768 + + def WIFEXITED(status): + return status >= 32768 + + def WIFSIGNALED(status): + return 32700 < status < 32768 + + def WEXITSTATUS(status): + self = ChildWatcherTestsMixin.instance + self.assertTrue(type(self).WIFEXITED(status)) + return status - 32768 + + def WTERMSIG(status): + self = ChildWatcherTestsMixin.instance + self.assertTrue(type(self).WIFSIGNALED(status)) + return 32768 - status + + def test_create_watcher(self): + self.m_add_signal_handler.assert_called_once_with( + signal.SIGCHLD, self.watcher._sig_chld) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld(self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, + m_WEXITSTATUS, m_WTERMSIG): + # register a child + callback = unittest.mock.Mock() + + with self.watcher: + self.running = True + self.watcher.add_child_handler(42, callback, 9, 10, 14) + + self.assertFalse(callback.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # child is running + self.watcher._sig_chld() + + self.assertFalse(callback.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # child terminates (returncode 12) + self.running = False + self.add_zombie(42, 12) + self.watcher._sig_chld() + + self.assertTrue(m_WIFEXITED.called) + self.assertTrue(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + callback.assert_called_once_with(42, 12, 9, 10, 14) + + m_WIFSIGNALED.reset_mock() + m_WIFEXITED.reset_mock() + m_WEXITSTATUS.reset_mock() + callback.reset_mock() + + # ensure that the child is effectively reaped + self.add_zombie(42, 13) + with self.ignore_warnings: + self.watcher._sig_chld() + + self.assertFalse(callback.called) + self.assertFalse(m_WTERMSIG.called) + + m_WIFSIGNALED.reset_mock() + m_WIFEXITED.reset_mock() + m_WEXITSTATUS.reset_mock() + + # sigchld called again + self.zombies.clear() + self.watcher._sig_chld() + + self.assertFalse(callback.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_two_children(self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, + m_WEXITSTATUS, m_WTERMSIG): + callback1 = unittest.mock.Mock() + callback2 = unittest.mock.Mock() + + # register child 1 + with self.watcher: + self.running = True + self.watcher.add_child_handler(43, callback1, 7, 8) + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # register child 2 + with self.watcher: + self.watcher.add_child_handler(44, callback2, 147, 18) + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # childen are running + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # child 1 terminates (signal 3) + self.add_zombie(43, -3) + self.watcher._sig_chld() + + callback1.assert_called_once_with(43, -3, 7, 8) + self.assertFalse(callback2.called) + self.assertTrue(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertTrue(m_WTERMSIG.called) + + m_WIFSIGNALED.reset_mock() + m_WIFEXITED.reset_mock() + m_WTERMSIG.reset_mock() + callback1.reset_mock() + + # child 2 still running + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # child 2 terminates (code 108) + self.add_zombie(44, 108) + self.running = False + self.watcher._sig_chld() + + callback2.assert_called_once_with(44, 108, 147, 18) + self.assertFalse(callback1.called) + self.assertTrue(m_WIFEXITED.called) + self.assertTrue(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + m_WIFSIGNALED.reset_mock() + m_WIFEXITED.reset_mock() + m_WEXITSTATUS.reset_mock() + callback2.reset_mock() + + # ensure that the children are effectively reaped + self.add_zombie(43, 14) + self.add_zombie(44, 15) + with self.ignore_warnings: + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WTERMSIG.called) + + m_WIFSIGNALED.reset_mock() + m_WIFEXITED.reset_mock() + m_WEXITSTATUS.reset_mock() + + # sigchld called again + self.zombies.clear() + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_two_children_terminating_together( + self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, m_WEXITSTATUS, + m_WTERMSIG): + callback1 = unittest.mock.Mock() + callback2 = unittest.mock.Mock() + + # register child 1 + with self.watcher: + self.running = True + self.watcher.add_child_handler(45, callback1, 17, 8) + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # register child 2 + with self.watcher: + self.watcher.add_child_handler(46, callback2, 1147, 18) + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # childen are running + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # child 1 terminates (code 78) + # child 2 terminates (signal 5) + self.add_zombie(45, 78) + self.add_zombie(46, -5) + self.running = False + self.watcher._sig_chld() + + callback1.assert_called_once_with(45, 78, 17, 8) + callback2.assert_called_once_with(46, -5, 1147, 18) + self.assertTrue(m_WIFSIGNALED.called) + self.assertTrue(m_WIFEXITED.called) + self.assertTrue(m_WEXITSTATUS.called) + self.assertTrue(m_WTERMSIG.called) + + m_WIFSIGNALED.reset_mock() + m_WIFEXITED.reset_mock() + m_WTERMSIG.reset_mock() + m_WEXITSTATUS.reset_mock() + callback1.reset_mock() + callback2.reset_mock() + + # ensure that the children are effectively reaped + self.add_zombie(45, 14) + self.add_zombie(46, 15) + with self.ignore_warnings: + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WTERMSIG.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_race_condition( + self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, m_WEXITSTATUS, + m_WTERMSIG): + # register a child + callback = unittest.mock.Mock() + + with self.watcher: + # child terminates before being registered + self.add_zombie(50, 4) + self.watcher._sig_chld() + + self.watcher.add_child_handler(50, callback, 1, 12) + + callback.assert_called_once_with(50, 4, 1, 12) + callback.reset_mock() + + # ensure that the child is effectively reaped + self.add_zombie(50, -1) + with self.ignore_warnings: + self.watcher._sig_chld() + + self.assertFalse(callback.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_replace_handler( + self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, m_WEXITSTATUS, + m_WTERMSIG): + callback1 = unittest.mock.Mock() + callback2 = unittest.mock.Mock() + + # register a child + with self.watcher: + self.running = True + self.watcher.add_child_handler(51, callback1, 19) + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # register the same child again + with self.watcher: + self.watcher.add_child_handler(51, callback2, 21) + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # child terminates (signal 8) + self.running = False + self.add_zombie(51, -8) + self.watcher._sig_chld() + + callback2.assert_called_once_with(51, -8, 21) + self.assertFalse(callback1.called) + self.assertTrue(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertTrue(m_WTERMSIG.called) + + m_WIFSIGNALED.reset_mock() + m_WIFEXITED.reset_mock() + m_WTERMSIG.reset_mock() + callback2.reset_mock() + + # ensure that the child is effectively reaped + self.add_zombie(51, 13) + with self.ignore_warnings: + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(m_WTERMSIG.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_remove_handler(self, m_waitpid, m_WIFEXITED, + m_WIFSIGNALED, m_WEXITSTATUS, m_WTERMSIG): + callback = unittest.mock.Mock() + + # register a child + with self.watcher: + self.running = True + self.watcher.add_child_handler(52, callback, 1984) + + self.assertFalse(callback.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # unregister the child + self.watcher.remove_child_handler(52) + + self.assertFalse(callback.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # child terminates (code 99) + self.running = False + self.add_zombie(52, 99) + with self.ignore_warnings: + self.watcher._sig_chld() + + self.assertFalse(callback.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_unknown_status(self, m_waitpid, m_WIFEXITED, + m_WIFSIGNALED, m_WEXITSTATUS, m_WTERMSIG): + callback = unittest.mock.Mock() + + # register a child + with self.watcher: + self.running = True + self.watcher.add_child_handler(53, callback, -19) + + self.assertFalse(callback.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # terminate with unknown status + self.zombies[53] = 1178 + self.running = False + self.watcher._sig_chld() + + callback.assert_called_once_with(53, 1178, -19) + self.assertTrue(m_WIFEXITED.called) + self.assertTrue(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + callback.reset_mock() + m_WIFEXITED.reset_mock() + m_WIFSIGNALED.reset_mock() + + # ensure that the child is effectively reaped + self.add_zombie(53, 101) + with self.ignore_warnings: + self.watcher._sig_chld() + + self.assertFalse(callback.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_remove_child_handler(self, m_waitpid, m_WIFEXITED, + m_WIFSIGNALED, m_WEXITSTATUS, m_WTERMSIG): + callback1 = unittest.mock.Mock() + callback2 = unittest.mock.Mock() + callback3 = unittest.mock.Mock() + + # register children + with self.watcher: + self.running = True + self.watcher.add_child_handler(54, callback1, 1) + self.watcher.add_child_handler(55, callback2, 2) + self.watcher.add_child_handler(56, callback3, 3) + + # remove child handler 1 + self.assertTrue(self.watcher.remove_child_handler(54)) + + # remove child handler 2 multiple times + self.assertTrue(self.watcher.remove_child_handler(55)) + self.assertFalse(self.watcher.remove_child_handler(55)) + self.assertFalse(self.watcher.remove_child_handler(55)) + + # all children terminate + self.add_zombie(54, 0) + self.add_zombie(55, 1) + self.add_zombie(56, 2) + self.running = False + with self.ignore_warnings: + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + callback3.assert_called_once_with(56, 2, 3) + + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_unhandled_exception(self, m_waitpid): + callback = unittest.mock.Mock() + + # register a child + with self.watcher: + self.running = True + self.watcher.add_child_handler(57, callback) + + # raise an exception + m_waitpid.side_effect = ValueError + + with unittest.mock.patch.object(unix_events.logger, + "exception") as m_exception: + + self.assertEqual(self.watcher._sig_chld(), None) + self.assertTrue(m_exception.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_child_reaped_elsewhere( + self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, m_WEXITSTATUS, + m_WTERMSIG): + + # register a child + callback = unittest.mock.Mock() + + with self.watcher: + self.running = True + self.watcher.add_child_handler(58, callback) + + self.assertFalse(callback.called) + self.assertFalse(m_WIFEXITED.called) + self.assertFalse(m_WIFSIGNALED.called) + self.assertFalse(m_WEXITSTATUS.called) + self.assertFalse(m_WTERMSIG.called) + + # child terminates + self.running = False + self.add_zombie(58, 4) + + # waitpid is called elsewhere + os.waitpid(58, os.WNOHANG) + + m_waitpid.reset_mock() + + # sigchld + with self.ignore_warnings: + self.watcher._sig_chld() + + callback.assert_called(m_waitpid) + if isinstance(self.watcher, unix_events.FastChildWatcher): + # here the FastChildWatche enters a deadlock + # (there is no way to prevent it) + self.assertFalse(callback.called) + else: + callback.assert_called_once_with(58, 255) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_sigchld_unknown_pid_during_registration( + self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, m_WEXITSTATUS, + m_WTERMSIG): + + # register two children + callback1 = unittest.mock.Mock() + callback2 = unittest.mock.Mock() + + with self.ignore_warnings, self.watcher: + self.running = True + # child 1 terminates + self.add_zombie(591, 7) + # an unknown child terminates + self.add_zombie(593, 17) + + self.watcher._sig_chld() + + self.watcher.add_child_handler(591, callback1) + self.watcher.add_child_handler(592, callback2) + + callback1.assert_called_once_with(591, 7) + self.assertFalse(callback2.called) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_set_loop( + self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, m_WEXITSTATUS, + m_WTERMSIG): + + # register a child + callback = unittest.mock.Mock() + + with self.watcher: + self.running = True + self.watcher.add_child_handler(60, callback) + + # attach a new loop + old_loop = self.loop + self.loop = test_utils.TestLoop() + + with unittest.mock.patch.object( + old_loop, + "remove_signal_handler") as m_old_remove_signal_handler, \ + unittest.mock.patch.object( + self.loop, + "add_signal_handler") as m_new_add_signal_handler: + + self.watcher.set_loop(self.loop) + + m_old_remove_signal_handler.assert_called_once_with( + signal.SIGCHLD) + m_new_add_signal_handler.assert_called_once_with( + signal.SIGCHLD, self.watcher._sig_chld) + + # child terminates + self.running = False + self.add_zombie(60, 9) + self.watcher._sig_chld() + + callback.assert_called_once_with(60, 9) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_set_loop_race_condition( + self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, m_WEXITSTATUS, + m_WTERMSIG): + + # register 3 children + callback1 = unittest.mock.Mock() + callback2 = unittest.mock.Mock() + callback3 = unittest.mock.Mock() + + with self.watcher: + self.running = True + self.watcher.add_child_handler(61, callback1) + self.watcher.add_child_handler(62, callback2) + self.watcher.add_child_handler(622, callback3) + + # detach the loop + old_loop = self.loop + self.loop = None + + with unittest.mock.patch.object( + old_loop, "remove_signal_handler") as m_remove_signal_handler: + + self.watcher.set_loop(None) + + m_remove_signal_handler.assert_called_once_with( + signal.SIGCHLD) + + # child 1 & 2 terminate + self.add_zombie(61, 11) + self.add_zombie(62, -5) + + # SIGCHLD was not catched + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + self.assertFalse(callback3.called) + + # attach a new loop + self.loop = test_utils.TestLoop() + + with unittest.mock.patch.object( + self.loop, "add_signal_handler") as m_add_signal_handler: + + self.watcher.set_loop(self.loop) + + m_add_signal_handler.assert_called_once_with( + signal.SIGCHLD, self.watcher._sig_chld) + callback1.assert_called_once_with(61, 11) # race condition! + callback2.assert_called_once_with(62, -5) # race condition! + self.assertFalse(callback3.called) + + callback1.reset_mock() + callback2.reset_mock() + + # child 3 terminates + self.running = False + self.add_zombie(622, 19) + self.watcher._sig_chld() + + self.assertFalse(callback1.called) + self.assertFalse(callback2.called) + callback3.assert_called_once_with(622, 19) + + @unittest.mock.patch('os.WTERMSIG', wraps=WTERMSIG) + @unittest.mock.patch('os.WEXITSTATUS', wraps=WEXITSTATUS) + @unittest.mock.patch('os.WIFSIGNALED', wraps=WIFSIGNALED) + @unittest.mock.patch('os.WIFEXITED', wraps=WIFEXITED) + @unittest.mock.patch('os.waitpid', wraps=waitpid) + def test_close( + self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED, m_WEXITSTATUS, + m_WTERMSIG): + + # register two children + callback1 = unittest.mock.Mock() + callback2 = unittest.mock.Mock() + + with self.watcher: + self.running = True + # child 1 terminates + self.add_zombie(63, 9) + # other child terminates + self.add_zombie(65, 18) + self.watcher._sig_chld() + + self.watcher.add_child_handler(63, callback1) + self.watcher.add_child_handler(64, callback1) + + self.assertEqual(len(self.watcher._callbacks), 1) + if isinstance(self.watcher, unix_events.FastChildWatcher): + self.assertEqual(len(self.watcher._zombies), 1) + + with unittest.mock.patch.object( + self.loop, + "remove_signal_handler") as m_remove_signal_handler: + + self.watcher.close() + + m_remove_signal_handler.assert_called_once_with( + signal.SIGCHLD) + self.assertFalse(self.watcher._callbacks) + if isinstance(self.watcher, unix_events.FastChildWatcher): + self.assertFalse(self.watcher._zombies) + + +class SafeChildWatcherTests (ChildWatcherTestsMixin, unittest.TestCase): + def create_watcher(self, loop): + return unix_events.SafeChildWatcher(loop) + + +class FastChildWatcherTests (ChildWatcherTestsMixin, unittest.TestCase): + def create_watcher(self, loop): + return unix_events.FastChildWatcher(loop) + + +class PolicyTests(unittest.TestCase): + + def create_policy(self): + return unix_events.DefaultEventLoopPolicy() + + def test_get_child_watcher(self): + policy = self.create_policy() + self.assertIsNone(policy._watcher) + + watcher = policy.get_child_watcher() + self.assertIsInstance(watcher, unix_events.SafeChildWatcher) + + self.asser