diff options
author | Georg Brandl <georg@python.org> | 2012-09-29 07:27:15 (GMT) |
---|---|---|
committer | Georg Brandl <georg@python.org> | 2012-09-29 07:27:15 (GMT) |
commit | 99a247fd01c1cd780c0c3ee1116657627f1ee744 (patch) | |
tree | 319e33cb6612c3fafb2eb82e15c5e85e3d771e4f /Lib | |
parent | 1628eaa5dc8892ff381ca7558cc7c8d80fac494d (diff) | |
parent | 8ed677db129171317b8ee7cd45b39b9013f5a2d6 (diff) | |
download | cpython-99a247fd01c1cd780c0c3ee1116657627f1ee744.zip cpython-99a247fd01c1cd780c0c3ee1116657627f1ee744.tar.gz cpython-99a247fd01c1cd780c0c3ee1116657627f1ee744.tar.bz2 |
Merge with main repo default branch.
Diffstat (limited to 'Lib')
54 files changed, 929 insertions, 133 deletions
diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 387c585..fa77ec1 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -895,12 +895,18 @@ class BytesIO(BufferedIOBase): return pos def readable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True def writable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True def seekable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True @@ -1562,6 +1568,8 @@ class TextIOWrapper(TextIOBase): return self._buffer def seekable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return self._seekable def readable(self): diff --git a/Lib/argparse.py b/Lib/argparse.py index 67bbef2..f25b1b6 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1725,10 +1725,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): if action.dest is not SUPPRESS: if not hasattr(namespace, action.dest): if action.default is not SUPPRESS: - default = action.default - if isinstance(action.default, str): - default = self._get_value(action, default) - setattr(namespace, action.dest, default) + setattr(namespace, action.dest, action.default) # add any parser defaults that aren't present for dest in self._defaults: @@ -1951,9 +1948,25 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): # if we didn't consume all the argument strings, there were extras extras.extend(arg_strings[stop_index:]) - # make sure all required actions were present - required_actions = [_get_action_name(action) for action in self._actions - if action.required and action not in seen_actions] + # make sure all required actions were present and also convert + # action defaults which were not given as arguments + required_actions = [] + for action in self._actions: + if action not in seen_actions: + if action.required: + required_actions.append(_get_action_name(action)) + else: + # Convert action default now instead of doing it before + # parsing arguments to avoid calling convert functions + # twice (which may fail) if the argument was given, but + # only if it was defined already in the namespace + if (action.default is not None and + isinstance(action.default, str) and + hasattr(namespace, action.dest) and + action.default is getattr(namespace, action.dest)): + setattr(namespace, action.dest, + self._get_value(action, action.default)) + if required_actions: self.error(_('the following arguments are required: %s') % ', '.join(required_actions)) diff --git a/Lib/calendar.py b/Lib/calendar.py index 0301d6b..3bbf399 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -161,7 +161,11 @@ class Calendar(object): oneday = datetime.timedelta(days=1) while True: yield date - date += oneday + try: + date += oneday + except OverflowError: + # Adding one day could fail after datetime.MAXYEAR + break if date.month != month and date.weekday() == self.firstweekday: break diff --git a/Lib/doctest.py b/Lib/doctest.py index 620451f..3af05fb 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -2334,7 +2334,12 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, elif not tests: # Why do we want to do this? Because it reveals a bug that might # otherwise be hidden. - raise ValueError(module, "has no tests") + # It is probably a bug that this exception is not also raised if the + # number of doctest examples in tests is zero (i.e. if no doctest + # examples were found). However, we should probably not be raising + # an exception at all here, though it is too late to make this change + # for a maintenance release. See also issue #14649. + raise ValueError(module, "has no docstrings") tests.sort() suite = unittest.TestSuite() diff --git a/Lib/gzip.py b/Lib/gzip.py index b6656a9..d8abffd 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -670,9 +670,9 @@ def _test(): if not chunk: break g.write(chunk) - if g is not sys.stdout: + if g is not sys.stdout.buffer: g.close() - if f is not sys.stdin: + if f is not sys.stdin.buffer: f.close() if __name__ == '__main__': diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py index 8f3f6dc..901e762 100644 --- a/Lib/http/cookiejar.py +++ b/Lib/http/cookiejar.py @@ -1825,7 +1825,7 @@ def lwp_cookie_str(cookie): class LWPCookieJar(FileCookieJar): """ - The LWPCookieJar saves a sequence of"Set-Cookie3" lines. + The LWPCookieJar saves a sequence of "Set-Cookie3" lines. "Set-Cookie3" is the format used by the libwww-perl libary, not known to be compatible with any browser, but which is easy to read and doesn't lose information about RFC 2965 cookies. @@ -1837,7 +1837,7 @@ class LWPCookieJar(FileCookieJar): """ def as_lwp_str(self, ignore_discard=True, ignore_expires=True): - """Return cookies as a string of "\n"-separated "Set-Cookie3" headers. + """Return cookies as a string of "\\n"-separated "Set-Cookie3" headers. ignore_discard and ignore_expires: see docstring for FileCookieJar.save diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 5c4d2c6..98361a7 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -419,8 +419,8 @@ def cache_from_source(path, debug_override=None): .pyc/.pyo file calculated as if the .py file were imported. The extension will be .pyc unless sys.flags.optimize is non-zero, then it will be .pyo. - If debug_override is not None, then it must be a boolean and is taken as - the value of bool(sys.flags.optimize) instead. + If debug_override is not None, then it must be a boolean and is used in + place of sys.flags.optimize. If sys.implementation.cache_tag is None then NotImplementedError is raised. @@ -1669,7 +1669,11 @@ def __import__(name, globals=None, locals=None, fromlist=(), level=0): elif not name: return module else: + # Figure out where to slice the module's name up to the first dot + # in 'name'. cut_off = len(name) - len(name.partition('.')[0]) + # Slice end needs to be positive to alleviate need to special-case + # when ``'.' not in name``. return sys.modules[module.__name__[:len(module.__name__)-cut_off]] else: return _handle_fromlist(module, fromlist, _gcd_import) diff --git a/Lib/lib2to3/fixer_util.py b/Lib/lib2to3/fixer_util.py index 92f0da9..2b5bb1d 100644 --- a/Lib/lib2to3/fixer_util.py +++ b/Lib/lib2to3/fixer_util.py @@ -274,9 +274,9 @@ def find_root(node): """Find the top level namespace.""" # Scamper up to the top level namespace while node.type != syms.file_input: - assert node.parent, "Tree is insane! root found before "\ - "file_input node was found." node = node.parent + if not node: + raise ValueError("root found before file_input node was found.") return node def does_tree_import(package, name, node): diff --git a/Lib/lib2to3/refactor.py b/Lib/lib2to3/refactor.py index 1e85810..201e193 100644 --- a/Lib/lib2to3/refactor.py +++ b/Lib/lib2to3/refactor.py @@ -445,7 +445,7 @@ class RefactoringTool(object): try: find_root(node) - except AssertionError: + except ValueError: # this node has been cut off from a # previous transformation ; skip continue diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 1841b0f..d3bf3fd 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -208,6 +208,9 @@ class Mailbox: raise ValueError("String input must be ASCII-only; " "use bytes or a Message instead") + # Whether each message must end in a newline + _append_newline = False + def _dump_message(self, message, target, mangle_from_=False): # This assumes the target file is open in binary mode. """Dump message contents to target file.""" @@ -219,6 +222,9 @@ class Mailbox: data = buffer.read() data = data.replace(b'\n', linesep) target.write(data) + if self._append_newline and not data.endswith(linesep): + # Make sure the message ends with a newline + target.write(linesep) elif isinstance(message, (str, bytes, io.StringIO)): if isinstance(message, io.StringIO): warnings.warn("Use of StringIO input is deprecated, " @@ -230,11 +236,15 @@ class Mailbox: message = message.replace(b'\nFrom ', b'\n>From ') message = message.replace(b'\n', linesep) target.write(message) + if self._append_newline and not message.endswith(linesep): + # Make sure the message ends with a newline + target.write(linesep) elif hasattr(message, 'read'): if hasattr(message, 'buffer'): warnings.warn("Use of text mode files is deprecated, " "use a binary mode file instead", DeprecationWarning, 3) message = message.buffer + lastline = None while True: line = message.readline() # Universal newline support. @@ -248,6 +258,10 @@ class Mailbox: line = b'>From ' + line[5:] line = line.replace(b'\n', linesep) target.write(line) + lastline = line + if self._append_newline and lastline and not lastline.endswith(linesep): + # Make sure the message ends with a newline + target.write(linesep) else: raise TypeError('Invalid message type: %s' % type(message)) @@ -833,30 +847,48 @@ class mbox(_mboxMMDF): _mangle_from_ = True + # All messages must end in a newline character, and + # _post_message_hooks outputs an empty line between messages. + _append_newline = True + def __init__(self, path, factory=None, create=True): """Initialize an mbox mailbox.""" self._message_factory = mboxMessage _mboxMMDF.__init__(self, path, factory, create) - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - if f.tell() != 0: - f.write(linesep) + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(linesep) def _generate_toc(self): """Generate key-to-(start, stop) table of contents.""" starts, stops = [], [] + last_was_empty = False self._file.seek(0) while True: line_pos = self._file.tell() line = self._file.readline() if line.startswith(b'From '): if len(stops) < len(starts): - stops.append(line_pos - len(linesep)) + if last_was_empty: + stops.append(line_pos - len(linesep)) + else: + # The last line before the "From " line wasn't + # blank, but we consider it a start of a + # message anyway. + stops.append(line_pos) starts.append(line_pos) + last_was_empty = False elif not line: - stops.append(line_pos) + if last_was_empty: + stops.append(line_pos - len(linesep)) + else: + stops.append(line_pos) break + elif line == linesep: + last_was_empty = True + else: + last_was_empty = False self._toc = dict(enumerate(zip(starts, stops))) self._next_key = len(self._toc) self._file_length = self._file.tell() diff --git a/Lib/multiprocessing/pool.py b/Lib/multiprocessing/pool.py index 9e07e32..ec57939 100644 --- a/Lib/multiprocessing/pool.py +++ b/Lib/multiprocessing/pool.py @@ -225,7 +225,6 @@ class Pool(object): Apply `func` to each element in `iterable`, collecting the results in a list that is returned. ''' - assert self._state == RUN return self._map_async(func, iterable, mapstar, chunksize).get() def starmap(self, func, iterable, chunksize=None): @@ -234,7 +233,6 @@ class Pool(object): be iterables as well and will be unpacked as arguments. Hence `func` and (a, b) becomes func(a, b). ''' - assert self._state == RUN return self._map_async(func, iterable, starmapstar, chunksize).get() def starmap_async(self, func, iterable, chunksize=None, callback=None, @@ -242,7 +240,6 @@ class Pool(object): ''' Asynchronous version of `starmap()` method. ''' - assert self._state == RUN return self._map_async(func, iterable, starmapstar, chunksize, callback, error_callback) @@ -250,7 +247,8 @@ class Pool(object): ''' Equivalent of `map()` -- can be MUCH slower than `Pool.map()`. ''' - assert self._state == RUN + if self._state != RUN: + raise ValueError("Pool not running") if chunksize == 1: result = IMapIterator(self._cache) self._taskqueue.put((((result._job, i, func, (x,), {}) @@ -268,7 +266,8 @@ class Pool(object): ''' Like `imap()` method but ordering of results is arbitrary. ''' - assert self._state == RUN + if self._state != RUN: + raise ValueError("Pool not running") if chunksize == 1: result = IMapUnorderedIterator(self._cache) self._taskqueue.put((((result._job, i, func, (x,), {}) @@ -287,7 +286,8 @@ class Pool(object): ''' Asynchronous version of `apply()` method. ''' - assert self._state == RUN + if self._state != RUN: + raise ValueError("Pool not running") result = ApplyResult(self._cache, callback, error_callback) self._taskqueue.put(([(result._job, None, func, args, kwds)], None)) return result @@ -297,7 +297,6 @@ class Pool(object): ''' Asynchronous version of `map()` method. ''' - assert self._state == RUN return self._map_async(func, iterable, mapstar, chunksize) def _map_async(self, func, iterable, mapper, chunksize=None, callback=None, @@ -305,6 +304,8 @@ class Pool(object): ''' Helper function to implement map, starmap and their async counterparts. ''' + if self._state != RUN: + raise ValueError("Pool not running") if not hasattr(iterable, '__len__'): iterable = list(iterable) diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py index 8a6aede..7495813 100644 --- a/Lib/multiprocessing/util.py +++ b/Lib/multiprocessing/util.py @@ -235,6 +235,12 @@ def _run_finalizers(minpriority=None): Finalizers with highest priority are called first; finalizers with the same priority will be called in reverse order of creation. ''' + if _finalizer_registry is None: + # This function may be called after this module's globals are + # destroyed. See the _exit_function function in this module for more + # notes. + return + if minpriority is None: f = lambda p : p[0][0] is not None else: @@ -266,7 +272,13 @@ def is_exiting(): _exiting = False -def _exit_function(): +def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers, + active_children=active_children, + current_process=current_process): + # We hold on to references to functions in the arglist due to the + # situation described below, where this function is called after this + # module's globals are destroyed. + global _exiting if not _exiting: @@ -276,14 +288,28 @@ def _exit_function(): debug('running all "atexit" finalizers with priority >= 0') _run_finalizers(0) - for p in active_children(): - if p._daemonic: - info('calling terminate() for daemon %s', p.name) - p._popen.terminate() - - for p in active_children(): - info('calling join() for process %s', p.name) - p.join() + if current_process() is not None: + # We check if the current process is None here because if + # it's None, any call to ``active_children()`` will throw + # an AttributeError (active_children winds up trying to + # get attributes from util._current_process). One + # situation where this can happen is if someone has + # manipulated sys.modules, causing this module to be + # garbage collected. The destructor for the module type + # then replaces all values in the module dict with None. + # For instance, after setuptools runs a test it replaces + # sys.modules with a copy created earlier. See issues + # #9775 and #15881. Also related: #4106, #9205, and + # #9207. + + for p in active_children(): + if p._daemonic: + info('calling terminate() for daemon %s', p.name) + p._popen.terminate() + + for p in active_children(): + info('calling join() for process %s', p.name) + p.join() debug('running the remaining "atexit" finalizers') _run_finalizers() diff --git a/Lib/socket.py b/Lib/socket.py index 1378b0f..d4f1b65 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -324,12 +324,23 @@ class SocketIO(io.RawIOBase): def readable(self): """True if the SocketIO is open for reading. """ - return self._reading and not self.closed + if self.closed: + raise ValueError("I/O operation on closed socket.") + return self._reading def writable(self): """True if the SocketIO is open for writing. """ - return self._writing and not self.closed + if self.closed: + raise ValueError("I/O operation on closed socket.") + return self._writing + + def seekable(self): + """True if the SocketIO is open for seeking. + """ + if self.closed: + raise ValueError("I/O operation on closed socket.") + return super().seekable() def fileno(self): """Return the file descriptor of the underlying socket. diff --git a/Lib/test/sample_doctest_no_docstrings.py b/Lib/test/sample_doctest_no_docstrings.py new file mode 100644 index 0000000..e4201ed --- /dev/null +++ b/Lib/test/sample_doctest_no_docstrings.py @@ -0,0 +1,12 @@ +# This is a sample module used for testing doctest. +# +# This module is for testing how doctest handles a module with no +# docstrings. + + +class Foo(object): + + # A class with no docstring. + + def __init__(self): + pass diff --git a/Lib/test/sample_doctest_no_doctests.py b/Lib/test/sample_doctest_no_doctests.py new file mode 100644 index 0000000..7daa572 --- /dev/null +++ b/Lib/test/sample_doctest_no_doctests.py @@ -0,0 +1,15 @@ +"""This is a sample module used for testing doctest. + +This module is for testing how doctest handles a module with docstrings +but no doctest examples. + +""" + + +class Foo(object): + """A docstring with no doctest examples. + + """ + + def __init__(self): + pass diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py index 8da3e77..e4688d0 100644 --- a/Lib/test/string_tests.py +++ b/Lib/test/string_tests.py @@ -1206,6 +1206,9 @@ class MixinStrUnicodeUserStringTest: self.checkraises(ValueError, '%%%df' % (2**64), '__mod__', (3.2)) self.checkraises(ValueError, '%%.%df' % (2**64), '__mod__', (3.2)) + class X(object): pass + self.checkraises(TypeError, 'abc', '__mod__', X()) + def test_floatformatting(self): # float formatting for prec in range(100): diff --git a/Lib/test/support.py b/Lib/test/support.py index 35ae76f..014bcf5 100644 --- a/Lib/test/support.py +++ b/Lib/test/support.py @@ -680,7 +680,7 @@ def temp_cwd(name='tempcwd', quiet=False, path=None): except OSError: if not quiet: raise - warnings.warn('tests may fail, unable to change the CWD to ' + name, + warnings.warn('tests may fail, unable to change the CWD to ' + path, RuntimeWarning, stacklevel=3) try: yield os.getcwd() diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 2e6584f..c06c940 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1463,6 +1463,22 @@ class TestFileTypeR(TempDirMixin, ParserTestCase): ('readonly', NS(x=None, spam=RFile('readonly'))), ] +class TestFileTypeDefaults(TempDirMixin, ParserTestCase): + """Test that a file is not created unless the default is needed""" + def setUp(self): + super(TestFileTypeDefaults, self).setUp() + file = open(os.path.join(self.temp_dir, 'good'), 'w') + file.write('good') + file.close() + + argument_signatures = [ + Sig('-c', type=argparse.FileType('r'), default='no-file.txt'), + ] + # should provoke no such file error + failures = [''] + # should not provoke error because default file is created + successes = [('-c good', NS(c=RFile('good')))] + class TestFileTypeRB(TempDirMixin, ParserTestCase): """Test the FileType option/argument type for reading files""" @@ -4559,6 +4575,71 @@ class TestMessageContentError(TestCase): self.assertNotIn(msg, 'optional_positional') +# ================================================ +# Check that the type function is called only once +# ================================================ + +class TestTypeFunctionCallOnlyOnce(TestCase): + + def test_type_function_call_only_once(self): + def spam(string_to_convert): + self.assertEqual(string_to_convert, 'spam!') + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default='bar') + args = parser.parse_args('--foo spam!'.split()) + self.assertEqual(NS(foo='foo_converted'), args) + +# ================================================================== +# Check semantics regarding the default argument and type conversion +# ================================================================== + +class TestTypeFunctionCalledOnDefault(TestCase): + + def test_type_function_call_with_non_string_default(self): + def spam(int_to_convert): + self.assertEqual(int_to_convert, 0) + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default=0) + args = parser.parse_args([]) + # foo should *not* be converted because its default is not a string. + self.assertEqual(NS(foo=0), args) + + def test_type_function_call_with_string_default(self): + def spam(int_to_convert): + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default='0') + args = parser.parse_args([]) + # foo is converted because its default is a string. + self.assertEqual(NS(foo='foo_converted'), args) + + def test_no_double_type_conversion_of_default(self): + def extend(str_to_convert): + return str_to_convert + '*' + + parser = argparse.ArgumentParser() + parser.add_argument('--test', type=extend, default='*') + args = parser.parse_args([]) + # The test argument will be two stars, one coming from the default + # value and one coming from the type conversion being called exactly + # once. + self.assertEqual(NS(test='**'), args) + + def test_issue_15906(self): + # Issue #15906: When action='append', type=str, default=[] are + # providing, the dest value was the string representation "[]" when it + # should have been an empty list. + parser = argparse.ArgumentParser() + parser.add_argument('--test', dest='test', type=str, + default=[], action='append') + args = parser.parse_args([]) + self.assertEqual(args.test, []) + # ====================== # parse_known_args tests # ====================== diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index aa08166..a8853c7 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -407,6 +407,14 @@ class ASTHelpers_Test(unittest.TestCase): b = compile('foo(1 + 1)', '<unknown>', 'exec', ast.PyCF_ONLY_AST) self.assertEqual(ast.dump(a), ast.dump(b)) + def test_parse_in_error(self): + try: + 1/0 + except Exception: + with self.assertRaises(SyntaxError) as e: + ast.literal_eval(r"'\U'") + self.assertIsNotNone(e.exception.__context__) + def test_dump(self): node = ast.parse('spam(eggs, "and cheese")') self.assertEqual(ast.dump(node), diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py index e0f6399..e594e01 100644 --- a/Lib/test/test_calendar.py +++ b/Lib/test/test_calendar.py @@ -6,6 +6,7 @@ from test.script_helper import assert_python_ok import time import locale import sys +import datetime result_2004_01_text = """ January 2004 @@ -464,6 +465,11 @@ class CalendarTestCase(unittest.TestCase): new_october = calendar.TextCalendar().formatmonthname(2010, 10, 10) self.assertEqual(old_october, new_october) + def test_itermonthdates(self): + # ensure itermonthdates doesn't overflow after datetime.MAXYEAR + # see #15421 + list(calendar.Calendar().itermonthdates(datetime.MAXYEAR, 12)) + class MonthCalendarTestCase(unittest.TestCase): def setUp(self): diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index 59179c4..4e808ec 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -1693,6 +1693,15 @@ class CharmapTest(unittest.TestCase): ) self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "strict", "\U0010FFFFbc"), + ("\U0010FFFFbc", 3) + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, b"\x00\x01\x02", "strict", "ab" + ) + + self.assertEqual( codecs.charmap_decode(b"\x00\x01\x02", "replace", "ab"), ("ab\ufffd", 3) ) @@ -1718,6 +1727,113 @@ class CharmapTest(unittest.TestCase): ("", len(allbytes)) ) + def test_decode_with_int2str_map(self): + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "strict", + {0: 'a', 1: 'b', 2: 'c'}), + ("abc", 3) + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "strict", + {0: 'Aa', 1: 'Bb', 2: 'Cc'}), + ("AaBbCc", 3) + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "strict", + {0: '\U0010FFFF', 1: 'b', 2: 'c'}), + ("\U0010FFFFbc", 3) + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "strict", + {0: 'a', 1: 'b', 2: ''}), + ("ab", 3) + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, b"\x00\x01\x02", "strict", + {0: 'a', 1: 'b'} + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "replace", + {0: 'a', 1: 'b'}), + ("ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "replace", + {0: 'a', 1: 'b', 2: None}), + ("ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "ignore", + {0: 'a', 1: 'b'}), + ("ab", 3) + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "ignore", + {0: 'a', 1: 'b', 2: None}), + ("ab", 3) + ) + + allbytes = bytes(range(256)) + self.assertEqual( + codecs.charmap_decode(allbytes, "ignore", {}), + ("", len(allbytes)) + ) + + def test_decode_with_int2int_map(self): + a = ord('a') + b = ord('b') + c = ord('c') + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "strict", + {0: a, 1: b, 2: c}), + ("abc", 3) + ) + + # Issue #15379 + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "strict", + {0: 0x10FFFF, 1: b, 2: c}), + ("\U0010FFFFbc", 3) + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "strict", + {0: sys.maxunicode, 1: b, 2: c}), + (chr(sys.maxunicode) + "bc", 3) + ) + + self.assertRaises(TypeError, + codecs.charmap_decode, b"\x00\x01\x02", "strict", + {0: sys.maxunicode + 1, 1: b, 2: c} + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, b"\x00\x01\x02", "strict", + {0: a, 1: b}, + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "replace", + {0: a, 1: b}), + ("ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode(b"\x00\x01\x02", "ignore", + {0: a, 1: b}), + ("ab", 3) + ) + + class WithStmtTest(unittest.TestCase): def test_encodedfile(self): f = io.BytesIO(b"\xc3\xbc") diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py index 6ec105c..ba9fe46 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -134,15 +134,21 @@ class EncodingTest(unittest.TestCase): class CommandLineTests(unittest.TestCase): """Test compileall's CLI.""" + def _get_run_args(self, args): + interp_args = ['-S'] + if sys.flags.optimize: + interp_args.append({1 : '-O', 2 : '-OO'}[sys.flags.optimize]) + return interp_args + ['-m', 'compileall'] + list(args) + def assertRunOK(self, *args, **env_vars): rc, out, err = script_helper.assert_python_ok( - '-S', '-m', 'compileall', *args, **env_vars) + *self._get_run_args(args), **env_vars) self.assertEqual(b'', err) return out def assertRunNotOK(self, *args, **env_vars): rc, out, err = script_helper.assert_python_failure( - '-S', '-m', 'compileall', *args, **env_vars) + *self._get_run_args(args), **env_vars) return rc, out, err def assertCompiled(self, fn): @@ -198,7 +204,9 @@ class CommandLineTests(unittest.TestCase): self.assertRunOK('-b', '-q', self.pkgdir) # Verify the __pycache__ directory contents. self.assertFalse(os.path.exists(self.pkgdir_cachedir)) - expected = sorted(['__init__.py', '__init__.pyc', 'bar.py', 'bar.pyc']) + opt = 'c' if __debug__ else 'o' + expected = sorted(['__init__.py', '__init__.py' + opt, 'bar.py', + 'bar.py' + opt]) self.assertEqual(sorted(os.listdir(self.pkgdir)), expected) def test_multiple_runs(self): @@ -326,7 +334,7 @@ class CommandLineTests(unittest.TestCase): f2 = script_helper.make_script(self.pkgdir, 'f2', '') f3 = script_helper.make_script(self.pkgdir, 'f3', '') f4 = script_helper.make_script(self.pkgdir, 'f4', '') - p = script_helper.spawn_python('-m', 'compileall', '-i', '-') + p = script_helper.spawn_python(*(self._get_run_args(()) + ['-i', '-'])) p.stdin.write((f3+os.linesep).encode('ascii')) script_helper.kill_python(p) self.assertNotCompiled(f1) diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 8ca1e62..55796a2 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -225,6 +225,15 @@ class Test_Csv(unittest.TestCase): self.assertRaises(csv.Error, self._read_test, ['a,b\nc,d'], []) self.assertRaises(csv.Error, self._read_test, ['a,b\r\nc,d'], []) + def test_read_eof(self): + self._read_test(['a,"'], [['a', '']]) + self._read_test(['"a'], [['a']]) + self._read_test(['^'], [['\n']], escapechar='^') + self.assertRaises(csv.Error, self._read_test, ['a,"'], [], strict=True) + self.assertRaises(csv.Error, self._read_test, ['"a'], [], strict=True) + self.assertRaises(csv.Error, self._read_test, + ['^'], [], escapechar='^', strict=True) + def test_read_escape(self): self._read_test(['a,\\b,c'], [['a', 'b', 'c']], escapechar='\\') self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\') diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 3ca5927..ec5db9f 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -34,7 +34,8 @@ import numbers import locale from test.support import (run_unittest, run_doctest, is_resource_enabled, requires_IEEE_754) -from test.support import check_warnings, import_fresh_module, TestFailed +from test.support import (check_warnings, import_fresh_module, TestFailed, + run_with_locale) import random import time import warnings @@ -1136,18 +1137,19 @@ class FormatTest(unittest.TestCase): self.assertEqual(get_fmt(Decimal('-1.5'), dotsep_wide, '020n'), '-0\u00b4000\u00b4000\u00b4000\u00b4001\u00bf5') + @run_with_locale('LC_ALL', 'ps_AF') def test_wide_char_separator_decimal_point(self): # locale with wide char separator and decimal point + import locale Decimal = self.decimal.Decimal - try: - locale.setlocale(locale.LC_ALL, 'ps_AF') - except locale.Error: + decimal_point = locale.localeconv()['decimal_point'] + thousands_sep = locale.localeconv()['thousands_sep'] + if decimal_point != '\u066b' or thousands_sep != '\u066c': return self.assertEqual(format(Decimal('100000000.123'), 'n'), '100\u066c000\u066c000\u066b123') - locale.resetlocale() class CFormatTest(FormatTest): decimal = C diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 44b9554..8f8c7c7 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -1986,6 +1986,31 @@ def test_DocTestSuite(): >>> suite.run(unittest.TestResult()) <unittest.result.TestResult run=9 errors=0 failures=4> + The module need not contain any doctest examples: + + >>> suite = doctest.DocTestSuite('test.sample_doctest_no_doctests') + >>> suite.run(unittest.TestResult()) + <unittest.result.TestResult run=0 errors=0 failures=0> + + However, if DocTestSuite finds no docstrings, it raises an error: + + >>> try: + ... doctest.DocTestSuite('test.sample_doctest_no_docstrings') + ... except ValueError as e: + ... error = e + + >>> print(error.args[1]) + has no docstrings + + You can prevent this error by passing a DocTestFinder instance with + the `exclude_empty` keyword argument set to False: + + >>> finder = doctest.DocTestFinder(exclude_empty=False) + >>> suite = doctest.DocTestSuite('test.sample_doctest_no_docstrings', + ... test_finder=finder) + >>> suite.run(unittest.TestResult()) + <unittest.result.TestResult run=0 errors=0 failures=0> + We can use the current module: >>> suite = test.sample_doctest.test_suite() diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py index 27fccd6..a872e68 100644 --- a/Lib/test/test_gdb.py +++ b/Lib/test/test_gdb.py @@ -154,6 +154,11 @@ class DebuggerTests(unittest.TestCase): err = err.replace("warning: Cannot initialize thread debugging" " library: Debugger service failed\n", '') + err = err.replace('warning: Could not load shared library symbols for ' + 'linux-vdso.so.1.\n' + 'Do you need "set solib-search-path" or ' + '"set sysroot"?\n', + '') # Ensure no unexpected error messages: self.assertEqual(err, '') diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py index 19863b9..0746411 100644 --- a/Lib/test/test_import.py +++ b/Lib/test/test_import.py @@ -591,7 +591,7 @@ class PycacheTests(unittest.TestCase): self.assertTrue(os.path.exists('__pycache__')) self.assertTrue(os.path.exists(os.path.join( '__pycache__', '{}.{}.py{}'.format( - TESTFN, self.tag, __debug__ and 'c' or 'o')))) + TESTFN, self.tag, 'c' if __debug__ else 'o')))) @unittest.skipUnless(os.name == 'posix', "test meaningful only on posix systems") diff --git a/Lib/test/test_importlib/test_locks.py b/Lib/test/test_importlib/test_locks.py index d36b71e..c373b11 100644 --- a/Lib/test/test_importlib/test_locks.py +++ b/Lib/test/test_importlib/test_locks.py @@ -1,4 +1,5 @@ from importlib import _bootstrap +import sys import time import unittest import weakref @@ -41,6 +42,17 @@ else: @unittest.skipUnless(threading, "threads needed for this test") class DeadlockAvoidanceTests(unittest.TestCase): + def setUp(self): + try: + self.old_switchinterval = sys.getswitchinterval() + sys.setswitchinterval(0.000001) + except AttributeError: + self.old_switchinterval = None + + def tearDown(self): + if self.old_switchinterval is not None: + sys.setswitchinterval(self.old_switchinterval) + def run_deadlock_avoidance_test(self, create_deadlock): NLOCKS = 10 locks = [LockType(str(i)) for i in range(NLOCKS)] @@ -75,10 +87,12 @@ class DeadlockAvoidanceTests(unittest.TestCase): def test_deadlock(self): results = self.run_deadlock_avoidance_test(True) - # One of the threads detected a potential deadlock on its second - # acquire() call. - self.assertEqual(results.count((True, False)), 1) - self.assertEqual(results.count((True, True)), len(results) - 1) + # At least one of the threads detected a potential deadlock on its + # second acquire() call. It may be several of them, because the + # deadlock avoidance mechanism is conservative. + nb_deadlocks = results.count((True, False)) + self.assertGreaterEqual(nb_deadlocks, 1) + self.assertEqual(results.count((True, True)), len(results) - nb_deadlocks) def test_no_deadlock(self): results = self.run_deadlock_avoidance_test(False) diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py index 437e323..227759f 100644 --- a/Lib/test/test_int.py +++ b/Lib/test/test_int.py @@ -305,6 +305,18 @@ class IntTestCases(unittest.TestCase): self.fail("Failed to raise TypeError with %s" % ((base, trunc_result_base),)) + # Regression test for bugs.python.org/issue16060. + class BadInt(trunc_result_base): + def __int__(self): + return 42.0 + + class TruncReturnsBadInt(base): + def __trunc__(self): + return BadInt() + + with self.assertRaises(TypeError): + int(TruncReturnsBadInt()) + def test_error_message(self): testlist = ('\xbd', '123\xbd', ' 123 456 ') for s in testlist: diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 5735350..d5eec7c 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2887,6 +2887,11 @@ class MiscIOTest(unittest.TestCase): with self.open(support.TESTFN, 'rb') as f: self.assertEqual(b"spam", f.read()) + def test_open_allargs(self): + # there used to be a buffer overflow in the parser for rawmode + self.assertRaises(ValueError, self.open, support.TESTFN, 'rwax+') + + class CMiscIOTest(MiscIOTest): io = io diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index 5515357..6b1e933 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -53,7 +53,7 @@ class TestMailbox(TestBase): maxDiff = None _factory = None # Overridden by subclasses to reuse tests - _template = 'From: foo\n\n%s' + _template = 'From: foo\n\n%s\n' def setUp(self): self._path = support.TESTFN @@ -232,7 +232,7 @@ class TestMailbox(TestBase): key0 = self._box.add(self._template % 0) msg = self._box.get(key0) self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.get_payload(), '0') + self.assertEqual(msg.get_payload(), '0\n') self.assertIs(self._box.get('foo'), None) self.assertIs(self._box.get('foo', False), False) self._box.close() @@ -240,14 +240,14 @@ class TestMailbox(TestBase): key1 = self._box.add(self._template % 1) msg = self._box.get(key1) self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.get_payload(), '1') + self.assertEqual(msg.get_payload(), '1\n') def test_getitem(self): # Retrieve message using __getitem__() key0 = self._box.add(self._template % 0) msg = self._box[key0] self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.get_payload(), '0') + self.assertEqual(msg.get_payload(), '0\n') self.assertRaises(KeyError, lambda: self._box['foo']) self._box.discard(key0) self.assertRaises(KeyError, lambda: self._box[key0]) @@ -259,7 +259,7 @@ class TestMailbox(TestBase): msg0 = self._box.get_message(key0) self.assertIsInstance(msg0, mailbox.Message) self.assertEqual(msg0['from'], 'foo') - self.assertEqual(msg0.get_payload(), '0') + self.assertEqual(msg0.get_payload(), '0\n') self._check_sample(self._box.get_message(key1)) def test_get_bytes(self): @@ -432,15 +432,15 @@ class TestMailbox(TestBase): self.assertIn(key0, self._box) key1 = self._box.add(self._template % 1) self.assertIn(key1, self._box) - self.assertEqual(self._box.pop(key0).get_payload(), '0') + self.assertEqual(self._box.pop(key0).get_payload(), '0\n') self.assertNotIn(key0, self._box) self.assertIn(key1, self._box) key2 = self._box.add(self._template % 2) self.assertIn(key2, self._box) - self.assertEqual(self._box.pop(key2).get_payload(), '2') + self.assertEqual(self._box.pop(key2).get_payload(), '2\n') self.assertNotIn(key2, self._box) self.assertIn(key1, self._box) - self.assertEqual(self._box.pop(key1).get_payload(), '1') + self.assertEqual(self._box.pop(key1).get_payload(), '1\n') self.assertNotIn(key1, self._box) self.assertEqual(len(self._box), 0) @@ -635,7 +635,7 @@ class TestMaildir(TestMailbox, unittest.TestCase): msg_returned = self._box.get_message(key) self.assertEqual(msg_returned.get_subdir(), 'new') self.assertEqual(msg_returned.get_flags(), '') - self.assertEqual(msg_returned.get_payload(), '1') + self.assertEqual(msg_returned.get_payload(), '1\n') msg2 = mailbox.MaildirMessage(self._template % 2) msg2.set_info('2,S') self._box[key] = msg2 @@ -643,7 +643,7 @@ class TestMaildir(TestMailbox, unittest.TestCase): msg_returned = self._box.get_message(key) self.assertEqual(msg_returned.get_subdir(), 'new') self.assertEqual(msg_returned.get_flags(), 'S') - self.assertEqual(msg_returned.get_payload(), '3') + self.assertEqual(msg_returned.get_payload(), '3\n') def test_consistent_factory(self): # Add a message. @@ -763,13 +763,13 @@ class TestMaildir(TestMailbox, unittest.TestCase): self.assertIsNot(match, None, "Invalid file name: '%s'" % tail) groups = match.groups() if previous_groups is not None: - self.assertTrue(int(groups[0] >= previous_groups[0]), + self.assertGreaterEqual(int(groups[0]), int(previous_groups[0]), "Non-monotonic seconds: '%s' before '%s'" % (previous_groups[0], groups[0])) - self.assertTrue(int(groups[1] >= previous_groups[1]) or - groups[0] != groups[1], - "Non-monotonic milliseconds: '%s' before '%s'" % - (previous_groups[1], groups[1])) + if int(groups[0]) == int(previous_groups[0]): + self.assertGreaterEqual(int(groups[1]), int(previous_groups[1]), + "Non-monotonic milliseconds: '%s' before '%s'" % + (previous_groups[1], groups[1])) self.assertEqual(int(groups[2]), pid, "Process ID mismatch: '%s' should be '%s'" % (groups[2], pid)) @@ -996,20 +996,20 @@ class _TestMboxMMDF(_TestSingleFile): def test_add_from_string(self): # Add a string starting with 'From ' to the mailbox - key = self._box.add('From foo@bar blah\nFrom: foo\n\n0') + key = self._box.add('From foo@bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo@bar blah') - self.assertEqual(self._box[key].get_payload(), '0') + self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_from_bytes(self): # Add a byte string starting with 'From ' to the mailbox - key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0') + key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo@bar blah') - self.assertEqual(self._box[key].get_payload(), '0') + self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_mbox_or_mmdf_message(self): # Add an mboxMessage or MMDFMessage for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): - msg = class_('From foo@bar blah\nFrom: foo\n\n0') + msg = class_('From foo@bar blah\nFrom: foo\n\n0\n') key = self._box.add(msg) def test_open_close_open(self): @@ -1116,6 +1116,29 @@ class TestMbox(_TestMboxMMDF, unittest.TestCase): perms = st.st_mode self.assertFalse((perms & 0o111)) # Execute bits should all be off. + def test_terminating_newline(self): + message = email.message.Message() + message['From'] = 'john@example.com' + message.set_payload('No newline at the end') + i = self._box.add(message) + + # A newline should have been appended to the payload + message = self._box.get(i) + self.assertEqual(message.get_payload(), 'No newline at the end\n') + + def test_message_separator(self): + # Check there's always a single blank line after each message + self._box.add('From: foo\n\n0') # No newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + self._box.add('From: foo\n\n0\n') # Newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + class TestMMDF(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py index 04ec8e7..e5db945 100644 --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -318,9 +318,9 @@ class MemoryTestMixin: self.assertEqual(memio.isatty(), False) self.assertEqual(memio.closed, False) memio.close() - self.assertEqual(memio.writable(), True) - self.assertEqual(memio.readable(), True) - self.assertEqual(memio.seekable(), True) + self.assertRaises(ValueError, memio.writable) + self.assertRaises(ValueError, memio.readable) + self.assertRaises(ValueError, memio.seekable) self.assertRaises(ValueError, memio.isatty) self.assertEqual(memio.closed, True) @@ -665,7 +665,6 @@ class CBytesIOTest(PyBytesIOTest): check(io.BytesIO(b'a'), basesize + 1 + 1 ) check(io.BytesIO(b'a' * 1000), basesize + 1000 + 1 ) - class CStringIOTest(PyStringIOTest): ioclass = io.StringIO UnsupportedOperation = io.UnsupportedOperation diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 2230028..a3de398 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -488,6 +488,15 @@ class MmapTests(unittest.TestCase): f.flush () return mmap.mmap (f.fileno(), 0) + def test_empty_file (self): + f = open (TESTFN, 'w+b') + f.close() + with open(TESTFN, "rb") as f : + self.assertRaisesRegex(ValueError, + "cannot mmap an empty file", + mmap.mmap, f.fileno(), 0, + access=mmap.ACCESS_READ) + def test_offset (self): f = open (TESTFN, 'w+b') diff --git a/Lib/test/test_multiprocessing.py b/Lib/test/test_multiprocessing.py index b70783a..e313dd6 100644 --- a/Lib/test/test_multiprocessing.py +++ b/Lib/test/test_multiprocessing.py @@ -1727,7 +1727,8 @@ class _TestPool(BaseTestCase): with multiprocessing.Pool(2) as p: r = p.map_async(sqr, L) self.assertEqual(r.get(), expected) - self.assertRaises(AssertionError, p.map_async, sqr, L) + print(p._state) + self.assertRaises(ValueError, p.map_async, sqr, L) def raising(): raise KeyError("key") diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 6219eff..7d6b377 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -514,23 +514,23 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol): return os.environ # Bug 1110478 + @unittest.skipUnless(os.path.exists('/bin/sh'), 'requires /bin/sh') def test_update2(self): os.environ.clear() - if os.path.exists("/bin/sh"): - os.environ.update(HELLO="World") - with os.popen("/bin/sh -c 'echo $HELLO'") as popen: - value = popen.read().strip() - self.assertEqual(value, "World") + os.environ.update(HELLO="World") + with os.popen("/bin/sh -c 'echo $HELLO'") as popen: + value = popen.read().strip() + self.assertEqual(value, "World") + @unittest.skipUnless(os.path.exists('/bin/sh'), 'requires /bin/sh') def test_os_popen_iter(self): - if os.path.exists("/bin/sh"): - with os.popen( - "/bin/sh -c 'echo \"line1\nline2\nline3\"'") as popen: - it = iter(popen) - self.assertEqual(next(it), "line1\n") - self.assertEqual(next(it), "line2\n") - self.assertEqual(next(it), "line3\n") - self.assertRaises(StopIteration, next, it) + with os.popen( + "/bin/sh -c 'echo \"line1\nline2\nline3\"'") as popen: + it = iter(popen) + self.assertEqual(next(it), "line1\n") + self.assertEqual(next(it), "line2\n") + self.assertEqual(next(it), "line3\n") + self.assertRaises(StopIteration, next, it) # Verify environ keys and values from the OS are of the # correct str type. diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 27eecb8..117bda0 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -641,6 +641,16 @@ class ForeignDTDTests(unittest.TestCase): parser.Parse("<?xml version='1.0'?><element/>") self.assertEqual(handler_call_args, [(None, None)]) + # test UseForeignDTD() is equal to UseForeignDTD(True) + handler_call_args[:] = [] + + parser = expat.ParserCreate() + parser.UseForeignDTD() + parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS) + parser.ExternalEntityRefHandler = resolve_entity + parser.Parse("<?xml version='1.0'?><element/>") + self.assertEqual(handler_call_args, [(None, None)]) + def test_ignore_use_foreign_dtd(self): """ If UseForeignDTD is passed True and a document with an external diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 7716d33..d7c9a31 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1245,6 +1245,17 @@ class GeneralModuleTests(unittest.TestCase): fp.close() self.assertEqual(repr(fp), "<_io.BufferedReader name=-1>") + def test_unusable_closed_socketio(self): + with socket.socket() as sock: + fp = sock.makefile("rb", buffering=0) + self.assertTrue(fp.readable()) + self.assertFalse(fp.writable()) + self.assertFalse(fp.seekable()) + fp.close() + self.assertRaises(ValueError, fp.readable) + self.assertRaises(ValueError, fp.writable) + self.assertRaises(ValueError, fp.seekable) + def test_pickle(self): sock = socket.socket() with sock: diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 51762cf..4ce98b6 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -125,12 +125,8 @@ class BasicSocketTests(unittest.TestCase): else: self.assertRaises(ssl.SSLError, ssl.RAND_bytes, 16) - try: - ssl.RAND_egd(1) - except TypeError: - pass - else: - print("didn't raise TypeError") + self.assertRaises(TypeError, ssl.RAND_egd, 1) + self.assertRaises(TypeError, ssl.RAND_egd, 'foo', 1) ssl.RAND_add("this is a random string", 75.0) def test_parse_cert(self): diff --git a/Lib/test/test_startfile.py b/Lib/test/test_startfile.py index dd505bf..ae9aeb9 100644 --- a/Lib/test/test_startfile.py +++ b/Lib/test/test_startfile.py @@ -10,8 +10,8 @@ import unittest from test import support import os +import sys from os import path -from time import sleep startfile = support.get_attribute(os, 'startfile') @@ -21,13 +21,12 @@ class TestCase(unittest.TestCase): self.assertRaises(OSError, startfile, "nonexisting.vbs") def test_empty(self): - empty = path.join(path.dirname(__file__), "empty.vbs") - startfile(empty) - startfile(empty, "open") - # Give the child process some time to exit before we finish. - # Otherwise the cleanup code will not be able to delete the cwd, - # because it is still in use. - sleep(0.1) + # Switch to an existing, but safe, working directory to let the + # cleanup code do its thing without permission errors. + with support.temp_cwd(path=path.dirname(sys.executable)): + empty = path.join(path.dirname(__file__), "empty.vbs") + startfile(empty) + startfile(empty, "open") def test_main(): support.run_unittest(TestCase) diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 32eb1c0..f6469cf 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -115,6 +115,21 @@ class TestSuper(unittest.TestCase): return __class__ self.assertIs(X.f(), X) + def test_obscure_super_errors(self): + def f(): + super() + self.assertRaises(RuntimeError, f) + def f(x): + del x + super() + self.assertRaises(RuntimeError, f, None) + class X: + def f(x): + nonlocal __class__ + del __class__ + super() + self.assertRaises(RuntimeError, X().f) + def test_main(): support.run_unittest(TestSuper) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 14fcdbf..f6ef5f6 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -95,6 +95,15 @@ class TestSupport(unittest.TestCase): self.assertFalse(os.path.exists(TESTFN)) self.assertTrue(os.path.basename(os.getcwd()), here) + def test_temp_cwd__chdir_warning(self): + """Check the warning message when os.chdir() fails.""" + path = TESTFN + '_does_not_exist' + with support.check_warnings() as recorder: + with support.temp_cwd(path=path, quiet=True): + pass + messages = [str(w.message) for w in recorder.warnings] + self.assertEqual(messages, ['tests may fail, unable to change the CWD to ' + path]) + def test_sortdict(self): self.assertEqual(support.sortdict({3:3, 2:2, 1:1}), "{1: 1, 2: 2, 3: 3}") diff --git a/Lib/test/test_textwrap.py b/Lib/test/test_textwrap.py index bb4a851..c86f5cf 100644 --- a/Lib/test/test_textwrap.py +++ b/Lib/test/test_textwrap.py @@ -22,7 +22,7 @@ class BaseTestCase(unittest.TestCase): result = [] for i in range(len(textin)): result.append(" %d: %r" % (i, textin[i])) - result = '\n'.join(result) + result = "\n".join(result) if result else " no lines" elif isinstance(textin, str): result = " %s\n" % repr(textin) return result @@ -66,6 +66,15 @@ class WrapTestCase(BaseTestCase): "I'm glad to hear it!"]) self.check_wrap(text, 80, [text]) + def test_empty_string(self): + # Check that wrapping the empty string returns an empty list. + self.check_wrap("", 6, []) + self.check_wrap("", 6, [], drop_whitespace=False) + + def test_empty_string_with_initial_indent(self): + # Check that the empty string is not indented. + self.check_wrap("", 6, [], initial_indent="++") + self.check_wrap("", 6, [], initial_indent="++", drop_whitespace=False) def test_whitespace(self): # Whitespace munging and end-of-sentence detection @@ -331,7 +340,32 @@ What a mess! ["blah", " ", "(ding", " ", "dong),", " ", "wubba"]) - def test_initial_whitespace(self): + def test_drop_whitespace_false(self): + # Check that drop_whitespace=False preserves whitespace. + # SF patch #1581073 + text = " This is a sentence with much whitespace." + self.check_wrap(text, 10, + [" This is a", " ", "sentence ", + "with ", "much white", "space."], + drop_whitespace=False) + + def test_drop_whitespace_false_whitespace_only(self): + # Check that drop_whitespace=False preserves a whitespace-only string. + self.check_wrap(" ", 6, [" "], drop_whitespace=False) + + def test_drop_whitespace_false_whitespace_only_with_indent(self): + # Check that a whitespace-only string gets indented (when + # drop_whitespace is False). + self.check_wrap(" ", 6, [" "], drop_whitespace=False, + initial_indent=" ") + + def test_drop_whitespace_whitespace_only(self): + # Check drop_whitespace on a whitespace-only string. + self.check_wrap(" ", 6, []) + + def test_drop_whitespace_leading_whitespace(self): + # Check that drop_whitespace does not drop leading whitespace (if + # followed by non-whitespace). # SF bug #622849 reported inconsistent handling of leading # whitespace; let's test that a bit, shall we? text = " This is a sentence with leading whitespace." @@ -340,13 +374,27 @@ What a mess! self.check_wrap(text, 30, [" This is a sentence with", "leading whitespace."]) - def test_no_drop_whitespace(self): - # SF patch #1581073 - text = " This is a sentence with much whitespace." - self.check_wrap(text, 10, - [" This is a", " ", "sentence ", - "with ", "much white", "space."], + def test_drop_whitespace_whitespace_line(self): + # Check that drop_whitespace skips the whole line if a non-leading + # line consists only of whitespace. + text = "abcd efgh" + # Include the result for drop_whitespace=False for comparison. + self.check_wrap(text, 6, ["abcd", " ", "efgh"], drop_whitespace=False) + self.check_wrap(text, 6, ["abcd", "efgh"]) + + def test_drop_whitespace_whitespace_only_with_indent(self): + # Check that initial_indent is not applied to a whitespace-only + # string. This checks a special case of the fact that dropping + # whitespace occurs before indenting. + self.check_wrap(" ", 6, [], initial_indent="++") + + def test_drop_whitespace_whitespace_indent(self): + # Check that drop_whitespace does not drop whitespace indents. + # This checks a special case of the fact that dropping whitespace + # occurs before indenting. + self.check_wrap("abcd efgh", 6, [" abcd", " efgh"], + initial_indent=" ", subsequent_indent=" ") def test_split(self): # Ensure that the standard _split() method works as advertised diff --git a/Lib/test/test_threaded_import.py b/Lib/test/test_threaded_import.py index cfc6842..4a5d7be 100644 --- a/Lib/test/test_threaded_import.py +++ b/Lib/test/test_threaded_import.py @@ -225,9 +225,11 @@ class ThreadedImportTests(unittest.TestCase): @reap_threads def test_main(): old_switchinterval = None + # Issue #15599: FreeBSD/KVM cannot handle gil_interval == 1. + new_switchinterval = 0.00001 if 'freebsd' in sys.platform else 0.00000001 try: old_switchinterval = sys.getswitchinterval() - sys.setswitchinterval(0.00000001) + sys.setswitchinterval(new_switchinterval) except AttributeError: pass try: diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 31ebd9c..3f5ac98 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -791,7 +791,7 @@ class ClassCreationTests(unittest.TestCase): self.assertEqual(C.y, 1) self.assertEqual(C.z, 2) - def test_new_class_exec_body(self): + def test_new_class_metaclass_keywords(self): #Test that keywords are passed to the metaclass: def meta_func(name, bases, ns, **kw): return name, bases, ns, kw diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py new file mode 100644 index 0000000..34b9364 --- /dev/null +++ b/Lib/test/test_webbrowser.py @@ -0,0 +1,192 @@ +import webbrowser +import unittest +import subprocess +from unittest import mock +from test import support + + +URL = 'http://www.example.com' +CMD_NAME = 'test' + + +class PopenMock(mock.MagicMock): + + def poll(self): + return 0 + + def wait(self, seconds=None): + return 0 + + +class CommandTestMixin: + + def _test(self, meth, *, args=[URL], kw={}, options, arguments): + """Given a web browser instance method name along with arguments and + keywords for same (which defaults to the single argument URL), creates + a browser instance from the class pointed to by self.browser, calls the + indicated instance method with the indicated arguments, and compares + the resulting options and arguments passed to Popen by the browser + instance against the 'options' and 'args' lists. Options are compared + in a position independent fashion, and the arguments are compared in + sequence order to whatever is left over after removing the options. + + """ + popen = PopenMock() + support.patch(self, subprocess, 'Popen', popen) + browser = self.browser_class(name=CMD_NAME) + getattr(browser, meth)(*args, **kw) + popen_args = subprocess.Popen.call_args[0][0] + self.assertEqual(popen_args[0], CMD_NAME) + popen_args.pop(0) + for option in options: + self.assertIn(option, popen_args) + popen_args.pop(popen_args.index(option)) + self.assertEqual(popen_args, arguments) + + +class GenericBrowserCommandTest(CommandTestMixin, unittest.TestCase): + + browser_class = webbrowser.GenericBrowser + + def test_open(self): + self._test('open', + options=[], + arguments=[URL]) + + +class BackgroundBrowserCommandTest(CommandTestMixin, unittest.TestCase): + + browser_class = webbrowser.BackgroundBrowser + + def test_open(self): + self._test('open', + options=[], + arguments=[URL]) + + +class ChromeCommandTest(CommandTestMixin, unittest.TestCase): + + browser_class = webbrowser.Chrome + + def test_open(self): + self._test('open', + options=[], + arguments=[URL]) + + def test_open_with_autoraise_false(self): + self._test('open', kw=dict(autoraise=False), + options=[], + arguments=[URL]) + + def test_open_new(self): + self._test('open_new', + options=['--new-window'], + arguments=[URL]) + + def test_open_new_tab(self): + self._test('open_new_tab', + options=[], + arguments=[URL]) + + +class MozillaCommandTest(CommandTestMixin, unittest.TestCase): + + browser_class = webbrowser.Mozilla + + def test_open(self): + self._test('open', + options=['-raise', '-remote'], + arguments=['openURL({})'.format(URL)]) + + def test_open_with_autoraise_false(self): + self._test('open', kw=dict(autoraise=False), + options=['-noraise', '-remote'], + arguments=['openURL({})'.format(URL)]) + + def test_open_new(self): + self._test('open_new', + options=['-raise', '-remote'], + arguments=['openURL({},new-window)'.format(URL)]) + + def test_open_new_tab(self): + self._test('open_new_tab', + options=['-raise', '-remote'], + arguments=['openURL({},new-tab)'.format(URL)]) + + +class GaleonCommandTest(CommandTestMixin, unittest.TestCase): + + browser_class = webbrowser.Galeon + + def test_open(self): + self._test('open', + options=['-n'], + arguments=[URL]) + + def test_open_with_autoraise_false(self): + self._test('open', kw=dict(autoraise=False), + options=['-noraise', '-n'], + arguments=[URL]) + + def test_open_new(self): + self._test('open_new', + options=['-w'], + arguments=[URL]) + + def test_open_new_tab(self): + self._test('open_new_tab', + options=['-w'], + arguments=[URL]) + + +class OperaCommandTest(CommandTestMixin, unittest.TestCase): + + browser_class = webbrowser.Opera + + def test_open(self): + self._test('open', + options=['-remote'], + arguments=['openURL({})'.format(URL)]) + + def test_open_with_autoraise_false(self): + self._test('open', kw=dict(autoraise=False), + options=['-remote', '-noraise'], + arguments=['openURL({})'.format(URL)]) + + def test_open_new(self): + self._test('open_new', + options=['-remote'], + arguments=['openURL({},new-window)'.format(URL)]) + + def test_open_new(self): + self._test('open_new_tab', + options=['-remote'], + arguments=['openURL({},new-page)'.format(URL)]) + + +class ELinksCommandTest(CommandTestMixin, unittest.TestCase): + + browser_class = webbrowser.Elinks + + def test_open(self): + self._test('open', options=['-remote'], + arguments=['openURL({})'.format(URL)]) + + def test_open_with_autoraise_false(self): + self._test('open', + options=['-remote'], + arguments=['openURL({})'.format(URL)]) + + def test_open_new(self): + self._test('open_new', + options=['-remote'], + arguments=['openURL({},new-window)'.format(URL)]) + + def test_open_new_tab(self): + self._test('open_new_tab', + options=['-remote'], + arguments=['openURL({},new-tab)'.format(URL)]) + + +if __name__=='__main__': + unittest.main() diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index da1ad09..97d64fc 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -1809,6 +1809,18 @@ class ElementTreeTest(unittest.TestCase): mye = MyElement('joe') self.assertEqual(mye.newmethod(), 'joe') + def test_html_empty_elems_serialization(self): + # issue 15970 + # from http://www.w3.org/TR/html401/index/elements.html + for element in ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'FRAME', 'HR', + 'IMG', 'INPUT', 'ISINDEX', 'LINK', 'META', 'PARAM']: + for elem in [element, element.lower()]: + expected = '<%s>' % elem + serialized = serialize(ET.XML('<%s />' % elem), method='html') + self.assertEqual(serialized, expected) + serialized = serialize(ET.XML('<%s></%s>' % (elem,elem)), + method='html') + self.assertEqual(serialized, expected) class ElementIterTest(unittest.TestCase): def _ilist(self, elem, tag=None): diff --git a/Lib/test/test_zipimport_support.py b/Lib/test/test_zipimport_support.py index 0c93a8c..f7f3398 100644 --- a/Lib/test/test_zipimport_support.py +++ b/Lib/test/test_zipimport_support.py @@ -29,7 +29,8 @@ verbose = test.support.verbose # test_cmd_line_script (covers the zipimport support in runpy) # Retrieve some helpers from other test cases -from test import test_doctest, sample_doctest +from test import (test_doctest, sample_doctest, sample_doctest_no_doctests, + sample_doctest_no_docstrings) def _run_object_doctest(obj, module): @@ -105,16 +106,26 @@ class ZipSupportTests(unittest.TestCase): "test_zipped_doctest") test_src = test_src.replace("test.sample_doctest", "sample_zipped_doctest") - sample_src = inspect.getsource(sample_doctest) - sample_src = sample_src.replace("test.test_doctest", - "test_zipped_doctest") + # The sample doctest files rewritten to include in the zipped version. + sample_sources = {} + for mod in [sample_doctest, sample_doctest_no_doctests, + sample_doctest_no_docstrings]: + src = inspect.getsource(mod) + src = src.replace("test.test_doctest", "test_zipped_doctest") + # Rewrite the module name so that, for example, + # "test.sample_doctest" becomes "sample_zipped_doctest". + mod_name = mod.__name__.split(".")[-1] + mod_name = mod_name.replace("sample_", "sample_zipped_") + sample_sources[mod_name] = src + with temp_dir() as d: script_name = make_script(d, 'test_zipped_doctest', test_src) zip_name, run_name = make_zip_script(d, 'test_zip', script_name) z = zipfile.ZipFile(zip_name, 'a') - z.writestr("sample_zipped_doctest.py", sample_src) + for mod_name, src in sample_sources.items(): + z.writestr(mod_name + ".py", src) z.close() if verbose: zip_file = zipfile.ZipFile(zip_name, 'r') diff --git a/Lib/unittest/__main__.py b/Lib/unittest/__main__.py index 7320050..798ebc0 100644 --- a/Lib/unittest/__main__.py +++ b/Lib/unittest/__main__.py @@ -2,7 +2,14 @@ import sys if sys.argv[0].endswith("__main__.py"): - sys.argv[0] = "python -m unittest" + import os.path + # We change sys.argv[0] to make help message more useful + # use executable without path, unquoted + # (it's just a hint anyway) + # (if you have spaces in your executable you get what you deserve!) + executable = os.path.basename(sys.executable) + sys.argv[0] = executable + " -m unittest" + del os __unittest = True diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 95570aa..324cf39 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -731,8 +731,8 @@ class NonCallableMock(Base): arguments.""" self = _mock_self if not self.call_count == 1: - msg = ("Expected to be called once. Called %s times." % - self.call_count) + msg = ("Expected '%s' to be called once. Called %s times." % + (self._mock_name or 'mock', self.call_count)) raise AssertionError(msg) return self.assert_called_with(*args, **kwargs) diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py index 10c4778..28b8865 100644 --- a/Lib/unittest/runner.py +++ b/Lib/unittest/runner.py @@ -35,7 +35,7 @@ class TextTestResult(result.TestResult): separator2 = '-' * 70 def __init__(self, stream, descriptions, verbosity): - super(TextTestResult, self).__init__() + super(TextTestResult, self).__init__(stream, descriptions, verbosity) self.stream = stream self.showAll = verbosity > 1 self.dots = verbosity == 1 diff --git a/Lib/unittest/test/test_runner.py b/Lib/unittest/test/test_runner.py index 8e95410..aed1e76 100644 --- a/Lib/unittest/test/test_runner.py +++ b/Lib/unittest/test/test_runner.py @@ -149,6 +149,19 @@ class Test_TextTestRunner(unittest.TestCase): self.assertEqual(runner.resultclass, unittest.TextTestResult) + def test_multiple_inheritance(self): + class AResult(unittest.TestResult): + def __init__(self, stream, descriptions, verbosity): + super(AResult, self).__init__(stream, descriptions, verbosity) + + class ATextResult(unittest.TextTestResult, AResult): + pass + + # This used to raise an exception due to TextTestResult not passing + # on arguments in its __init__ super call + ATextResult(None, None, 1) + + def testBufferAndFailfast(self): class Test(unittest.TestCase): def testFoo(self): diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 64fd1a1..2c6f128 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -463,6 +463,13 @@ class MockTest(unittest.TestCase): mock.assert_called_with) + def test_assert_called_once_with_message(self): + mock = Mock(name='geoffrey') + self.assertRaisesRegex(AssertionError, + r"Expected 'geoffrey' to be called once\.", + mock.assert_called_once_with) + + def test__name__(self): mock = Mock() self.assertRaises(AttributeError, lambda: mock.__name__) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index ce5b48e..94d4ad4 100644 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -206,12 +206,18 @@ class UnixBrowser(BaseBrowser): """Parent class for all Unix browsers with remote functionality.""" raise_opts = None + background = False + redirect_stdout = True + # In remote_args, %s will be replaced with the requested URL. %action will + # be replaced depending on the value of 'new' passed to open. + # remote_action is used for new=0 (open). If newwin is not None, it is + # used for new=1 (open_new). If newtab is not None, it is used for + # new=3 (open_new_tab). After both substitutions are made, any empty + # strings in the transformed remote_args list will be removed. remote_args = ['%action', '%s'] remote_action = None remote_action_newwin = None remote_action_newtab = None - background = False - redirect_stdout = True def _invoke(self, args, remote, autoraise): raise_opt = [] @@ -224,7 +230,7 @@ class UnixBrowser(BaseBrowser): cmdline = [self.name] + raise_opt + args if remote or self.background: - inout = io.open(os.devnull, "r+") + inout = subprocess.DEVNULL else: # for TTY browsers, we need stdin/out inout = None @@ -264,6 +270,7 @@ class UnixBrowser(BaseBrowser): args = [arg.replace("%s", url).replace("%action", action) for arg in self.remote_args] + args = [arg for arg in args if arg] success = self._invoke(args, True, autoraise) if not success: # remote invocation failed, try straight way @@ -347,7 +354,7 @@ class Konqueror(BaseBrowser): else: action = "openURL" - devnull = io.open(os.devnull, "r+") + devnull = subprocess.DEVNULL # if possible, put browser in separate process group, so # keyboard interrupts don't affect browser as well as Python setsid = getattr(os, 'setsid', None) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index ea4fa7a..b9d8df6 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -995,7 +995,7 @@ def _serialize_xml(write, elem, qnames, namespaces): write(_escape_cdata(elem.tail)) HTML_EMPTY = ("area", "base", "basefont", "br", "col", "frame", "hr", - "img", "input", "isindex", "link", "meta" "param") + "img", "input", "isindex", "link", "meta", "param") try: HTML_EMPTY = set(HTML_EMPTY) |