diff --git a/Lib/doctest.py b/Lib/doctest.py index ecac54ad5a5..a66888d8fc9 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -109,6 +109,8 @@ def _test(): from _colorize import ANSIColors, can_colorize +__unittest = True + class TestResults(namedtuple('TestResults', 'failed attempted')): def __new__(cls, failed, attempted, *, skipped=0): results = super().__new__(cls, failed, attempted) @@ -390,11 +392,11 @@ def __init__(self, out): # still use input() to get user input self.use_rawinput = 1 - def set_trace(self, frame=None): + def set_trace(self, frame=None, *, commands=None): self.__debugger_used = True if frame is None: frame = sys._getframe().f_back - pdb.Pdb.set_trace(self, frame) + pdb.Pdb.set_trace(self, frame, commands=commands) def set_continue(self): # Calling set_continue unconditionally would break unit test @@ -1230,7 +1232,7 @@ class DocTestRunner: `OutputChecker` to the constructor. The test runner's display output can be controlled in two ways. - First, an output function (`out) can be passed to + First, an output function (`out`) can be passed to `TestRunner.run`; this function will be called with strings that should be displayed. It defaults to `sys.stdout.write`. If capturing the output is not sufficient, then the display output @@ -1398,11 +1400,11 @@ def __run(self, test, compileflags, out): exec(compile(example.source, filename, "single", compileflags, True), test.globs) self.debugger.set_continue() # ==== Example Finished ==== - exception = None + exc_info = None except KeyboardInterrupt: raise - except: - exception = sys.exc_info() + except BaseException as exc: + exc_info = type(exc), exc, exc.__traceback__.tb_next self.debugger.set_continue() # ==== Example Finished ==== got = self._fakeout.getvalue() # the actual output @@ -1411,21 +1413,21 @@ def __run(self, test, compileflags, out): # If the example executed without raising any exceptions, # verify its output. - if exception is None: + if exc_info is None: if check(example.want, got, self.optionflags): outcome = SUCCESS # The example raised an exception: check if it was expected. else: - formatted_ex = traceback.format_exception_only(*exception[:2]) - if issubclass(exception[0], SyntaxError): + formatted_ex = traceback.format_exception_only(*exc_info[:2]) + if issubclass(exc_info[0], SyntaxError): # SyntaxError / IndentationError is special: # we don't care about the carets / suggestions / etc # We only care about the error message and notes. # They start with `SyntaxError:` (or any other class name) exception_line_prefixes = ( - f"{exception[0].__qualname__}:", - f"{exception[0].__module__}.{exception[0].__qualname__}:", + f"{exc_info[0].__qualname__}:", + f"{exc_info[0].__module__}.{exc_info[0].__qualname__}:", ) exc_msg_index = next( index @@ -1436,7 +1438,7 @@ def __run(self, test, compileflags, out): exc_msg = "".join(formatted_ex) if not quiet: - got += _exception_traceback(exception) + got += _exception_traceback(exc_info) # If `example.exc_msg` is None, then we weren't expecting # an exception. @@ -1465,7 +1467,7 @@ def __run(self, test, compileflags, out): elif outcome is BOOM: if not quiet: self.report_unexpected_exception(out, test, example, - exception) + exc_info) failures += 1 else: assert False, ("unknown outcome", outcome) @@ -2327,7 +2329,7 @@ def runTest(self): sys.stdout = old if results.failed: - raise self.failureException(self.format_failure(new.getvalue())) + raise self.failureException(self.format_failure(new.getvalue().rstrip('\n'))) def format_failure(self, err): test = self._dt_test @@ -2737,7 +2739,7 @@ def testsource(module, name): return testsrc def debug_src(src, pm=False, globs=None): - """Debug a single doctest docstring, in argument `src`'""" + """Debug a single doctest docstring, in argument `src`""" testsrc = script_from_examples(src) debug_script(testsrc, pm, globs) @@ -2873,7 +2875,7 @@ def get(self): def _test(): import argparse - parser = argparse.ArgumentParser(description="doctest runner") + parser = argparse.ArgumentParser(description="doctest runner", color=True) parser.add_argument('-v', '--verbose', action='store_true', default=False, help='print very verbose output for all tests') parser.add_argument('-o', '--option', action='append', diff --git a/Lib/pdb.py b/Lib/pdb.py index bf503f1e73e..ec6cf06e58b 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -185,6 +185,15 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, self.commands_bnum = None # The breakpoint number for which we are # defining a list + def set_trace(self, frame=None, *, commands=None): + if frame is None: + frame = sys._getframe().f_back + + if commands is not None: + self.rcLines.extend(commands) + + super().set_trace(frame) + def sigint_handler(self, signum, frame): if self.allow_kbdint: raise KeyboardInterrupt diff --git a/Lib/test/test_concurrent_futures/test_wait.py b/Lib/test/test_concurrent_futures/test_wait.py index d98ddec4c64..818e0d51a2c 100644 --- a/Lib/test/test_concurrent_futures/test_wait.py +++ b/Lib/test/test_concurrent_futures/test_wait.py @@ -205,7 +205,7 @@ class ProcessPoolForkWaitTest(ProcessPoolForkWaitTest): # TODO: RUSTPYTHON def test_first_completed(self): super().test_first_completed() # TODO: RUSTPYTHON @unittest.skipIf(sys.platform == 'linux', "TODO: RUSTPYTHON Fatal Python error: Segmentation fault") def test_first_completed_some_already_completed(self): super().test_first_completed_some_already_completed() # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform == 'linux', "TODO: RUSTPYTHON flaky") + @unittest.skipIf(sys.platform != 'win32', "TODO: RUSTPYTHON flaky") def test_first_exception(self): super().test_first_exception() # TODO: RUSTPYTHON @unittest.skipIf(sys.platform == 'linux', "TODO: RUSTPYTHON flaky") def test_first_exception_one_already_failed(self): super().test_first_exception_one_already_failed() # TODO: RUSTPYTHON diff --git a/Lib/test/test_dbm.py b/Lib/test/test_dbm.py index 6785aa273ac..e68eca29564 100644 --- a/Lib/test/test_dbm.py +++ b/Lib/test/test_dbm.py @@ -1,5 +1,6 @@ """Test script for the dbm.open function based on testdumbdbm.py""" +import sys import unittest import dbm import os @@ -252,10 +253,13 @@ def setUp(self): assert mod.__name__.startswith('dbm.') suffix = mod.__name__[4:] testname = f'TestCase_{suffix}' - globals()[testname] = type(testname, - (AnyDBMTestCase, unittest.TestCase), - {'module': mod}) - + cls = type(testname, + (AnyDBMTestCase, unittest.TestCase), + {'module': mod}) + # TODO: RUSTPYTHON; sqlite3 file locking prevents cleanup on Windows + if suffix == 'sqlite3' and sys.platform == 'win32': + cls = unittest.skip("TODO: RUSTPYTHON; sqlite3 file locking on Windows")(cls) + globals()[testname] = cls if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index 48d5ff6f73f..ec008ed38b6 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -4,7 +4,6 @@ from test import support from test.support import import_helper -from test.support.pty_helper import FakeInput # used in doctests import doctest import functools import os @@ -16,7 +15,6 @@ import tempfile import types import contextlib -import _colorize def doctest_skip_if(condition): @@ -471,7 +469,7 @@ def basics(): r""" >>> tests = finder.find(sample_func) >>> print(tests) # doctest: +ELLIPSIS - [] + [] The exact name depends on how test_doctest was invoked, so allow for leading path components. @@ -733,47 +731,44 @@ def basics(): r""" [1, 9, 12] """ -# TODO: RUSTPYTHON -# Currently, only 5 builtins types exist in RustPython -# So this test will fail -# if int.__doc__: # simple check for --without-doc-strings, skip if lacking -# def non_Python_modules(): r""" - -# Finding Doctests in Modules Not Written in Python -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# DocTestFinder can also find doctests in most modules not written in Python. -# We'll use builtins as an example, since it almost certainly isn't written in -# plain ol' Python and is guaranteed to be available. - -# >>> import builtins -# >>> tests = doctest.DocTestFinder().find(builtins) -# >>> 830 < len(tests) < 860 # approximate number of objects with docstrings -# True -# >>> real_tests = [t for t in tests if len(t.examples) > 0] -# >>> len(real_tests) # objects that actually have doctests -# 14 -# >>> for t in real_tests: -# ... print('{} {}'.format(len(t.examples), t.name)) -# ... -# 1 builtins.bin -# 5 builtins.bytearray.hex -# 5 builtins.bytes.hex -# 3 builtins.float.as_integer_ratio -# 2 builtins.float.fromhex -# 2 builtins.float.hex -# 1 builtins.hex -# 1 builtins.int -# 3 builtins.int.as_integer_ratio -# 2 builtins.int.bit_count -# 2 builtins.int.bit_length -# 5 builtins.memoryview.hex -# 1 builtins.oct -# 1 builtins.zip - -# Note here that 'bin', 'oct', and 'hex' are functions; 'float.as_integer_ratio', -# 'float.hex', and 'int.bit_length' are methods; 'float.fromhex' is a classmethod, -# and 'int' is a type. -# """ + if int.__doc__: # simple check for --without-doc-strings, skip if lacking + def non_Python_modules(): r""" + +Finding Doctests in Modules Not Written in Python +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +DocTestFinder can also find doctests in most modules not written in Python. +We'll use builtins as an example, since it almost certainly isn't written in +plain ol' Python and is guaranteed to be available. + + >>> import builtins + >>> tests = doctest.DocTestFinder().find(builtins) + >>> 750 < len(tests) < 800 # approximate number of objects with docstrings # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + True + >>> real_tests = [t for t in tests if len(t.examples) > 0] + >>> len(real_tests) # objects that actually have doctests # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + 14 + >>> for t in real_tests: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + ... print('{} {}'.format(len(t.examples), t.name)) + ... + 1 builtins.bin + 5 builtins.bytearray.hex + 5 builtins.bytes.hex + 3 builtins.float.as_integer_ratio + 2 builtins.float.fromhex + 2 builtins.float.hex + 1 builtins.hex + 1 builtins.int + 3 builtins.int.as_integer_ratio + 2 builtins.int.bit_count + 2 builtins.int.bit_length + 5 builtins.memoryview.hex + 1 builtins.oct + 1 builtins.zip + +Note here that 'bin', 'oct', and 'hex' are functions; 'float.as_integer_ratio', +'float.hex', and 'int.bit_length' are methods; 'float.fromhex' is a classmethod, +and 'int' is a type. +""" class TestDocTest(unittest.TestCase): @@ -900,6 +895,7 @@ def basics(): r""" DocTestRunner is used to run DocTest test cases, and to accumulate statistics. Here's a simple DocTest case we can use: + >>> import _colorize >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False @@ -1034,6 +1030,7 @@ def exceptions(): r""" lines between the first line and the type/value may be omitted or replaced with any other string: + >>> import _colorize >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False @@ -1042,7 +1039,7 @@ def exceptions(): r""" ... >>> x = 12 ... >>> print(x//0) ... Traceback (most recent call last): - ... ZeroDivisionError: integer division or modulo by zero + ... ZeroDivisionError: division by zero ... ''' >>> test = doctest.DocTestFinder().find(f)[0] >>> doctest.DocTestRunner(verbose=False).run(test) @@ -1059,7 +1056,7 @@ def exceptions(): r""" ... >>> print('pre-exception output', x//0) ... pre-exception output ... Traceback (most recent call last): - ... ZeroDivisionError: integer division or modulo by zero + ... ZeroDivisionError: division by zero ... ''' >>> test = doctest.DocTestFinder().find(f)[0] >>> doctest.DocTestRunner(verbose=False).run(test) @@ -1070,7 +1067,7 @@ def exceptions(): r""" print('pre-exception output', x//0) Exception raised: ... - ZeroDivisionError: integer division or modulo by zero + ZeroDivisionError: division by zero TestResults(failed=1, attempted=2) Exception messages may contain newlines: @@ -1265,7 +1262,7 @@ def exceptions(): r""" Exception raised: Traceback (most recent call last): ... - ZeroDivisionError: integer division or modulo by zero + ZeroDivisionError: division by zero TestResults(failed=1, attempted=1) >>> _colorize.COLORIZE = save_colorize @@ -1310,6 +1307,7 @@ def optionflags(): r""" The DONT_ACCEPT_TRUE_FOR_1 flag disables matches between True/False and 1/0: + >>> import _colorize >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False @@ -1743,6 +1741,7 @@ def option_directives(): r""" single example. To turn an option on for an example, follow that example with a comment of the form ``# doctest: +OPTION``: + >>> import _colorize >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False @@ -1997,851 +1996,752 @@ def test_testsource(): r""" """ -# TODO: RUSTPYTHON -# Issue with pdb -# def test_debug(): r""" - -# Create a docstring that we want to debug: - -# >>> s = ''' -# ... >>> x = 12 -# ... >>> print(x) -# ... 12 -# ... ''' - -# Create some fake stdin input, to feed to the debugger: - -# >>> real_stdin = sys.stdin -# >>> sys.stdin = FakeInput(['next', 'print(x)', 'continue']) - -# Run the debugger on the docstring, and then restore sys.stdin. - -# >>> try: doctest.debug_src(s) -# ... finally: sys.stdin = real_stdin -# > (1)() -# (Pdb) next -# 12 -# --Return-- -# > (1)()->None -# (Pdb) print(x) -# 12 -# (Pdb) continue - -# """ - -# TODO: RUSTPYTHON -# Issue with pdb -# AttributeError: 'frame' object has no attribute 'f_trace_opcodes'. Did you mean: 'f_trace_lines'? -# if not hasattr(sys, 'gettrace') or not sys.gettrace(): -# def test_pdb_set_trace(): -# """Using pdb.set_trace from a doctest. - -# You can use pdb.set_trace from a doctest. To do so, you must -# retrieve the set_trace function from the pdb module at the time -# you use it. The doctest module changes sys.stdout so that it can -# capture program output. It also temporarily replaces pdb.set_trace -# with a version that restores stdout. This is necessary for you to -# see debugger output. - -# >>> save_colorize = _colorize.COLORIZE -# >>> _colorize.COLORIZE = False - -# >>> doc = ''' -# ... >>> x = 42 -# ... >>> raise Exception('clé') -# ... Traceback (most recent call last): -# ... Exception: clé -# ... >>> import pdb; pdb.set_trace() -# ... ''' -# >>> parser = doctest.DocTestParser() -# >>> test = parser.get_doctest(doc, {}, "foo-bar@baz", "foo-bar@baz.py", 0) -# >>> runner = doctest.DocTestRunner(verbose=False) - -# To demonstrate this, we'll create a fake standard input that -# captures our debugger input: - -# >>> real_stdin = sys.stdin -# >>> sys.stdin = FakeInput([ -# ... 'print(x)', # print data defined by the example -# ... 'continue', # stop debugging -# ... '']) - -# >>> try: runner.run(test) -# ... finally: sys.stdin = real_stdin -# > (1)() -# -> import pdb; pdb.set_trace() -# (Pdb) print(x) -# 42 -# (Pdb) continue -# TestResults(failed=0, attempted=3) - -# You can also put pdb.set_trace in a function called from a test: - -# >>> def calls_set_trace(): -# ... y=2 -# ... import pdb; pdb.set_trace() - -# >>> doc = ''' -# ... >>> x=1 -# ... >>> calls_set_trace() -# ... ''' -# >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) -# >>> real_stdin = sys.stdin -# >>> sys.stdin = FakeInput([ -# ... 'print(y)', # print data defined in the function -# ... 'up', # out of function -# ... 'print(x)', # print data defined by the example -# ... 'continue', # stop debugging -# ... '']) - -# >>> try: -# ... runner.run(test) -# ... finally: -# ... sys.stdin = real_stdin -# > (3)calls_set_trace() -# -> import pdb; pdb.set_trace() -# (Pdb) print(y) -# 2 -# (Pdb) up -# > (1)() -# -> calls_set_trace() -# (Pdb) print(x) -# 1 -# (Pdb) continue -# TestResults(failed=0, attempted=2) - -# During interactive debugging, source code is shown, even for -# doctest examples: - -# >>> doc = ''' -# ... >>> def f(x): -# ... ... g(x*2) -# ... >>> def g(x): -# ... ... print(x+3) -# ... ... import pdb; pdb.set_trace() -# ... >>> f(3) -# ... ''' -# >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) -# >>> real_stdin = sys.stdin -# >>> sys.stdin = FakeInput([ -# ... 'step', # return event of g -# ... 'list', # list source from example 2 -# ... 'next', # return from g() -# ... 'list', # list source from example 1 -# ... 'next', # return from f() -# ... 'list', # list source from example 3 -# ... 'continue', # stop debugging -# ... '']) -# >>> try: runner.run(test) -# ... finally: sys.stdin = real_stdin -# ... # doctest: +NORMALIZE_WHITESPACE -# > (3)g() -# -> import pdb; pdb.set_trace() -# (Pdb) step -# --Return-- -# > (3)g()->None -# -> import pdb; pdb.set_trace() -# (Pdb) list -# 1 def g(x): -# 2 print(x+3) -# 3 -> import pdb; pdb.set_trace() -# [EOF] -# (Pdb) next -# --Return-- -# > (2)f()->None -# -> g(x*2) -# (Pdb) list -# 1 def f(x): -# 2 -> g(x*2) -# [EOF] -# (Pdb) next -# --Return-- -# > (1)()->None -# -> f(3) -# (Pdb) list -# 1 -> f(3) -# [EOF] -# (Pdb) continue -# ********************************************************************** -# File "foo-bar@baz.py", line 7, in foo-bar@baz -# Failed example: -# f(3) -# Expected nothing -# Got: -# 9 -# TestResults(failed=1, attempted=3) - -# >>> _colorize.COLORIZE = save_colorize -# """ - - # TODO: RUSTPYTHON - # Issue with pdb - # AttributeError: 'frame' object has no attribute 'f_trace_opcodes'. Did you mean: 'f_trace_lines'? - # def test_pdb_set_trace_nested(): - # """This illustrates more-demanding use of set_trace with nested functions. - - # >>> class C(object): - # ... def calls_set_trace(self): - # ... y = 1 - # ... import pdb; pdb.set_trace() - # ... self.f1() - # ... y = 2 - # ... def f1(self): - # ... x = 1 - # ... self.f2() - # ... x = 2 - # ... def f2(self): - # ... z = 1 - # ... z = 2 - - # >>> calls_set_trace = C().calls_set_trace - - # >>> doc = ''' - # ... >>> a = 1 - # ... >>> calls_set_trace() - # ... ''' - # >>> parser = doctest.DocTestParser() - # >>> runner = doctest.DocTestRunner(verbose=False) - # >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) - # >>> real_stdin = sys.stdin - # >>> sys.stdin = FakeInput([ - # ... 'step', - # ... 'print(y)', # print data defined in the function - # ... 'step', 'step', 'step', 'step', 'step', 'step', 'print(z)', - # ... 'up', 'print(x)', - # ... 'up', 'print(y)', - # ... 'up', 'print(foo)', - # ... 'continue', # stop debugging - # ... '']) - - # >>> try: - # ... runner.run(test) - # ... finally: - # ... sys.stdin = real_stdin - # ... # doctest: +REPORT_NDIFF - # > (4)calls_set_trace() - # -> import pdb; pdb.set_trace() - # (Pdb) step - # > (5)calls_set_trace() - # -> self.f1() - # (Pdb) print(y) - # 1 - # (Pdb) step - # --Call-- - # > (7)f1() - # -> def f1(self): - # (Pdb) step - # > (8)f1() - # -> x = 1 - # (Pdb) step - # > (9)f1() - # -> self.f2() - # (Pdb) step - # --Call-- - # > (11)f2() - # -> def f2(self): - # (Pdb) step - # > (12)f2() - # -> z = 1 - # (Pdb) step - # > (13)f2() - # -> z = 2 - # (Pdb) print(z) - # 1 - # (Pdb) up - # > (9)f1() - # -> self.f2() - # (Pdb) print(x) - # 1 - # (Pdb) up - # > (5)calls_set_trace() - # -> self.f1() - # (Pdb) print(y) - # 1 - # (Pdb) up - # > (1)() - # -> calls_set_trace() - # (Pdb) print(foo) - # *** NameError: name 'foo' is not defined - # (Pdb) continue - # TestResults(failed=0, attempted=2) - # """ - -# TODO: RUSTPYTHON -# Issue with pdb -# AttributeError: 'frame' object has no attribute 'f_trace_opcodes'. Did you mean: 'f_trace_lines'? -# def test_DocTestSuite(): -# """DocTestSuite creates a unittest test suite from a doctest. - -# We create a Suite by providing a module. A module can be provided -# by passing a module object: - -# >>> import unittest -# >>> import test.test_doctest.sample_doctest -# >>> suite = doctest.DocTestSuite(test.test_doctest.sample_doctest) -# >>> result = suite.run(unittest.TestResult()) -# >>> result -# -# >>> for tst, _ in result.failures: -# ... print(tst) -# bad (test.test_doctest.sample_doctest.__test__) -# foo (test.test_doctest.sample_doctest) -# test_silly_setup (test.test_doctest.sample_doctest) -# y_is_one (test.test_doctest.sample_doctest) - -# We can also supply the module by name: - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest') -# >>> result = suite.run(unittest.TestResult()) -# >>> result -# - -# The module need not contain any doctest examples: - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_doctests') -# >>> suite.run(unittest.TestResult()) -# - -# The module need not contain any docstrings either: - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_docstrings') -# >>> suite.run(unittest.TestResult()) -# - -# If all examples in a docstring are skipped, unittest will report it as a -# skipped test: - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_skip') -# >>> result = suite.run(unittest.TestResult()) -# >>> result -# -# >>> len(result.skipped) -# 2 -# >>> for tst, _ in result.skipped: -# ... print(tst) -# double_skip (test.test_doctest.sample_doctest_skip) -# single_skip (test.test_doctest.sample_doctest_skip) -# >>> for tst, _ in result.failures: -# ... print(tst) -# no_skip_fail (test.test_doctest.sample_doctest_skip) -# partial_skip_fail (test.test_doctest.sample_doctest_skip) - -# We can use the current module: - -# >>> suite = test.test_doctest.sample_doctest.test_suite() -# >>> suite.run(unittest.TestResult()) -# - -# We can also provide a DocTestFinder: - -# >>> finder = doctest.DocTestFinder() -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', -# ... test_finder=finder) -# >>> suite.run(unittest.TestResult()) -# - -# The DocTestFinder need not return any tests: - -# >>> finder = doctest.DocTestFinder() -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_docstrings', -# ... test_finder=finder) -# >>> suite.run(unittest.TestResult()) -# - -# We can supply global variables. If we pass globs, they will be -# used instead of the module globals. Here we'll pass an empty -# globals, triggering an extra error: - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', globs={}) -# >>> suite.run(unittest.TestResult()) -# - -# Alternatively, we can provide extra globals. Here we'll make an -# error go away by providing an extra global variable: - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', -# ... extraglobs={'y': 1}) -# >>> suite.run(unittest.TestResult()) -# - -# You can pass option flags. Here we'll cause an extra error -# by disabling the blank-line feature: - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', -# ... optionflags=doctest.DONT_ACCEPT_BLANKLINE) -# >>> suite.run(unittest.TestResult()) -# - -# You can supply setUp and tearDown functions: - -# >>> def setUp(t): -# ... from test.test_doctest import test_doctest -# ... test_doctest.sillySetup = True - -# >>> def tearDown(t): -# ... from test.test_doctest import test_doctest -# ... del test_doctest.sillySetup - -# Here, we installed a silly variable that the test expects: - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', -# ... setUp=setUp, tearDown=tearDown) -# >>> suite.run(unittest.TestResult()) -# - -# But the tearDown restores sanity: - -# >>> from test.test_doctest import test_doctest -# >>> test_doctest.sillySetup -# Traceback (most recent call last): -# ... -# AttributeError: module 'test.test_doctest.test_doctest' has no attribute 'sillySetup' - -# The setUp and tearDown functions are passed test objects. Here -# we'll use the setUp function to supply the missing variable y: - -# >>> def setUp(test): -# ... test.globs['y'] = 1 - -# >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', setUp=setUp) -# >>> suite.run(unittest.TestResult()) -# - -# Here, we didn't need to use a tearDown function because we -# modified the test globals, which are a copy of the -# sample_doctest module dictionary. The test globals are -# automatically cleared for us after a test. -# """ - -# TODO: RUSTPYTHON -# traceback + error message is different than in CPython -# def test_DocTestSuite_errors(): -# """Tests for error reporting in DocTestSuite. - -# >>> import unittest -# >>> import test.test_doctest.sample_doctest_errors as mod -# >>> suite = doctest.DocTestSuite(mod) -# >>> result = suite.run(unittest.TestResult()) -# >>> result -# -# >>> print(result.failures[0][1]) # doctest: +ELLIPSIS -# Traceback (most recent call last): -# File ... -# raise self.failureException(self.format_failure(new.getvalue())) -# AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors -# File "...sample_doctest_errors.py", line 0, in sample_doctest_errors -# -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line 5, in test.test_doctest.sample_doctest_errors -# Failed example: -# 2 + 2 -# Expected: -# 5 -# Got: -# 4 -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line 7, in test.test_doctest.sample_doctest_errors -# Failed example: -# 1/0 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# 1/0 -# ~^~ -# ZeroDivisionError: division by zero -# -# -# >>> print(result.failures[1][1]) # doctest: +ELLIPSIS -# Traceback (most recent call last): -# File ... -# raise self.failureException(self.format_failure(new.getvalue())) -# AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.__test__.bad -# File "...sample_doctest_errors.py", line unknown line number, in bad -# -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad -# Failed example: -# 2 + 2 -# Expected: -# 5 -# Got: -# 4 -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad -# Failed example: -# 1/0 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# 1/0 -# ~^~ -# ZeroDivisionError: division by zero -# -# -# >>> print(result.failures[2][1]) # doctest: +ELLIPSIS -# Traceback (most recent call last): -# File ... -# raise self.failureException(self.format_failure(new.getvalue())) -# AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.errors -# File "...sample_doctest_errors.py", line 14, in errors -# -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line 16, in test.test_doctest.sample_doctest_errors.errors -# Failed example: -# 2 + 2 -# Expected: -# 5 -# Got: -# 4 -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line 18, in test.test_doctest.sample_doctest_errors.errors -# Failed example: -# 1/0 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# 1/0 -# ~^~ -# ZeroDivisionError: division by zero -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line 23, in test.test_doctest.sample_doctest_errors.errors -# Failed example: -# f() -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# f() -# ~^^ -# File "", line 2, in f -# 2 + '2' -# ~~^~~~~ -# TypeError: ... -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line 25, in test.test_doctest.sample_doctest_errors.errors -# Failed example: -# g() -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# g() -# ~^^ -# File "...sample_doctest_errors.py", line 12, in g -# [][0] # line 12 -# ~~^^^ -# IndexError: list index out of range -# -# -# >>> print(result.failures[3][1]) # doctest: +ELLIPSIS -# Traceback (most recent call last): -# File ... -# raise self.failureException(self.format_failure(new.getvalue())) -# AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.syntax_error -# File "...sample_doctest_errors.py", line 29, in syntax_error -# -# ---------------------------------------------------------------------- -# File "...sample_doctest_errors.py", line 31, in test.test_doctest.sample_doctest_errors.syntax_error -# Failed example: -# 2+*3 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^ -# File "", line 1 -# 2+*3 -# ^ -# SyntaxError: invalid syntax -# -# -# """ - -# TODO: RUSTPYTHON -# try finally does not work in the interactive shell -# def test_DocFileSuite(): -# """We can test tests found in text files using a DocFileSuite. - -# We create a suite by providing the names of one or more text -# files that include examples: - -# >>> import unittest -# >>> suite = doctest.DocFileSuite('test_doctest.txt', -# ... 'test_doctest2.txt', -# ... 'test_doctest4.txt') -# >>> suite.run(unittest.TestResult()) -# - -# The test files are looked for in the directory containing the -# calling module. A package keyword argument can be provided to -# specify a different relative location. - -# >>> import unittest -# >>> suite = doctest.DocFileSuite('test_doctest.txt', -# ... 'test_doctest2.txt', -# ... 'test_doctest4.txt', -# ... package='test.test_doctest') -# >>> suite.run(unittest.TestResult()) -# - -# Support for using a package's __loader__.get_data() is also -# provided. - -# >>> import unittest, pkgutil, test -# >>> added_loader = False -# >>> if not hasattr(test, '__loader__'): -# ... test.__loader__ = pkgutil.get_loader(test) -# ... added_loader = True -# >>> try: -# ... suite = doctest.DocFileSuite('test_doctest.txt', -# ... 'test_doctest2.txt', -# ... 'test_doctest4.txt', -# ... package='test.test_doctest') -# ... suite.run(unittest.TestResult()) -# ... finally: -# ... if added_loader: -# ... del test.__loader__ -# - -# '/' should be used as a path separator. It will be converted -# to a native separator at run time: - -# >>> suite = doctest.DocFileSuite('../test_doctest/test_doctest.txt') -# >>> suite.run(unittest.TestResult()) -# - -# If DocFileSuite is used from an interactive session, then files -# are resolved relative to the directory of sys.argv[0]: - -# >>> import types, os.path -# >>> from test.test_doctest import test_doctest -# >>> save_argv = sys.argv -# >>> sys.argv = [test_doctest.__file__] -# >>> suite = doctest.DocFileSuite('test_doctest.txt', -# ... package=types.ModuleType('__main__')) -# >>> sys.argv = save_argv - -# By setting `module_relative=False`, os-specific paths may be -# used (including absolute paths and paths relative to the -# working directory): - -# >>> # Get the absolute path of the test package. -# >>> test_doctest_path = os.path.abspath(test_doctest.__file__) -# >>> test_pkg_path = os.path.split(test_doctest_path)[0] - -# >>> # Use it to find the absolute path of test_doctest.txt. -# >>> test_file = os.path.join(test_pkg_path, 'test_doctest.txt') - -# >>> suite = doctest.DocFileSuite(test_file, module_relative=False) -# >>> suite.run(unittest.TestResult()) -# - -# It is an error to specify `package` when `module_relative=False`: - -# >>> suite = doctest.DocFileSuite(test_file, module_relative=False, -# ... package='test') -# Traceback (most recent call last): -# ValueError: Package may only be specified for module-relative paths. - -# If all examples in a file are skipped, unittest will report it as a -# skipped test: - -# >>> suite = doctest.DocFileSuite('test_doctest.txt', -# ... 'test_doctest4.txt', -# ... 'test_doctest_skip.txt', -# ... 'test_doctest_skip2.txt') -# >>> result = suite.run(unittest.TestResult()) -# >>> result -# -# >>> len(result.skipped) -# 1 -# >>> for tst, _ in result.skipped: # doctest: +ELLIPSIS -# ... print('=', tst) -# = ...test_doctest_skip.txt - -# You can specify initial global variables: - -# >>> suite = doctest.DocFileSuite('test_doctest.txt', -# ... 'test_doctest2.txt', -# ... 'test_doctest4.txt', -# ... globs={'favorite_color': 'blue'}) -# >>> suite.run(unittest.TestResult()) -# - -# In this case, we supplied a missing favorite color. You can -# provide doctest options: - -# >>> suite = doctest.DocFileSuite('test_doctest.txt', -# ... 'test_doctest2.txt', -# ... 'test_doctest4.txt', -# ... optionflags=doctest.DONT_ACCEPT_BLANKLINE, -# ... globs={'favorite_color': 'blue'}) -# >>> suite.run(unittest.TestResult()) -# - -# And, you can provide setUp and tearDown functions: - -# >>> def setUp(t): -# ... from test.test_doctest import test_doctest -# ... test_doctest.sillySetup = True - -# >>> def tearDown(t): -# ... from test.test_doctest import test_doctest -# ... del test_doctest.sillySetup - -# Here, we installed a silly variable that the test expects: - -# >>> suite = doctest.DocFileSuite('test_doctest.txt', -# ... 'test_doctest2.txt', -# ... 'test_doctest4.txt', -# ... setUp=setUp, tearDown=tearDown) -# >>> suite.run(unittest.TestResult()) -# - -# But the tearDown restores sanity: - -# >>> from test.test_doctest import test_doctest -# >>> test_doctest.sillySetup -# Traceback (most recent call last): -# ... -# AttributeError: module 'test.test_doctest.test_doctest' has no attribute 'sillySetup' - -# The setUp and tearDown functions are passed test objects. -# Here, we'll use a setUp function to set the favorite color in -# test_doctest.txt: - -# >>> def setUp(test): -# ... test.globs['favorite_color'] = 'blue' - -# >>> suite = doctest.DocFileSuite('test_doctest.txt', setUp=setUp) -# >>> suite.run(unittest.TestResult()) -# - -# Here, we didn't need to use a tearDown function because we -# modified the test globals. The test globals are -# automatically cleared for us after a test. - -# Tests in a file run using `DocFileSuite` can also access the -# `__file__` global, which is set to the name of the file -# containing the tests: - -# >>> suite = doctest.DocFileSuite('test_doctest3.txt') -# >>> suite.run(unittest.TestResult()) -# - -# If the tests contain non-ASCII characters, we have to specify which -# encoding the file is encoded with. We do so by using the `encoding` -# parameter: - -# >>> suite = doctest.DocFileSuite('test_doctest.txt', -# ... 'test_doctest2.txt', -# ... 'test_doctest4.txt', -# ... encoding='utf-8') -# >>> suite.run(unittest.TestResult()) -# -# """ - -# TODO: RUSTPYTHON -# traceback + error message is different than in CPython -# def test_DocFileSuite_errors(): -# """Tests for error reporting in DocTestSuite. - -# >>> import unittest -# >>> suite = doctest.DocFileSuite('test_doctest_errors.txt') -# >>> result = suite.run(unittest.TestResult()) -# >>> result -# -# >>> print(result.failures[0][1]) # doctest: +ELLIPSIS -# Traceback (most recent call last): -# File ... -# raise self.failureException(self.format_failure(new.getvalue())) -# AssertionError: Failed doctest test for test_doctest_errors.txt -# File "...test_doctest_errors.txt", line 0 -# -# ---------------------------------------------------------------------- -# File "...test_doctest_errors.txt", line 4, in test_doctest_errors.txt -# Failed example: -# 2 + 2 -# Expected: -# 5 -# Got: -# 4 -# ---------------------------------------------------------------------- -# File "...test_doctest_errors.txt", line 6, in test_doctest_errors.txt -# Failed example: -# 1/0 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# 1/0 -# ~^~ -# ZeroDivisionError: division by zero -# ---------------------------------------------------------------------- -# File "...test_doctest_errors.txt", line 11, in test_doctest_errors.txt -# Failed example: -# f() -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# f() -# ~^^ -# File "", line 2, in f -# 2 + '2' -# ~~^~~~~ -# TypeError: ... -# ---------------------------------------------------------------------- -# File "...test_doctest_errors.txt", line 13, in test_doctest_errors.txt -# Failed example: -# 2+*3 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^ -# File "", line 1 -# 2+*3 -# ^ -# SyntaxError: invalid syntax -# -# -# """ +def test_debug(): r""" + +Create a docstring that we want to debug: + + >>> s = ''' + ... >>> x = 12 + ... >>> print(x) + ... 12 + ... ''' + +Create some fake stdin input, to feed to the debugger: + + >>> from test.support.pty_helper import FakeInput + >>> real_stdin = sys.stdin + >>> sys.stdin = FakeInput(['next', 'print(x)', 'continue']) + +Run the debugger on the docstring, and then restore sys.stdin. + + >>> try: doctest.debug_src(s) + ... finally: sys.stdin = real_stdin + > (1)() + (Pdb) next + 12 + --Return-- + > (1)()->None + (Pdb) print(x) + 12 + (Pdb) continue + +""" + +if not hasattr(sys, 'gettrace') or not sys.gettrace(): + def test_pdb_set_trace(): + """Using pdb.set_trace from a doctest. + + You can use pdb.set_trace from a doctest. To do so, you must + retrieve the set_trace function from the pdb module at the time + you use it. The doctest module changes sys.stdout so that it can + capture program output. It also temporarily replaces pdb.set_trace + with a version that restores stdout. This is necessary for you to + see debugger output. + + >>> import _colorize + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> doc = ''' + ... >>> x = 42 + ... >>> raise Exception('clé') + ... Traceback (most recent call last): + ... Exception: clé + ... >>> import pdb; pdb.set_trace() + ... ''' + >>> parser = doctest.DocTestParser() + >>> test = parser.get_doctest(doc, {}, "foo-bar@baz", "foo-bar@baz.py", 0) + >>> runner = doctest.DocTestRunner(verbose=False) + + To demonstrate this, we'll create a fake standard input that + captures our debugger input: + + >>> from test.support.pty_helper import FakeInput + >>> real_stdin = sys.stdin + >>> sys.stdin = FakeInput([ + ... 'print(x)', # print data defined by the example + ... 'continue', # stop debugging + ... '']) + + >>> try: runner.run(test) # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + ... finally: sys.stdin = real_stdin + > (1)() + -> import pdb; pdb.set_trace() + (Pdb) print(x) + 42 + (Pdb) continue + TestResults(failed=0, attempted=3) + + You can also put pdb.set_trace in a function called from a test: + + >>> def calls_set_trace(): + ... y=2 + ... import pdb; pdb.set_trace() + + >>> doc = ''' + ... >>> x=1 + ... >>> calls_set_trace() + ... ''' + >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) + >>> real_stdin = sys.stdin + >>> sys.stdin = FakeInput([ + ... 'print(y)', # print data defined in the function + ... 'up', # out of function + ... 'print(x)', # print data defined by the example + ... 'continue', # stop debugging + ... '']) + + >>> try: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + ... runner.run(test) + ... finally: + ... sys.stdin = real_stdin + > (3)calls_set_trace() + -> import pdb; pdb.set_trace() + (Pdb) print(y) + 2 + (Pdb) up + > (1)() + -> calls_set_trace() + (Pdb) print(x) + 1 + (Pdb) continue + TestResults(failed=0, attempted=2) + + During interactive debugging, source code is shown, even for + doctest examples: + + >>> doc = ''' + ... >>> def f(x): + ... ... g(x*2) + ... >>> def g(x): + ... ... print(x+3) + ... ... import pdb; pdb.set_trace() + ... >>> f(3) + ... ''' + >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) + >>> real_stdin = sys.stdin + >>> sys.stdin = FakeInput([ + ... 'step', # return event of g + ... 'list', # list source from example 2 + ... 'next', # return from g() + ... 'list', # list source from example 1 + ... 'next', # return from f() + ... 'list', # list source from example 3 + ... 'continue', # stop debugging + ... '']) + >>> try: runner.run(test) # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + ... finally: sys.stdin = real_stdin + ... # doctest: +NORMALIZE_WHITESPACE + > (3)g() + -> import pdb; pdb.set_trace() + (Pdb) step + --Return-- + > (3)g()->None + -> import pdb; pdb.set_trace() + (Pdb) list + 1 def g(x): + 2 print(x+3) + 3 -> import pdb; pdb.set_trace() + [EOF] + (Pdb) next + --Return-- + > (2)f()->None + -> g(x*2) + (Pdb) list + 1 def f(x): + 2 -> g(x*2) + [EOF] + (Pdb) next + --Return-- + > (1)()->None + -> f(3) + (Pdb) list + 1 -> f(3) + [EOF] + (Pdb) continue + ********************************************************************** + File "foo-bar@baz.py", line 7, in foo-bar@baz + Failed example: + f(3) + Expected nothing + Got: + 9 + TestResults(failed=1, attempted=3) + + >>> _colorize.COLORIZE = save_colorize + """ + + def test_pdb_set_trace_nested(): + """This illustrates more-demanding use of set_trace with nested functions. + + >>> class C(object): + ... def calls_set_trace(self): + ... y = 1 + ... import pdb; pdb.set_trace() + ... self.f1() + ... y = 2 + ... def f1(self): + ... x = 1 + ... self.f2() + ... x = 2 + ... def f2(self): + ... z = 1 + ... z = 2 + + >>> calls_set_trace = C().calls_set_trace + + >>> doc = ''' + ... >>> a = 1 + ... >>> calls_set_trace() + ... ''' + >>> parser = doctest.DocTestParser() + >>> runner = doctest.DocTestRunner(verbose=False) + >>> test = parser.get_doctest(doc, globals(), "foo-bar@baz", "foo-bar@baz.py", 0) + >>> from test.support.pty_helper import FakeInput + >>> real_stdin = sys.stdin + >>> sys.stdin = FakeInput([ + ... 'step', + ... 'print(y)', # print data defined in the function + ... 'step', 'step', 'step', 'step', 'step', 'step', 'print(z)', + ... 'up', 'print(x)', + ... 'up', 'print(y)', + ... 'up', 'print(foo)', + ... 'continue', # stop debugging + ... '']) + + >>> try: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + ... runner.run(test) + ... finally: + ... sys.stdin = real_stdin + ... # doctest: +REPORT_NDIFF + > (4)calls_set_trace() + -> import pdb; pdb.set_trace() + (Pdb) step + > (5)calls_set_trace() + -> self.f1() + (Pdb) print(y) + 1 + (Pdb) step + --Call-- + > (7)f1() + -> def f1(self): + (Pdb) step + > (8)f1() + -> x = 1 + (Pdb) step + > (9)f1() + -> self.f2() + (Pdb) step + --Call-- + > (11)f2() + -> def f2(self): + (Pdb) step + > (12)f2() + -> z = 1 + (Pdb) step + > (13)f2() + -> z = 2 + (Pdb) print(z) + 1 + (Pdb) up + > (9)f1() + -> self.f2() + (Pdb) print(x) + 1 + (Pdb) up + > (5)calls_set_trace() + -> self.f1() + (Pdb) print(y) + 1 + (Pdb) up + > (1)() + -> calls_set_trace() + (Pdb) print(foo) + *** NameError: name 'foo' is not defined + (Pdb) continue + TestResults(failed=0, attempted=2) + """ + +def test_DocTestSuite(): + """DocTestSuite creates a unittest test suite from a doctest. + + We create a Suite by providing a module. A module can be provided + by passing a module object: + + >>> import unittest + >>> import test.test_doctest.sample_doctest + >>> suite = doctest.DocTestSuite(test.test_doctest.sample_doctest) + >>> result = suite.run(unittest.TestResult()) + >>> result + + >>> for tst, _ in result.failures: + ... print(tst) + bad (test.test_doctest.sample_doctest.__test__) + foo (test.test_doctest.sample_doctest) + test_silly_setup (test.test_doctest.sample_doctest) + y_is_one (test.test_doctest.sample_doctest) + + We can also supply the module by name: + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest') + >>> result = suite.run(unittest.TestResult()) + >>> result + + + The module need not contain any doctest examples: + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_doctests') + >>> suite.run(unittest.TestResult()) + + + The module need not contain any docstrings either: + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_docstrings') + >>> suite.run(unittest.TestResult()) + + + If all examples in a docstring are skipped, unittest will report it as a + skipped test: + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_skip') + >>> result = suite.run(unittest.TestResult()) + >>> result + + >>> len(result.skipped) + 2 + >>> for tst, _ in result.skipped: + ... print(tst) + double_skip (test.test_doctest.sample_doctest_skip) + single_skip (test.test_doctest.sample_doctest_skip) + >>> for tst, _ in result.failures: + ... print(tst) + no_skip_fail (test.test_doctest.sample_doctest_skip) + partial_skip_fail (test.test_doctest.sample_doctest_skip) + + We can use the current module: + + >>> suite = test.test_doctest.sample_doctest.test_suite() + >>> suite.run(unittest.TestResult()) + + + We can also provide a DocTestFinder: + + >>> finder = doctest.DocTestFinder() + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', + ... test_finder=finder) + >>> suite.run(unittest.TestResult()) + + + The DocTestFinder need not return any tests: + + >>> finder = doctest.DocTestFinder() + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest_no_docstrings', + ... test_finder=finder) + >>> suite.run(unittest.TestResult()) + + + We can supply global variables. If we pass globs, they will be + used instead of the module globals. Here we'll pass an empty + globals, triggering an extra error: + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', globs={}) + >>> suite.run(unittest.TestResult()) + + + Alternatively, we can provide extra globals. Here we'll make an + error go away by providing an extra global variable: + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', + ... extraglobs={'y': 1}) + >>> suite.run(unittest.TestResult()) + + + You can pass option flags. Here we'll cause an extra error + by disabling the blank-line feature: + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', + ... optionflags=doctest.DONT_ACCEPT_BLANKLINE) + >>> suite.run(unittest.TestResult()) + + + You can supply setUp and tearDown functions: + + >>> def setUp(t): + ... from test.test_doctest import test_doctest + ... test_doctest.sillySetup = True + + >>> def tearDown(t): + ... from test.test_doctest import test_doctest + ... del test_doctest.sillySetup + + Here, we installed a silly variable that the test expects: + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', + ... setUp=setUp, tearDown=tearDown) + >>> suite.run(unittest.TestResult()) + + + But the tearDown restores sanity: + + >>> from test.test_doctest import test_doctest + >>> test_doctest.sillySetup + Traceback (most recent call last): + ... + AttributeError: module 'test.test_doctest.test_doctest' has no attribute 'sillySetup' + + The setUp and tearDown functions are passed test objects. Here + we'll use the setUp function to supply the missing variable y: + + >>> def setUp(test): + ... test.globs['y'] = 1 + + >>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', setUp=setUp) + >>> suite.run(unittest.TestResult()) + + + Here, we didn't need to use a tearDown function because we + modified the test globals, which are a copy of the + sample_doctest module dictionary. The test globals are + automatically cleared for us after a test. + """ + +def test_DocTestSuite_errors(): + """Tests for error reporting in DocTestSuite. + + >>> import unittest + >>> import test.test_doctest.sample_doctest_errors as mod + >>> suite = doctest.DocTestSuite(mod) + >>> result = suite.run(unittest.TestResult()) + >>> result + + >>> print(result.failures[0][1]) # doctest: +ELLIPSIS + AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors + File "...sample_doctest_errors.py", line 0, in sample_doctest_errors + + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line 5, in test.test_doctest.sample_doctest_errors + Failed example: + 2 + 2 + Expected: + 5 + Got: + 4 + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line 7, in test.test_doctest.sample_doctest_errors + Failed example: + 1/0 + Exception raised: + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + + >>> print(result.failures[1][1]) # doctest: +ELLIPSIS + AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.__test__.bad + File "...sample_doctest_errors.py", line unknown line number, in bad + + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad + Failed example: + 2 + 2 + Expected: + 5 + Got: + 4 + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad + Failed example: + 1/0 + Exception raised: + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + + >>> print(result.failures[2][1]) # doctest: +ELLIPSIS + AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.errors + File "...sample_doctest_errors.py", line 14, in errors + + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line 16, in test.test_doctest.sample_doctest_errors.errors + Failed example: + 2 + 2 + Expected: + 5 + Got: + 4 + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line 18, in test.test_doctest.sample_doctest_errors.errors + Failed example: + 1/0 + Exception raised: + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line 23, in test.test_doctest.sample_doctest_errors.errors + Failed example: + f() + Exception raised: + Traceback (most recent call last): + File "", line 1, in + f() + ~^^ + File "", line 2, in f + 2 + '2' + ~~^~~~~ + TypeError: ... + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line 25, in test.test_doctest.sample_doctest_errors.errors + Failed example: + g() + Exception raised: + Traceback (most recent call last): + File "", line 1, in + g() + ~^^ + File "...sample_doctest_errors.py", line 12, in g + [][0] # line 12 + ~~^^^ + IndexError: list index out of range + + >>> print(result.failures[3][1]) # doctest: +ELLIPSIS + AssertionError: Failed doctest test for test.test_doctest.sample_doctest_errors.syntax_error + File "...sample_doctest_errors.py", line 29, in syntax_error + + ---------------------------------------------------------------------- + File "...sample_doctest_errors.py", line 31, in test.test_doctest.sample_doctest_errors.syntax_error + Failed example: + 2+*3 + Exception raised: + File "", line 1 + 2+*3 + ^ + SyntaxError: invalid syntax + + """ + +def test_DocFileSuite(): + """We can test tests found in text files using a DocFileSuite. + + We create a suite by providing the names of one or more text + files that include examples: + + >>> import unittest + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest2.txt', + ... 'test_doctest4.txt') + >>> suite.run(unittest.TestResult()) + + + The test files are looked for in the directory containing the + calling module. A package keyword argument can be provided to + specify a different relative location. + + >>> import unittest + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest2.txt', + ... 'test_doctest4.txt', + ... package='test.test_doctest') + >>> suite.run(unittest.TestResult()) + + + '/' should be used as a path separator. It will be converted + to a native separator at run time: + + >>> suite = doctest.DocFileSuite('../test_doctest/test_doctest.txt') + >>> suite.run(unittest.TestResult()) + + + If DocFileSuite is used from an interactive session, then files + are resolved relative to the directory of sys.argv[0]: + + >>> import types, os.path + >>> from test.test_doctest import test_doctest + >>> save_argv = sys.argv + >>> sys.argv = [test_doctest.__file__] + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... package=types.ModuleType('__main__')) + >>> sys.argv = save_argv + + By setting `module_relative=False`, os-specific paths may be + used (including absolute paths and paths relative to the + working directory): + + >>> # Get the absolute path of the test package. + >>> test_doctest_path = os.path.abspath(test_doctest.__file__) + >>> test_pkg_path = os.path.split(test_doctest_path)[0] + + >>> # Use it to find the absolute path of test_doctest.txt. + >>> test_file = os.path.join(test_pkg_path, 'test_doctest.txt') + + >>> suite = doctest.DocFileSuite(test_file, module_relative=False) + >>> suite.run(unittest.TestResult()) + + + It is an error to specify `package` when `module_relative=False`: + + >>> suite = doctest.DocFileSuite(test_file, module_relative=False, + ... package='test') + Traceback (most recent call last): + ValueError: Package may only be specified for module-relative paths. + + If all examples in a file are skipped, unittest will report it as a + skipped test: + + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest4.txt', + ... 'test_doctest_skip.txt', + ... 'test_doctest_skip2.txt') + >>> result = suite.run(unittest.TestResult()) + >>> result + + >>> len(result.skipped) + 1 + >>> for tst, _ in result.skipped: # doctest: +ELLIPSIS + ... print('=', tst) + = ...test_doctest_skip.txt + + You can specify initial global variables: + + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest2.txt', + ... 'test_doctest4.txt', + ... globs={'favorite_color': 'blue'}) + >>> suite.run(unittest.TestResult()) + + + In this case, we supplied a missing favorite color. You can + provide doctest options: + + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest2.txt', + ... 'test_doctest4.txt', + ... optionflags=doctest.DONT_ACCEPT_BLANKLINE, + ... globs={'favorite_color': 'blue'}) + >>> suite.run(unittest.TestResult()) + + + And, you can provide setUp and tearDown functions: + + >>> def setUp(t): + ... from test.test_doctest import test_doctest + ... test_doctest.sillySetup = True + + >>> def tearDown(t): + ... from test.test_doctest import test_doctest + ... del test_doctest.sillySetup + + Here, we installed a silly variable that the test expects: + + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest2.txt', + ... 'test_doctest4.txt', + ... setUp=setUp, tearDown=tearDown) + >>> suite.run(unittest.TestResult()) + + + But the tearDown restores sanity: + + >>> from test.test_doctest import test_doctest + >>> test_doctest.sillySetup + Traceback (most recent call last): + ... + AttributeError: module 'test.test_doctest.test_doctest' has no attribute 'sillySetup' + + The setUp and tearDown functions are passed test objects. + Here, we'll use a setUp function to set the favorite color in + test_doctest.txt: + + >>> def setUp(test): + ... test.globs['favorite_color'] = 'blue' + + >>> suite = doctest.DocFileSuite('test_doctest.txt', setUp=setUp) + >>> suite.run(unittest.TestResult()) + + + Here, we didn't need to use a tearDown function because we + modified the test globals. The test globals are + automatically cleared for us after a test. + + Tests in a file run using `DocFileSuite` can also access the + `__file__` global, which is set to the name of the file + containing the tests: + + >>> suite = doctest.DocFileSuite('test_doctest3.txt') + >>> suite.run(unittest.TestResult()) + + + If the tests contain non-ASCII characters, we have to specify which + encoding the file is encoded with. We do so by using the `encoding` + parameter: + + >>> suite = doctest.DocFileSuite('test_doctest.txt', + ... 'test_doctest2.txt', + ... 'test_doctest4.txt', + ... encoding='utf-8') + >>> suite.run(unittest.TestResult()) + + """ + +def test_DocFileSuite_errors(): + """Tests for error reporting in DocTestSuite. + + >>> import unittest + >>> suite = doctest.DocFileSuite('test_doctest_errors.txt') + >>> result = suite.run(unittest.TestResult()) + >>> result + + >>> print(result.failures[0][1]) # doctest: +ELLIPSIS + AssertionError: Failed doctest test for test_doctest_errors.txt + File "...test_doctest_errors.txt", line 0 + + ---------------------------------------------------------------------- + File "...test_doctest_errors.txt", line 4, in test_doctest_errors.txt + Failed example: + 2 + 2 + Expected: + 5 + Got: + 4 + ---------------------------------------------------------------------- + File "...test_doctest_errors.txt", line 6, in test_doctest_errors.txt + Failed example: + 1/0 + Exception raised: + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + ---------------------------------------------------------------------- + File "...test_doctest_errors.txt", line 11, in test_doctest_errors.txt + Failed example: + f() + Exception raised: + Traceback (most recent call last): + File "", line 1, in + f() + ~^^ + File "", line 2, in f + 2 + '2' + ~~^~~~~ + TypeError: ... + ---------------------------------------------------------------------- + File "...test_doctest_errors.txt", line 13, in test_doctest_errors.txt + Failed example: + 2+*3 + Exception raised: + File "", line 1 + 2+*3 + ^ + SyntaxError: invalid syntax + + """ def test_trailing_space_in_test(): """ @@ -2913,7 +2813,8 @@ def test_unittest_reportflags(): >>> result >>> print(result.failures[0][1]) # doctest: +ELLIPSIS - Traceback ... + AssertionError: Failed doctest test for test_doctest.txt + ... Failed example: favorite_color ... @@ -2932,14 +2833,14 @@ def test_unittest_reportflags(): >>> result >>> print(result.failures[0][1]) # doctest: +ELLIPSIS - Traceback ... + AssertionError: Failed doctest test for test_doctest.txt + ... Failed example: favorite_color Exception raised: ... NameError: name 'favorite_color' is not defined - We get only the first failure. @@ -2959,7 +2860,8 @@ def test_unittest_reportflags(): the trailing whitespace using `\x20` in the diff below. >>> print(result.failures[0][1]) # doctest: +ELLIPSIS - Traceback ... + AssertionError: Failed doctest test for test_doctest.txt + ... Failed example: favorite_color ... @@ -2974,7 +2876,6 @@ def test_unittest_reportflags(): +\x20 b - Test runners can restore the formatting flags after they run: @@ -2991,6 +2892,7 @@ def test_testfile(): r""" We don't want color or `-v` in sys.argv for these tests. + >>> import _colorize >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False @@ -3163,74 +3065,56 @@ def test_testfile(): r""" >>> _colorize.COLORIZE = save_colorize """ -# TODO: RUSTPYTHON -# traceback + error message is different than in CPython -# def test_testfile_errors(): r""" -# Tests for error reporting in the testfile() function. - -# >>> doctest.testfile('test_doctest_errors.txt', verbose=False) # doctest: +ELLIPSIS -# ********************************************************************** -# File "...test_doctest_errors.txt", line 4, in test_doctest_errors.txt -# Failed example: -# 2 + 2 -# Expected: -# 5 -# Got: -# 4 -# ********************************************************************** -# File "...test_doctest_errors.txt", line 6, in test_doctest_errors.txt -# Failed example: -# 1/0 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# 1/0 -# ~^~ -# ZeroDivisionError: division by zero -# ********************************************************************** -# File "...test_doctest_errors.txt", line 11, in test_doctest_errors.txt -# Failed example: -# f() -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# f() -# ~^^ -# File "", line 2, in f -# 2 + '2' -# ~~^~~~~ -# TypeError: ... -# ********************************************************************** -# File "...test_doctest_errors.txt", line 13, in test_doctest_errors.txt -# Failed example: -# 2+*3 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^ -# File "", line 1 -# 2+*3 -# ^ -# SyntaxError: invalid syntax -# ********************************************************************** -# 1 item had failures: -# 4 of 5 in test_doctest_errors.txt -# ***Test Failed*** 4 failures. -# TestResults(failed=4, attempted=5) -# """ +def test_testfile_errors(): r""" +Tests for error reporting in the testfile() function. + + >>> doctest.testfile('test_doctest_errors.txt', verbose=False) # doctest: +ELLIPSIS + ********************************************************************** + File "...test_doctest_errors.txt", line 4, in test_doctest_errors.txt + Failed example: + 2 + 2 + Expected: + 5 + Got: + 4 + ********************************************************************** + File "...test_doctest_errors.txt", line 6, in test_doctest_errors.txt + Failed example: + 1/0 + Exception raised: + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + ********************************************************************** + File "...test_doctest_errors.txt", line 11, in test_doctest_errors.txt + Failed example: + f() + Exception raised: + Traceback (most recent call last): + File "", line 1, in + f() + ~^^ + File "", line 2, in f + 2 + '2' + ~~^~~~~ + TypeError: ... + ********************************************************************** + File "...test_doctest_errors.txt", line 13, in test_doctest_errors.txt + Failed example: + 2+*3 + Exception raised: + File "", line 1 + 2+*3 + ^ + SyntaxError: invalid syntax + ********************************************************************** + 1 item had failures: + 4 of 5 in test_doctest_errors.txt + ***Test Failed*** 4 failures. + TestResults(failed=4, attempted=5) +""" class TestImporter(importlib.abc.MetaPathFinder): @@ -3276,82 +3160,80 @@ def test_hook(pathdir): finally: hook.remove() -# TODO: RUSTPYTHON -# f.write(b'Test:\r\n\r\n >>> x = 1 + 1\r\n\r\nDone.\r\n') -# does not return in with... -# def test_lineendings(): r""" -# *nix systems use \n line endings, while Windows systems use \r\n, and -# old Mac systems used \r, which Python still recognizes as a line ending. Python -# handles this using universal newline mode for reading files. Let's make -# sure doctest does so (issue 8473) by creating temporary test files using each -# of the three line disciplines. At least one will not match either the universal -# newline \n or os.linesep for the platform the test is run on. - -# Windows line endings first: - -# >>> import tempfile, os -# >>> fn = tempfile.mktemp() -# >>> with open(fn, 'wb') as f: -# ... f.write(b'Test:\r\n\r\n >>> x = 1 + 1\r\n\r\nDone.\r\n') -# 35 -# >>> doctest.testfile(fn, module_relative=False, verbose=False) -# TestResults(failed=0, attempted=1) -# >>> os.remove(fn) - -# And now *nix line endings: - -# >>> fn = tempfile.mktemp() -# >>> with open(fn, 'wb') as f: -# ... f.write(b'Test:\n\n >>> x = 1 + 1\n\nDone.\n') -# 30 -# >>> doctest.testfile(fn, module_relative=False, verbose=False) -# TestResults(failed=0, attempted=1) -# >>> os.remove(fn) - -# And finally old Mac line endings: - -# >>> fn = tempfile.mktemp() -# >>> with open(fn, 'wb') as f: -# ... f.write(b'Test:\r\r >>> x = 1 + 1\r\rDone.\r') -# 30 -# >>> doctest.testfile(fn, module_relative=False, verbose=False) -# TestResults(failed=0, attempted=1) -# >>> os.remove(fn) - -# Now we test with a package loader that has a get_data method, since that -# bypasses the standard universal newline handling so doctest has to do the -# newline conversion itself; let's make sure it does so correctly (issue 1812). -# We'll write a file inside the package that has all three kinds of line endings -# in it, and use a package hook to install a custom loader; on any platform, -# at least one of the line endings will raise a ValueError for inconsistent -# whitespace if doctest does not correctly do the newline conversion. - -# >>> from test.support import os_helper -# >>> import shutil -# >>> dn = tempfile.mkdtemp() -# >>> pkg = os.path.join(dn, "doctest_testpkg") -# >>> os.mkdir(pkg) -# >>> os_helper.create_empty_file(os.path.join(pkg, "__init__.py")) -# >>> fn = os.path.join(pkg, "doctest_testfile.txt") -# >>> with open(fn, 'wb') as f: -# ... f.write( -# ... b'Test:\r\n\r\n' -# ... b' >>> x = 1 + 1\r\n\r\n' -# ... b'Done.\r\n' -# ... b'Test:\n\n' -# ... b' >>> x = 1 + 1\n\n' -# ... b'Done.\n' -# ... b'Test:\r\r' -# ... b' >>> x = 1 + 1\r\r' -# ... b'Done.\r' -# ... ) -# 95 -# >>> with test_hook(dn): -# ... doctest.testfile("doctest_testfile.txt", package="doctest_testpkg", verbose=False) -# TestResults(failed=0, attempted=3) -# >>> shutil.rmtree(dn) - -# """ + +def test_lineendings(): r""" +*nix systems use \n line endings, while Windows systems use \r\n, and +old Mac systems used \r, which Python still recognizes as a line ending. Python +handles this using universal newline mode for reading files. Let's make +sure doctest does so (issue 8473) by creating temporary test files using each +of the three line disciplines. At least one will not match either the universal +newline \n or os.linesep for the platform the test is run on. + +Windows line endings first: + + >>> import tempfile, os + >>> fn = tempfile.mktemp() + >>> with open(fn, 'wb') as f: + ... f.write(b'Test:\r\n\r\n >>> x = 1 + 1\r\n\r\nDone.\r\n') + 35 + >>> doctest.testfile(fn, module_relative=False, verbose=False) + TestResults(failed=0, attempted=1) + >>> os.remove(fn) + +And now *nix line endings: + + >>> fn = tempfile.mktemp() + >>> with open(fn, 'wb') as f: + ... f.write(b'Test:\n\n >>> x = 1 + 1\n\nDone.\n') + 30 + >>> doctest.testfile(fn, module_relative=False, verbose=False) + TestResults(failed=0, attempted=1) + >>> os.remove(fn) + +And finally old Mac line endings: + + >>> fn = tempfile.mktemp() + >>> with open(fn, 'wb') as f: + ... f.write(b'Test:\r\r >>> x = 1 + 1\r\rDone.\r') + 30 + >>> doctest.testfile(fn, module_relative=False, verbose=False) + TestResults(failed=0, attempted=1) + >>> os.remove(fn) + +Now we test with a package loader that has a get_data method, since that +bypasses the standard universal newline handling so doctest has to do the +newline conversion itself; let's make sure it does so correctly (issue 1812). +We'll write a file inside the package that has all three kinds of line endings +in it, and use a package hook to install a custom loader; on any platform, +at least one of the line endings will raise a ValueError for inconsistent +whitespace if doctest does not correctly do the newline conversion. + + >>> from test.support import os_helper + >>> import shutil + >>> dn = tempfile.mkdtemp() + >>> pkg = os.path.join(dn, "doctest_testpkg") + >>> os.mkdir(pkg) + >>> os_helper.create_empty_file(os.path.join(pkg, "__init__.py")) + >>> fn = os.path.join(pkg, "doctest_testfile.txt") + >>> with open(fn, 'wb') as f: + ... f.write( + ... b'Test:\r\n\r\n' + ... b' >>> x = 1 + 1\r\n\r\n' + ... b'Done.\r\n' + ... b'Test:\n\n' + ... b' >>> x = 1 + 1\n\n' + ... b'Done.\n' + ... b'Test:\r\r' + ... b' >>> x = 1 + 1\r\r' + ... b'Done.\r' + ... ) + 95 + >>> with test_hook(dn): + ... doctest.testfile("doctest_testfile.txt", package="doctest_testpkg", verbose=False) + TestResults(failed=0, attempted=3) + >>> shutil.rmtree(dn) + +""" def test_testmod(): r""" Tests for the testmod function. More might be useful, but for now we're just @@ -3364,142 +3246,109 @@ def test_testmod(): r""" TestResults(failed=0, attempted=0) """ -# TODO: RUSTPYTHON -# traceback + error message is different than in CPython -# def test_testmod_errors(): r""" -# Tests for error reporting in the testmod() function. - -# >>> import test.test_doctest.sample_doctest_errors as mod -# >>> doctest.testmod(mod, verbose=False) # doctest: +ELLIPSIS -# ********************************************************************** -# File "...sample_doctest_errors.py", line 5, in test.test_doctest.sample_doctest_errors -# Failed example: -# 2 + 2 -# Expected: -# 5 -# Got: -# 4 -# ********************************************************************** -# File "...sample_doctest_errors.py", line 7, in test.test_doctest.sample_doctest_errors -# Failed example: -# 1/0 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# 1/0 -# ~^~ -# ZeroDivisionError: division by zero -# ********************************************************************** -# File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad -# Failed example: -# 2 + 2 -# Expected: -# 5 -# Got: -# 4 -# ********************************************************************** -# File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad -# Failed example: -# 1/0 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# 1/0 -# ~^~ -# ZeroDivisionError: division by zero -# ********************************************************************** -# File "...sample_doctest_errors.py", line 16, in test.test_doctest.sample_doctest_errors.errors -# Failed example: -# 2 + 2 -# Expected: -# 5 -# Got: -# 4 -# ********************************************************************** -# File "...sample_doctest_errors.py", line 18, in test.test_doctest.sample_doctest_errors.errors -# Failed example: -# 1/0 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# 1/0 -# ~^~ -# ZeroDivisionError: division by zero -# ********************************************************************** -# File "...sample_doctest_errors.py", line 23, in test.test_doctest.sample_doctest_errors.errors -# Failed example: -# f() -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# f() -# ~^^ -# File "", line 2, in f -# 2 + '2' -# ~~^~~~~ -# TypeError: ... -# ********************************************************************** -# File "...sample_doctest_errors.py", line 25, in test.test_doctest.sample_doctest_errors.errors -# Failed example: -# g() -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# g() -# ~^^ -# File "...sample_doctest_errors.py", line 12, in g -# [][0] # line 12 -# ~~^^^ -# IndexError: list index out of range -# ********************************************************************** -# File "...sample_doctest_errors.py", line 31, in test.test_doctest.sample_doctest_errors.syntax_error -# Failed example: -# 2+*3 -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^ -# File "", line 1 -# 2+*3 -# ^ -# SyntaxError: invalid syntax -# ********************************************************************** -# 4 items had failures: -# 2 of 2 in test.test_doctest.sample_doctest_errors -# 2 of 2 in test.test_doctest.sample_doctest_errors.__test__.bad -# 4 of 5 in test.test_doctest.sample_doctest_errors.errors -# 1 of 1 in test.test_doctest.sample_doctest_errors.syntax_error -# ***Test Failed*** 9 failures. -# TestResults(failed=9, attempted=10) -# """ +def test_testmod_errors(): r""" +Tests for error reporting in the testmod() function. + + >>> import test.test_doctest.sample_doctest_errors as mod + >>> doctest.testmod(mod, verbose=False) # doctest: +ELLIPSIS + ********************************************************************** + File "...sample_doctest_errors.py", line 5, in test.test_doctest.sample_doctest_errors + Failed example: + 2 + 2 + Expected: + 5 + Got: + 4 + ********************************************************************** + File "...sample_doctest_errors.py", line 7, in test.test_doctest.sample_doctest_errors + Failed example: + 1/0 + Exception raised: + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + ********************************************************************** + File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad + Failed example: + 2 + 2 + Expected: + 5 + Got: + 4 + ********************************************************************** + File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad + Failed example: + 1/0 + Exception raised: + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + ********************************************************************** + File "...sample_doctest_errors.py", line 16, in test.test_doctest.sample_doctest_errors.errors + Failed example: + 2 + 2 + Expected: + 5 + Got: + 4 + ********************************************************************** + File "...sample_doctest_errors.py", line 18, in test.test_doctest.sample_doctest_errors.errors + Failed example: + 1/0 + Exception raised: + Traceback (most recent call last): + File "", line 1, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + ********************************************************************** + File "...sample_doctest_errors.py", line 23, in test.test_doctest.sample_doctest_errors.errors + Failed example: + f() + Exception raised: + Traceback (most recent call last): + File "", line 1, in + f() + ~^^ + File "", line 2, in f + 2 + '2' + ~~^~~~~ + TypeError: ... + ********************************************************************** + File "...sample_doctest_errors.py", line 25, in test.test_doctest.sample_doctest_errors.errors + Failed example: + g() + Exception raised: + Traceback (most recent call last): + File "", line 1, in + g() + ~^^ + File "...sample_doctest_errors.py", line 12, in g + [][0] # line 12 + ~~^^^ + IndexError: list index out of range + ********************************************************************** + File "...sample_doctest_errors.py", line 31, in test.test_doctest.sample_doctest_errors.syntax_error + Failed example: + 2+*3 + Exception raised: + File "", line 1 + 2+*3 + ^ + SyntaxError: invalid syntax + ********************************************************************** + 4 items had failures: + 2 of 2 in test.test_doctest.sample_doctest_errors + 2 of 2 in test.test_doctest.sample_doctest_errors.__test__.bad + 4 of 5 in test.test_doctest.sample_doctest_errors.errors + 1 of 1 in test.test_doctest.sample_doctest_errors.syntax_error + ***Test Failed*** 9 failures. + TestResults(failed=9, attempted=10) +""" try: os.fsencode("foo-bär@baz.py") @@ -3508,278 +3357,269 @@ def test_testmod(): r""" # Skip the test: the filesystem encoding is unable to encode the filename supports_unicode = False -# TODO: RUSTPYTHON -# traceback message is different than in CPython -# if supports_unicode: -# def test_unicode(): """ -# Check doctest with a non-ascii filename: - -# >>> save_colorize = _colorize.COLORIZE -# >>> _colorize.COLORIZE = False - -# >>> doc = ''' -# ... >>> raise Exception('clé') -# ... ''' -# ... -# >>> parser = doctest.DocTestParser() -# >>> test = parser.get_doctest(doc, {}, "foo-bär@baz", "foo-bär@baz.py", 0) -# >>> test -# -# >>> runner = doctest.DocTestRunner(verbose=False) -# >>> runner.run(test) # doctest: +ELLIPSIS -# ********************************************************************** -# File "foo-bär@baz.py", line 2, in foo-bär@baz -# Failed example: -# raise Exception('clé') -# Exception raised: -# Traceback (most recent call last): -# File ... -# exec(compile(example.source, filename, "single", -# ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# compileflags, True), test.globs) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# File "", line 1, in -# raise Exception('clé') -# Exception: clé -# TestResults(failed=1, attempted=1) - -# >>> _colorize.COLORIZE = save_colorize -# """ - - -# TODO: RUSTPYTHON -# Difference in Error: -# FileNotFoundError: [Errno ...] ...nosuchfile... vs FileNotFoundError: (2, 'No such file or directory') -# @doctest_skip_if(not support.has_subprocess_support) -# def test_CLI(): r""" -# The doctest module can be used to run doctests against an arbitrary file. -# These tests test this CLI functionality. - -# We'll use the support module's script_helpers for this, and write a test files -# to a temp dir to run the command against. Due to a current limitation in -# script_helpers, though, we need a little utility function to turn the returned -# output into something we can doctest against: - -# >>> def normalize(s): -# ... return '\n'.join(s.decode().splitlines()) - -# With those preliminaries out of the way, we'll start with a file with two -# simple tests and no errors. We'll run both the unadorned doctest command, and -# the verbose version, and then check the output: - -# >>> from test.support import script_helper -# >>> from test.support.os_helper import temp_dir -# >>> with temp_dir() as tmpdir: -# ... fn = os.path.join(tmpdir, 'myfile.doc') -# ... with open(fn, 'w', encoding='utf-8') as f: -# ... _ = f.write('This is a very simple test file.\n') -# ... _ = f.write(' >>> 1 + 1\n') -# ... _ = f.write(' 2\n') -# ... _ = f.write(' >>> "a"\n') -# ... _ = f.write(" 'a'\n") -# ... _ = f.write('\n') -# ... _ = f.write('And that is it.\n') -# ... rc1, out1, err1 = script_helper.assert_python_ok( -# ... '-m', 'doctest', fn) -# ... rc2, out2, err2 = script_helper.assert_python_ok( -# ... '-m', 'doctest', '-v', fn) - -# With no arguments and passing tests, we should get no output: - -# >>> rc1, out1, err1 -# (0, b'', b'') - -# With the verbose flag, we should see the test output, but no error output: - -# >>> rc2, err2 -# (0, b'') -# >>> print(normalize(out2)) -# Trying: -# 1 + 1 -# Expecting: -# 2 -# ok -# Trying: -# "a" -# Expecting: -# 'a' -# ok -# 1 item passed all tests: -# 2 tests in myfile.doc -# 2 tests in 1 item. -# 2 passed. -# Test passed. - -# Now we'll write a couple files, one with three tests, the other a python module -# with two tests, both of the files having "errors" in the tests that can be made -# non-errors by applying the appropriate doctest options to the run (ELLIPSIS in -# the first file, NORMALIZE_WHITESPACE in the second). This combination will -# allow thoroughly testing the -f and -o flags, as well as the doctest command's -# ability to process more than one file on the command line and, since the second -# file ends in '.py', its handling of python module files (as opposed to straight -# text files). - -# >>> from test.support import script_helper -# >>> from test.support.os_helper import temp_dir -# >>> with temp_dir() as tmpdir: -# ... fn = os.path.join(tmpdir, 'myfile.doc') -# ... with open(fn, 'w', encoding="utf-8") as f: -# ... _ = f.write('This is another simple test file.\n') -# ... _ = f.write(' >>> 1 + 1\n') -# ... _ = f.write(' 2\n') -# ... _ = f.write(' >>> "abcdef"\n') -# ... _ = f.write(" 'a...f'\n") -# ... _ = f.write(' >>> "ajkml"\n') -# ... _ = f.write(" 'a...l'\n") -# ... _ = f.write('\n') -# ... _ = f.write('And that is it.\n') -# ... fn2 = os.path.join(tmpdir, 'myfile2.py') -# ... with open(fn2, 'w', encoding='utf-8') as f: -# ... _ = f.write('def test_func():\n') -# ... _ = f.write(' \"\"\"\n') -# ... _ = f.write(' This is simple python test function.\n') -# ... _ = f.write(' >>> 1 + 1\n') -# ... _ = f.write(' 2\n') -# ... _ = f.write(' >>> "abc def"\n') -# ... _ = f.write(" 'abc def'\n") -# ... _ = f.write("\n") -# ... _ = f.write(' \"\"\"\n') -# ... rc1, out1, err1 = script_helper.assert_python_failure( -# ... '-m', 'doctest', fn, fn2) -# ... rc2, out2, err2 = script_helper.assert_python_ok( -# ... '-m', 'doctest', '-o', 'ELLIPSIS', fn) -# ... rc3, out3, err3 = script_helper.assert_python_ok( -# ... '-m', 'doctest', '-o', 'ELLIPSIS', -# ... '-o', 'NORMALIZE_WHITESPACE', fn, fn2) -# ... rc4, out4, err4 = script_helper.assert_python_failure( -# ... '-m', 'doctest', '-f', fn, fn2) -# ... rc5, out5, err5 = script_helper.assert_python_ok( -# ... '-m', 'doctest', '-v', '-o', 'ELLIPSIS', -# ... '-o', 'NORMALIZE_WHITESPACE', fn, fn2) - -# Our first test run will show the errors from the first file (doctest stops if a -# file has errors). Note that doctest test-run error output appears on stdout, -# not stderr: - -# >>> rc1, err1 -# (1, b'') -# >>> print(normalize(out1)) # doctest: +ELLIPSIS -# ********************************************************************** -# File "...myfile.doc", line 4, in myfile.doc -# Failed example: -# "abcdef" -# Expected: -# 'a...f' -# Got: -# 'abcdef' -# ********************************************************************** -# File "...myfile.doc", line 6, in myfile.doc -# Failed example: -# "ajkml" -# Expected: -# 'a...l' -# Got: -# 'ajkml' -# ********************************************************************** -# 1 item had failures: -# 2 of 3 in myfile.doc -# ***Test Failed*** 2 failures. - -# With -o ELLIPSIS specified, the second run, against just the first file, should -# produce no errors, and with -o NORMALIZE_WHITESPACE also specified, neither -# should the third, which ran against both files: - -# >>> rc2, out2, err2 -# (0, b'', b'') -# >>> rc3, out3, err3 -# (0, b'', b'') - -# The fourth run uses FAIL_FAST, so we should see only one error: - -# >>> rc4, err4 -# (1, b'') -# >>> print(normalize(out4)) # doctest: +ELLIPSIS -# ********************************************************************** -# File "...myfile.doc", line 4, in myfile.doc -# Failed example: -# "abcdef" -# Expected: -# 'a...f' -# Got: -# 'abcdef' -# ********************************************************************** -# 1 item had failures: -# 1 of 2 in myfile.doc -# ***Test Failed*** 1 failure. - -# The fifth test uses verbose with the two options, so we should get verbose -# success output for the tests in both files: - -# >>> rc5, err5 -# (0, b'') -# >>> print(normalize(out5)) -# Trying: -# 1 + 1 -# Expecting: -# 2 -# ok -# Trying: -# "abcdef" -# Expecting: -# 'a...f' -# ok -# Trying: -# "ajkml" -# Expecting: -# 'a...l' -# ok -# 1 item passed all tests: -# 3 tests in myfile.doc -# 3 tests in 1 item. -# 3 passed. -# Test passed. -# Trying: -# 1 + 1 -# Expecting: -# 2 -# ok -# Trying: -# "abc def" -# Expecting: -# 'abc def' -# ok -# 1 item had no tests: -# myfile2 -# 1 item passed all tests: -# 2 tests in myfile2.test_func -# 2 tests in 2 items. -# 2 passed. -# Test passed. - -# We should also check some typical error cases. - -# Invalid file name: - -# >>> rc, out, err = script_helper.assert_python_failure( -# ... '-m', 'doctest', 'nosuchfile') -# >>> rc, out -# (1, b'') -# >>> # The exact error message changes depending on the platform. -# >>> print(normalize(err)) # doctest: +ELLIPSIS -# Traceback (most recent call last): -# ... -# FileNotFoundError: [Errno ...] ...nosuchfile... - -# Invalid doctest option: - -# >>> rc, out, err = script_helper.assert_python_failure( -# ... '-m', 'doctest', '-o', 'nosuchoption') -# >>> rc, out -# (2, b'') -# >>> print(normalize(err)) # doctest: +ELLIPSIS -# usage...invalid...nosuchoption... - -# """ +if supports_unicode: + def test_unicode(): """ +Check doctest with a non-ascii filename: + + >>> import _colorize + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + + >>> doc = ''' + ... >>> raise Exception('clé') + ... ''' + ... + >>> parser = doctest.DocTestParser() + >>> test = parser.get_doctest(doc, {}, "foo-bär@baz", "foo-bär@baz.py", 0) + >>> test + + >>> runner = doctest.DocTestRunner(verbose=False) + >>> runner.run(test) # doctest: +ELLIPSIS + ********************************************************************** + File "foo-bär@baz.py", line 2, in foo-bär@baz + Failed example: + raise Exception('clé') + Exception raised: + Traceback (most recent call last): + File "", line 1, in + raise Exception('clé') + Exception: clé + TestResults(failed=1, attempted=1) + + >>> _colorize.COLORIZE = save_colorize + """ + + +@doctest_skip_if(not support.has_subprocess_support) +def test_CLI(): r""" +The doctest module can be used to run doctests against an arbitrary file. +These tests test this CLI functionality. + +We'll use the support module's script_helpers for this, and write a test files +to a temp dir to run the command against. Due to a current limitation in +script_helpers, though, we need a little utility function to turn the returned +output into something we can doctest against: + + >>> def normalize(s): + ... return '\n'.join(s.decode().splitlines()) + +With those preliminaries out of the way, we'll start with a file with two +simple tests and no errors. We'll run both the unadorned doctest command, and +the verbose version, and then check the output: + + >>> from test.support import script_helper + >>> from test.support.os_helper import temp_dir + >>> with temp_dir() as tmpdir: + ... fn = os.path.join(tmpdir, 'myfile.doc') + ... with open(fn, 'w', encoding='utf-8') as f: + ... _ = f.write('This is a very simple test file.\n') + ... _ = f.write(' >>> 1 + 1\n') + ... _ = f.write(' 2\n') + ... _ = f.write(' >>> "a"\n') + ... _ = f.write(" 'a'\n") + ... _ = f.write('\n') + ... _ = f.write('And that is it.\n') + ... rc1, out1, err1 = script_helper.assert_python_ok( + ... '-m', 'doctest', fn) + ... rc2, out2, err2 = script_helper.assert_python_ok( + ... '-m', 'doctest', '-v', fn) + +With no arguments and passing tests, we should get no output: + + >>> rc1, out1, err1 + (0, b'', b'') + +With the verbose flag, we should see the test output, but no error output: + + >>> rc2, err2 + (0, b'') + >>> print(normalize(out2)) + Trying: + 1 + 1 + Expecting: + 2 + ok + Trying: + "a" + Expecting: + 'a' + ok + 1 item passed all tests: + 2 tests in myfile.doc + 2 tests in 1 item. + 2 passed. + Test passed. + +Now we'll write a couple files, one with three tests, the other a python module +with two tests, both of the files having "errors" in the tests that can be made +non-errors by applying the appropriate doctest options to the run (ELLIPSIS in +the first file, NORMALIZE_WHITESPACE in the second). This combination will +allow thoroughly testing the -f and -o flags, as well as the doctest command's +ability to process more than one file on the command line and, since the second +file ends in '.py', its handling of python module files (as opposed to straight +text files). + + >>> from test.support import script_helper + >>> from test.support.os_helper import temp_dir + >>> with temp_dir() as tmpdir: + ... fn = os.path.join(tmpdir, 'myfile.doc') + ... with open(fn, 'w', encoding="utf-8") as f: + ... _ = f.write('This is another simple test file.\n') + ... _ = f.write(' >>> 1 + 1\n') + ... _ = f.write(' 2\n') + ... _ = f.write(' >>> "abcdef"\n') + ... _ = f.write(" 'a...f'\n") + ... _ = f.write(' >>> "ajkml"\n') + ... _ = f.write(" 'a...l'\n") + ... _ = f.write('\n') + ... _ = f.write('And that is it.\n') + ... fn2 = os.path.join(tmpdir, 'myfile2.py') + ... with open(fn2, 'w', encoding='utf-8') as f: + ... _ = f.write('def test_func():\n') + ... _ = f.write(' \"\"\"\n') + ... _ = f.write(' This is simple python test function.\n') + ... _ = f.write(' >>> 1 + 1\n') + ... _ = f.write(' 2\n') + ... _ = f.write(' >>> "abc def"\n') + ... _ = f.write(" 'abc def'\n") + ... _ = f.write("\n") + ... _ = f.write(' \"\"\"\n') + ... rc1, out1, err1 = script_helper.assert_python_failure( + ... '-m', 'doctest', fn, fn2) + ... rc2, out2, err2 = script_helper.assert_python_ok( + ... '-m', 'doctest', '-o', 'ELLIPSIS', fn) + ... rc3, out3, err3 = script_helper.assert_python_ok( + ... '-m', 'doctest', '-o', 'ELLIPSIS', + ... '-o', 'NORMALIZE_WHITESPACE', fn, fn2) + ... rc4, out4, err4 = script_helper.assert_python_failure( + ... '-m', 'doctest', '-f', fn, fn2) + ... rc5, out5, err5 = script_helper.assert_python_ok( + ... '-m', 'doctest', '-v', '-o', 'ELLIPSIS', + ... '-o', 'NORMALIZE_WHITESPACE', fn, fn2) + +Our first test run will show the errors from the first file (doctest stops if a +file has errors). Note that doctest test-run error output appears on stdout, +not stderr: + + >>> rc1, err1 + (1, b'') + >>> print(normalize(out1)) # doctest: +ELLIPSIS + ********************************************************************** + File "...myfile.doc", line 4, in myfile.doc + Failed example: + "abcdef" + Expected: + 'a...f' + Got: + 'abcdef' + ********************************************************************** + File "...myfile.doc", line 6, in myfile.doc + Failed example: + "ajkml" + Expected: + 'a...l' + Got: + 'ajkml' + ********************************************************************** + 1 item had failures: + 2 of 3 in myfile.doc + ***Test Failed*** 2 failures. + +With -o ELLIPSIS specified, the second run, against just the first file, should +produce no errors, and with -o NORMALIZE_WHITESPACE also specified, neither +should the third, which ran against both files: + + >>> rc2, out2, err2 + (0, b'', b'') + >>> rc3, out3, err3 + (0, b'', b'') + +The fourth run uses FAIL_FAST, so we should see only one error: + + >>> rc4, err4 + (1, b'') + >>> print(normalize(out4)) # doctest: +ELLIPSIS + ********************************************************************** + File "...myfile.doc", line 4, in myfile.doc + Failed example: + "abcdef" + Expected: + 'a...f' + Got: + 'abcdef' + ********************************************************************** + 1 item had failures: + 1 of 2 in myfile.doc + ***Test Failed*** 1 failure. + +The fifth test uses verbose with the two options, so we should get verbose +success output for the tests in both files: + + >>> rc5, err5 + (0, b'') + >>> print(normalize(out5)) + Trying: + 1 + 1 + Expecting: + 2 + ok + Trying: + "abcdef" + Expecting: + 'a...f' + ok + Trying: + "ajkml" + Expecting: + 'a...l' + ok + 1 item passed all tests: + 3 tests in myfile.doc + 3 tests in 1 item. + 3 passed. + Test passed. + Trying: + 1 + 1 + Expecting: + 2 + ok + Trying: + "abc def" + Expecting: + 'abc def' + ok + 1 item had no tests: + myfile2 + 1 item passed all tests: + 2 tests in myfile2.test_func + 2 tests in 2 items. + 2 passed. + Test passed. + +We should also check some typical error cases. + +Invalid file name: + + >>> rc, out, err = script_helper.assert_python_failure( + ... '-m', 'doctest', 'nosuchfile') + >>> rc, out + (1, b'') + >>> # The exact error message changes depending on the platform. + >>> print(normalize(err)) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + FileNotFoundError: [Errno ...] ...nosuchfile... + +Invalid doctest option: + + >>> rc, out, err = script_helper.assert_python_failure( + ... '-m', 'doctest', '-o', 'nosuchoption') + >>> rc, out + (2, b'') + >>> print(normalize(err)) # doctest: +ELLIPSIS + usage...invalid...nosuchoption... + +""" def test_no_trailing_whitespace_stripping(): r""" @@ -3841,6 +3681,7 @@ def test_run_doctestsuite_multiple_times(): def test_exception_with_note(note): """ + >>> import _colorize >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False @@ -3975,6 +3816,7 @@ def test_syntax_error_subclass_from_stdlib(): def test_syntax_error_with_incorrect_expected_note(): """ + >>> import _colorize >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False @@ -4013,7 +3855,8 @@ def test_syntax_error_with_incorrect_expected_note(): def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite(doctest)) - tests.addTest(doctest.DocTestSuite()) + from test.support.rustpython import DocTestChecker # TODO: RUSTPYTHON + tests.addTest(doctest.DocTestSuite(checker=DocTestChecker())) # TODO: RUSTPYTHON return tests diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py index 4574821739b..274ecc40024 100644 --- a/Lib/test/test_extcall.py +++ b/Lib/test/test_extcall.py @@ -435,7 +435,7 @@ empty keyword dictionary to pass without a complaint, but raise a TypeError if te dictionary is not empty - >>> try: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + >>> try: ... silence = id(1, *{}) ... True ... except: diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 5559d58cad4..c6069f6fb2e 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -6,6 +6,7 @@ import unittest import weakref import inspect +import textwrap import types from test import support @@ -48,7 +49,6 @@ def test_raise_and_yield_from(self): class FinalizationTest(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_frame_resurrect(self): # A generator frame can be resurrected by a generator's finalization. def gen(): @@ -68,7 +68,7 @@ def gen(): del frame support.gc_collect() - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true def test_refcycle(self): # A generator caught in a refcycle gets finalized anyway. old_garbage = gc.garbage[:] @@ -84,7 +84,7 @@ def gen(): g = gen() next(g) g.send(g) - self.assertGreater(sys.getrefcount(g), 2) + self.assertGreaterEqual(sys.getrefcount(g), 2) self.assertFalse(finalized) del g support.gc_collect() @@ -114,6 +114,28 @@ def g3(): return (yield from f()) gen.send(2) self.assertEqual(cm.exception.value, 2) + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 1 + def test_generator_resurrect(self): + # Test that a resurrected generator still has a valid gi_code + resurrected = [] + + # Resurrect a generator in a finalizer + exec(textwrap.dedent(""" + def gen(): + try: + yield + except: + resurrected.append(g) + + g = gen() + next(g) + """), {"resurrected": resurrected}) + + support.gc_collect() + + self.assertEqual(len(resurrected), 1) + self.assertIsInstance(resurrected[0].gi_code, types.CodeType) + class GeneratorTest(unittest.TestCase): @@ -248,6 +270,28 @@ def loop(): #This should not raise loop() + def test_genexpr_only_calls_dunder_iter_once(self): + + class Iterator: + + def __init__(self): + self.val = 0 + + def __next__(self): + if self.val == 2: + raise StopIteration + self.val += 1 + return self.val + + # No __iter__ method + + class C: + + def __iter__(self): + return Iterator() + + self.assertEqual([1, 2], list(i for i in C())) + class ModifyUnderlyingIterableTest(unittest.TestCase): iterables = [ @@ -276,23 +320,27 @@ def gen(it): yield x return gen(range(10)) - def process_tests(self, get_generator): + def process_tests(self, get_generator, is_expr): + err_iterator = "'.*' object is not an iterator" + err_iterable = "'.*' object is not iterable" for obj in self.iterables: g_obj = get_generator(obj) with self.subTest(g_obj=g_obj, obj=obj): - self.assertListEqual(list(g_obj), list(obj)) + if is_expr: + self.assertRaisesRegex(TypeError, err_iterator, list, g_obj) + else: + self.assertListEqual(list(g_obj), list(obj)) g_iter = get_generator(iter(obj)) with self.subTest(g_iter=g_iter, obj=obj): self.assertListEqual(list(g_iter), list(obj)) - err_regex = "'.*' object is not iterable" for obj in self.non_iterables: g_obj = get_generator(obj) with self.subTest(g_obj=g_obj): - self.assertRaisesRegex(TypeError, err_regex, list, g_obj) + err = err_iterator if is_expr else err_iterable + self.assertRaisesRegex(TypeError, err, list, g_obj) - @unittest.expectedFailure # AssertionError: TypeError not raised by list def test_modify_f_locals(self): def modify_f_locals(g, local, obj): g.gi_frame.f_locals[local] = obj @@ -304,10 +352,9 @@ def get_generator_genexpr(obj): def get_generator_genfunc(obj): return modify_f_locals(self.genfunc(), 'it', obj) - self.process_tests(get_generator_genexpr) - self.process_tests(get_generator_genfunc) + self.process_tests(get_generator_genexpr, True) + self.process_tests(get_generator_genfunc, False) - @unittest.expectedFailure # AssertionError: "'.*' object is not iterable" does not match "'complex' object is not an iterator" def test_new_gen_from_gi_code(self): def new_gen_from_gi_code(g, obj): generator_func = types.FunctionType(g.gi_code, {}) @@ -319,8 +366,8 @@ def get_generator_genexpr(obj): def get_generator_genfunc(obj): return new_gen_from_gi_code(self.genfunc(), obj) - self.process_tests(get_generator_genexpr) - self.process_tests(get_generator_genfunc) + self.process_tests(get_generator_genexpr, True) + self.process_tests(get_generator_genfunc, False) class ExceptionTest(unittest.TestCase): @@ -451,7 +498,6 @@ def gen(): self.assertEqual(next(g), "done") self.assertIsNone(sys.exception()) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_except_throw_bad_exception(self): class E(Exception): def __new__(cls, *args, **kwargs): @@ -543,7 +589,6 @@ def f(): gen.send(None) self.assertIsNone(gen.close()) - @unittest.expectedFailure # AssertionError: None != 0 def test_close_return_value(self): def f(): try: @@ -590,7 +635,6 @@ def f(): next(gen) self.assertIsNone(gen.close()) - @unittest.expectedFailure # AssertionError: None != 0 def test_close_closed(self): def f(): try: @@ -616,7 +660,7 @@ def f(): with self.assertRaises(RuntimeError): gen.close() - @unittest.expectedFailure # AssertionError: .Foo object at 0xb400007e3c212160> is not None + @unittest.expectedFailure # TODO: RUSTPYTHON; no deterministic GC finalization def test_close_releases_frame_locals(self): # See gh-118272 @@ -679,7 +723,7 @@ def get_frame(index): self.assertIn('a', frame_locals) self.assertEqual(frame_locals['a'], 42) - @unittest.expectedFailure # AssertionError: 'a' not found in {'frame_locals1': None} + @unittest.expectedFailure # TODO: RUSTPYTHON; frame locals don't survive generator deallocation def test_frame_locals_outlive_generator(self): frame_locals1 = None @@ -828,7 +872,8 @@ def check_stack_names(self, frame, expected): while frame: name = frame.f_code.co_name # Stop checking frames when we get to our test helper. - if name.startswith('check_') or name.startswith('call_'): + if (name.startswith('check_') or name.startswith('call_') + or name.startswith('test')): break names.append(name) @@ -869,9 +914,27 @@ def call_throw(gen): self.check_yield_from_example(call_throw) + def test_throw_with_yield_from_custom_generator(self): + + class CustomGen: + def __init__(self, test): + self.test = test + def throw(self, *args): + self.test.check_stack_names(sys._getframe(), ['throw', 'g']) + def __iter__(self): + return self + def __next__(self): + return 42 + + def g(target): + yield from target + + gen = g(CustomGen(self)) + gen.send(None) + gen.throw(RuntimeError) + class YieldFromTests(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_generator_gi_yieldfrom(self): def a(): self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_RUNNING) @@ -1077,7 +1140,7 @@ def b(): File "", line 1, in ? File "", line 2, in g File "", line 2, in f - ZeroDivisionError: integer division or modulo by zero + ZeroDivisionError: division by zero >>> next(k) # and the generator cannot be resumed Traceback (most recent call last): File "", line 1, in ? @@ -1271,7 +1334,7 @@ def b(): >>> [s for s in dir(i) if not s.startswith('_')] ['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw'] >>> from test.support import HAVE_DOCSTRINGS ->>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') +>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE Implement next(self). >>> iter(i) is i True @@ -2423,17 +2486,17 @@ def printsolution(self, x): ... SyntaxError: 'yield from' outside function ->>> def f(): x = yield = y +>>> def f(): x = yield = y # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE Traceback (most recent call last): ... SyntaxError: assignment to yield expression not possible ->>> def f(): (yield bar) = y +>>> def f(): (yield bar) = y # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE Traceback (most recent call last): ... SyntaxError: cannot assign to yield expression here. Maybe you meant '==' instead of '='? ->>> def f(): (yield bar) += y +>>> def f(): (yield bar) += y # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE Traceback (most recent call last): ... SyntaxError: 'yield expression' is an illegal expression for augmented assignment @@ -2629,17 +2692,21 @@ def printsolution(self, x): Our ill-behaved code should be invoked during GC: ->>> with support.catch_unraisable_exception() as cm: +>>> with support.catch_unraisable_exception() as cm: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE ... g = f() ... next(g) +... gen_repr = repr(g) ... del g ... +... cm.unraisable.err_msg == (f'Exception ignored while closing ' +... f'generator {gen_repr}') ... cm.unraisable.exc_type == RuntimeError ... "generator ignored GeneratorExit" in str(cm.unraisable.exc_value) ... cm.unraisable.exc_traceback is not None True True True +True And errors thrown during closing should propagate: @@ -2743,11 +2810,13 @@ def printsolution(self, x): ... raise RuntimeError(message) ... invoke("del failed") ... ->>> with support.catch_unraisable_exception() as cm: -... l = Leaker() -... del l +>>> with support.catch_unraisable_exception() as cm: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE +... leaker = Leaker() +... del_repr = repr(type(leaker).__del__) +... del leaker ... -... cm.unraisable.object == Leaker.__del__ +... cm.unraisable.err_msg == (f'Exception ignored while ' +... f'calling deallocator {del_repr}') ... cm.unraisable.exc_type == RuntimeError ... str(cm.unraisable.exc_value) == "del failed" ... cm.unraisable.exc_traceback is not None @@ -2774,7 +2843,8 @@ def printsolution(self, x): } def load_tests(loader, tests, pattern): - # tests.addTest(doctest.DocTestSuite()) # TODO: RUSTPYTHON + from test.support.rustpython import DocTestChecker # TODO: RUSTPYTHON + tests.addTest(doctest.DocTestSuite(checker=DocTestChecker())) # TODO: RUSTPYTHON return tests diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index b7ea0999517..4de421180ca 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -122,7 +122,7 @@ def istest(self, predicate, exp): else: self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp)) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; First has 0, Second has 1: 'iskeyword' def test__all__(self): support.check__all__(self, inspect, not_exported=("modulesbyfile",), extra=("get_annotations",)) @@ -175,7 +175,7 @@ def __get__(self, instance, owner): class TestPredicates(IsTestBase): - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: type object 'defaultdict' has no attribute 'default_factory' def test_excluding_predicates(self): global tb self.istest(inspect.isbuiltin, 'sys.exit') @@ -237,7 +237,7 @@ class FakePackage: self.assertFalse(inspect.ispackage(FakePackage())) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true def test_iscoroutine(self): async_gen_coro = async_generator_function_example(1) gen_coro = gen_coroutine_function_example(1) @@ -480,7 +480,7 @@ class C(object): self.assertIn('a', members) self.assertNotIn('b', members) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true def test_isabstract(self): from abc import ABCMeta, abstractmethod @@ -503,7 +503,7 @@ def foo(self): self.assertFalse(inspect.isabstract(int)) self.assertFalse(inspect.isabstract(5)) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; + [True, False] def test_isabstract_during_init_subclass(self): from abc import ABCMeta, abstractmethod isabstract_checks = [] @@ -589,7 +589,7 @@ def test_frame(self): self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals), '(x=11, y=14)') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'NoneType' object has no attribute 'f_code' def test_previous_frame(self): args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back) self.assertEqual(args, ['a', 'b', 'c', 'd', 'e', 'f']) @@ -888,7 +888,7 @@ def test_getsource_on_generated_class(self): self.assertRaises(OSError, inspect.getsourcelines, A) self.assertIsNone(inspect.getcomments(A)) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: OSError not raised by getsource def test_getsource_on_class_without_firstlineno(self): __firstlineno__ = 1 class C: @@ -910,7 +910,7 @@ def test_getsource_stdlib_tomllib(self): self.assertRaises(OSError, inspect.getsource, tomllib.TOMLDecodeError) self.assertRaises(OSError, inspect.getsourcelines, tomllib.TOMLDecodeError) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: OSError not raised by getsource def test_getsource_stdlib_abc(self): # Pure Python implementation abc = import_helper.import_fresh_module('abc', blocked=['_abc']) @@ -962,7 +962,7 @@ def test_range_traceback_toplevel_frame(self): class TestDecorators(GetSourceBase): fodderModule = mod2 - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; pass def test_wrapped_decorator(self): self.assertSourceEqual(mod2.wrapped, 14, 17) @@ -1163,7 +1163,7 @@ def test_nested_class_definition(self): self.assertSourceEqual(mod2.cls183, 183, 188) self.assertSourceEqual(mod2.cls183.cls185, 185, 188) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; pass def test_class_decorator(self): self.assertSourceEqual(mod2.cls196, 194, 201) self.assertSourceEqual(mod2.cls196.cls200, 198, 201) @@ -1260,7 +1260,7 @@ def test_class(self): class TestComplexDecorator(GetSourceBase): fodderModule = mod2 - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; return foo + bar() def test_parens_in_decorator(self): self.assertSourceEqual(self.fodderModule.complex_decorated, 273, 275) @@ -1537,7 +1537,7 @@ def m1(self): pass self.assertIn(('md', 'method', A), attrs, 'missing method descriptor') self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ('to_bytes', 'method', ) not found in [('__abs__', 'method', ), ('__add__', 'method', ), ('__and__', 'method', ), ('__bool__', 'method', ), ('__ceil__', 'class method', ), ('__class__', 'data', ), ('__delattr__', 'class method', ), ('__dir__', 'class method', ), ('__divmod__', 'method', ), ('__doc__', 'data', ), ('__eq__', 'class method', ), ('__float__', 'method', ), ('__floor__', 'class method', ), ('__floordiv__', 'method', ), ('__format__', 'class method', ), ('__ge__', 'class method', ), ('__getattribute__', 'class method', ), ('__getnewargs__', 'class method', ), ('__getstate__', 'class method', ), ('__gt__', 'class method', ), ('__hash__', 'method', ), ('__index__', 'method', ), ('__init__', 'method', ), ('__init_subclass__', 'class method', ), ('__int__', 'method', ), ('__invert__', 'method', ), ('__le__', 'class method', ), ('__lshift__', 'method', ), ('__lt__', 'class method', ), ('__mod__', 'method', ), ('__mul__', 'method', ), ('__ne__', 'class method', ), ('__neg__', 'method', ), ('__new__', 'static method', ), ('__or__', 'method', ), ('__pos__', 'method', ), ('__pow__', 'method', ), ('__radd__', 'method', ), ('__rand__', 'method', ), ('__rdivmod__', 'method', ), ('__reduce__', 'class method', ), ('__reduce_ex__', 'class method', ), ('__repr__', 'method', ), ('__rfloordiv__', 'method', ), ('__rlshift__', 'method', ), ('__rmod__', 'method', ), ('__rmul__', 'method', ), ('__ror__', 'method', ), ('__round__', 'class method', ), ('__rpow__', 'method', ), ('__rrshift__', 'method', ), ('__rshift__', 'method', ), ('__rsub__', 'method', ), ('__rtruediv__', 'method', ), ('__rxor__', 'method', ), ('__setattr__', 'class method', ), ('__sizeof__', 'class method', ), ('__str__', 'method', ), ('__sub__', 'method', ), ('__subclasshook__', 'class method', ), ('__truediv__', 'method', ), ('__trunc__', 'class method', ), ('__xor__', 'method', ), ('as_integer_ratio', 'class method', ), ('bit_count', 'class method', ), ('bit_length', 'class method', ), ('conjugate', 'class method', ), ('denominator', 'data', ), ('from_bytes', 'class method', ), ('imag', 'data', ), ('is_integer', 'class method', ), ('numerator', 'data', ), ('real', 'data', ), ('to_bytes', 'class method', )] : missing plain method def test_classify_builtin_types(self): # Simple sanity check that all built-in types can have their # attributes classified. @@ -2020,7 +2020,7 @@ def function(): _global_ref = object() class TestGetClosureVars(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[132 chars]={'print': }, unbound=set()) != Closu[132 chars]={'print': }, unbound={'unbound_ref'}) def test_name_resolution(self): # Basic test of the 4 different resolution mechanisms def f(nonlocal_ref): @@ -2036,7 +2036,7 @@ def g(local_ref): builtin_vars, unbound_names) self.assertEqual(inspect.getclosurevars(f(_arg)), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[132 chars]={'print': }, unbound=set()) != Closu[132 chars]={'print': }, unbound={'unbound_ref'}) def test_generator_closure(self): def f(nonlocal_ref): def g(local_ref): @@ -2052,7 +2052,7 @@ def g(local_ref): builtin_vars, unbound_names) self.assertEqual(inspect.getclosurevars(f(_arg)), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[132 chars]={'print': }, unbound=set()) != Closu[132 chars]={'print': }, unbound={'unbound_ref'}) def test_method_closure(self): class C: def f(self, nonlocal_ref): @@ -2068,7 +2068,7 @@ def g(local_ref): builtin_vars, unbound_names) self.assertEqual(inspect.getclosurevars(C().f(_arg)), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[139 chars]als={}, builtins={'print': }, unbound=set()) != Closu[34 chars]uiltins={'print': }, unbound={'path'}) def test_builtins_fallback(self): f, ns = self._private_globals() ns.pop("__builtins__", None) expected = inspect.ClosureVars({}, {}, {"print":print}, {"path"}) self.assertEqual(inspect.getclosurevars(f), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ClosureVars(nonlocals={}, globals={}, builtins={}, unbound={'print'}) != ClosureVars(nonlocals={}, globals={}, builtins={'path': 1}, unbound={'print'}) def test_builtins_as_dict(self): f, ns = self._private_globals() ns["__builtins__"] = {"path":1} expected = inspect.ClosureVars({}, {}, {"path":1}, {"print"}) self.assertEqual(inspect.getclosurevars(f), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[38 chars]ins={}, unbound={'print'}) != Closu[38 chars]ins={'path': () got an unexpected keyword argument 'x' def test_errors(self): f0 = self.makeCallable('') f1 = self.makeCallable('a, b') @@ -2743,28 +2743,23 @@ def number_generator(): def _generatorstate(self): return inspect.getgeneratorstate(self.generator) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_created(self): self.assertEqual(self._generatorstate(), inspect.GEN_CREATED) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_suspended(self): next(self.generator) self.assertEqual(self._generatorstate(), inspect.GEN_SUSPENDED) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_closed_after_exhaustion(self): for i in self.generator: pass self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_closed_after_immediate_exception(self): with self.assertRaises(RuntimeError): self.generator.throw(RuntimeError) self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_closed_after_close(self): self.generator.close() self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) @@ -2882,16 +2877,16 @@ def tearDown(self): def _coroutinestate(self): return inspect.getcoroutinestate(self.coroutine) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'coroutine' object has no attribute 'cr_suspended' def test_created(self): self.assertEqual(self._coroutinestate(), inspect.CORO_CREATED) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'coroutine' object has no attribute 'cr_suspended' def test_suspended(self): self.coroutine.send(None) self.assertEqual(self._coroutinestate(), inspect.CORO_SUSPENDED) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'coroutine' object has no attribute 'cr_suspended' def test_closed_after_exhaustion(self): while True: try: @@ -2901,13 +2896,13 @@ def test_closed_after_exhaustion(self): self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'coroutine' object has no attribute 'cr_suspended' def test_closed_after_immediate_exception(self): with self.assertRaises(RuntimeError): self.coroutine.throw(RuntimeError) self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'coroutine' object has no attribute 'cr_suspended' def test_closed_after_close(self): self.coroutine.close() self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) @@ -2957,17 +2952,17 @@ def tearDownClass(cls): def _asyncgenstate(self): return inspect.getasyncgenstate(self.asyncgen) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' def test_created(self): self.assertEqual(self._asyncgenstate(), inspect.AGEN_CREATED) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' async def test_suspended(self): value = await anext(self.asyncgen) self.assertEqual(self._asyncgenstate(), inspect.AGEN_SUSPENDED) self.assertEqual(value, 0) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' async def test_closed_after_exhaustion(self): countdown = 7 with self.assertRaises(StopAsyncIteration): @@ -2976,13 +2971,13 @@ async def test_closed_after_exhaustion(self): self.assertEqual(countdown, 1) self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' async def test_closed_after_immediate_exception(self): with self.assertRaises(RuntimeError): await self.asyncgen.athrow(RuntimeError) self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' async def test_running(self): async def running_check_asyncgen(): for number in range(5): @@ -3548,7 +3543,7 @@ def m1d(*args, **kwargs): ('arg2', 1, ..., "positional_or_keyword")), int)) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: no signature found for builtin type def test_signature_on_classmethod(self): if not support.MISSING_C_DOCSTRINGS: self.assertEqual(self.signature(classmethod), @@ -3572,7 +3567,7 @@ def foo(cls, arg1, *, arg2=1): ('arg2', 1, ..., "keyword_only")), ...)) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: no signature found for builtin type def test_signature_on_staticmethod(self): if not support.MISSING_C_DOCSTRINGS: self.assertEqual(self.signature(staticmethod), @@ -5167,7 +5162,7 @@ def test(): sig = test.__signature__ = inspect.Signature(parameters=(spam_param,)) self.assertEqual(sig, inspect.signature(test)) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; Ellipsis) def test_signature_on_mangled_parameters(self): class Spam: def foo(self, __p1:1=2, *, __p2:2=3): @@ -6010,7 +6005,7 @@ def _strip_non_python_syntax(self, input, self.assertEqual(computed_clean_signature, clean_signature) self.assertEqual(computed_self_parameter, self_parameter) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; + (module, /, path, mode, *, dir_fd=None, effective_ids=False, follow_symlinks=True) def test_signature_strip_non_python_syntax(self): self._strip_non_python_syntax( "($module, /, path, mode, *, dir_fd=None, " + @@ -6314,7 +6309,7 @@ def test_tokenize_module_has_signatures(self): import tokenize self._test_module_has_signatures(tokenize) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; ModuleNotFoundError: No module named 'tracemalloc' def test_tracemalloc_module_has_signatures(self): import tracemalloc self._test_module_has_signatures(tracemalloc) @@ -6342,7 +6337,7 @@ def test_weakref_module_has_signatures(self): no_signature = {'ReferenceType', 'ref'} self._test_module_has_signatures(weakref, no_signature) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: .func at 0xa4c07a580> builtin has invalid signature def test_python_function_override_signature(self): def func(*args, **kwargs): pass @@ -6373,7 +6368,7 @@ def func(*args, **kwargs): with self.assertRaises(ValueError): inspect.signature(func) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None != '(raw, buffer_size=DEFAULT_BUFFER_SIZE)' @support.requires_docstrings def test_base_class_have_text_signature(self): # see issue 43118 @@ -6592,7 +6587,7 @@ def run_on_interactive_mode(self, source): raise ValueError("Process didn't exit properly.") return output - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'The source is: <<>>' not found in 'Traceback (most recent call last):\n File "", line 1, in \n File "/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/inspect.py", line 1161, in getsource\n lines, lnum = getsourcelines(object)\n ~~~~~~~~~~~~~~^^^^^^^^\n File "/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/inspect.py", line 1143, in getsourcelines\n lines, lnum = findsource(object)\n ~~~~~~~~~~^^^^^^^^\n File "/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/inspect.py", line 978, in findsource\n raise OSError(\'could not get source code\')\nOSError: could not get source code\n' @unittest.skipIf(not has_subprocess_support, "test requires subprocess") def test_getsource(self): output = self.run_on_interactive_mode(textwrap.dedent("""\ diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py index 605169f4b40..ca253585e1a 100644 --- a/Lib/test/test_raise.py +++ b/Lib/test/test_raise.py @@ -185,7 +185,6 @@ def test_class_cause(self): else: self.fail("No exception raised") - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: 'classmethod' object is not callable def test_class_cause_nonexception_result(self): # See https://github.com/python/cpython/issues/140530. class ConstructMortal(BaseException): diff --git a/Lib/test/test_sys_setprofile.py b/Lib/test/test_sys_setprofile.py index d5e3206a5ca..d23f5bf6d9b 100644 --- a/Lib/test/test_sys_setprofile.py +++ b/Lib/test/test_sys_setprofile.py @@ -100,8 +100,6 @@ class ProfileHookTestCase(TestCaseBase): def new_watcher(self): return HookWatcher() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_simple(self): def f(p): pass @@ -110,8 +108,7 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (112, 'f'))] def test_exception(self): def f(p): 1/0 @@ -120,8 +117,6 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caught_exception(self): def f(p): try: 1/0 @@ -131,8 +126,6 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caught_nested_exception(self): def f(p): try: 1/0 @@ -142,8 +135,7 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (138, 'f'))] def test_nested_exception(self): def f(p): 1/0 @@ -155,8 +147,7 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; (1, 'return', (151, 'g'))] def test_exception_in_except_clause(self): def f(p): 1/0 @@ -176,8 +167,7 @@ def g(p): (1, 'return', g_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; (1, 'falling through', (170, 'g'))] def test_exception_propagation(self): def f(p): 1/0 @@ -193,8 +183,7 @@ def g(p): (1, 'return', g_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (183, 'f'))] def test_raise_twice(self): def f(p): try: 1/0 @@ -204,8 +193,7 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (192, 'f'))] def test_raise_reraise(self): def f(p): try: 1/0 @@ -215,8 +203,7 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (201, 'f'))] def test_raise(self): def f(p): raise Exception() @@ -225,8 +212,7 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; (5, 'call', (209, 'f'))] def test_distant_exception(self): def f(): 1/0 @@ -255,8 +241,6 @@ def j(p): (1, 'return', j_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generator(self): def f(): for i in range(2): @@ -279,8 +263,6 @@ def g(p): (1, 'return', g_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_stop_iteration(self): def f(): for i in range(2): @@ -307,8 +289,6 @@ class ProfileSimulatorTestCase(TestCaseBase): def new_watcher(self): return ProfileSimulator(self) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_simple(self): def f(p): pass @@ -317,8 +297,7 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (293, 'f'))] def test_basic_exception(self): def f(p): 1/0 @@ -327,8 +306,6 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caught_exception(self): def f(p): try: 1/0 @@ -338,8 +315,7 @@ def f(p): (1, 'return', f_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; (5, 'call', (310, 'f'))] def test_distant_exception(self): def f(): 1/0 @@ -368,8 +344,6 @@ def j(p): (1, 'return', j_ident), ]) - # TODO: RUSTPYTHON - @unittest.expectedFailure # bpo-34125: profiling method_descriptor with **kwargs def test_unbound_method(self): kwargs = {} @@ -379,9 +353,8 @@ def f(p): self.check_events(f, [(1, 'call', f_ident), (1, 'return', f_ident)]) - # TODO: RUSTPYTHON - @unittest.expectedFailure # Test an invalid call (bpo-34126) + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (348, 'f'))] def test_unbound_method_no_args(self): def f(p): dict.get() @@ -389,9 +362,8 @@ def f(p): self.check_events(f, [(1, 'call', f_ident), (1, 'return', f_ident)]) - # TODO: RUSTPYTHON - @unittest.expectedFailure # Test an invalid call (bpo-34126) + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (356, 'f'))] def test_unbound_method_invalid_args(self): def f(p): dict.get(print, 42) @@ -399,9 +371,8 @@ def f(p): self.check_events(f, [(1, 'call', f_ident), (1, 'return', f_ident)]) - # TODO: RUSTPYTHON - @unittest.expectedFailure # Test an invalid call (bpo-34125) + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (365, 'f'))] def test_unbound_method_no_keyword_args(self): kwargs = {} def f(p): @@ -410,9 +381,8 @@ def f(p): self.check_events(f, [(1, 'call', f_ident), (1, 'return', f_ident)]) - # TODO: RUSTPYTHON - @unittest.expectedFailure # Test an invalid call (bpo-34125) + @unittest.expectedFailure # TODO: RUSTPYTHON; [(1, 'call', (374, 'f'))] def test_unbound_method_invalid_keyword_args(self): kwargs = {} def f(p): diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py index a90c9d0baaf..e7cc4039750 100644 --- a/Lib/test/test_trace.py +++ b/Lib/test/test_trace.py @@ -130,8 +130,7 @@ def setUp(self): self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) self.my_py_filename = fix_ext_py(__file__) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; + ('/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/test/test_trace.py', 48): 1} def test_traced_func_linear(self): result = self.tracer.runfunc(traced_func_linear, 2, 5) self.assertEqual(result, 7) @@ -144,8 +143,7 @@ def test_traced_func_linear(self): self.assertEqual(self.tracer.results().counts, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; + ('/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/test/test_trace.py', 54): 1} def test_traced_func_loop(self): self.tracer.runfunc(traced_func_loop, 2, 3) @@ -158,8 +156,7 @@ def test_traced_func_loop(self): } self.assertEqual(self.tracer.results().counts, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; + ('/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/test/tracedmodules/testmod.py', 3): 1} def test_traced_func_importing(self): self.tracer.runfunc(traced_func_importing, 2, 5) @@ -172,8 +169,7 @@ def test_traced_func_importing(self): self.assertEqual(self.tracer.results().counts, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; + ('/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/test/test_trace.py', 76): 10} def test_trace_func_generator(self): self.tracer.runfunc(traced_func_calling_generator) @@ -189,8 +185,7 @@ def test_trace_func_generator(self): } self.assertEqual(self.tracer.results().counts, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; + ('/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/test/test_trace.py', 87): 1} def test_trace_list_comprehension(self): self.tracer.runfunc(traced_caller_list_comprehension) @@ -204,8 +199,7 @@ def test_trace_list_comprehension(self): } self.assertEqual(self.tracer.results().counts, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 996 characters long. Set self.maxDiff to None to see it. def test_traced_decorated_function(self): self.tracer.runfunc(traced_decorated_function) @@ -225,8 +219,7 @@ def test_traced_decorated_function(self): } self.assertEqual(self.tracer.results().counts, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; + {('/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/test/test_trace.py', 108): 1} def test_linear_methods(self): # XXX todo: later add 'static_method_linear' and 'class_method_linear' # here, once issue1764286 is resolved @@ -250,8 +243,7 @@ def setUp(self): self.my_py_filename = fix_ext_py(__file__) self.addCleanup(sys.settrace, sys.gettrace()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; KeyError: ('/Users/al03219714/Projects/RustPython4/crates/pylib/Lib/test/test_trace.py', 51) def test_exec_counts(self): self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) code = r'''traced_func_loop(2, 5)''' @@ -286,8 +278,6 @@ def tearDown(self): if self._saved_tracefunc is not None: sys.settrace(self._saved_tracefunc) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_simple_caller(self): self.tracer.runfunc(traced_func_simple_caller, 1) @@ -305,8 +295,6 @@ def test_arg_errors(self): with self.assertRaises(TypeError): self.tracer.runfunc() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_loop_caller_importing(self): self.tracer.runfunc(traced_func_importing_caller, 1) @@ -319,8 +307,6 @@ def test_loop_caller_importing(self): } self.assertEqual(self.tracer.results().calledfuncs, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'pre-existing trace function throws off measurements') @requires_gil_enabled("gh-117783: immortalization of types affects traced method names") @@ -335,8 +321,6 @@ def test_inst_method_calling(self): } self.assertEqual(self.tracer.results().calledfuncs, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_traced_decorated_function(self): self.tracer.runfunc(traced_decorated_function) @@ -357,8 +341,6 @@ def setUp(self): self.tracer = Trace(count=0, trace=0, countcallers=1) self.filemod = my_file_and_modname() - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'pre-existing trace function throws off measurements') @requires_gil_enabled("gh-117783: immortalization of types affects traced method names") @@ -401,8 +383,7 @@ def _coverage(self, tracer, cmd=DEFAULT_SCRIPT): r = tracer.results() r.write_results(show_missing=True, summary=True, coverdir=TESTFN) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'pprint.py' not found in '' @requires_resource('cpu') def test_coverage(self): tracer = trace.Trace(trace=0, count=1) @@ -427,8 +408,7 @@ def test_coverage_ignore(self): files = os.listdir(TESTFN) self.assertEqual(files, ['_importlib.cover']) # Ignore __import__ - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'test.tracedmodules.testmod' not found in {} def test_issue9936(self): tracer = trace.Trace(trace=0, count=1) modname = 'test.tracedmodules.testmod' @@ -493,8 +473,7 @@ def tearDown(self): unlink(self.codefile) unlink(self.coverfile) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; --- def test_cover_files_written_no_highlight(self): # Test also that the cover file for the trace module is not created # (issue #34171). @@ -515,8 +494,7 @@ def test_cover_files_written_no_highlight(self): " print('unreachable')\n" ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; --- def test_cover_files_written_with_highlight(self): argv = '-m trace --count --missing'.split() + [self.codefile] status, stdout, stderr = assert_python_ok(*argv) @@ -544,8 +522,6 @@ def test_failures(self): *_, stderr = assert_python_failure('-m', 'trace', *args) self.assertIn(message, stderr) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_listfuncs_flag_success(self): filename = TESTFN + '.py' modulename = os.path.basename(TESTFN) @@ -569,8 +545,7 @@ def test_sys_argv_list(self): PYTHONIOENCODING='utf-8') self.assertIn(direct_stdout.strip(), trace_stdout) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'lines cov% module (path)' not found in '' def test_count_and_summary(self): filename = f'{TESTFN}.py' coverfilename = f'{TESTFN}.cover' @@ -606,8 +581,7 @@ def setUp(self): self.tracer = Trace(count=0, trace=1) self.filemod = my_file_and_modname() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: list index out of range def test_no_source_file(self): filename = "" co = traced_func_linear.__code__ diff --git a/Lib/test/test_yield_from.py b/Lib/test/test_yield_from.py index 88fa1b88c90..e0e3db0839e 100644 --- a/Lib/test/test_yield_from.py +++ b/Lib/test/test_yield_from.py @@ -786,7 +786,6 @@ def outer(): repr(value), ]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_throwing_GeneratorExit_into_subgen_that_returns(self): """ Test throwing GeneratorExit into a subgenerator that @@ -817,7 +816,6 @@ def g(): "Enter f", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_throwing_GeneratorExit_into_subgenerator_that_yields(self): """ Test throwing GeneratorExit into a subgenerator that @@ -1135,7 +1133,6 @@ def outer(): self.assertIsNone(caught.exception.__context__) self.assert_stop_iteration(g) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: GeneratorExit() is not GeneratorExit() def test_close_and_throw_raise_generator_exit(self): yielded_first = object() @@ -1524,7 +1521,6 @@ def outer(): self.assertIsNone(caught.exception.__context__) self.assert_stop_iteration(g) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_close_and_throw_return(self): yielded_first = object() diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 826848ff271..25bee4d9967 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -151,6 +151,9 @@ struct Compiler { ctx: CompileContext, opts: CompileOpts, in_annotation: bool, + /// True when compiling in "single" (interactive) mode. + /// Expression statements at module scope emit CALL_INTRINSIC_1(Print). + interactive: bool, } #[derive(Clone, Copy)] @@ -461,6 +464,7 @@ impl Compiler { }, opts, in_annotation: false, + interactive: false, } } @@ -1706,6 +1710,7 @@ impl Compiler { body: &[ast::Stmt], symbol_table: SymbolTable, ) -> CompileResult<()> { + self.interactive = true; // Set future_annotations from symbol table (detected during symbol table scan) self.future_annotations = symbol_table.future_annotations; self.symbol_table_stack.push(symbol_table); @@ -2151,7 +2156,15 @@ impl Compiler { ast::Stmt::Expr(ast::StmtExpr { value, .. }) => { self.compile_expression(value)?; - // Pop result of stack, since we not use it: + if self.interactive && !self.ctx.in_func() && !self.ctx.in_class { + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::Print + } + ); + } + emit!(self, Instruction::PopTop); } ast::Stmt::Global(_) | ast::Stmt::Nonlocal(_) => { diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index bca00f84367..c8547b8a41f 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -7,7 +7,7 @@ use crate::{ function::OptionalArg, object::{Traverse, TraverseFn}, protocol::PyIterReturn, - types::{IterNext, Iterable, Representable, SelfIter}, + types::{Destructor, IterNext, Iterable, Representable, SelfIter}, }; use crossbeam_utils::atomic::AtomicCell; @@ -31,7 +31,10 @@ impl PyPayload for PyCoroutine { } } -#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py, IterNext, Representable))] +#[pyclass( + flags(DISALLOW_INSTANTIATION), + with(Py, IterNext, Representable, Destructor) +)] impl PyCoroutine { pub const fn as_coro(&self) -> &Coro { &self.inner @@ -130,7 +133,7 @@ impl Py { } #[pymethod] - fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + fn close(&self, vm: &VirtualMachine) -> PyResult { self.inner.close(self.as_object(), vm) } } @@ -149,6 +152,22 @@ impl IterNext for PyCoroutine { } } +impl Destructor for PyCoroutine { + fn del(zelf: &Py, vm: &VirtualMachine) -> PyResult<()> { + if zelf.inner.closed() || zelf.inner.running() { + return Ok(()); + } + if zelf.inner.frame().lasti() == 0 { + zelf.inner.closed.store(true); + return Ok(()); + } + if let Err(e) = zelf.inner.close(zelf.as_object(), vm) { + vm.run_unraisable(e, None, zelf.as_object().to_owned()); + } + Ok(()) + } +} + #[pyclass(module = false, name = "coroutine_wrapper", traverse = "manual")] #[derive(Debug)] // PyCoroWrapper_Type in CPython @@ -209,7 +228,7 @@ impl PyCoroutineWrapper { } #[pymethod] - fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + fn close(&self, vm: &VirtualMachine) -> PyResult { self.closed.store(true); self.coro.close(vm) } diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index d6bbbf82754..5d7510d8ff8 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -43,7 +43,10 @@ impl Frame { #[pygetset] fn f_locals(&self, vm: &VirtualMachine) -> PyResult { - self.locals(vm).map(Into::into) + let result = self.locals(vm).map(Into::into); + self.locals_dirty + .store(true, core::sync::atomic::Ordering::Release); + result } #[pygetset] diff --git a/crates/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs index f4deb8cc7a2..cdbb2af440a 100644 --- a/crates/vm/src/builtins/generator.rs +++ b/crates/vm/src/builtins/generator.rs @@ -11,7 +11,7 @@ use crate::{ function::OptionalArg, object::{Traverse, TraverseFn}, protocol::PyIterReturn, - types::{IterNext, Iterable, Representable, SelfIter}, + types::{Destructor, IterNext, Iterable, Representable, SelfIter}, }; #[pyclass(module = false, name = "generator", traverse = "manual")] @@ -33,7 +33,10 @@ impl PyPayload for PyGenerator { } } -#[pyclass(flags(DISALLOW_INSTANTIATION), with(Py, IterNext, Iterable))] +#[pyclass( + flags(DISALLOW_INSTANTIATION), + with(Py, IterNext, Iterable, Representable, Destructor) +)] impl PyGenerator { pub const fn as_coro(&self) -> &Coro { &self.inner @@ -89,6 +92,11 @@ impl PyGenerator { self.inner.frame().yield_from_target() } + #[pygetset] + fn gi_suspended(&self, _vm: &VirtualMachine) -> bool { + self.inner.suspended() + } + #[pyclassmethod] fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { PyGenericAlias::from_args(cls, args, vm) @@ -121,7 +129,7 @@ impl Py { } #[pymethod] - fn close(&self, vm: &VirtualMachine) -> PyResult<()> { + fn close(&self, vm: &VirtualMachine) -> PyResult { self.inner.close(self.as_object(), vm) } } @@ -140,6 +148,25 @@ impl IterNext for PyGenerator { } } +impl Destructor for PyGenerator { + fn del(zelf: &Py, vm: &VirtualMachine) -> PyResult<()> { + // _PyGen_Finalize: close the generator if it's still suspended + if zelf.inner.closed() || zelf.inner.running() { + return Ok(()); + } + // Generator was never started, just mark as closed + if zelf.inner.frame().lasti() == 0 { + zelf.inner.closed.store(true); + return Ok(()); + } + // Throw GeneratorExit to run finally blocks + if let Err(e) = zelf.inner.close(zelf.as_object(), vm) { + vm.run_unraisable(e, None, zelf.as_object().to_owned()); + } + Ok(()) + } +} + impl Drop for PyGenerator { fn drop(&mut self) { self.inner.frame().clear_generator(); diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 273199f69ba..bbbc7d17673 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -122,7 +122,7 @@ fn inner_pow(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult { fn inner_mod(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult { if int2.is_zero() { - Err(vm.new_zero_division_error("integer modulo by zero")) + Err(vm.new_zero_division_error("division by zero")) } else { Ok(vm.ctx.new_int(int1.mod_floor(int2)).into()) } @@ -130,7 +130,7 @@ fn inner_mod(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult { fn inner_floordiv(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult { if int2.is_zero() { - Err(vm.new_zero_division_error("integer division or modulo by zero")) + Err(vm.new_zero_division_error("division by zero")) } else { Ok(vm.ctx.new_int(int1.div_floor(int2)).into()) } @@ -138,7 +138,7 @@ fn inner_floordiv(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult fn inner_divmod(int1: &BigInt, int2: &BigInt, vm: &VirtualMachine) -> PyResult { if int2.is_zero() { - return Err(vm.new_zero_division_error("integer division or modulo by zero")); + return Err(vm.new_zero_division_error("division by zero")); } let (div, modulo) = int1.div_mod_floor(int2); Ok(vm.new_tuple((div, modulo)).into()) diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 7203110c7da..7e22f73f8ec 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -225,7 +225,13 @@ impl PyList { fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult { match SequenceIndex::try_from_borrowed_object(vm, needle, "list")? { - SequenceIndex::Int(i) => self.borrow_vec().getitem_by_index(vm, i), + SequenceIndex::Int(i) => { + let vec = self.borrow_vec(); + let pos = vec + .wrap_index(i) + .ok_or_else(|| vm.new_index_error("list index out of range"))?; + Ok(vec.do_get(pos)) + } SequenceIndex::Slice(slice) => self .borrow_vec() .getitem_by_slice(vm, slice) @@ -448,9 +454,12 @@ impl AsSequence for PyList { .map(|x| x.into()) }), item: atomic_func!(|seq, i, vm| { - PyList::sequence_downcast(seq) - .borrow_vec() - .getitem_by_index(vm, i) + let list = PyList::sequence_downcast(seq); + let vec = list.borrow_vec(); + let pos = vec + .wrap_index(i) + .ok_or_else(|| vm.new_index_error("list index out of range"))?; + Ok(vec.do_get(pos)) }), ass_item: atomic_func!(|seq, i, value, vm| { let zelf = PyList::sequence_downcast(seq); diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index 0ee48d959a7..a066c9944fe 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -1,5 +1,5 @@ use crate::{ - AsObject, Py, PyObject, PyObjectRef, PyResult, VirtualMachine, + AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyStrRef}, common::lock::PyMutex, exceptions::types::PyBaseException, @@ -135,6 +135,7 @@ impl Coro { if self.closed.load() { return Ok(PyIterReturn::StopIteration(None)); } + self.frame.locals_to_fast(vm)?; let value = if self.frame.lasti() > 0 { Some(value) } else if !vm.is_none(&value) { @@ -176,22 +177,37 @@ impl Coro { exc_tb: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { + // Validate throw arguments (matching CPython _gen_throw) + if exc_type.fast_isinstance(vm.ctx.exceptions.base_exception_type) && !vm.is_none(&exc_val) + { + return Err( + vm.new_type_error("instance exception may not have a separate value".to_owned()) + ); + } + if !vm.is_none(&exc_tb) && !exc_tb.fast_isinstance(vm.ctx.types.traceback_type) { + return Err( + vm.new_type_error("throw() third argument must be a traceback object".to_owned()) + ); + } if self.closed.load() { return Err(vm.normalize_exception(exc_type, exc_val, exc_tb)?); } + // Validate exception type before entering generator context. + // Invalid types propagate to caller without closing the generator. + crate::exceptions::ExceptionCtor::try_from_object(vm, exc_type.clone())?; let result = self.run_with_context(jen, vm, |f| f.gen_throw(vm, exc_type, exc_val, exc_tb)); self.maybe_close(&result); Ok(result?.into_iter_return(vm)) } - pub fn close(&self, jen: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + pub fn close(&self, jen: &PyObject, vm: &VirtualMachine) -> PyResult { if self.closed.load() { - return Ok(()); + return Ok(vm.ctx.none()); } // If generator hasn't started (FRAME_CREATED), just mark as closed if self.frame.lasti() == 0 { self.closed.store(true); - return Ok(()); + return Ok(vm.ctx.none()); } let result = self.run_with_context(jen, vm, |f| { f.gen_throw( @@ -207,10 +223,15 @@ impl Coro { Err(vm.new_runtime_error(format!("{} ignored GeneratorExit", gen_name(jen, vm)))) } Err(e) if !is_gen_exit(&e, vm) => Err(e), - _ => Ok(()), + Ok(ExecutionResult::Return(value)) => Ok(value), + _ => Ok(vm.ctx.none()), } } + pub fn suspended(&self) -> bool { + !self.closed.load() && !self.running.load() && self.frame.lasti() > 0 + } + pub fn running(&self) -> bool { self.running.load() } @@ -240,10 +261,11 @@ impl Coro { } pub fn repr(&self, jen: &PyObject, id: usize, vm: &VirtualMachine) -> String { + let qualname = self.qualname(); format!( "<{} object {} at {:#x}>", gen_name(jen, vm), - self.name.lock(), + qualname.as_str(), id ) } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 3a1652a28de..192549d096b 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -348,7 +348,13 @@ impl VirtualMachine { ) -> PyResult { // TODO: fast-path built-in exceptions by directly instantiating them? Is that really worth it? let res = PyType::call(&cls, args.into_args(self), self)?; - PyBaseExceptionRef::try_from_object(self, res) + res.downcast::().map_err(|obj| { + self.new_type_error(format!( + "calling {} should have returned an instance of BaseException, not {}", + cls, + obj.class() + )) + }) } } @@ -1307,6 +1313,16 @@ impl OSErrorBuilder { self } + /// Strip winerror from the builder. Used for C runtime errors + /// (e.g. `_wopen`, `open`) that should produce `[Errno X]` format + /// instead of `[WinError X]`. + #[must_use] + #[cfg(windows)] + pub(crate) fn without_winerror(mut self) -> Self { + self.winerror = None; + self + } + pub fn build(self, vm: &VirtualMachine) -> PyRef { use types::PyOSError; @@ -1391,12 +1407,10 @@ impl ToOSErrorBuilder for std::io::Error { #[allow(unused_mut)] let mut builder = OSErrorBuilder::with_errno(errno, msg, vm); - #[cfg(windows)] if let Some(winerror) = self.raw_os_error() { builder = builder.winerror(winerror.to_pyobject(vm)); } - builder } } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index f197effb7e0..f1647eb6fc1 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -121,6 +121,8 @@ pub struct Frame { /// Used by `frame.clear()` to reject clearing an executing frame, /// even when called from a different thread. pub(crate) owner: atomic::AtomicI8, + /// Set when f_locals is accessed. Cleared after locals_to_fast() sync. + pub(crate) locals_dirty: atomic::AtomicBool, } impl PyPayload for Frame { @@ -212,6 +214,7 @@ impl Frame { generator: PyAtomicBorrow::new(), previous: AtomicPtr::new(core::ptr::null_mut()), owner: atomic::AtomicI8::new(FrameOwner::FrameObject as i8), + locals_dirty: atomic::AtomicBool::new(false), } } @@ -255,6 +258,28 @@ impl Frame { } } + /// Sync locals dict back to fastlocals. Called before generator/coroutine resume + /// to apply any modifications made via f_locals. + pub fn locals_to_fast(&self, vm: &VirtualMachine) -> PyResult<()> { + if !self.locals_dirty.load(atomic::Ordering::Acquire) { + return Ok(()); + } + let code = &**self.code; + let mut fastlocals = self.fastlocals.lock(); + for (i, &varname) in code.varnames.iter().enumerate() { + if i >= fastlocals.len() { + break; + } + match self.locals.mapping().subscript(varname, vm) { + Ok(value) => fastlocals[i] = Some(value), + Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => {} + Err(e) => return Err(e), + } + } + self.locals_dirty.store(false, atomic::Ordering::Release); + Ok(()) + } + pub fn locals(&self, vm: &VirtualMachine) -> PyResult { let locals = &self.locals; let code = &**self.code; @@ -438,8 +463,20 @@ impl ExecutingFrame<'_> { // Execute until return or exception: let instructions = &self.code.instructions; let mut arg_state = bytecode::OpArgState::default(); + let mut prev_line: usize = 0; loop { let idx = self.lasti() as usize; + // Fire 'line' trace event when line number changes. + // Only fire if this frame has a per-frame trace function set + // (frames entered before sys.settrace() have trace=None). + if vm.use_tracing.get() + && !vm.is_none(&self.object.trace.lock()) + && let Some((loc, _)) = self.code.locations.get(idx) + && loc.line.get() != prev_line + { + prev_line = loc.line.get(); + vm.trace_event(crate::protocol::TraceEvent::Line, None)?; + } self.update_lasti(|i| *i += 1); let bytecode::CodeUnit { op, arg } = instructions[idx]; let arg = arg_state.extend(arg); @@ -598,44 +635,74 @@ impl ExecutingFrame<'_> { exc_tb: PyObjectRef, ) -> PyResult { if let Some(jen) = self.yield_from_target() { - // borrow checker shenanigans - we only need to use exc_type/val/tb if the following - // variable is Some - let thrower = if let Some(coro) = self.builtin_coro(jen) { - Some(Either::A(coro)) + // Check if the exception is GeneratorExit (type or instance). + // For GeneratorExit, close the sub-iterator instead of throwing. + let is_gen_exit = if let Some(typ) = exc_type.downcast_ref::() { + typ.fast_issubclass(vm.ctx.exceptions.generator_exit) } else { - vm.get_attribute_opt(jen.to_owned(), "throw")? - .map(Either::B) + exc_type.fast_isinstance(vm.ctx.exceptions.generator_exit) }; - if let Some(thrower) = thrower { - let ret = match thrower { - Either::A(coro) => coro - .throw(jen, exc_type, exc_val, exc_tb, vm) - .to_pyresult(vm), - Either::B(meth) => meth.call((exc_type, exc_val, exc_tb), vm), + + if is_gen_exit { + // gen_close_iter: close the sub-iterator + let close_result = if let Some(coro) = self.builtin_coro(jen) { + coro.close(jen, vm).map(|_| ()) + } else if let Some(close_meth) = vm.get_attribute_opt(jen.to_owned(), "close")? { + close_meth.call((), vm).map(|_| ()) + } else { + Ok(()) }; - return ret.map(ExecutionResult::Yield).or_else(|err| { - // This pushes Py_None to stack and restarts evalloop in exception mode. - // Stack before throw: [receiver] (YIELD_VALUE already popped yielded value) - // After pushing None: [receiver, None] - // Exception handler will push exc: [receiver, None, exc] - // CLEANUP_THROW expects: [sub_iter, last_sent_val, exc] + if let Err(err) = close_result { self.push_value(vm.ctx.none()); - - // Set __context__ on the exception (_PyErr_ChainStackItem) vm.contextualize_exception(&err); - - // Use unwind_blocks to let exception table route to CLEANUP_THROW - match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) { + return match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) { Ok(None) => self.run(vm), Ok(Some(result)) => Ok(result), Err(exception) => Err(exception), - } - }); + }; + } + // Fall through to throw_here to raise GeneratorExit in the generator + } else { + // For non-GeneratorExit, delegate throw to sub-iterator + let thrower = if let Some(coro) = self.builtin_coro(jen) { + Some(Either::A(coro)) + } else { + vm.get_attribute_opt(jen.to_owned(), "throw")? + .map(Either::B) + }; + if let Some(thrower) = thrower { + let ret = match thrower { + Either::A(coro) => coro + .throw(jen, exc_type, exc_val, exc_tb, vm) + .to_pyresult(vm), + Either::B(meth) => meth.call((exc_type, exc_val, exc_tb), vm), + }; + return ret.map(ExecutionResult::Yield).or_else(|err| { + self.push_value(vm.ctx.none()); + vm.contextualize_exception(&err); + match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) { + Ok(None) => self.run(vm), + Ok(Some(result)) => Ok(result), + Err(exception) => Err(exception), + } + }); + } } } // throw_here: no delegate has throw method, or not in yield-from - // gen_send_ex pushes Py_None to stack and restarts evalloop in exception mode - let exception = vm.normalize_exception(exc_type, exc_val, exc_tb)?; + // Validate the exception type first. Invalid types propagate directly to + // the caller. Valid types with failed instantiation (e.g. __new__ returns + // wrong type) get thrown into the generator via PyErr_SetObject path. + let ctor = ExceptionCtor::try_from_object(vm, exc_type)?; + let exception = match ctor.instantiate_value(exc_val, vm) { + Ok(exc) => { + if let Some(tb) = Option::>::try_from_object(vm, exc_tb)? { + exc.set_traceback_typed(Some(tb)); + } + exc + } + Err(err) => err, + }; // Add traceback entry for the generator frame at the yield site let idx = self.lasti().saturating_sub(1) as usize; diff --git a/crates/vm/src/ospath.rs b/crates/vm/src/ospath.rs index b9efccde399..00195460ea3 100644 --- a/crates/vm/src/ospath.rs +++ b/crates/vm/src/ospath.rs @@ -327,4 +327,21 @@ impl crate::exceptions::OSErrorBuilder { let builder = builder.filename(filename.into().filename(vm)); builder.build(vm).upcast() } + + /// Like `with_filename`, but strips winerror on Windows. + /// Use for C runtime errors (open, fstat, etc.) that should produce + /// `[Errno X]` format instead of `[WinError X]`. + #[must_use] + pub(crate) fn with_filename_from_errno<'a>( + error: &std::io::Error, + filename: impl Into>, + vm: &VirtualMachine, + ) -> crate::builtins::PyBaseExceptionRef { + use crate::exceptions::ToOSErrorBuilder; + let builder = error.to_os_error_builder(vm); + #[cfg(windows)] + let builder = builder.without_winerror(); + let builder = builder.filename(filename.into().filename(vm)); + builder.build(vm).upcast() + } } diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index fa5e48d58ba..9308ec8ffe2 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -1,7 +1,8 @@ use crate::{ + builtins::{PyBoundMethod, PyFunction}, function::{FuncArgs, IntoFuncArgs}, types::GenericMethod, - {AsObject, PyObject, PyResult, VirtualMachine}, + {AsObject, PyObject, PyObjectRef, PyResult, VirtualMachine}, }; impl PyObject { @@ -48,17 +49,35 @@ impl<'a> PyCallable<'a> { pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult { let args = args.into_args(vm); - vm.trace_event(TraceEvent::Call)?; - let result = (self.call)(self.obj, args, vm); - vm.trace_event(TraceEvent::Return)?; - result + // Python functions get 'call'/'return' events from with_frame(). + // Bound methods delegate to the inner callable, which fires its own events. + // All other callables (built-in functions, etc.) get 'c_call'/'c_return'/'c_exception'. + let is_python_callable = self.obj.downcast_ref::().is_some() + || self.obj.downcast_ref::().is_some(); + if is_python_callable { + (self.call)(self.obj, args, vm) + } else { + let callable = self.obj.to_owned(); + vm.trace_event(TraceEvent::CCall, Some(callable.clone()))?; + let result = (self.call)(self.obj, args, vm); + if result.is_ok() { + vm.trace_event(TraceEvent::CReturn, Some(callable))?; + } else { + let _ = vm.trace_event(TraceEvent::CException, Some(callable)); + } + result + } } } /// Trace events for sys.settrace and sys.setprofile. -enum TraceEvent { +pub(crate) enum TraceEvent { Call, Return, + Line, + CCall, + CReturn, + CException, } impl core::fmt::Display for TraceEvent { @@ -67,6 +86,10 @@ impl core::fmt::Display for TraceEvent { match self { Call => write!(f, "call"), Return => write!(f, "return"), + Line => write!(f, "line"), + CCall => write!(f, "c_call"), + CReturn => write!(f, "c_return"), + CException => write!(f, "c_exception"), } } } @@ -74,14 +97,14 @@ impl core::fmt::Display for TraceEvent { impl VirtualMachine { /// Call registered trace function. #[inline] - fn trace_event(&self, event: TraceEvent) -> PyResult<()> { + pub(crate) fn trace_event(&self, event: TraceEvent, arg: Option) -> PyResult<()> { if self.use_tracing.get() { - self._trace_event_inner(event) + self._trace_event_inner(event, arg) } else { Ok(()) } } - fn _trace_event_inner(&self, event: TraceEvent) -> PyResult<()> { + fn _trace_event_inner(&self, event: TraceEvent, arg: Option) -> PyResult<()> { let trace_func = self.trace_func.borrow().to_owned(); let profile_func = self.profile_func.borrow().to_owned(); if self.is_none(&trace_func) && self.is_none(&profile_func) { @@ -95,7 +118,7 @@ impl VirtualMachine { let frame = frame_ref.unwrap().as_object().to_owned(); let event = self.ctx.new_str(event.to_string()).into(); - let args = vec![frame, event, self.ctx.none()]; + let args = vec![frame, event, arg.unwrap_or_else(|| self.ctx.none())]; // temporarily disable tracing, during the call to the // tracing function itself. diff --git a/crates/vm/src/protocol/mod.rs b/crates/vm/src/protocol/mod.rs index 6eb2909f167..411aa4dfad3 100644 --- a/crates/vm/src/protocol/mod.rs +++ b/crates/vm/src/protocol/mod.rs @@ -8,6 +8,7 @@ mod sequence; pub use buffer::{BufferDescriptor, BufferMethods, BufferResizeGuard, PyBuffer, VecBuffer}; pub use callable::PyCallable; +pub(crate) use callable::TraceEvent; pub use iter::{PyIter, PyIterIter, PyIterReturn}; pub use mapping::{PyMapping, PyMappingMethods, PyMappingSlots}; pub use number::{ diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index b15c0e80404..89f567d8665 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -5266,7 +5266,9 @@ mod fileio { let filename = OsPathOrFd::Path(path); match fd { Ok(fd) => (fd.into_raw(), Some(filename)), - Err(e) => return Err(OSErrorBuilder::with_filename(&e, filename, vm)), + Err(e) => { + return Err(OSErrorBuilder::with_filename_from_errno(&e, filename, vm)); + } } } }; diff --git a/crates/vm/src/stdlib/itertools.rs b/crates/vm/src/stdlib/itertools.rs index 5a3cfee391b..d1c188b8892 100644 --- a/crates/vm/src/stdlib/itertools.rs +++ b/crates/vm/src/stdlib/itertools.rs @@ -16,6 +16,7 @@ mod decl { stdlib::sys, types::{Constructor, IterNext, Iterable, Representable, SelfIter}, }; + use core::sync::atomic::{AtomicBool, Ordering}; use crossbeam_utils::atomic::AtomicCell; use malachite_bigint::BigInt; use num_traits::One; @@ -943,6 +944,7 @@ mod decl { struct PyItertoolsTeeData { iterable: PyIter, values: PyMutex>, + running: AtomicBool, } impl PyItertoolsTeeData { @@ -950,19 +952,33 @@ mod decl { Ok(PyRc::new(Self { iterable, values: PyMutex::new(vec![]), + running: AtomicBool::new(false), })) } fn get_item(&self, vm: &VirtualMachine, index: usize) -> PyResult { + // Return cached value if available + { + let Some(values) = self.values.try_lock() else { + return Err(vm.new_runtime_error("cannot re-enter the tee iterator")); + }; + if index < values.len() { + return Ok(PyIterReturn::Return(values[index].clone())); + } + } + // Prevent concurrent/reentrant calls to iterable.next() + if self.running.swap(true, Ordering::Acquire) { + return Err(vm.new_runtime_error("cannot re-enter the tee iterator")); + } + let result = self.iterable.next(vm); + self.running.store(false, Ordering::Release); + let obj = raise_if_stop!(result?); let Some(mut values) = self.values.try_lock() else { return Err(vm.new_runtime_error("cannot re-enter the tee iterator")); }; - if values.len() == index { - let obj = raise_if_stop!(self.iterable.next(vm)?); values.push(obj); } - Ok(PyIterReturn::Return(values[index].clone())) } } diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 0f4eb0ce422..9993d8b6360 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -274,7 +274,7 @@ pub(super) mod _os { crt_fd::open(&name, flags, mode) } }; - fd.map_err(|err| OSErrorBuilder::with_filename(&err, name, vm)) + fd.map_err(|err| OSErrorBuilder::with_filename_from_errno(&err, name, vm)) } #[pyfunction] diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index aa689e85f8c..934c79b2ebd 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1018,7 +1018,28 @@ impl VirtualMachine { crate::frame::FrameOwner::Thread as i8, core::sync::atomic::Ordering::AcqRel, ); - let result = f(frame); + use crate::protocol::TraceEvent; + // Fire 'call' trace event after pushing frame + // (current_frame() now returns the callee's frame) + let result = match self.trace_event(TraceEvent::Call, None) { + Ok(()) => { + // Set per-frame trace function so line events fire for this frame. + // Frames entered before sys.settrace() keep trace=None and skip line events. + if self.use_tracing.get() { + let trace_func = self.trace_func.borrow().clone(); + if !self.is_none(&trace_func) { + *frame.trace.lock() = trace_func; + } + } + let result = f(frame); + // Fire 'return' trace event on success + if result.is_ok() { + let _ = self.trace_event(TraceEvent::Return, None); + } + result + } + Err(e) => Err(e), + }; // SAFETY: frame_ptr is valid because self.frames holds a clone // of the frame, keeping the underlying allocation alive. unsafe { &*frame_ptr } diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 2a06fda17f6..7c6035b62d1 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -501,6 +501,7 @@ impl VirtualMachine { if let Some(msg) = msg.get_mut(..1) { msg.make_ascii_lowercase(); } + let mut narrow_caret = false; match error { #[cfg(feature = "parser")] crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { @@ -523,6 +524,14 @@ impl VirtualMachine { }) => { msg = "invalid syntax".to_owned(); } + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: ruff_python_parser::ParseErrorType::InvalidStarredExpressionUsage, + .. + }) => { + msg = "invalid syntax".to_owned(); + narrow_caret = true; + } _ => {} } if syntax_error_type.is(self.ctx.exceptions.tab_error) { @@ -543,6 +552,12 @@ impl VirtualMachine { // Set end_lineno and end_offset if available if let Some((end_lineno, end_offset)) = error.python_end_location() { + let (end_lineno, end_offset) = if narrow_caret { + let (l, o) = error.python_location(); + (l, o + 1) + } else { + (end_lineno, end_offset) + }; let end_lineno = self.ctx.new_int(end_lineno); let end_offset = self.ctx.new_int(end_offset); syntax_error