summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/zipfile.rst17
-rw-r--r--Lib/pathlib.py4
-rwxr-xr-xLib/test/test_pathlib.py14
-rw-r--r--Lib/test/test_zipfile.py4
-rw-r--r--Lib/test/test_zipfile64.py4
-rw-r--r--Lib/tkinter/test/test_tkinter/test_widgets.py3
-rw-r--r--Lib/tkinter/test/widget_tests.py4
-rw-r--r--Lib/unittest/loader.py60
-rw-r--r--Lib/unittest/test/test_discovery.py80
-rw-r--r--Lib/unittest/test/testmock/testmock.py17
-rw-r--r--Lib/zipfile.py8
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS9
-rw-r--r--Misc/python-wing5.wpr18
-rw-r--r--Modules/posixmodule.c9
-rw-r--r--Modules/timemodule.c2
16 files changed, 197 insertions, 57 deletions
diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst
index f7303ea..72b76a0 100644
--- a/Doc/library/zipfile.rst
+++ b/Doc/library/zipfile.rst
@@ -130,7 +130,7 @@ ZipFile Objects
---------------
-.. class:: ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=False)
+.. class:: ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True)
Open a ZIP file, where *file* can be either a path to a file (a string) or a
file-like object. The *mode* parameter should be ``'r'`` to read an existing
@@ -147,12 +147,9 @@ ZipFile Objects
:const:`ZIP_BZIP2` or :const:`ZIP_LZMA` is specified but the corresponding module
(:mod:`zlib`, :mod:`bz2` or :mod:`lzma`) is not available, :exc:`RuntimeError`
is also raised. The default is :const:`ZIP_STORED`. If *allowZip64* is
- ``True`` zipfile will create ZIP files that use the ZIP64 extensions when
- the zipfile is larger than 2 GiB. If it is false (the default) :mod:`zipfile`
+ ``True`` (the default) zipfile will create ZIP files that use the ZIP64
+ extensions when the zipfile is larger than 2 GiB. If it is false :mod:`zipfile`
will raise an exception when the ZIP file would require ZIP64 extensions.
- ZIP64 extensions are disabled by default because the default :program:`zip`
- and :program:`unzip` commands on Unix (the InfoZIP utilities) don't support
- these extensions.
If the file is created with mode ``'a'`` or ``'w'`` and then
:meth:`closed <close>` without adding any files to the archive, the appropriate
@@ -171,6 +168,9 @@ ZipFile Objects
.. versionchanged:: 3.3
Added support for :mod:`bzip2 <bz2>` and :mod:`lzma` compression.
+ .. versionchanged:: 3.4
+ ZIP64 extensions are enabled by default.
+
.. method:: ZipFile.close()
@@ -374,12 +374,15 @@ PyZipFile Objects
The :class:`PyZipFile` constructor takes the same parameters as the
:class:`ZipFile` constructor, and one additional parameter, *optimize*.
-.. class:: PyZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=False, \
+.. class:: PyZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True, \
optimize=-1)
.. versionadded:: 3.2
The *optimize* parameter.
+ .. versionchanged:: 3.4
+ ZIP64 extensions are enabled by default.
+
Instances have one method in addition to those of :class:`ZipFile` objects:
.. method:: PyZipFile.writepy(pathname, basename='', filterfunc=None)
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index 06cbae5..e73eca7 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -6,7 +6,6 @@ import os
import posixpath
import re
import sys
-import time
import weakref
try:
import threading
@@ -1076,9 +1075,8 @@ class Path(PurePath):
# First try to bump modification time
# Implementation note: GNU touch uses the UTIME_NOW option of
# the utimensat() / futimens() functions.
- t = time.time()
try:
- self._accessor.utime(self, (t, t))
+ self._accessor.utime(self, None)
except OSError:
# Avoid exception chaining
pass
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
index 6663ffa..4108d5e 100755
--- a/Lib/test/test_pathlib.py
+++ b/Lib/test/test_pathlib.py
@@ -1391,11 +1391,8 @@ class _BasePathTest(object):
# The file mtime should be refreshed by calling touch() again
p.touch()
st = p.stat()
- # Issue #19715: there can be an inconsistency under Windows between
- # the timestamp rounding when creating a file, and the timestamp
- # rounding done when calling utime(). `delta` makes up for this.
- delta = 1e-6 if os.name == 'nt' else 0
- self.assertGreaterEqual(st.st_mtime, old_mtime - delta)
+ self.assertGreaterEqual(st.st_mtime_ns, old_mtime_ns)
+ self.assertGreaterEqual(st.st_mtime, old_mtime)
# Now with exist_ok=False
p = P / 'newfileB'
self.assertFalse(p.exists())
@@ -1403,6 +1400,13 @@ class _BasePathTest(object):
self.assertTrue(p.exists())
self.assertRaises(OSError, p.touch, exist_ok=False)
+ def test_touch_nochange(self):
+ P = self.cls(BASE)
+ p = P / 'fileA'
+ p.touch()
+ with p.open('rb') as f:
+ self.assertEqual(f.read().strip(), b"this is file A")
+
def test_mkdir(self):
P = self.cls(BASE)
p = P / 'newdirA'
diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py
index 7249b13..c15ea9f 100644
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -506,12 +506,12 @@ class StoredTestZip64InSmallFiles(AbstractTestZip64InSmallFiles,
compression = zipfile.ZIP_STORED
def large_file_exception_test(self, f, compression):
- with zipfile.ZipFile(f, "w", compression) as zipfp:
+ with zipfile.ZipFile(f, "w", compression, allowZip64=False) as zipfp:
self.assertRaises(zipfile.LargeZipFile,
zipfp.write, TESTFN, "another.name")
def large_file_exception_test2(self, f, compression):
- with zipfile.ZipFile(f, "w", compression) as zipfp:
+ with zipfile.ZipFile(f, "w", compression, allowZip64=False) as zipfp:
self.assertRaises(zipfile.LargeZipFile,
zipfp.writestr, "another.name", self.data)
diff --git a/Lib/test/test_zipfile64.py b/Lib/test/test_zipfile64.py
index a8fb7ab..498d464 100644
--- a/Lib/test/test_zipfile64.py
+++ b/Lib/test/test_zipfile64.py
@@ -38,7 +38,7 @@ class TestsWithSourceFile(unittest.TestCase):
def zipTest(self, f, compression):
# Create the ZIP archive.
- zipfp = zipfile.ZipFile(f, "w", compression, allowZip64=True)
+ zipfp = zipfile.ZipFile(f, "w", compression)
# It will contain enough copies of self.data to reach about 6GB of
# raw data to store.
@@ -92,7 +92,7 @@ class OtherTests(unittest.TestCase):
def testMoreThan64kFiles(self):
# This test checks that more than 64k files can be added to an archive,
# and that the resulting archive can be read properly by ZipFile
- zipf = zipfile.ZipFile(TESTFN, mode="w")
+ zipf = zipfile.ZipFile(TESTFN, mode="w", allowZip64=False)
zipf.debug = 100
numfiles = (1 << 16) * 3//2
for i in range(numfiles):
diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py
index 166a752..4de6531 100644
--- a/Lib/tkinter/test/test_tkinter/test_widgets.py
+++ b/Lib/tkinter/test/test_tkinter/test_widgets.py
@@ -1,6 +1,7 @@
import unittest
import tkinter
import os
+import sys
from test.support import requires
from tkinter.test.support import (tcl_version, requires_tcl,
@@ -262,6 +263,8 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
test_highlightthickness = StandardOptionsTests.test_highlightthickness
+ @unittest.skipIf(sys.platform == 'darwin',
+ 'crashes with Cocoa Tk (issue19733)')
def test_image(self):
widget = self.create()
image = tkinter.PhotoImage('image1')
diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py
index 14b4ca1..bd274e6 100644
--- a/Lib/tkinter/test/widget_tests.py
+++ b/Lib/tkinter/test/widget_tests.py
@@ -1,5 +1,7 @@
# Common tests for test_tkinter/test_widgets.py and test_ttk/test_widgets.py
+import unittest
+import sys
import tkinter
from tkinter.ttk import setup_master, Scale
from tkinter.test.support import (tcl_version, requires_tcl, get_tk_patchlevel,
@@ -289,6 +291,8 @@ class StandardOptionsTests:
self.checkParam(widget, 'highlightthickness', -2, expected=0,
conv=self._conv_pixels)
+ @unittest.skipIf(sys.platform == 'darwin',
+ 'crashes with Cocoa Tk (issue19733)')
def test_image(self):
widget = self.create()
self.checkImageParam(widget, 'image')
diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py
index e872fcc..808c50e 100644
--- a/Lib/unittest/loader.py
+++ b/Lib/unittest/loader.py
@@ -61,8 +61,9 @@ class TestLoader(object):
def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all tests cases contained in testCaseClass"""
if issubclass(testCaseClass, suite.TestSuite):
- raise TypeError("Test cases should not be derived from TestSuite." \
- " Maybe you meant to derive from TestCase?")
+ raise TypeError("Test cases should not be derived from "
+ "TestSuite. Maybe you meant to derive from "
+ "TestCase?")
testCaseNames = self.getTestCaseNames(testCaseClass)
if not testCaseNames and hasattr(testCaseClass, 'runTest'):
testCaseNames = ['runTest']
@@ -200,6 +201,8 @@ class TestLoader(object):
self._top_level_dir = top_level_dir
is_not_importable = False
+ is_namespace = False
+ tests = []
if os.path.isdir(os.path.abspath(start_dir)):
start_dir = os.path.abspath(start_dir)
if start_dir != top_level_dir:
@@ -213,15 +216,52 @@ class TestLoader(object):
else:
the_module = sys.modules[start_dir]
top_part = start_dir.split('.')[0]
- start_dir = os.path.abspath(os.path.dirname((the_module.__file__)))
+ try:
+ start_dir = os.path.abspath(
+ os.path.dirname((the_module.__file__)))
+ except AttributeError:
+ # look for namespace packages
+ try:
+ spec = the_module.__spec__
+ except AttributeError:
+ spec = None
+
+ if spec and spec.loader is None:
+ if spec.submodule_search_locations is not None:
+ is_namespace = True
+
+ for path in the_module.__path__:
+ if (not set_implicit_top and
+ not path.startswith(top_level_dir)):
+ continue
+ self._top_level_dir = \
+ (path.split(the_module.__name__
+ .replace(".", os.path.sep))[0])
+ tests.extend(self._find_tests(path,
+ pattern,
+ namespace=True))
+ elif the_module.__name__ in sys.builtin_module_names:
+ # builtin module
+ raise TypeError('Can not use builtin modules '
+ 'as dotted module names') from None
+ else:
+ raise TypeError(
+ 'don\'t know how to discover from {!r}'
+ .format(the_module)) from None
+
if set_implicit_top:
- self._top_level_dir = self._get_directory_containing_module(top_part)
- sys.path.remove(top_level_dir)
+ if not is_namespace:
+ self._top_level_dir = \
+ self._get_directory_containing_module(top_part)
+ sys.path.remove(top_level_dir)
+ else:
+ sys.path.remove(top_level_dir)
if is_not_importable:
raise ImportError('Start directory is not importable: %r' % start_dir)
- tests = list(self._find_tests(start_dir, pattern))
+ if not is_namespace:
+ tests = list(self._find_tests(start_dir, pattern))
return self.suiteClass(tests)
def _get_directory_containing_module(self, module_name):
@@ -254,7 +294,7 @@ class TestLoader(object):
# override this method to use alternative matching strategy
return fnmatch(path, pattern)
- def _find_tests(self, start_dir, pattern):
+ def _find_tests(self, start_dir, pattern, namespace=False):
"""Used by discovery. Yields test suites it loads."""
paths = sorted(os.listdir(start_dir))
@@ -287,7 +327,8 @@ class TestLoader(object):
raise ImportError(msg % (mod_name, module_dir, expected_dir))
yield self.loadTestsFromModule(module)
elif os.path.isdir(full_path):
- if not os.path.isfile(os.path.join(full_path, '__init__.py')):
+ if (not namespace and
+ not os.path.isfile(os.path.join(full_path, '__init__.py'))):
continue
load_tests = None
@@ -304,7 +345,8 @@ class TestLoader(object):
# tests loaded from package file
yield tests
# recurse into the package
- yield from self._find_tests(full_path, pattern)
+ yield from self._find_tests(full_path, pattern,
+ namespace=namespace)
else:
try:
yield load_tests(self, tests, pattern)
diff --git a/Lib/unittest/test/test_discovery.py b/Lib/unittest/test/test_discovery.py
index d4eff40..6b7b128 100644
--- a/Lib/unittest/test/test_discovery.py
+++ b/Lib/unittest/test/test_discovery.py
@@ -1,6 +1,8 @@
import os
import re
import sys
+import types
+import builtins
from test import support
import unittest
@@ -173,7 +175,7 @@ class TestDiscovery(unittest.TestCase):
self.addCleanup(restore_isdir)
_find_tests_args = []
- def _find_tests(start_dir, pattern):
+ def _find_tests(start_dir, pattern, namespace=None):
_find_tests_args.append((start_dir, pattern))
return ['tests']
loader._find_tests = _find_tests
@@ -436,7 +438,7 @@ class TestDiscovery(unittest.TestCase):
expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__))
self.wasRun = False
- def _find_tests(start_dir, pattern):
+ def _find_tests(start_dir, pattern, namespace=None):
self.wasRun = True
self.assertEqual(start_dir, expectedPath)
return tests
@@ -446,5 +448,79 @@ class TestDiscovery(unittest.TestCase):
self.assertEqual(suite._tests, tests)
+ def test_discovery_from_dotted_path_builtin_modules(self):
+
+ loader = unittest.TestLoader()
+
+ listdir = os.listdir
+ os.listdir = lambda _: ['test_this_does_not_exist.py']
+ isfile = os.path.isfile
+ isdir = os.path.isdir
+ os.path.isdir = lambda _: False
+ orig_sys_path = sys.path[:]
+ def restore():
+ os.path.isfile = isfile
+ os.path.isdir = isdir
+ os.listdir = listdir
+ sys.path[:] = orig_sys_path
+ self.addCleanup(restore)
+
+ with self.assertRaises(TypeError) as cm:
+ loader.discover('sys')
+ self.assertEqual(str(cm.exception),
+ 'Can not use builtin modules '
+ 'as dotted module names')
+
+ def test_discovery_from_dotted_namespace_packages(self):
+ loader = unittest.TestLoader()
+
+ orig_import = __import__
+ package = types.ModuleType('package')
+ package.__path__ = ['/a', '/b']
+ package.__spec__ = types.SimpleNamespace(
+ loader=None,
+ submodule_search_locations=['/a', '/b']
+ )
+
+ def _import(packagename, *args, **kwargs):
+ sys.modules[packagename] = package
+ return package
+
+ def cleanup():
+ builtins.__import__ = orig_import
+ self.addCleanup(cleanup)
+ builtins.__import__ = _import
+
+ _find_tests_args = []
+ def _find_tests(start_dir, pattern, namespace=None):
+ _find_tests_args.append((start_dir, pattern))
+ return ['%s/tests' % start_dir]
+
+ loader._find_tests = _find_tests
+ loader.suiteClass = list
+ suite = loader.discover('package')
+ self.assertEqual(suite, ['/a/tests', '/b/tests'])
+
+ def test_discovery_failed_discovery(self):
+ loader = unittest.TestLoader()
+ package = types.ModuleType('package')
+ orig_import = __import__
+
+ def _import(packagename, *args, **kwargs):
+ sys.modules[packagename] = package
+ return package
+
+ def cleanup():
+ builtins.__import__ = orig_import
+ self.addCleanup(cleanup)
+ builtins.__import__ = _import
+
+ with self.assertRaises(TypeError) as cm:
+ loader.discover('package')
+ self.assertEqual(str(cm.exception),
+ 'don\'t know how to discover from {!r}'
+ .format(package))
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py
index ac0efeb..20cc654 100644
--- a/Lib/unittest/test/testmock/testmock.py
+++ b/Lib/unittest/test/testmock/testmock.py
@@ -1302,20 +1302,6 @@ class MockTest(unittest.TestCase):
self.assertEqual(m.method_calls, [])
- def test_attribute_deletion(self):
- # this behaviour isn't *useful*, but at least it's now tested...
- for Klass in Mock, MagicMock, NonCallableMagicMock, NonCallableMock:
- m = Klass()
- original = m.foo
- m.foo = 3
- del m.foo
- self.assertEqual(m.foo, original)
-
- new = m.foo = Mock()
- del m.foo
- self.assertEqual(m.foo, new)
-
-
def test_mock_parents(self):
for Klass in Mock, MagicMock:
m = Klass()
@@ -1379,7 +1365,8 @@ class MockTest(unittest.TestCase):
def test_attribute_deletion(self):
- for mock in Mock(), MagicMock():
+ for mock in (Mock(), MagicMock(), NonCallableMagicMock(),
+ NonCallableMock()):
self.assertTrue(hasattr(mock, 'm'))
del mock.m
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index ca2611d..ae65b42 100644
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -876,7 +876,7 @@ class ZipExtFile(io.BufferedIOBase):
class ZipFile:
""" Class with methods to open, read, write, close, list zip files.
- z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=False)
+ z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=True)
file: Either the path to the file, or a file-like object.
If it is a path, the file will be opened and closed by ZipFile.
@@ -892,7 +892,7 @@ class ZipFile:
fp = None # Set here since __del__ checks it
_windows_illegal_name_trans_table = None
- def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False):
+ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
"""Open the ZIP file with mode read "r", write "w" or append "a"."""
if mode not in ("r", "w", "a"):
raise RuntimeError('ZipFile() requires mode "r", "w", or "a"')
@@ -1561,7 +1561,7 @@ class PyZipFile(ZipFile):
"""Class to create ZIP archives with Python library files and packages."""
def __init__(self, file, mode="r", compression=ZIP_STORED,
- allowZip64=False, optimize=-1):
+ allowZip64=True, optimize=-1):
ZipFile.__init__(self, file, mode=mode, compression=compression,
allowZip64=allowZip64)
self._optimize = optimize
@@ -1783,7 +1783,7 @@ def main(args = None):
os.path.join(path, nm), os.path.join(zippath, nm))
# else: ignore
- with ZipFile(args[1], 'w', allowZip64=True) as zf:
+ with ZipFile(args[1], 'w') as zf:
for src in args[2:]:
addToZip(zf, src, os.path.basename(src))
diff --git a/Misc/ACKS b/Misc/ACKS
index 9436af8..8c2aa4d 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -806,6 +806,7 @@ Marek Majkowski
Grzegorz Makarewicz
David Malcolm
Greg Malcolm
+William Mallard
Ken Manheimer
Vladimir Marangozov
Colin Marc
diff --git a/Misc/NEWS b/Misc/NEWS
index b7b9b3b..2b804e8 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -71,6 +71,12 @@ Library
- Issue #19689: Add ssl.create_default_context() factory function. It creates
a new SSLContext object with secure default settings.
+- Issue #19727: os.utime(..., None) is now potentially more precise
+ under Windows.
+
+- Issue #17201: ZIP64 extensions now are enabled by default. Patch by
+ William Mallard.
+
- Issue #19292: Add SSLContext.load_default_certs() to load default root CA
certificates from default stores or system stores. By default the method
loads CA certs for authentication of server certs.
@@ -482,6 +488,9 @@ Core and Builtins
Library
-------
+- Issue #17457: unittest test discovery now works with namespace packages.
+ Patch by Claudiu Popa.
+
- Issue #18235: Fix the sysconfig variables LDSHARED and BLDSHARED under AIX.
Patch by David Edelsohn.
diff --git a/Misc/python-wing5.wpr b/Misc/python-wing5.wpr
new file mode 100644
index 0000000..0e1ae63
--- /dev/null
+++ b/Misc/python-wing5.wpr
@@ -0,0 +1,18 @@
+#!wing
+#!version=5.0
+##################################################################
+# Wing IDE project file #
+##################################################################
+[project attributes]
+proj.directory-list = [{'dirloc': loc('..'),
+ 'excludes': [u'.hg',
+ u'Lib/unittest/__pycache__',
+ u'Lib/unittest/test/__pycache__',
+ u'Lib/__pycache__',
+ u'build',
+ u'Doc/build'],
+ 'filter': '*',
+ 'include_hidden': False,
+ 'recursive': True,
+ 'watch_for_changes': True}]
+proj.file-type = 'shared'
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 71b1437..4c96204 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -4953,13 +4953,8 @@ posix_utime(PyObject *self, PyObject *args, PyObject *kwargs)
}
if (utime.now) {
- SYSTEMTIME now;
- GetSystemTime(&now);
- if (!SystemTimeToFileTime(&now, &mtime) ||
- !SystemTimeToFileTime(&now, &atime)) {
- PyErr_SetFromWindowsErr(0);
- goto exit;
- }
+ GetSystemTimeAsFileTime(&mtime);
+ atime = mtime;
}
else {
time_t_to_FILE_TIME(utime.atime_s, utime.atime_ns, &atime);
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index ca39906..d3878b9 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -650,7 +650,7 @@ time_strftime(PyObject *self, PyObject *args)
return NULL;
}
}
-#elif defined(_AIX) && defined(HAVE_WCSFTIME)
+#elif (defined(_AIX) || defined(sun)) && defined(HAVE_WCSFTIME)
for(outbuf = wcschr(fmt, '%');
outbuf != NULL;
outbuf = wcschr(outbuf+2, '%'))