From 2c1108b44aca4493f2e364912b56f66d5a959d13 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 16:37:29 +0300 Subject: [PATCH 01/26] Add colour to doctest output and create _colorize module --- Lib/_colorize.py | 45 +++++++++++++++++++ Lib/doctest.py | 61 +++++++++++++++++++------- Lib/test/test_doctest/test_doctest.py | 9 +++- Lib/test/test_doctest/test_doctest2.py | 12 +++++ Python/stdlib_module_names.h | 1 + 5 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 Lib/_colorize.py diff --git a/Lib/_colorize.py b/Lib/_colorize.py new file mode 100644 index 00000000000000..412ead5d35f4e4 --- /dev/null +++ b/Lib/_colorize.py @@ -0,0 +1,45 @@ +import io +import os +import sys + +_COLORIZE = True + + +class _ANSIColors: + BOLD_GREEN = "\x1b[1;32m" + BOLD_MAGENTA = "\x1b[1;35m" + BOLD_RED = "\x1b[1;31m" + GREEN = "\x1b[32m" + GREY = "\x1b[90m" + MAGENTA = "\x1b[35m" + RED = "\x1b[31m" + RESET = "\x1b[0m" + YELLOW = "\x1b[33m" + + +def _can_colorize(): + if sys.platform == "win32": + try: + import nt + + if not nt._supports_virtual_terminal(): + return False + except (ImportError, AttributeError): + return False + + if os.environ.get("PYTHON_COLORS") == "0": + return False + if os.environ.get("PYTHON_COLORS") == "1": + return True + if "NO_COLOR" in os.environ: + return False + if not _COLORIZE: + return False + if "FORCE_COLOR" in os.environ: + return True + if os.environ.get("TERM") == "dumb": + return False + try: + return os.isatty(sys.stderr.fileno()) + except io.UnsupportedOperation: + return sys.stderr.isatty() diff --git a/Lib/doctest.py b/Lib/doctest.py index fc0da590018b40..ce899d66988c3b 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -105,6 +105,8 @@ def _test(): from io import StringIO, IncrementalNewlineDecoder from collections import namedtuple +import _colorize # Used in doctests + class TestResults(namedtuple('TestResults', 'failed attempted')): def __new__(cls, failed, attempted, *, skipped=0): @@ -1172,6 +1174,9 @@ class DocTestRunner: The `run` method is used to process a single DocTest case. It returns a TestResults instance. + >>> save_colorize = _colorize._COLORIZE + >>> _colorize._COLORIZE = False + >>> tests = DocTestFinder().find(_TestClass) >>> runner = DocTestRunner(verbose=False) >>> tests.sort(key = lambda test: test.name) @@ -1222,6 +1227,8 @@ class DocTestRunner: can be also customized by subclassing DocTestRunner, and overriding the methods `report_start`, `report_success`, `report_unexpected_exception`, and `report_failure`. + + >>> _colorize._COLORIZE = save_colorize """ # This divider string is used to separate failure messages, and to # separate sections of the summary. @@ -1566,10 +1573,12 @@ def summarize(self, verbose=None): summary is. If the verbosity is not specified, then the DocTestRunner's verbosity is used. """ + from _colorize import _ANSIColors, _can_colorize + if verbose is None: verbose = self._verbose - notests, passed, failed = [], [], [] + no_tests, passed, failed = [], [], [] total_tries = total_failures = total_skips = 0 for name, (failures, tries, skips) in self._stats.items(): @@ -1579,47 +1588,65 @@ def summarize(self, verbose=None): total_skips += skips if tries == 0: - notests.append(name) + no_tests.append(name) elif failures == 0: passed.append((name, tries)) else: failed.append((name, (failures, tries, skips))) + if _can_colorize(): + bold_green = _ANSIColors.BOLD_GREEN + bold_red = _ANSIColors.BOLD_RED + green = _ANSIColors.GREEN + red = _ANSIColors.RED + reset = _ANSIColors.RESET + yellow = _ANSIColors.YELLOW + else: + bold_green = "" + bold_red = "" + green = "" + red = "" + reset = "" + yellow = "" + if verbose: - if notests: - print(f"{_n_items(notests)} had no tests:") - notests.sort() - for name in notests: + if no_tests: + print(f"{_n_items(no_tests)} had no tests:") + no_tests.sort() + for name in no_tests: print(f" {name}") if passed: - print(f"{_n_items(passed)} passed all tests:") + print(f"{green}{_n_items(passed)} passed all tests:{reset}") for name, count in sorted(passed): s = "" if count == 1 else "s" - print(f" {count:3d} test{s} in {name}") + print(f" {green}{count:3d} test{s} in {name}{reset}") if failed: print(self.DIVIDER) - print(f"{_n_items(failed)} had failures:") + print(f"{red}{_n_items(failed)} had failures:{reset}") for name, (failures, tries, skips) in sorted(failed): - print(f" {failures:3d} of {tries:3d} in {name}") + print(f"{red} {failures:3d} of {tries:3d} in {name}{reset}") if verbose: s = "" if total_tries == 1 else "s" print(f"{total_tries} test{s} in {_n_items(self._stats)}.") - and_f = f" and {total_failures} failed" if total_failures else "" - print(f"{total_tries - total_failures} passed{and_f}.") + and_f = ( + f" and {red}{total_failures} failed{reset}" + if total_failures else "" + ) + print(f"{green}{total_tries - total_failures} passed{reset}{and_f}.") if total_failures: s = "" if total_failures == 1 else "s" - msg = f"***Test Failed*** {total_failures} failure{s}" + msg = f"{bold_red}***Test Failed*** {total_failures} failure{s}{reset}" if total_skips: s = "" if total_skips == 1 else "s" - msg = f"{msg} and {total_skips} skipped test{s}" + msg = f"{msg} and {yellow}{total_skips} skipped test{s}{reset}" print(f"{msg}.") elif verbose: - print("Test passed.") + print(f"{bold_green}Test passed.{reset}") return TestResults(total_failures, total_tries, skipped=total_skips) @@ -1637,7 +1664,7 @@ def merge(self, other): d[name] = (failures, tries, skips) -def _n_items(items: list) -> str: +def _n_items(items: list | dict) -> str: """ Helper to pluralise the number of items in a list. """ @@ -1648,7 +1675,7 @@ def _n_items(items: list) -> str: class OutputChecker: """ - A class used to check the whether the actual output from a doctest + A class used to check whether the actual output from a doctest example matches the expected output. `OutputChecker` defines two methods: `check_output`, which compares a given pair of outputs, and returns true if they match; and `output_difference`, which diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index dd8cc9be3a4a8a..13ab3c9e6ca200 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -17,6 +17,8 @@ import types import contextlib +import _colorize # used in doctests + if not support.has_subprocess_support: raise unittest.SkipTest("test_CLI requires subprocess support.") @@ -466,7 +468,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. @@ -2634,8 +2636,10 @@ def test_testfile(): r""" called with the name of a file, which is taken to be relative to the calling module. The return value is (#failures, #tests). -We don't want `-v` in sys.argv for these tests. +We don't want colour or `-v` in sys.argv for these tests. + >>> save_colorize = _colorize._COLORIZE + >>> _colorize._COLORIZE = False >>> save_argv = sys.argv >>> if '-v' in sys.argv: ... sys.argv = [arg for arg in save_argv if arg != '-v'] @@ -2802,6 +2806,7 @@ def test_testfile(): r""" TestResults(failed=0, attempted=2) >>> doctest.master = None # Reset master. >>> sys.argv = save_argv + >>> _colorize._COLORIZE = save_colorize """ class TestImporter(importlib.abc.MetaPathFinder, importlib.abc.ResourceLoader): diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py index ab8a0696736e23..ea9b430fad80a4 100644 --- a/Lib/test/test_doctest/test_doctest2.py +++ b/Lib/test/test_doctest/test_doctest2.py @@ -13,6 +13,9 @@ import sys import unittest + +import _colorize + if sys.flags.optimize >= 2: raise unittest.SkipTest("Cannot test docstrings with -O2") @@ -108,6 +111,15 @@ def clsm(cls, val): class Test(unittest.TestCase): + def setUp(self): + super().setUp() + self.colorize = _colorize._COLORIZE + _colorize._COLORIZE = False + + def tearDown(self): + super().tearDown() + _colorize._COLORIZE = self.colorize + def test_testmod(self): import doctest, sys EXPECTED = 19 diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h index ac9d91b5e12885..b8fbb4f43434e7 100644 --- a/Python/stdlib_module_names.h +++ b/Python/stdlib_module_names.h @@ -19,6 +19,7 @@ static const char* _Py_stdlib_module_names[] = { "_codecs_tw", "_collections", "_collections_abc", +"_colorize", "_compat_pickle", "_compression", "_contextvars", From d27c0a853d44d768ef7e0f7fdcaacf0ab04035ae Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 16:38:42 +0300 Subject: [PATCH 02/26] Use _colorize in traceback module --- Lib/test/test_traceback.py | 55 ++++++++++++++++++++------------------ Lib/traceback.py | 41 ++++------------------------ 2 files changed, 34 insertions(+), 62 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index dd9b1850adf086..2f55d2392c484b 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -29,6 +29,8 @@ from functools import partial from pathlib import Path +import _colorize + MODULE_PREFIX = f'{__name__}.' if __name__ == '__main__' else '' test_code = namedtuple('code', ['co_filename', 'co_name']) @@ -45,12 +47,12 @@ class TracebackCases(unittest.TestCase): # formatting of SyntaxErrors works based on changes for 2.1. def setUp(self): super().setUp() - self.colorize = traceback._COLORIZE - traceback._COLORIZE = False + self.colorize = _colorize._COLORIZE + _colorize._COLORIZE = False def tearDown(self): super().tearDown() - traceback._COLORIZE = self.colorize + _colorize._COLORIZE = self.colorize def get_exception_format(self, func, exc): try: @@ -4291,9 +4293,9 @@ def bar(): e, capture_locals=True ) lines = "".join(exc.format(colorize=True)) - red = traceback._ANSIColors.RED - boldr = traceback._ANSIColors.BOLD_RED - reset = traceback._ANSIColors.RESET + red = _colorize._ANSIColors.RED + boldr = _colorize._ANSIColors.BOLD_RED + reset = _colorize._ANSIColors.RESET self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines) self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines) self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines) @@ -4309,11 +4311,11 @@ def test_colorized_syntax_error(self): e, capture_locals=True ) actual = "".join(exc.format(colorize=True)) - red = traceback._ANSIColors.RED - magenta = traceback._ANSIColors.MAGENTA - boldm = traceback._ANSIColors.BOLD_MAGENTA - boldr = traceback._ANSIColors.BOLD_RED - reset = traceback._ANSIColors.RESET + red = _colorize._ANSIColors.RED + magenta = _colorize._ANSIColors.MAGENTA + boldm = _colorize._ANSIColors.BOLD_MAGENTA + boldr = _colorize._ANSIColors.BOLD_RED + reset = _colorize._ANSIColors.RESET expected = "".join([ f' File {magenta}""{reset}, line {magenta}1{reset}\n', f' a {boldr}${reset} b\n', @@ -4332,15 +4334,15 @@ def foo(): self.fail("No exception thrown.") except Exception as e: with captured_output("stderr") as tbstderr: - with unittest.mock.patch('traceback._can_colorize', return_value=True): + with unittest.mock.patch('_colorize._can_colorize', return_value=True): exception_print(e) actual = tbstderr.getvalue().splitlines() - red = traceback._ANSIColors.RED - boldr = traceback._ANSIColors.BOLD_RED - magenta = traceback._ANSIColors.MAGENTA - boldm = traceback._ANSIColors.BOLD_MAGENTA - reset = traceback._ANSIColors.RESET + red = _colorize._ANSIColors.RED + boldr = _colorize._ANSIColors.BOLD_RED + magenta = _colorize._ANSIColors.MAGENTA + boldm = _colorize._ANSIColors.BOLD_MAGENTA + reset = _colorize._ANSIColors.RESET lno_foo = foo.__code__.co_firstlineno expected = ['Traceback (most recent call last):', f' File {magenta}"{__file__}"{reset}, ' @@ -4363,23 +4365,24 @@ def test_colorized_detection_checks_for_environment_variables(self): with unittest.mock.patch("os.isatty") as isatty_mock: isatty_mock.return_value = True with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): - self.assertEqual(traceback._can_colorize(), False) + self.assertEqual(_colorize._can_colorize(), False) with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): - self.assertEqual(traceback._can_colorize(), True) + self.assertEqual(_colorize._can_colorize(), True) with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): - self.assertEqual(traceback._can_colorize(), False) + self.assertEqual(_colorize._can_colorize(), False) with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): - self.assertEqual(traceback._can_colorize(), False) + self.assertEqual(_colorize._can_colorize(), False) with unittest.mock.patch("os.environ", {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): - self.assertEqual(traceback._can_colorize(), True) + self.assertEqual(_colorize._can_colorize(), True) with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): - self.assertEqual(traceback._can_colorize(), True) + self.assertEqual(_colorize._can_colorize(), True) with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): - self.assertEqual(traceback._can_colorize(), False) + self.assertEqual(_colorize._can_colorize(), False) with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): - self.assertEqual(traceback._can_colorize(), False) + self.assertEqual(_colorize._can_colorize(), False) isatty_mock.return_value = False - self.assertEqual(traceback._can_colorize(), False) + self.assertEqual(_colorize._can_colorize(), False) + if __name__ == "__main__": unittest.main() diff --git a/Lib/traceback.py b/Lib/traceback.py index d27c7a726d2bb6..a294fe21574c5a 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1,7 +1,5 @@ """Extract, format and print information about Python stack traces.""" -import os -import io import collections.abc import itertools import linecache @@ -10,6 +8,8 @@ import warnings from contextlib import suppress +from _colorize import _ANSIColors + __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'format_exception_only', 'format_list', 'format_stack', 'format_tb', 'print_exc', 'format_exc', 'print_exception', @@ -17,12 +17,11 @@ 'FrameSummary', 'StackSummary', 'TracebackException', 'walk_stack', 'walk_tb'] + # # Formatting and printing lists of traceback lines. # -_COLORIZE = True - def print_list(extracted_list, file=None): """Print the list of tuples as returned by extract_tb() or extract_stack() as a formatted stack trace to the given file.""" @@ -133,33 +132,10 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ BUILTIN_EXCEPTION_LIMIT = object() -def _can_colorize(): - if sys.platform == "win32": - try: - import nt - if not nt._supports_virtual_terminal(): - return False - except (ImportError, AttributeError): - return False - - if os.environ.get("PYTHON_COLORS") == "0": - return False - if os.environ.get("PYTHON_COLORS") == "1": - return True - if "NO_COLOR" in os.environ: - return False - if not _COLORIZE: - return False - if "FORCE_COLOR" in os.environ: - return True - if os.environ.get("TERM") == "dumb": - return False - try: - return os.isatty(sys.stderr.fileno()) - except io.UnsupportedOperation: - return sys.stderr.isatty() def _print_exception_bltin(exc, /): + from _colorize import _can_colorize + file = sys.stderr if sys.stderr is not None else sys.__stderr__ colorize = _can_colorize() return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) @@ -443,13 +419,6 @@ def _get_code_position(code, instruction_index): _RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c. -class _ANSIColors: - RED = '\x1b[31m' - BOLD_RED = '\x1b[1;31m' - MAGENTA = '\x1b[35m' - BOLD_MAGENTA = '\x1b[1;35m' - GREY = '\x1b[90m' - RESET = '\x1b[0m' class StackSummary(list): """A list of FrameSummary objects, representing a stack of frames.""" From bb591b62dd8f6b5c573403b28f03a3dc8f4a4f38 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 17:19:02 +0300 Subject: [PATCH 03/26] Fix whitespace --- Lib/doctest.py | 38 ++++++++++++++++++++++++++ Lib/test/test_doctest/test_doctest.py | 25 +++++++++++++++++ Lib/test/test_doctest/test_doctest2.py | 1 + 3 files changed, 64 insertions(+) diff --git a/Lib/doctest.py b/Lib/doctest.py index ce899d66988c3b..749a187bc3742f 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -147,10 +147,13 @@ def __repr__(self): # Option constants. OPTIONFLAGS_BY_NAME = {} + + def register_optionflag(name): # Create a new flag unless `name` is already known. return OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(OPTIONFLAGS_BY_NAME)) + DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1') DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE') NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE') @@ -194,6 +197,7 @@ def register_optionflag(name): # 8. Debugging Support # 9. Example Usage + ###################################################################### ## 1. Utility Functions ###################################################################### @@ -210,6 +214,7 @@ def _extract_future_flags(globs): flags |= feature.compiler_flag return flags + def _normalize_module(module, depth=2): """ Return the module specified by `module`. In particular: @@ -235,10 +240,12 @@ def _normalize_module(module, depth=2): else: raise TypeError("Expected a module, string, or None") + def _newline_convert(data): # The IO module provides a handy decoder for universal newline conversion return IncrementalNewlineDecoder(None, True).decode(data, True) + def _load_testfile(filename, package, module_relative, encoding): if module_relative: package = _normalize_module(package, 3) @@ -257,6 +264,7 @@ def _load_testfile(filename, package, module_relative, encoding): with open(filename, encoding=encoding) as f: return f.read(), filename + def _indent(s, indent=4): """ Add the given number of space characters to the beginning of @@ -265,6 +273,7 @@ def _indent(s, indent=4): # This regexp matches the start of non-blank lines: return re.sub('(?m)^(?!$)', indent*' ', s) + def _exception_traceback(exc_info): """ Return a string containing a traceback message for the given @@ -276,6 +285,7 @@ def _exception_traceback(exc_info): traceback.print_exception(exc_type, exc_val, exc_tb, file=excout) return excout.getvalue() + # Override some StringIO methods. class _SpoofOut(StringIO): def getvalue(self): @@ -291,6 +301,7 @@ def truncate(self, size=None): self.seek(size) StringIO.truncate(self) + # Worst-case linear-time ellipsis matching. def _ellipsis_match(want, got): """ @@ -341,6 +352,7 @@ def _ellipsis_match(want, got): return True + def _comment_line(line): "Return a commented form of the given line" line = line.rstrip() @@ -349,6 +361,7 @@ def _comment_line(line): else: return '#' + def _strip_exception_details(msg): # Support for IGNORE_EXCEPTION_DETAIL. # Get rid of everything except the exception name; in particular, drop @@ -375,6 +388,7 @@ def _strip_exception_details(msg): start = i+1 return msg[start: end] + class _OutputRedirectingPdb(pdb.Pdb): """ A specialized version of the python debugger that redirects stdout @@ -411,6 +425,7 @@ def trace_dispatch(self, *args): finally: sys.stdout = save_stdout + # [XX] Normalize with respect to os.path.pardir? def _module_relative_path(module, test_path): if not inspect.ismodule(module): @@ -446,6 +461,7 @@ def _module_relative_path(module, test_path): # Combine the base directory and the test path. return os.path.join(basedir, test_path) + ###################################################################### ## 2. Example & DocTest ###################################################################### @@ -526,6 +542,7 @@ def __hash__(self): return hash((self.source, self.want, self.lineno, self.indent, self.exc_msg)) + class DocTest: """ A collection of doctest examples that should be run in a single @@ -599,6 +616,7 @@ def __lt__(self, other): < (other.name, other.filename, other_lno, id(other))) + ###################################################################### ## 3. DocTestParser ###################################################################### @@ -1164,6 +1182,7 @@ def _find_lineno(self, obj, source_lines): # We couldn't find the line number. return None + ###################################################################### ## 5. DocTest Runner ###################################################################### @@ -1483,6 +1502,7 @@ def __record_outcome(self, test, failures, tries, skips): __LINECACHE_FILENAME_RE = re.compile(r'.+)' r'\[(?P\d+)\]>$') + def __patched_linecache_getlines(self, filename, module_globals=None): m = self.__LINECACHE_FILENAME_RE.match(filename) if m and m.group('name') == self.test.name: @@ -1819,6 +1839,7 @@ def output_difference(self, example, got, optionflags): else: return 'Expected nothing\nGot nothing\n' + class DocTestFailure(Exception): """A DocTest example has failed in debugging mode. @@ -1838,6 +1859,7 @@ def __init__(self, test, example, got): def __str__(self): return str(self.test) + class UnexpectedException(Exception): """A DocTest example has encountered an unexpected exception @@ -1857,6 +1879,7 @@ def __init__(self, test, example, exc_info): def __str__(self): return str(self.test) + class DebugRunner(DocTestRunner): r"""Run doc tests but raise an exception as soon as there is a failure. @@ -1960,6 +1983,7 @@ def report_unexpected_exception(self, out, test, example, exc_info): def report_failure(self, out, test, example, got): raise DocTestFailure(test, example, got) + ###################################################################### ## 6. Test Functions ###################################################################### @@ -1969,6 +1993,7 @@ def report_failure(self, out, test, example, got): # class, updated by testmod. master = None + def testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False): @@ -2221,12 +2246,14 @@ def run_docstring_examples(f, globs, verbose=False, name="NoName", for test in finder.find(f, name, globs=globs): runner.run(test, compileflags=compileflags) + ###################################################################### ## 7. Unittest Support ###################################################################### _unittest_reportflags = 0 + def set_unittest_reportflags(flags): """Sets the unittest option flags. @@ -2427,6 +2454,7 @@ def __repr__(self): def shortDescription(self): return "Doctest: " + self._dt_test.name + class SkipDocTestCase(DocTestCase): def __init__(self, module): self.module = module @@ -2514,6 +2542,7 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, return suite + class DocFileCase(DocTestCase): def id(self): @@ -2527,6 +2556,7 @@ def format_failure(self, err): % (self._dt_test.name, self._dt_test.filename, err) ) + def DocFileTest(path, module_relative=True, package=None, globs=None, parser=DocTestParser(), encoding=None, **options): @@ -2553,6 +2583,7 @@ def DocFileTest(path, module_relative=True, package=None, test = parser.get_doctest(doc, globs, name, path, 0) return DocFileCase(test, **options) + def DocFileSuite(*paths, **kw): """A unittest suite for one or more doctest files. @@ -2622,6 +2653,7 @@ def DocFileSuite(*paths, **kw): return suite + ###################################################################### ## 8. Debugging Support ###################################################################### @@ -2708,6 +2740,7 @@ def script_from_examples(s): # Add a courtesy newline to prevent exec from choking (see bug #1172785) return '\n'.join(output) + '\n' + def testsource(module, name): """Extract the test sources from a doctest docstring as a script. @@ -2724,11 +2757,13 @@ def testsource(module, name): testsrc = script_from_examples(test.docstring) return testsrc + def debug_src(src, pm=False, globs=None): """Debug a single doctest docstring, in argument `src`'""" testsrc = script_from_examples(src) debug_script(testsrc, pm, globs) + def debug_script(src, pm=False, globs=None): "Debug a test script. `src` is the script, as a string." import pdb @@ -2749,6 +2784,7 @@ def debug_script(src, pm=False, globs=None): else: pdb.Pdb(nosigint=True).run("exec(%r)" % src, globs, globs) + def debug(module, name, pm=False): """Debug a single doctest docstring. @@ -2760,6 +2796,7 @@ def debug(module, name, pm=False): testsrc = testsource(module, name) debug_script(testsrc, pm, module.__dict__) + ###################################################################### ## 9. Example Usage ###################################################################### @@ -2807,6 +2844,7 @@ def get(self): return self.val + __test__ = {"_TestClass": _TestClass, "string": r""" Example of a string object, searched as-is. diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index 13ab3c9e6ca200..e1727aa9b17b2d 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -43,6 +43,7 @@ def sample_func(v): """ return v+v + class SampleClass: """ >>> print(1) @@ -135,11 +136,14 @@ def __init__(self, val=0): 0 """ self.val = val + def square(self): return SampleClass.NestedClass(self.val*self.val) + def get(self): return self.val + class SampleNewStyleClass(object): r""" >>> print('1\n2\n3') @@ -168,6 +172,7 @@ def get(self): """ return self.val + ###################################################################### ## Test Cases ###################################################################### @@ -293,6 +298,7 @@ def test_Example(): r""" True """ + def test_DocTest(): r""" Unit tests for the `DocTest` class. @@ -443,6 +449,7 @@ def test_DocTest(): r""" """ + class test_DocTestFinder: def basics(): r""" Unit tests for the `DocTestFinder` class. @@ -828,6 +835,7 @@ def test_empty_namespace_package(self): self.assertEqual(len(include_empty_finder.find(mod)), 1) self.assertEqual(len(exclude_empty_finder.find(mod)), 0) + def test_DocTestParser(): r""" Unit tests for the `DocTestParser` class. @@ -883,6 +891,7 @@ def test_DocTestParser(): r""" ('x+y\n', '5\n', 9) """ + class test_DocTestRunner: def basics(): r""" Unit tests for the `DocTestRunner` class. @@ -1928,6 +1937,7 @@ def option_directives(): r""" ValueError: line 0 of the doctest for s has an option directive on a line with no example: '# doctest: +ELLIPSIS' """ + def test_testsource(): r""" Unit tests for `testsource()`. @@ -1968,6 +1978,7 @@ def test_testsource(): r""" """ + def test_debug(): r""" Create a docstring that we want to debug: @@ -1998,6 +2009,7 @@ def test_debug(): r""" """ + if not hasattr(sys, 'gettrace') or not sys.gettrace(): def test_pdb_set_trace(): """Using pdb.set_trace from a doctest. @@ -2219,6 +2231,7 @@ def test_pdb_set_trace_nested(): TestResults(failed=0, attempted=2) """ + def test_DocTestSuite(): """DocTestSuite creates a unittest test suite from a doctest. @@ -2346,6 +2359,7 @@ def test_DocTestSuite(): automatically cleared for us after a test. """ + def test_DocFileSuite(): """We can test tests found in text files using a DocFileSuite. @@ -2525,6 +2539,7 @@ def test_DocFileSuite(): """ + def test_trailing_space_in_test(): """ Trailing spaces in expected output are significant: @@ -2534,6 +2549,7 @@ def test_trailing_space_in_test(): foo \n """ + class Wrapper: def __init__(self, func): self.func = func @@ -2542,6 +2558,7 @@ def __init__(self, func): def __call__(self, *args, **kwargs): self.func(*args, **kwargs) + @Wrapper def test_look_in_unwrapped(): """ @@ -2551,6 +2568,7 @@ def test_look_in_unwrapped(): 'one other test' """ + def test_unittest_reportflags(): """Default unittest reporting flags can be set to control reporting @@ -2630,6 +2648,7 @@ def test_unittest_reportflags(): """ + def test_testfile(): r""" Tests for the `testfile()` function. This function runs all the doctest examples in a given file. In its simple invocation, it is @@ -2809,6 +2828,7 @@ def test_testfile(): r""" >>> _colorize._COLORIZE = save_colorize """ + class TestImporter(importlib.abc.MetaPathFinder, importlib.abc.ResourceLoader): def find_spec(self, fullname, path, target=None): @@ -2818,6 +2838,7 @@ def get_data(self, path): with open(path, mode='rb') as f: return f.read() + class TestHook: def __init__(self, pathdir): @@ -2922,6 +2943,7 @@ def test_lineendings(): r""" """ + def test_testmod(): r""" Tests for the testmod function. More might be useful, but for now we're just testing the case raised by Issue 6195, where trying to doctest a C module would @@ -2933,6 +2955,7 @@ def test_testmod(): r""" TestResults(failed=0, attempted=0) """ + try: os.fsencode("foo-bär@baz.py") supports_unicode = True @@ -2971,6 +2994,7 @@ def test_unicode(): """ TestResults(failed=1, attempted=1) """ + def test_CLI(): r""" The doctest module can be used to run doctests against an arbitrary file. These tests test this CLI functionality. @@ -3201,6 +3225,7 @@ def test_CLI(): r""" """ + def test_no_trailing_whitespace_stripping(): r""" The fancy reports had a bug for a long time where any trailing whitespace on diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py index ea9b430fad80a4..73bc04ba11571c 100644 --- a/Lib/test/test_doctest/test_doctest2.py +++ b/Lib/test/test_doctest/test_doctest2.py @@ -19,6 +19,7 @@ if sys.flags.optimize >= 2: raise unittest.SkipTest("Cannot test docstrings with -O2") + class C(object): """Class C. From 42079be22a157b34db9d89ac81158f149fab8f81 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 17:19:10 +0300 Subject: [PATCH 04/26] Use f-strings --- Lib/doctest.py | 14 +++++++------- Lib/test/test_doctest/test_doctest2.py | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 749a187bc3742f..f537eca715bfd6 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1050,7 +1050,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): # Look for tests in a module's contained objects. if inspect.ismodule(obj) and self._recurse: for valname, val in obj.__dict__.items(): - valname = '%s.%s' % (name, valname) + valname = f'{name}.{valname}' # Recurse to functions & classes. if ((self._is_routine(val) or inspect.isclass(val)) and @@ -1071,7 +1071,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): "must be strings, functions, methods, " "classes, or modules: %r" % (type(val),)) - valname = '%s.__test__.%s' % (name, valname) + valname = f'{name}.__test__.{valname}' self._find(tests, val, valname, module, source_lines, globs, seen) @@ -1086,7 +1086,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): if ((inspect.isroutine(val) or inspect.isclass(val) or isinstance(val, property)) and self._from_module(module, val)): - valname = '%s.%s' % (name, valname) + valname = f'{name}.{valname}' self._find(tests, val, valname, module, source_lines, globs, seen) @@ -1335,7 +1335,7 @@ def _failure_header(self, test, example): out.append('File "%s", line %s, in %s' % (test.filename, lineno, test.name)) else: - out.append('Line %s, in %s' % (example.lineno+1, test.name)) + out.append(f'Line {example.lineno+1}, in {test.name}') out.append('Failed example:') source = example.source out.append(_indent(source)) @@ -1831,7 +1831,7 @@ def output_difference(self, example, got, optionflags): # If we're not using diff, then simply list the expected # output followed by the actual output. if want and got: - return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got)) + return f'Expected:\n{_indent(want)}Got:\n{_indent(got)}' elif want: return 'Expected:\n%sGot nothing\n' % _indent(want) elif got: @@ -2071,7 +2071,7 @@ class doctest.Tester, then merges the results into (or creates) # Check that we were actually given a module. if not inspect.ismodule(m): - raise TypeError("testmod: module required; %r" % (m,)) + raise TypeError(f"testmod: module required; {m!r}") # If no name was given, then use the module's name. if name is None: @@ -2447,7 +2447,7 @@ def __hash__(self): def __repr__(self): name = self._dt_test.name.split('.') - return "%s (%s)" % (name[-1], '.'.join(name[:-1])) + return f"{name[-1]} ({'.'.join(name[:-1])})" __str__ = object.__str__ diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py index 73bc04ba11571c..a7765e05eafd78 100644 --- a/Lib/test/test_doctest/test_doctest2.py +++ b/Lib/test/test_doctest/test_doctest2.py @@ -122,13 +122,14 @@ def tearDown(self): _colorize._COLORIZE = self.colorize def test_testmod(self): - import doctest, sys + import doctest + import sys EXPECTED = 19 f, t = doctest.testmod(sys.modules[__name__]) if f: - self.fail("%d of %d doctests failed" % (f, t)) + self.fail(f"{f} of {t} doctests failed") if t != EXPECTED: - self.fail("expected %d tests to run, not %d" % (EXPECTED, t)) + self.fail(f"expected {EXPECTED} tests to run, not {t}") # Pollute the namespace with a bunch of imported functions and classes, From 0088579e2760a53242816b7d94a99602bff964a2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 11:48:21 -0600 Subject: [PATCH 05/26] Remove underscores from members of an underscored module Co-authored-by: Alex Waygood --- Lib/_colorize.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 412ead5d35f4e4..b984480a9af270 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -2,10 +2,10 @@ import os import sys -_COLORIZE = True +COLORIZE = True -class _ANSIColors: +class ANSIColors: BOLD_GREEN = "\x1b[1;32m" BOLD_MAGENTA = "\x1b[1;35m" BOLD_RED = "\x1b[1;31m" @@ -17,7 +17,7 @@ class _ANSIColors: YELLOW = "\x1b[33m" -def _can_colorize(): +def can_colorize(): if sys.platform == "win32": try: import nt From d3034fa57a6f2ac95ef6f22c0d4d9e72bc71190f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 18:41:50 +0300 Subject: [PATCH 06/26] Add blurb --- .../next/Library/2024-04-06-18-41-36.gh-issue-117225.tJh1Hw.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-04-06-18-41-36.gh-issue-117225.tJh1Hw.rst diff --git a/Misc/NEWS.d/next/Library/2024-04-06-18-41-36.gh-issue-117225.tJh1Hw.rst b/Misc/NEWS.d/next/Library/2024-04-06-18-41-36.gh-issue-117225.tJh1Hw.rst new file mode 100644 index 00000000000000..6a0da1c3bc9388 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-04-06-18-41-36.gh-issue-117225.tJh1Hw.rst @@ -0,0 +1 @@ +Add colour to doctest output. Patch by Hugo van Kemenade. From 39780cb079fabc629d5afd69f2e3159983d01574 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:53:23 +0300 Subject: [PATCH 07/26] Remove underscores from members of an underscored module --- Lib/_colorize.py | 2 +- Lib/doctest.py | 22 +++++------ Lib/test/test_doctest/test_doctest.py | 6 +-- Lib/test/test_doctest/test_doctest2.py | 6 +-- Lib/test/test_traceback.py | 52 +++++++++++++------------- Lib/traceback.py | 52 +++++++++++++------------- 6 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Lib/_colorize.py b/Lib/_colorize.py index b984480a9af270..4590062c30b357 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -33,7 +33,7 @@ def can_colorize(): return True if "NO_COLOR" in os.environ: return False - if not _COLORIZE: + if not COLORIZE: return False if "FORCE_COLOR" in os.environ: return True diff --git a/Lib/doctest.py b/Lib/doctest.py index f537eca715bfd6..7b98b932f04fa3 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1193,8 +1193,8 @@ class DocTestRunner: The `run` method is used to process a single DocTest case. It returns a TestResults instance. - >>> save_colorize = _colorize._COLORIZE - >>> _colorize._COLORIZE = False + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False >>> tests = DocTestFinder().find(_TestClass) >>> runner = DocTestRunner(verbose=False) @@ -1247,7 +1247,7 @@ class DocTestRunner: overriding the methods `report_start`, `report_success`, `report_unexpected_exception`, and `report_failure`. - >>> _colorize._COLORIZE = save_colorize + >>> _colorize.COLORIZE = save_colorize """ # This divider string is used to separate failure messages, and to # separate sections of the summary. @@ -1593,7 +1593,7 @@ def summarize(self, verbose=None): summary is. If the verbosity is not specified, then the DocTestRunner's verbosity is used. """ - from _colorize import _ANSIColors, _can_colorize + from _colorize import ANSIColors, can_colorize if verbose is None: verbose = self._verbose @@ -1614,13 +1614,13 @@ def summarize(self, verbose=None): else: failed.append((name, (failures, tries, skips))) - if _can_colorize(): - bold_green = _ANSIColors.BOLD_GREEN - bold_red = _ANSIColors.BOLD_RED - green = _ANSIColors.GREEN - red = _ANSIColors.RED - reset = _ANSIColors.RESET - yellow = _ANSIColors.YELLOW + if can_colorize(): + bold_green = ANSIColors.BOLD_GREEN + bold_red = ANSIColors.BOLD_RED + green = ANSIColors.GREEN + red = ANSIColors.RED + reset = ANSIColors.RESET + yellow = ANSIColors.YELLOW else: bold_green = "" bold_red = "" diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index e1727aa9b17b2d..e5fb82880cb201 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -2657,8 +2657,8 @@ def test_testfile(): r""" We don't want colour or `-v` in sys.argv for these tests. - >>> save_colorize = _colorize._COLORIZE - >>> _colorize._COLORIZE = False + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False >>> save_argv = sys.argv >>> if '-v' in sys.argv: ... sys.argv = [arg for arg in save_argv if arg != '-v'] @@ -2825,7 +2825,7 @@ def test_testfile(): r""" TestResults(failed=0, attempted=2) >>> doctest.master = None # Reset master. >>> sys.argv = save_argv - >>> _colorize._COLORIZE = save_colorize + >>> _colorize.COLORIZE = save_colorize """ diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py index a7765e05eafd78..472fac7f21e337 100644 --- a/Lib/test/test_doctest/test_doctest2.py +++ b/Lib/test/test_doctest/test_doctest2.py @@ -114,12 +114,12 @@ def clsm(cls, val): class Test(unittest.TestCase): def setUp(self): super().setUp() - self.colorize = _colorize._COLORIZE - _colorize._COLORIZE = False + self.colorize = _colorize.COLORIZE + _colorize.COLORIZE = False def tearDown(self): super().tearDown() - _colorize._COLORIZE = self.colorize + _colorize.COLORIZE = self.colorize def test_testmod(self): import doctest diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 2f55d2392c484b..d267b8cd10a497 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -47,12 +47,12 @@ class TracebackCases(unittest.TestCase): # formatting of SyntaxErrors works based on changes for 2.1. def setUp(self): super().setUp() - self.colorize = _colorize._COLORIZE - _colorize._COLORIZE = False + self.colorize = _colorize.COLORIZE + _colorize.COLORIZE = False def tearDown(self): super().tearDown() - _colorize._COLORIZE = self.colorize + _colorize.COLORIZE = self.colorize def get_exception_format(self, func, exc): try: @@ -4293,9 +4293,9 @@ def bar(): e, capture_locals=True ) lines = "".join(exc.format(colorize=True)) - red = _colorize._ANSIColors.RED - boldr = _colorize._ANSIColors.BOLD_RED - reset = _colorize._ANSIColors.RESET + red = _colorize.ANSIColors.RED + boldr = _colorize.ANSIColors.BOLD_RED + reset = _colorize.ANSIColors.RESET self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines) self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines) self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines) @@ -4311,11 +4311,11 @@ def test_colorized_syntax_error(self): e, capture_locals=True ) actual = "".join(exc.format(colorize=True)) - red = _colorize._ANSIColors.RED - magenta = _colorize._ANSIColors.MAGENTA - boldm = _colorize._ANSIColors.BOLD_MAGENTA - boldr = _colorize._ANSIColors.BOLD_RED - reset = _colorize._ANSIColors.RESET + red = _colorize.ANSIColors.RED + magenta = _colorize.ANSIColors.MAGENTA + boldm = _colorize.ANSIColors.BOLD_MAGENTA + boldr = _colorize.ANSIColors.BOLD_RED + reset = _colorize.ANSIColors.RESET expected = "".join([ f' File {magenta}""{reset}, line {magenta}1{reset}\n', f' a {boldr}${reset} b\n', @@ -4334,15 +4334,15 @@ def foo(): self.fail("No exception thrown.") except Exception as e: with captured_output("stderr") as tbstderr: - with unittest.mock.patch('_colorize._can_colorize', return_value=True): + with unittest.mock.patch('_colorize.can_colorize', return_value=True): exception_print(e) actual = tbstderr.getvalue().splitlines() - red = _colorize._ANSIColors.RED - boldr = _colorize._ANSIColors.BOLD_RED - magenta = _colorize._ANSIColors.MAGENTA - boldm = _colorize._ANSIColors.BOLD_MAGENTA - reset = _colorize._ANSIColors.RESET + red = _colorize.ANSIColors.RED + boldr = _colorize.ANSIColors.BOLD_RED + magenta = _colorize.ANSIColors.MAGENTA + boldm = _colorize.ANSIColors.BOLD_MAGENTA + reset = _colorize.ANSIColors.RESET lno_foo = foo.__code__.co_firstlineno expected = ['Traceback (most recent call last):', f' File {magenta}"{__file__}"{reset}, ' @@ -4365,23 +4365,23 @@ def test_colorized_detection_checks_for_environment_variables(self): with unittest.mock.patch("os.isatty") as isatty_mock: isatty_mock.return_value = True with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): - self.assertEqual(_colorize._can_colorize(), False) + self.assertEqual(_colorize.can_colorize(), False) with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): - self.assertEqual(_colorize._can_colorize(), True) + self.assertEqual(_colorize.can_colorize(), True) with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): - self.assertEqual(_colorize._can_colorize(), False) + self.assertEqual(_colorize.can_colorize(), False) with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): - self.assertEqual(_colorize._can_colorize(), False) + self.assertEqual(_colorize.can_colorize(), False) with unittest.mock.patch("os.environ", {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): - self.assertEqual(_colorize._can_colorize(), True) + self.assertEqual(_colorize.can_colorize(), True) with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): - self.assertEqual(_colorize._can_colorize(), True) + self.assertEqual(_colorize.can_colorize(), True) with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): - self.assertEqual(_colorize._can_colorize(), False) + self.assertEqual(_colorize.can_colorize(), False) with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): - self.assertEqual(_colorize._can_colorize(), False) + self.assertEqual(_colorize.can_colorize(), False) isatty_mock.return_value = False - self.assertEqual(_colorize._can_colorize(), False) + self.assertEqual(_colorize.can_colorize(), False) if __name__ == "__main__": diff --git a/Lib/traceback.py b/Lib/traceback.py index a294fe21574c5a..c70fefcf069915 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -8,7 +8,7 @@ import warnings from contextlib import suppress -from _colorize import _ANSIColors +from _colorize import ANSIColors __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'format_exception_only', 'format_list', 'format_stack', @@ -134,10 +134,10 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ def _print_exception_bltin(exc, /): - from _colorize import _can_colorize + from _colorize import can_colorize file = sys.stderr if sys.stderr is not None else sys.__stderr__ - colorize = _can_colorize() + colorize = can_colorize() return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) @@ -184,9 +184,9 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize= end_char = "\n" if insert_final_newline else "" if colorize: if value is None or not valuestr: - line = f"{_ANSIColors.BOLD_MAGENTA}{etype}{_ANSIColors.RESET}{end_char}" + line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}{end_char}" else: - line = f"{_ANSIColors.BOLD_MAGENTA}{etype}{_ANSIColors.RESET}: {_ANSIColors.MAGENTA}{valuestr}{_ANSIColors.RESET}{end_char}" + line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}: {ANSIColors.MAGENTA}{valuestr}{ANSIColors.RESET}{end_char}" else: if value is None or not valuestr: line = f"{etype}{end_char}" @@ -523,15 +523,15 @@ def format_frame_summary(self, frame_summary, **kwargs): filename = "" if colorize: row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format( - _ANSIColors.MAGENTA, + ANSIColors.MAGENTA, filename, - _ANSIColors.RESET, - _ANSIColors.MAGENTA, + ANSIColors.RESET, + ANSIColors.MAGENTA, frame_summary.lineno, - _ANSIColors.RESET, - _ANSIColors.MAGENTA, + ANSIColors.RESET, + ANSIColors.MAGENTA, frame_summary.name, - _ANSIColors.RESET, + ANSIColors.RESET, ) ) else: @@ -658,11 +658,11 @@ def output_line(lineno): for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]): caret_group = list(group) if color == "^": - colorized_line_parts.append(_ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + _ANSIColors.RESET) - colorized_carets_parts.append(_ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + _ANSIColors.RESET) + colorized_line_parts.append(ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET) + colorized_carets_parts.append(ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET) elif color == "~": - colorized_line_parts.append(_ANSIColors.RED + "".join(char for char, _ in caret_group) + _ANSIColors.RESET) - colorized_carets_parts.append(_ANSIColors.RED + "".join(caret for _, caret in caret_group) + _ANSIColors.RESET) + colorized_line_parts.append(ANSIColors.RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET) + colorized_carets_parts.append(ANSIColors.RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET) else: colorized_line_parts.append("".join(char for char, _ in caret_group)) colorized_carets_parts.append("".join(caret for _, caret in caret_group)) @@ -1238,12 +1238,12 @@ def _format_syntax_error(self, stype, **kwargs): if self.lineno is not None: if colorize: yield ' File {}"{}"{}, line {}{}{}\n'.format( - _ANSIColors.MAGENTA, + ANSIColors.MAGENTA, self.filename or "", - _ANSIColors.RESET, - _ANSIColors.MAGENTA, + ANSIColors.RESET, + ANSIColors.MAGENTA, self.lineno, - _ANSIColors.RESET, + ANSIColors.RESET, ) else: yield ' File "{}", line {}\n'.format( @@ -1283,11 +1283,11 @@ def _format_syntax_error(self, stype, **kwargs): # colorize from colno to end_colno ltext = ( ltext[:colno] + - _ANSIColors.BOLD_RED + ltext[colno:end_colno] + _ANSIColors.RESET + + ANSIColors.BOLD_RED + ltext[colno:end_colno] + ANSIColors.RESET + ltext[end_colno:] ) - start_color = _ANSIColors.BOLD_RED - end_color = _ANSIColors.RESET + start_color = ANSIColors.BOLD_RED + end_color = ANSIColors.RESET yield ' {}\n'.format(ltext) yield ' {}{}{}{}\n'.format( "".join(caretspace), @@ -1300,12 +1300,12 @@ def _format_syntax_error(self, stype, **kwargs): msg = self.msg or "" if colorize: yield "{}{}{}: {}{}{}{}\n".format( - _ANSIColors.BOLD_MAGENTA, + ANSIColors.BOLD_MAGENTA, stype, - _ANSIColors.RESET, - _ANSIColors.MAGENTA, + ANSIColors.RESET, + ANSIColors.MAGENTA, msg, - _ANSIColors.RESET, + ANSIColors.RESET, filename_suffix) else: yield "{}: {}{}\n".format(stype, msg, filename_suffix) From c5aec154928269d878e3f6fd7d6c54f2e535cca1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:54:03 +0300 Subject: [PATCH 08/26] Revert "Fix whitespace" This reverts commit bb591b62dd8f6b5c573403b28f03a3dc8f4a4f38. --- Lib/doctest.py | 38 -------------------------- Lib/test/test_doctest/test_doctest.py | 25 ----------------- Lib/test/test_doctest/test_doctest2.py | 1 - 3 files changed, 64 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 7b98b932f04fa3..6a361d28703260 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -147,13 +147,10 @@ def __repr__(self): # Option constants. OPTIONFLAGS_BY_NAME = {} - - def register_optionflag(name): # Create a new flag unless `name` is already known. return OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(OPTIONFLAGS_BY_NAME)) - DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1') DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE') NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE') @@ -197,7 +194,6 @@ def register_optionflag(name): # 8. Debugging Support # 9. Example Usage - ###################################################################### ## 1. Utility Functions ###################################################################### @@ -214,7 +210,6 @@ def _extract_future_flags(globs): flags |= feature.compiler_flag return flags - def _normalize_module(module, depth=2): """ Return the module specified by `module`. In particular: @@ -240,12 +235,10 @@ def _normalize_module(module, depth=2): else: raise TypeError("Expected a module, string, or None") - def _newline_convert(data): # The IO module provides a handy decoder for universal newline conversion return IncrementalNewlineDecoder(None, True).decode(data, True) - def _load_testfile(filename, package, module_relative, encoding): if module_relative: package = _normalize_module(package, 3) @@ -264,7 +257,6 @@ def _load_testfile(filename, package, module_relative, encoding): with open(filename, encoding=encoding) as f: return f.read(), filename - def _indent(s, indent=4): """ Add the given number of space characters to the beginning of @@ -273,7 +265,6 @@ def _indent(s, indent=4): # This regexp matches the start of non-blank lines: return re.sub('(?m)^(?!$)', indent*' ', s) - def _exception_traceback(exc_info): """ Return a string containing a traceback message for the given @@ -285,7 +276,6 @@ def _exception_traceback(exc_info): traceback.print_exception(exc_type, exc_val, exc_tb, file=excout) return excout.getvalue() - # Override some StringIO methods. class _SpoofOut(StringIO): def getvalue(self): @@ -301,7 +291,6 @@ def truncate(self, size=None): self.seek(size) StringIO.truncate(self) - # Worst-case linear-time ellipsis matching. def _ellipsis_match(want, got): """ @@ -352,7 +341,6 @@ def _ellipsis_match(want, got): return True - def _comment_line(line): "Return a commented form of the given line" line = line.rstrip() @@ -361,7 +349,6 @@ def _comment_line(line): else: return '#' - def _strip_exception_details(msg): # Support for IGNORE_EXCEPTION_DETAIL. # Get rid of everything except the exception name; in particular, drop @@ -388,7 +375,6 @@ def _strip_exception_details(msg): start = i+1 return msg[start: end] - class _OutputRedirectingPdb(pdb.Pdb): """ A specialized version of the python debugger that redirects stdout @@ -425,7 +411,6 @@ def trace_dispatch(self, *args): finally: sys.stdout = save_stdout - # [XX] Normalize with respect to os.path.pardir? def _module_relative_path(module, test_path): if not inspect.ismodule(module): @@ -461,7 +446,6 @@ def _module_relative_path(module, test_path): # Combine the base directory and the test path. return os.path.join(basedir, test_path) - ###################################################################### ## 2. Example & DocTest ###################################################################### @@ -542,7 +526,6 @@ def __hash__(self): return hash((self.source, self.want, self.lineno, self.indent, self.exc_msg)) - class DocTest: """ A collection of doctest examples that should be run in a single @@ -616,7 +599,6 @@ def __lt__(self, other): < (other.name, other.filename, other_lno, id(other))) - ###################################################################### ## 3. DocTestParser ###################################################################### @@ -1182,7 +1164,6 @@ def _find_lineno(self, obj, source_lines): # We couldn't find the line number. return None - ###################################################################### ## 5. DocTest Runner ###################################################################### @@ -1502,7 +1483,6 @@ def __record_outcome(self, test, failures, tries, skips): __LINECACHE_FILENAME_RE = re.compile(r'.+)' r'\[(?P\d+)\]>$') - def __patched_linecache_getlines(self, filename, module_globals=None): m = self.__LINECACHE_FILENAME_RE.match(filename) if m and m.group('name') == self.test.name: @@ -1839,7 +1819,6 @@ def output_difference(self, example, got, optionflags): else: return 'Expected nothing\nGot nothing\n' - class DocTestFailure(Exception): """A DocTest example has failed in debugging mode. @@ -1859,7 +1838,6 @@ def __init__(self, test, example, got): def __str__(self): return str(self.test) - class UnexpectedException(Exception): """A DocTest example has encountered an unexpected exception @@ -1879,7 +1857,6 @@ def __init__(self, test, example, exc_info): def __str__(self): return str(self.test) - class DebugRunner(DocTestRunner): r"""Run doc tests but raise an exception as soon as there is a failure. @@ -1983,7 +1960,6 @@ def report_unexpected_exception(self, out, test, example, exc_info): def report_failure(self, out, test, example, got): raise DocTestFailure(test, example, got) - ###################################################################### ## 6. Test Functions ###################################################################### @@ -1993,7 +1969,6 @@ def report_failure(self, out, test, example, got): # class, updated by testmod. master = None - def testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False): @@ -2246,14 +2221,12 @@ def run_docstring_examples(f, globs, verbose=False, name="NoName", for test in finder.find(f, name, globs=globs): runner.run(test, compileflags=compileflags) - ###################################################################### ## 7. Unittest Support ###################################################################### _unittest_reportflags = 0 - def set_unittest_reportflags(flags): """Sets the unittest option flags. @@ -2454,7 +2427,6 @@ def __repr__(self): def shortDescription(self): return "Doctest: " + self._dt_test.name - class SkipDocTestCase(DocTestCase): def __init__(self, module): self.module = module @@ -2542,7 +2514,6 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, return suite - class DocFileCase(DocTestCase): def id(self): @@ -2556,7 +2527,6 @@ def format_failure(self, err): % (self._dt_test.name, self._dt_test.filename, err) ) - def DocFileTest(path, module_relative=True, package=None, globs=None, parser=DocTestParser(), encoding=None, **options): @@ -2583,7 +2553,6 @@ def DocFileTest(path, module_relative=True, package=None, test = parser.get_doctest(doc, globs, name, path, 0) return DocFileCase(test, **options) - def DocFileSuite(*paths, **kw): """A unittest suite for one or more doctest files. @@ -2653,7 +2622,6 @@ def DocFileSuite(*paths, **kw): return suite - ###################################################################### ## 8. Debugging Support ###################################################################### @@ -2740,7 +2708,6 @@ def script_from_examples(s): # Add a courtesy newline to prevent exec from choking (see bug #1172785) return '\n'.join(output) + '\n' - def testsource(module, name): """Extract the test sources from a doctest docstring as a script. @@ -2757,13 +2724,11 @@ def testsource(module, name): testsrc = script_from_examples(test.docstring) return testsrc - def debug_src(src, pm=False, globs=None): """Debug a single doctest docstring, in argument `src`'""" testsrc = script_from_examples(src) debug_script(testsrc, pm, globs) - def debug_script(src, pm=False, globs=None): "Debug a test script. `src` is the script, as a string." import pdb @@ -2784,7 +2749,6 @@ def debug_script(src, pm=False, globs=None): else: pdb.Pdb(nosigint=True).run("exec(%r)" % src, globs, globs) - def debug(module, name, pm=False): """Debug a single doctest docstring. @@ -2796,7 +2760,6 @@ def debug(module, name, pm=False): testsrc = testsource(module, name) debug_script(testsrc, pm, module.__dict__) - ###################################################################### ## 9. Example Usage ###################################################################### @@ -2844,7 +2807,6 @@ def get(self): return self.val - __test__ = {"_TestClass": _TestClass, "string": r""" Example of a string object, searched as-is. diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index e5fb82880cb201..6a368915746695 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -43,7 +43,6 @@ def sample_func(v): """ return v+v - class SampleClass: """ >>> print(1) @@ -136,14 +135,11 @@ def __init__(self, val=0): 0 """ self.val = val - def square(self): return SampleClass.NestedClass(self.val*self.val) - def get(self): return self.val - class SampleNewStyleClass(object): r""" >>> print('1\n2\n3') @@ -172,7 +168,6 @@ def get(self): """ return self.val - ###################################################################### ## Test Cases ###################################################################### @@ -298,7 +293,6 @@ def test_Example(): r""" True """ - def test_DocTest(): r""" Unit tests for the `DocTest` class. @@ -449,7 +443,6 @@ def test_DocTest(): r""" """ - class test_DocTestFinder: def basics(): r""" Unit tests for the `DocTestFinder` class. @@ -835,7 +828,6 @@ def test_empty_namespace_package(self): self.assertEqual(len(include_empty_finder.find(mod)), 1) self.assertEqual(len(exclude_empty_finder.find(mod)), 0) - def test_DocTestParser(): r""" Unit tests for the `DocTestParser` class. @@ -891,7 +883,6 @@ def test_DocTestParser(): r""" ('x+y\n', '5\n', 9) """ - class test_DocTestRunner: def basics(): r""" Unit tests for the `DocTestRunner` class. @@ -1937,7 +1928,6 @@ def option_directives(): r""" ValueError: line 0 of the doctest for s has an option directive on a line with no example: '# doctest: +ELLIPSIS' """ - def test_testsource(): r""" Unit tests for `testsource()`. @@ -1978,7 +1968,6 @@ def test_testsource(): r""" """ - def test_debug(): r""" Create a docstring that we want to debug: @@ -2009,7 +1998,6 @@ def test_debug(): r""" """ - if not hasattr(sys, 'gettrace') or not sys.gettrace(): def test_pdb_set_trace(): """Using pdb.set_trace from a doctest. @@ -2231,7 +2219,6 @@ def test_pdb_set_trace_nested(): TestResults(failed=0, attempted=2) """ - def test_DocTestSuite(): """DocTestSuite creates a unittest test suite from a doctest. @@ -2359,7 +2346,6 @@ def test_DocTestSuite(): automatically cleared for us after a test. """ - def test_DocFileSuite(): """We can test tests found in text files using a DocFileSuite. @@ -2539,7 +2525,6 @@ def test_DocFileSuite(): """ - def test_trailing_space_in_test(): """ Trailing spaces in expected output are significant: @@ -2549,7 +2534,6 @@ def test_trailing_space_in_test(): foo \n """ - class Wrapper: def __init__(self, func): self.func = func @@ -2558,7 +2542,6 @@ def __init__(self, func): def __call__(self, *args, **kwargs): self.func(*args, **kwargs) - @Wrapper def test_look_in_unwrapped(): """ @@ -2568,7 +2551,6 @@ def test_look_in_unwrapped(): 'one other test' """ - def test_unittest_reportflags(): """Default unittest reporting flags can be set to control reporting @@ -2648,7 +2630,6 @@ def test_unittest_reportflags(): """ - def test_testfile(): r""" Tests for the `testfile()` function. This function runs all the doctest examples in a given file. In its simple invocation, it is @@ -2828,7 +2809,6 @@ def test_testfile(): r""" >>> _colorize.COLORIZE = save_colorize """ - class TestImporter(importlib.abc.MetaPathFinder, importlib.abc.ResourceLoader): def find_spec(self, fullname, path, target=None): @@ -2838,7 +2818,6 @@ def get_data(self, path): with open(path, mode='rb') as f: return f.read() - class TestHook: def __init__(self, pathdir): @@ -2943,7 +2922,6 @@ def test_lineendings(): r""" """ - def test_testmod(): r""" Tests for the testmod function. More might be useful, but for now we're just testing the case raised by Issue 6195, where trying to doctest a C module would @@ -2955,7 +2933,6 @@ def test_testmod(): r""" TestResults(failed=0, attempted=0) """ - try: os.fsencode("foo-bär@baz.py") supports_unicode = True @@ -2994,7 +2971,6 @@ def test_unicode(): """ TestResults(failed=1, attempted=1) """ - def test_CLI(): r""" The doctest module can be used to run doctests against an arbitrary file. These tests test this CLI functionality. @@ -3225,7 +3201,6 @@ def test_CLI(): r""" """ - def test_no_trailing_whitespace_stripping(): r""" The fancy reports had a bug for a long time where any trailing whitespace on diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py index 472fac7f21e337..9ed2b7aaa2118d 100644 --- a/Lib/test/test_doctest/test_doctest2.py +++ b/Lib/test/test_doctest/test_doctest2.py @@ -19,7 +19,6 @@ if sys.flags.optimize >= 2: raise unittest.SkipTest("Cannot test docstrings with -O2") - class C(object): """Class C. From 7e40133f94a4797802c6f3486670f4f2850f553a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:58:11 +0300 Subject: [PATCH 09/26] Move _colorize to stdlib block, colour->color --- Lib/doctest.py | 1 - Lib/test/test_doctest/test_doctest.py | 3 +-- Lib/test/test_doctest/test_doctest2.py | 1 - Lib/test/test_traceback.py | 1 - Lib/traceback.py | 1 - 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 6a361d28703260..11db44e234b60b 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -104,7 +104,6 @@ def _test(): import unittest from io import StringIO, IncrementalNewlineDecoder from collections import namedtuple - import _colorize # Used in doctests diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index 6a368915746695..54b0392848c670 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -16,7 +16,6 @@ import tempfile import types import contextlib - import _colorize # used in doctests @@ -2636,7 +2635,7 @@ def test_testfile(): r""" called with the name of a file, which is taken to be relative to the calling module. The return value is (#failures, #tests). -We don't want colour or `-v` in sys.argv for these tests. +We don't want color or `-v` in sys.argv for these tests. >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py index 9ed2b7aaa2118d..8194cf036eaaa5 100644 --- a/Lib/test/test_doctest/test_doctest2.py +++ b/Lib/test/test_doctest/test_doctest2.py @@ -13,7 +13,6 @@ import sys import unittest - import _colorize if sys.flags.optimize >= 2: diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index d267b8cd10a497..48b38bb7256bd7 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -28,7 +28,6 @@ import contextlib from functools import partial from pathlib import Path - import _colorize MODULE_PREFIX = f'{__name__}.' if __name__ == '__main__' else '' diff --git a/Lib/traceback.py b/Lib/traceback.py index c70fefcf069915..fb9e762134677c 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -7,7 +7,6 @@ import textwrap import warnings from contextlib import suppress - from _colorize import ANSIColors __all__ = ['extract_stack', 'extract_tb', 'format_exception', From e484465124a04ef95ab3585942c2cb6d946f953d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:03:26 +0300 Subject: [PATCH 10/26] Move imports together --- Lib/traceback.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index fb9e762134677c..6c92e971c12907 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -7,7 +7,8 @@ import textwrap import warnings from contextlib import suppress -from _colorize import ANSIColors + +from _colorize import ANSIColors, can_colorize __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'format_exception_only', 'format_list', 'format_stack', @@ -133,8 +134,6 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ def _print_exception_bltin(exc, /): - from _colorize import can_colorize - file = sys.stderr if sys.stderr is not None else sys.__stderr__ colorize = can_colorize() return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) From 1c7b025f91daee5a57381acf1cd1dc56e4e9dd8a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:09:51 +0300 Subject: [PATCH 11/26] Move imports together --- Lib/doctest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 11db44e234b60b..205455feede07d 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -105,6 +105,7 @@ def _test(): from io import StringIO, IncrementalNewlineDecoder from collections import namedtuple import _colorize # Used in doctests +from _colorize import ANSIColors, can_colorize class TestResults(namedtuple('TestResults', 'failed attempted')): @@ -1572,8 +1573,6 @@ def summarize(self, verbose=None): summary is. If the verbosity is not specified, then the DocTestRunner's verbosity is used. """ - from _colorize import ANSIColors, can_colorize - if verbose is None: verbose = self._verbose From ab2c94c82fb8dc9ca8c1855abe560c1c8c2429c7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:12:03 +0300 Subject: [PATCH 12/26] Move imports together --- Lib/test/test_doctest/test_doctest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index 54b0392848c670..20c4673c673acb 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -467,7 +467,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. From 1aaeab85010b92868544c5232e3d6c95549223b8 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:12:14 +0300 Subject: [PATCH 13/26] Revert notests -> no_tests --- Lib/doctest.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 205455feede07d..9f5550bcff3ef7 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1576,7 +1576,7 @@ def summarize(self, verbose=None): if verbose is None: verbose = self._verbose - no_tests, passed, failed = [], [], [] + notests, passed, failed = [], [], [] total_tries = total_failures = total_skips = 0 for name, (failures, tries, skips) in self._stats.items(): @@ -1586,7 +1586,7 @@ def summarize(self, verbose=None): total_skips += skips if tries == 0: - no_tests.append(name) + notests.append(name) elif failures == 0: passed.append((name, tries)) else: @@ -1608,10 +1608,10 @@ def summarize(self, verbose=None): yellow = "" if verbose: - if no_tests: - print(f"{_n_items(no_tests)} had no tests:") - no_tests.sort() - for name in no_tests: + if notests: + print(f"{_n_items(notests)} had no tests:") + notests.sort() + for name in notests: print(f" {name}") if passed: From cd02e4abecf8df66a09106ca4be260c78b3652ca Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:12:41 +0300 Subject: [PATCH 14/26] Revert "Use f-strings" This reverts commit 42079be22a157b34db9d89ac81158f149fab8f81. --- Lib/doctest.py | 14 +++++++------- Lib/test/test_doctest/test_doctest2.py | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 9f5550bcff3ef7..00ff1a107c466a 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1032,7 +1032,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): # Look for tests in a module's contained objects. if inspect.ismodule(obj) and self._recurse: for valname, val in obj.__dict__.items(): - valname = f'{name}.{valname}' + valname = '%s.%s' % (name, valname) # Recurse to functions & classes. if ((self._is_routine(val) or inspect.isclass(val)) and @@ -1053,7 +1053,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): "must be strings, functions, methods, " "classes, or modules: %r" % (type(val),)) - valname = f'{name}.__test__.{valname}' + valname = '%s.__test__.%s' % (name, valname) self._find(tests, val, valname, module, source_lines, globs, seen) @@ -1068,7 +1068,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): if ((inspect.isroutine(val) or inspect.isclass(val) or isinstance(val, property)) and self._from_module(module, val)): - valname = f'{name}.{valname}' + valname = '%s.%s' % (name, valname) self._find(tests, val, valname, module, source_lines, globs, seen) @@ -1316,7 +1316,7 @@ def _failure_header(self, test, example): out.append('File "%s", line %s, in %s' % (test.filename, lineno, test.name)) else: - out.append(f'Line {example.lineno+1}, in {test.name}') + out.append('Line %s, in %s' % (example.lineno+1, test.name)) out.append('Failed example:') source = example.source out.append(_indent(source)) @@ -1809,7 +1809,7 @@ def output_difference(self, example, got, optionflags): # If we're not using diff, then simply list the expected # output followed by the actual output. if want and got: - return f'Expected:\n{_indent(want)}Got:\n{_indent(got)}' + return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got)) elif want: return 'Expected:\n%sGot nothing\n' % _indent(want) elif got: @@ -2044,7 +2044,7 @@ class doctest.Tester, then merges the results into (or creates) # Check that we were actually given a module. if not inspect.ismodule(m): - raise TypeError(f"testmod: module required; {m!r}") + raise TypeError("testmod: module required; %r" % (m,)) # If no name was given, then use the module's name. if name is None: @@ -2418,7 +2418,7 @@ def __hash__(self): def __repr__(self): name = self._dt_test.name.split('.') - return f"{name[-1]} ({'.'.join(name[:-1])})" + return "%s (%s)" % (name[-1], '.'.join(name[:-1])) __str__ = object.__str__ diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py index 8194cf036eaaa5..cce77a695efce0 100644 --- a/Lib/test/test_doctest/test_doctest2.py +++ b/Lib/test/test_doctest/test_doctest2.py @@ -120,14 +120,13 @@ def tearDown(self): _colorize.COLORIZE = self.colorize def test_testmod(self): - import doctest - import sys + import doctest, sys EXPECTED = 19 f, t = doctest.testmod(sys.modules[__name__]) if f: - self.fail(f"{f} of {t} doctests failed") + self.fail("%d of %d doctests failed" % (f, t)) if t != EXPECTED: - self.fail(f"expected {EXPECTED} tests to run, not {t}") + self.fail("expected %d tests to run, not %d" % (EXPECTED, t)) # Pollute the namespace with a bunch of imported functions and classes, From 06543ffe916f1ff436dfe2f200f207308df9188c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:22:30 +0300 Subject: [PATCH 15/26] Fix local tests --- Lib/traceback.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index 6c92e971c12907..1922fd8caee762 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -7,8 +7,8 @@ import textwrap import warnings from contextlib import suppress - -from _colorize import ANSIColors, can_colorize +import _colorize +from _colorize import ANSIColors __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'format_exception_only', 'format_list', 'format_stack', @@ -135,7 +135,7 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ def _print_exception_bltin(exc, /): file = sys.stderr if sys.stderr is not None else sys.__stderr__ - colorize = can_colorize() + colorize = _colorize.can_colorize() return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) From 31c6647f36c7e1148d47a8b4dd39eebbb22b8ec1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 11:01:36 +0300 Subject: [PATCH 16/26] Use red divider for failed test --- Lib/doctest.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 00ff1a107c466a..80d0b70592e8cc 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1307,7 +1307,10 @@ def report_unexpected_exception(self, out, test, example, exc_info): 'Exception raised:\n' + _indent(_exception_traceback(exc_info))) def _failure_header(self, test, example): - out = [self.DIVIDER] + red, reset = ( + (ANSIColors.RED, ANSIColors.RESET) if can_colorize() else ("", "") + ) + out = [f"{red}{self.DIVIDER}{reset}"] if test.filename: if test.lineno is not None and example.lineno is not None: lineno = test.lineno + example.lineno + 1 @@ -1621,7 +1624,7 @@ def summarize(self, verbose=None): print(f" {green}{count:3d} test{s} in {name}{reset}") if failed: - print(self.DIVIDER) + print(f"{red}{self.DIVIDER}{reset}") print(f"{red}{_n_items(failed)} had failures:{reset}") for name, (failures, tries, skips) in sorted(failed): print(f"{red} {failures:3d} of {tries:3d} in {name}{reset}") From 9be3d810130ab9fa2cb21a1175bad6acac140d4d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 13:58:06 +0300 Subject: [PATCH 17/26] Fix local tests --- Lib/test/test_doctest/test_doctest.py | 43 ++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index 20c4673c673acb..1457a3790b68be 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -889,6 +889,9 @@ def basics(): r""" DocTestRunner is used to run DocTest test cases, and to accumulate statistics. Here's a simple DocTest case we can use: + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + >>> def f(x): ... ''' ... >>> x = 12 @@ -943,6 +946,8 @@ def basics(): r""" 6 ok TestResults(failed=1, attempted=3) + + >>> _colorize.COLORIZE = save_colorize """ def verbose_flag(): r""" The `verbose` flag makes the test runner generate more detailed @@ -1018,6 +1023,9 @@ def exceptions(): r""" lines between the first line and the type/value may be omitted or replaced with any other string: + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + >>> def f(x): ... ''' ... >>> x = 12 @@ -1248,6 +1256,8 @@ def exceptions(): r""" ... ZeroDivisionError: integer division or modulo by zero TestResults(failed=1, attempted=1) + + >>> _colorize.COLORIZE = save_colorize """ def displayhook(): r""" Test that changing sys.displayhook doesn't matter for doctest. @@ -1289,6 +1299,9 @@ def optionflags(): r""" The DONT_ACCEPT_TRUE_FOR_1 flag disables matches between True/False and 1/0: + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + >>> def f(x): ... '>>> True\n1\n' @@ -1708,6 +1721,7 @@ def optionflags(): r""" Clean up. >>> del doctest.OPTIONFLAGS_BY_NAME[unlikely] + >>> _colorize.COLORIZE = save_colorize """ @@ -1718,6 +1732,9 @@ 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``: + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + >>> def f(x): r''' ... >>> print(list(range(10))) # should fail: no ellipsis ... [0, 1, ..., 9] @@ -1925,6 +1942,8 @@ def option_directives(): r""" >>> test = doctest.DocTestParser().get_doctest(s, {}, 's', 's.py', 0) Traceback (most recent call last): ValueError: line 0 of the doctest for s has an option directive on a line with no example: '# doctest: +ELLIPSIS' + + >>> _colorize.COLORIZE = save_colorize """ def test_testsource(): r""" @@ -2008,6 +2027,9 @@ def test_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é') @@ -2062,7 +2084,7 @@ def test_pdb_set_trace(): ... finally: ... sys.stdin = real_stdin --Return-- - > (3)calls_set_trace()->None + > (3)calls_set_trace()->None -> import pdb; pdb.set_trace() (Pdb) print(y) 2 @@ -2130,6 +2152,8 @@ def test_pdb_set_trace(): Got: 9 TestResults(failed=1, attempted=3) + + >>> _colorize.COLORIZE = save_colorize """ def test_pdb_set_trace_nested(): @@ -2639,6 +2663,7 @@ def test_testfile(): r""" >>> save_colorize = _colorize.COLORIZE >>> _colorize.COLORIZE = False + >>> save_argv = sys.argv >>> if '-v' in sys.argv: ... sys.argv = [arg for arg in save_argv if arg != '-v'] @@ -2943,6 +2968,9 @@ def test_testmod(): r""" def test_unicode(): """ Check doctest with a non-ascii filename: + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + >>> doc = ''' ... >>> raise Exception('clé') ... ''' @@ -2968,8 +2996,11 @@ def test_unicode(): """ raise Exception('clé') Exception: clé TestResults(failed=1, attempted=1) + + >>> _colorize.COLORIZE = save_colorize """ + def test_CLI(): r""" The doctest module can be used to run doctests against an arbitrary file. These tests test this CLI functionality. @@ -3260,6 +3291,9 @@ def test_run_doctestsuite_multiple_times(): def test_exception_with_note(note): """ + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + >>> test_exception_with_note('Note') Traceback (most recent call last): ... @@ -3309,6 +3343,8 @@ def test_exception_with_note(note): ValueError: message note TestResults(failed=1, attempted=...) + + >>> _colorize.COLORIZE = save_colorize """ exc = ValueError('Text') exc.add_note(note) @@ -3389,6 +3425,9 @@ def test_syntax_error_subclass_from_stdlib(): def test_syntax_error_with_incorrect_expected_note(): """ + >>> save_colorize = _colorize.COLORIZE + >>> _colorize.COLORIZE = False + >>> def f(x): ... r''' ... >>> exc = SyntaxError("error", ("x.py", 23, None, "bad syntax")) @@ -3417,6 +3456,8 @@ def test_syntax_error_with_incorrect_expected_note(): note1 note2 TestResults(failed=1, attempted=...) + + >>> _colorize.COLORIZE = save_colorize """ From e4ff3e3dc0d4e085b740955724693dc80849e1d5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:08:59 +0300 Subject: [PATCH 18/26] Less red --- Lib/doctest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 80d0b70592e8cc..7ea71b0d11ce66 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1625,9 +1625,9 @@ def summarize(self, verbose=None): if failed: print(f"{red}{self.DIVIDER}{reset}") - print(f"{red}{_n_items(failed)} had failures:{reset}") + print(f"{_n_items(failed)} had failures:") for name, (failures, tries, skips) in sorted(failed): - print(f"{red} {failures:3d} of {tries:3d} in {name}{reset}") + print(f" {failures:3d} of {tries:3d} in {name}") if verbose: s = "" if total_tries == 1 else "s" From b62500accf60865be8f176df05c41927da88ff30 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:28:06 +0300 Subject: [PATCH 19/26] Revert unnecessary changes --- Lib/test/test_doctest/test_doctest2.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Lib/test/test_doctest/test_doctest2.py b/Lib/test/test_doctest/test_doctest2.py index cce77a695efce0..ab8a0696736e23 100644 --- a/Lib/test/test_doctest/test_doctest2.py +++ b/Lib/test/test_doctest/test_doctest2.py @@ -13,8 +13,6 @@ import sys import unittest -import _colorize - if sys.flags.optimize >= 2: raise unittest.SkipTest("Cannot test docstrings with -O2") @@ -110,15 +108,6 @@ def clsm(cls, val): class Test(unittest.TestCase): - def setUp(self): - super().setUp() - self.colorize = _colorize.COLORIZE - _colorize.COLORIZE = False - - def tearDown(self): - super().tearDown() - _colorize.COLORIZE = self.colorize - def test_testmod(self): import doctest, sys EXPECTED = 19 From eb4f8dc23fdf148f9d63e6c6b0d3831486c7e2f7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 16:53:38 +0300 Subject: [PATCH 20/26] Move colour tests to test__colorize.py --- Lib/test/test__colorize.py | 128 +++++++++++++++++++++++++++++++++++++ Lib/test/test_traceback.py | 112 -------------------------------- 2 files changed, 128 insertions(+), 112 deletions(-) create mode 100644 Lib/test/test__colorize.py diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py new file mode 100644 index 00000000000000..2a7bdeafadbab9 --- /dev/null +++ b/Lib/test/test__colorize.py @@ -0,0 +1,128 @@ +import contextlib +import sys +import traceback +import unittest +import unittest.mock +import _colorize +from test.support import captured_output + + +class TestColorizedTraceback(unittest.TestCase): + def test_colorized_traceback(self): + def foo(*args): + x = {'a':{'b': None}} + y = x['a']['b']['c'] + + def baz(*args): + return foo(1,2,3,4) + + def bar(): + return baz(1, + 2,3 + ,4) + try: + bar() + except Exception as e: + exc = traceback.TracebackException.from_exception( + e, capture_locals=True + ) + lines = "".join(exc.format(colorize=True)) + red = _colorize.ANSIColors.RED + boldr = _colorize.ANSIColors.BOLD_RED + reset = _colorize.ANSIColors.RESET + self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines) + self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines) + self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines) + self.assertIn(boldr + "2,3" + reset, lines) + self.assertIn(boldr + ",4)" + reset, lines) + self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines) + + def test_colorized_syntax_error(self): + try: + compile("a $ b", "", "exec") + except SyntaxError as e: + exc = traceback.TracebackException.from_exception( + e, capture_locals=True + ) + actual = "".join(exc.format(colorize=True)) + magenta = _colorize.ANSIColors.MAGENTA + boldm = _colorize.ANSIColors.BOLD_MAGENTA + boldr = _colorize.ANSIColors.BOLD_RED + reset = _colorize.ANSIColors.RESET + expected = "".join( + [ + f' File {magenta}""{reset}, line {magenta}1{reset}\n', + f' a {boldr}${reset} b\n', + f' {boldr}^{reset}\n', + f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n', + ] + ) + self.assertIn(expected, actual) + + def test_colorized_traceback_is_the_default(self): + def foo(): + 1/0 + + from _testcapi import exception_print + + try: + foo() + self.fail("No exception thrown.") + except Exception as e: + with captured_output("stderr") as tbstderr: + with unittest.mock.patch('_colorize.can_colorize', return_value=True): + exception_print(e) + actual = tbstderr.getvalue().splitlines() + + red = _colorize.ANSIColors.RED + boldr = _colorize.ANSIColors.BOLD_RED + magenta = _colorize.ANSIColors.MAGENTA + boldm = _colorize.ANSIColors.BOLD_MAGENTA + reset = _colorize.ANSIColors.RESET + lno_foo = foo.__code__.co_firstlineno + expected = [ + 'Traceback (most recent call last):', + f' File {magenta}"{__file__}"{reset}, ' + f'line {magenta}{lno_foo+6}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}', + f' {red}foo{reset+boldr}(){reset}', + f' {red}~~~{reset+boldr}^^{reset}', + f' File {magenta}"{__file__}"{reset}, ' + f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}', + f' {red}1{reset+boldr}/{reset+red}0{reset}', + f' {red}~{reset+boldr}^{reset+red}~{reset}', + f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}', + ] + self.assertEqual(actual, expected) + + def test_colorized_detection_checks_for_environment_variables(self): + if sys.platform == "win32": + virtual_patching = unittest.mock.patch( + "nt._supports_virtual_terminal", return_value=True + ) + else: + virtual_patching = contextlib.nullcontext() + with virtual_patching: + with unittest.mock.patch("os.isatty") as isatty_mock: + isatty_mock.return_value = True + with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): + self.assertEqual(_colorize.can_colorize(), False) + isatty_mock.return_value = False + self.assertEqual(_colorize.can_colorize(), False) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 48b38bb7256bd7..57cf9adf059a3b 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -7,7 +7,6 @@ import types import inspect import builtins -import unittest import unittest.mock import re import tempfile @@ -25,7 +24,6 @@ import json import textwrap import traceback -import contextlib from functools import partial from pathlib import Path import _colorize @@ -4272,116 +4270,6 @@ def test_levenshtein_distance_short_circuit(self): res3 = traceback._levenshtein_distance(a, b, threshold) self.assertGreater(res3, threshold, msg=(a, b, threshold)) -class TestColorizedTraceback(unittest.TestCase): - def test_colorized_traceback(self): - def foo(*args): - x = {'a':{'b': None}} - y = x['a']['b']['c'] - - def baz(*args): - return foo(1,2,3,4) - - def bar(): - return baz(1, - 2,3 - ,4) - try: - bar() - except Exception as e: - exc = traceback.TracebackException.from_exception( - e, capture_locals=True - ) - lines = "".join(exc.format(colorize=True)) - red = _colorize.ANSIColors.RED - boldr = _colorize.ANSIColors.BOLD_RED - reset = _colorize.ANSIColors.RESET - self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines) - self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines) - self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines) - self.assertIn(boldr + "2,3" + reset, lines) - self.assertIn(boldr + ",4)" + reset, lines) - self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines) - - def test_colorized_syntax_error(self): - try: - compile("a $ b", "", "exec") - except SyntaxError as e: - exc = traceback.TracebackException.from_exception( - e, capture_locals=True - ) - actual = "".join(exc.format(colorize=True)) - red = _colorize.ANSIColors.RED - magenta = _colorize.ANSIColors.MAGENTA - boldm = _colorize.ANSIColors.BOLD_MAGENTA - boldr = _colorize.ANSIColors.BOLD_RED - reset = _colorize.ANSIColors.RESET - expected = "".join([ - f' File {magenta}""{reset}, line {magenta}1{reset}\n', - f' a {boldr}${reset} b\n', - f' {boldr}^{reset}\n', - f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n'] - ) - self.assertIn(expected, actual) - - def test_colorized_traceback_is_the_default(self): - def foo(): - 1/0 - - from _testcapi import exception_print - try: - foo() - self.fail("No exception thrown.") - except Exception as e: - with captured_output("stderr") as tbstderr: - with unittest.mock.patch('_colorize.can_colorize', return_value=True): - exception_print(e) - actual = tbstderr.getvalue().splitlines() - - red = _colorize.ANSIColors.RED - boldr = _colorize.ANSIColors.BOLD_RED - magenta = _colorize.ANSIColors.MAGENTA - boldm = _colorize.ANSIColors.BOLD_MAGENTA - reset = _colorize.ANSIColors.RESET - lno_foo = foo.__code__.co_firstlineno - expected = ['Traceback (most recent call last):', - f' File {magenta}"{__file__}"{reset}, ' - f'line {magenta}{lno_foo+5}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}', - f' {red}foo{reset+boldr}(){reset}', - f' {red}~~~{reset+boldr}^^{reset}', - f' File {magenta}"{__file__}"{reset}, ' - f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}', - f' {red}1{reset+boldr}/{reset+red}0{reset}', - f' {red}~{reset+boldr}^{reset+red}~{reset}', - f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}'] - self.assertEqual(actual, expected) - - def test_colorized_detection_checks_for_environment_variables(self): - if sys.platform == "win32": - virtual_patching = unittest.mock.patch("nt._supports_virtual_terminal", return_value=True) - else: - virtual_patching = contextlib.nullcontext() - with virtual_patching: - with unittest.mock.patch("os.isatty") as isatty_mock: - isatty_mock.return_value = True - with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): - self.assertEqual(_colorize.can_colorize(), False) - isatty_mock.return_value = False - self.assertEqual(_colorize.can_colorize(), False) - if __name__ == "__main__": unittest.main() From 976bfb4c11d8b82733ff4536206572622eab5d5e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 17:10:38 +0300 Subject: [PATCH 21/26] Refactor asserts --- Lib/test/test__colorize.py | 50 +++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py index 2a7bdeafadbab9..f23fc0e1593afb 100644 --- a/Lib/test/test__colorize.py +++ b/Lib/test/test__colorize.py @@ -30,12 +30,17 @@ def bar(): red = _colorize.ANSIColors.RED boldr = _colorize.ANSIColors.BOLD_RED reset = _colorize.ANSIColors.RESET - self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines) - self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines) - self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines) - self.assertIn(boldr + "2,3" + reset, lines) - self.assertIn(boldr + ",4)" + reset, lines) - self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines) + + expected = [ + "y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, + "return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, + "return " + red + "baz" + reset + boldr + "(1," + reset, + boldr + "2,3" + reset, + boldr + ",4)" + reset, + red + "bar" + reset + boldr + "()" + reset, + ] + for line in expected: + self.assertIn(line, lines) def test_colorized_syntax_error(self): try: @@ -101,25 +106,26 @@ def test_colorized_detection_checks_for_environment_variables(self): ) else: virtual_patching = contextlib.nullcontext() + + env_vars_expected = [ + ({'TERM': 'dumb'}, False), + ({'PYTHON_COLORS': '1'}, True), + ({'PYTHON_COLORS': '0'}, False), + ({'NO_COLOR': '1'}, False), + ({'NO_COLOR': '1', "PYTHON_COLORS": '1'}, True), + ({'FORCE_COLOR': '1'}, True), + ({'FORCE_COLOR': '1', 'NO_COLOR': '1'}, False), + ({'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}, False), + ] + with virtual_patching: with unittest.mock.patch("os.isatty") as isatty_mock: isatty_mock.return_value = True - with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): - self.assertEqual(_colorize.can_colorize(), False) + + for env_vars, expected in env_vars_expected: + with unittest.mock.patch("os.environ", env_vars): + self.assertEqual(_colorize.can_colorize(), expected) + isatty_mock.return_value = False self.assertEqual(_colorize.can_colorize(), False) From ad7a946c7124da73a2e82cb455ac06c125db38a6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 17:58:14 +0300 Subject: [PATCH 22/26] Add missing captured_output to test.support's __all__ to fix IDE warning --- Lib/test/support/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 2be9cd099a68d6..fb4b0a5071d71f 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -26,7 +26,7 @@ "Error", "TestFailed", "TestDidNotRun", "ResourceDenied", # io "record_original_stdout", "get_original_stdout", "captured_stdout", - "captured_stdin", "captured_stderr", + "captured_stdin", "captured_stderr", "captured_output", # unittest "is_resource_enabled", "requires", "requires_freebsd_version", "requires_gil_enabled", "requires_linux_version", "requires_mac_ver", From 796e9f228a70da282296309cf71aab63d72d7aa3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:49:44 +0300 Subject: [PATCH 23/26] Only move test_colorized_detection_checks_for_environment_variables from test_traceback to test__colorize --- Lib/test/test__colorize.py | 91 -------------------------------------- Lib/test/test_traceback.py | 84 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 91 deletions(-) diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py index f23fc0e1593afb..f9794de2efdb9b 100644 --- a/Lib/test/test__colorize.py +++ b/Lib/test/test__colorize.py @@ -8,97 +8,6 @@ class TestColorizedTraceback(unittest.TestCase): - def test_colorized_traceback(self): - def foo(*args): - x = {'a':{'b': None}} - y = x['a']['b']['c'] - - def baz(*args): - return foo(1,2,3,4) - - def bar(): - return baz(1, - 2,3 - ,4) - try: - bar() - except Exception as e: - exc = traceback.TracebackException.from_exception( - e, capture_locals=True - ) - lines = "".join(exc.format(colorize=True)) - red = _colorize.ANSIColors.RED - boldr = _colorize.ANSIColors.BOLD_RED - reset = _colorize.ANSIColors.RESET - - expected = [ - "y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, - "return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, - "return " + red + "baz" + reset + boldr + "(1," + reset, - boldr + "2,3" + reset, - boldr + ",4)" + reset, - red + "bar" + reset + boldr + "()" + reset, - ] - for line in expected: - self.assertIn(line, lines) - - def test_colorized_syntax_error(self): - try: - compile("a $ b", "", "exec") - except SyntaxError as e: - exc = traceback.TracebackException.from_exception( - e, capture_locals=True - ) - actual = "".join(exc.format(colorize=True)) - magenta = _colorize.ANSIColors.MAGENTA - boldm = _colorize.ANSIColors.BOLD_MAGENTA - boldr = _colorize.ANSIColors.BOLD_RED - reset = _colorize.ANSIColors.RESET - expected = "".join( - [ - f' File {magenta}""{reset}, line {magenta}1{reset}\n', - f' a {boldr}${reset} b\n', - f' {boldr}^{reset}\n', - f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n', - ] - ) - self.assertIn(expected, actual) - - def test_colorized_traceback_is_the_default(self): - def foo(): - 1/0 - - from _testcapi import exception_print - - try: - foo() - self.fail("No exception thrown.") - except Exception as e: - with captured_output("stderr") as tbstderr: - with unittest.mock.patch('_colorize.can_colorize', return_value=True): - exception_print(e) - actual = tbstderr.getvalue().splitlines() - - red = _colorize.ANSIColors.RED - boldr = _colorize.ANSIColors.BOLD_RED - magenta = _colorize.ANSIColors.MAGENTA - boldm = _colorize.ANSIColors.BOLD_MAGENTA - reset = _colorize.ANSIColors.RESET - lno_foo = foo.__code__.co_firstlineno - expected = [ - 'Traceback (most recent call last):', - f' File {magenta}"{__file__}"{reset}, ' - f'line {magenta}{lno_foo+6}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}', - f' {red}foo{reset+boldr}(){reset}', - f' {red}~~~{reset+boldr}^^{reset}', - f' File {magenta}"{__file__}"{reset}, ' - f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}', - f' {red}1{reset+boldr}/{reset+red}0{reset}', - f' {red}~{reset+boldr}^{reset+red}~{reset}', - f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}', - ] - self.assertEqual(actual, expected) - def test_colorized_detection_checks_for_environment_variables(self): if sys.platform == "win32": virtual_patching = unittest.mock.patch( diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 57cf9adf059a3b..2e174a7184f2bc 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -7,6 +7,7 @@ import types import inspect import builtins +import unittest import unittest.mock import re import tempfile @@ -4270,6 +4271,89 @@ def test_levenshtein_distance_short_circuit(self): res3 = traceback._levenshtein_distance(a, b, threshold) self.assertGreater(res3, threshold, msg=(a, b, threshold)) +class TestColorizedTraceback(unittest.TestCase): + def test_colorized_traceback(self): + def foo(*args): + x = {'a':{'b': None}} + y = x['a']['b']['c'] + + def baz(*args): + return foo(1,2,3,4) + + def bar(): + return baz(1, + 2,3 + ,4) + try: + bar() + except Exception as e: + exc = traceback.TracebackException.from_exception( + e, capture_locals=True + ) + lines = "".join(exc.format(colorize=True)) + red = _colorize.ANSIColors.RED + boldr = _colorize.ANSIColors.BOLD_RED + reset = _colorize.ANSIColors.RESET + self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines) + self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines) + self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines) + self.assertIn(boldr + "2,3" + reset, lines) + self.assertIn(boldr + ",4)" + reset, lines) + self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines) + + def test_colorized_syntax_error(self): + try: + compile("a $ b", "", "exec") + except SyntaxError as e: + exc = traceback.TracebackException.from_exception( + e, capture_locals=True + ) + actual = "".join(exc.format(colorize=True)) + red = _colorize.ANSIColors.RED + magenta = _colorize.ANSIColors.MAGENTA + boldm = _colorize.ANSIColors.BOLD_MAGENTA + boldr = _colorize.ANSIColors.BOLD_RED + reset = _colorize.ANSIColors.RESET + expected = "".join([ + f' File {magenta}""{reset}, line {magenta}1{reset}\n', + f' a {boldr}${reset} b\n', + f' {boldr}^{reset}\n', + f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n'] + ) + self.assertIn(expected, actual) + + def test_colorized_traceback_is_the_default(self): + def foo(): + 1/0 + + from _testcapi import exception_print + try: + foo() + self.fail("No exception thrown.") + except Exception as e: + with captured_output("stderr") as tbstderr: + with unittest.mock.patch('_colorize.can_colorize', return_value=True): + exception_print(e) + actual = tbstderr.getvalue().splitlines() + + red = _colorize.ANSIColors.RED + boldr = _colorize.ANSIColors.BOLD_RED + magenta = _colorize.ANSIColors.MAGENTA + boldm = _colorize.ANSIColors.BOLD_MAGENTA + reset = _colorize.ANSIColors.RESET + lno_foo = foo.__code__.co_firstlineno + expected = ['Traceback (most recent call last):', + f' File {magenta}"{__file__}"{reset}, ' + f'line {magenta}{lno_foo+5}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}', + f' {red}foo{reset+boldr}(){reset}', + f' {red}~~~{reset+boldr}^^{reset}', + f' File {magenta}"{__file__}"{reset}, ' + f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}', + f' {red}1{reset+boldr}/{reset+red}0{reset}', + f' {red}~{reset+boldr}^{reset+red}~{reset}', + f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}'] + self.assertEqual(actual, expected) + if __name__ == "__main__": unittest.main() From 99d4d0c5b1d5ff076ce8c207d3f41e27e0ed6da5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 11:50:57 -0600 Subject: [PATCH 24/26] Apply suggestions from code review Co-authored-by: Alex Waygood --- Lib/test/test__colorize.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py index f9794de2efdb9b..8abbb680ca2de5 100644 --- a/Lib/test/test__colorize.py +++ b/Lib/test/test__colorize.py @@ -7,7 +7,7 @@ from test.support import captured_output -class TestColorizedTraceback(unittest.TestCase): +class TestColorizeFunction(unittest.TestCase): def test_colorized_detection_checks_for_environment_variables(self): if sys.platform == "win32": virtual_patching = unittest.mock.patch( @@ -32,8 +32,9 @@ def test_colorized_detection_checks_for_environment_variables(self): isatty_mock.return_value = True for env_vars, expected in env_vars_expected: - with unittest.mock.patch("os.environ", env_vars): - self.assertEqual(_colorize.can_colorize(), expected) + with self.subTest(env_vars=env_vars, expected_color=expected): + with unittest.mock.patch("os.environ", env_vars): + self.assertEqual(_colorize.can_colorize(), expected) isatty_mock.return_value = False self.assertEqual(_colorize.can_colorize(), False) From 95b983134099aa29f00d9cf3ffe9690e2fd1ece8 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 7 Apr 2024 20:54:55 +0300 Subject: [PATCH 25/26] Use unittest's enterContext Co-authored-by: Alex Waygood --- Lib/test/test__colorize.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py index 8abbb680ca2de5..4415459f0254ae 100644 --- a/Lib/test/test__colorize.py +++ b/Lib/test/test__colorize.py @@ -1,21 +1,11 @@ -import contextlib import sys -import traceback import unittest import unittest.mock import _colorize -from test.support import captured_output class TestColorizeFunction(unittest.TestCase): def test_colorized_detection_checks_for_environment_variables(self): - if sys.platform == "win32": - virtual_patching = unittest.mock.patch( - "nt._supports_virtual_terminal", return_value=True - ) - else: - virtual_patching = contextlib.nullcontext() - env_vars_expected = [ ({'TERM': 'dumb'}, False), ({'PYTHON_COLORS': '1'}, True), @@ -27,17 +17,23 @@ def test_colorized_detection_checks_for_environment_variables(self): ({'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}, False), ] - with virtual_patching: - with unittest.mock.patch("os.isatty") as isatty_mock: - isatty_mock.return_value = True + if sys.platform == "win32": + self.enterContext( + unittest.mock.patch( + "nt._supports_virtual_terminal", return_value=True + ) + ) + + isatty_mock = self.enterContext(unittest.mock.patch("os.isatty")) + isatty_mock.return_value = True - for env_vars, expected in env_vars_expected: - with self.subTest(env_vars=env_vars, expected_color=expected): - with unittest.mock.patch("os.environ", env_vars): - self.assertEqual(_colorize.can_colorize(), expected) + for env_vars, expected in env_vars_expected: + with self.subTest(env_vars=env_vars, expected_color=expected): + with unittest.mock.patch("os.environ", env_vars): + self.assertEqual(_colorize.can_colorize(), expected) - isatty_mock.return_value = False - self.assertEqual(_colorize.can_colorize(), False) + isatty_mock.return_value = False + self.assertEqual(_colorize.can_colorize(), False) if __name__ == "__main__": From ece3ce0f70d5a44e682d1545dd7d5146cba2d39a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:37:17 +0300 Subject: [PATCH 26/26] Keep colorize functionality in traceback module for now --- Lib/_colorize.py | 45 ------------- Lib/doctest.py | 25 ++++---- Lib/test/test__colorize.py | 40 ------------ Lib/test/test_doctest/test_doctest.py | 58 ++++++++--------- Lib/test/test_traceback.py | 62 ++++++++++++------ Lib/traceback.py | 91 +++++++++++++++++++-------- Python/stdlib_module_names.h | 1 - 7 files changed, 149 insertions(+), 173 deletions(-) delete mode 100644 Lib/_colorize.py delete mode 100644 Lib/test/test__colorize.py diff --git a/Lib/_colorize.py b/Lib/_colorize.py deleted file mode 100644 index 4590062c30b357..00000000000000 --- a/Lib/_colorize.py +++ /dev/null @@ -1,45 +0,0 @@ -import io -import os -import sys - -COLORIZE = True - - -class ANSIColors: - BOLD_GREEN = "\x1b[1;32m" - BOLD_MAGENTA = "\x1b[1;35m" - BOLD_RED = "\x1b[1;31m" - GREEN = "\x1b[32m" - GREY = "\x1b[90m" - MAGENTA = "\x1b[35m" - RED = "\x1b[31m" - RESET = "\x1b[0m" - YELLOW = "\x1b[33m" - - -def can_colorize(): - if sys.platform == "win32": - try: - import nt - - if not nt._supports_virtual_terminal(): - return False - except (ImportError, AttributeError): - return False - - if os.environ.get("PYTHON_COLORS") == "0": - return False - if os.environ.get("PYTHON_COLORS") == "1": - return True - if "NO_COLOR" in os.environ: - return False - if not COLORIZE: - return False - if "FORCE_COLOR" in os.environ: - return True - if os.environ.get("TERM") == "dumb": - return False - try: - return os.isatty(sys.stderr.fileno()) - except io.UnsupportedOperation: - return sys.stderr.isatty() diff --git a/Lib/doctest.py b/Lib/doctest.py index e003e30786ed93..a3b42fdfb12254 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -104,8 +104,7 @@ def _test(): import unittest from io import StringIO, IncrementalNewlineDecoder from collections import namedtuple -import _colorize # Used in doctests -from _colorize import ANSIColors, can_colorize +from traceback import _ANSIColors, _can_colorize class TestResults(namedtuple('TestResults', 'failed attempted')): @@ -1181,8 +1180,8 @@ class DocTestRunner: The `run` method is used to process a single DocTest case. It returns a TestResults instance. - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> tests = DocTestFinder().find(_TestClass) >>> runner = DocTestRunner(verbose=False) @@ -1235,7 +1234,7 @@ class DocTestRunner: overriding the methods `report_start`, `report_success`, `report_unexpected_exception`, and `report_failure`. - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ # This divider string is used to separate failure messages, and to # separate sections of the summary. @@ -1315,7 +1314,7 @@ def report_unexpected_exception(self, out, test, example, exc_info): def _failure_header(self, test, example): red, reset = ( - (ANSIColors.RED, ANSIColors.RESET) if can_colorize() else ("", "") + (_ANSIColors.RED, _ANSIColors.RESET) if _can_colorize() else ("", "") ) out = [f"{red}{self.DIVIDER}{reset}"] if test.filename: @@ -1602,13 +1601,13 @@ def summarize(self, verbose=None): else: failed.append((name, (failures, tries, skips))) - if can_colorize(): - bold_green = ANSIColors.BOLD_GREEN - bold_red = ANSIColors.BOLD_RED - green = ANSIColors.GREEN - red = ANSIColors.RED - reset = ANSIColors.RESET - yellow = ANSIColors.YELLOW + if _can_colorize(): + bold_green = _ANSIColors.BOLD_GREEN + bold_red = _ANSIColors.BOLD_RED + green = _ANSIColors.GREEN + red = _ANSIColors.RED + reset = _ANSIColors.RESET + yellow = _ANSIColors.YELLOW else: bold_green = "" bold_red = "" diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py deleted file mode 100644 index 4415459f0254ae..00000000000000 --- a/Lib/test/test__colorize.py +++ /dev/null @@ -1,40 +0,0 @@ -import sys -import unittest -import unittest.mock -import _colorize - - -class TestColorizeFunction(unittest.TestCase): - def test_colorized_detection_checks_for_environment_variables(self): - env_vars_expected = [ - ({'TERM': 'dumb'}, False), - ({'PYTHON_COLORS': '1'}, True), - ({'PYTHON_COLORS': '0'}, False), - ({'NO_COLOR': '1'}, False), - ({'NO_COLOR': '1', "PYTHON_COLORS": '1'}, True), - ({'FORCE_COLOR': '1'}, True), - ({'FORCE_COLOR': '1', 'NO_COLOR': '1'}, False), - ({'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}, False), - ] - - if sys.platform == "win32": - self.enterContext( - unittest.mock.patch( - "nt._supports_virtual_terminal", return_value=True - ) - ) - - isatty_mock = self.enterContext(unittest.mock.patch("os.isatty")) - isatty_mock.return_value = True - - for env_vars, expected in env_vars_expected: - with self.subTest(env_vars=env_vars, expected_color=expected): - with unittest.mock.patch("os.environ", env_vars): - self.assertEqual(_colorize.can_colorize(), expected) - - isatty_mock.return_value = False - self.assertEqual(_colorize.can_colorize(), False) - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index 6da6999cac7c0c..2ad16558d5574c 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -16,7 +16,7 @@ import tempfile import types import contextlib -import _colorize # used in doctests +import traceback def doctest_skip_if(condition): @@ -471,7 +471,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. @@ -893,8 +893,8 @@ def basics(): r""" DocTestRunner is used to run DocTest test cases, and to accumulate statistics. Here's a simple DocTest case we can use: - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> def f(x): ... ''' @@ -951,7 +951,7 @@ def basics(): r""" ok TestResults(failed=1, attempted=3) - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ def verbose_flag(): r""" The `verbose` flag makes the test runner generate more detailed @@ -1027,8 +1027,8 @@ def exceptions(): r""" lines between the first line and the type/value may be omitted or replaced with any other string: - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> def f(x): ... ''' @@ -1261,7 +1261,7 @@ def exceptions(): r""" ZeroDivisionError: integer division or modulo by zero TestResults(failed=1, attempted=1) - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ def displayhook(): r""" Test that changing sys.displayhook doesn't matter for doctest. @@ -1303,8 +1303,8 @@ def optionflags(): r""" The DONT_ACCEPT_TRUE_FOR_1 flag disables matches between True/False and 1/0: - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> def f(x): ... '>>> True\n1\n' @@ -1725,7 +1725,7 @@ def optionflags(): r""" Clean up. >>> del doctest.OPTIONFLAGS_BY_NAME[unlikely] - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ @@ -1736,8 +1736,8 @@ 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``: - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> def f(x): r''' ... >>> print(list(range(10))) # should fail: no ellipsis @@ -1947,7 +1947,7 @@ def option_directives(): r""" Traceback (most recent call last): ValueError: line 0 of the doctest for s has an option directive on a line with no example: '# doctest: +ELLIPSIS' - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ def test_testsource(): r""" @@ -2031,8 +2031,8 @@ def test_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 + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> doc = ''' ... >>> x = 42 @@ -2157,7 +2157,7 @@ def test_pdb_set_trace(): 9 TestResults(failed=1, attempted=3) - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ def test_pdb_set_trace_nested(): @@ -2679,8 +2679,8 @@ def test_testfile(): r""" We don't want color or `-v` in sys.argv for these tests. - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> save_argv = sys.argv >>> if '-v' in sys.argv: @@ -2848,7 +2848,7 @@ def test_testfile(): r""" TestResults(failed=0, attempted=2) >>> doctest.master = None # Reset master. >>> sys.argv = save_argv - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ class TestImporter(importlib.abc.MetaPathFinder, importlib.abc.ResourceLoader): @@ -2986,8 +2986,8 @@ def test_testmod(): r""" def test_unicode(): """ Check doctest with a non-ascii filename: - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> doc = ''' ... >>> raise Exception('clé') @@ -3015,7 +3015,7 @@ def test_unicode(): """ Exception: clé TestResults(failed=1, attempted=1) - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ @@ -3310,8 +3310,8 @@ def test_run_doctestsuite_multiple_times(): def test_exception_with_note(note): """ - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> test_exception_with_note('Note') Traceback (most recent call last): @@ -3363,7 +3363,7 @@ def test_exception_with_note(note): note TestResults(failed=1, attempted=...) - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ exc = ValueError('Text') exc.add_note(note) @@ -3444,8 +3444,8 @@ def test_syntax_error_subclass_from_stdlib(): def test_syntax_error_with_incorrect_expected_note(): """ - >>> save_colorize = _colorize.COLORIZE - >>> _colorize.COLORIZE = False + >>> save_colorize = traceback._COLORIZE + >>> traceback._COLORIZE = False >>> def f(x): ... r''' @@ -3476,7 +3476,7 @@ def test_syntax_error_with_incorrect_expected_note(): note2 TestResults(failed=1, attempted=...) - >>> _colorize.COLORIZE = save_colorize + >>> traceback._COLORIZE = save_colorize """ diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 2e174a7184f2bc..dd9b1850adf086 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -25,9 +25,9 @@ import json import textwrap import traceback +import contextlib from functools import partial from pathlib import Path -import _colorize MODULE_PREFIX = f'{__name__}.' if __name__ == '__main__' else '' @@ -45,12 +45,12 @@ class TracebackCases(unittest.TestCase): # formatting of SyntaxErrors works based on changes for 2.1. def setUp(self): super().setUp() - self.colorize = _colorize.COLORIZE - _colorize.COLORIZE = False + self.colorize = traceback._COLORIZE + traceback._COLORIZE = False def tearDown(self): super().tearDown() - _colorize.COLORIZE = self.colorize + traceback._COLORIZE = self.colorize def get_exception_format(self, func, exc): try: @@ -4291,9 +4291,9 @@ def bar(): e, capture_locals=True ) lines = "".join(exc.format(colorize=True)) - red = _colorize.ANSIColors.RED - boldr = _colorize.ANSIColors.BOLD_RED - reset = _colorize.ANSIColors.RESET + red = traceback._ANSIColors.RED + boldr = traceback._ANSIColors.BOLD_RED + reset = traceback._ANSIColors.RESET self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines) self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines) self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines) @@ -4309,11 +4309,11 @@ def test_colorized_syntax_error(self): e, capture_locals=True ) actual = "".join(exc.format(colorize=True)) - red = _colorize.ANSIColors.RED - magenta = _colorize.ANSIColors.MAGENTA - boldm = _colorize.ANSIColors.BOLD_MAGENTA - boldr = _colorize.ANSIColors.BOLD_RED - reset = _colorize.ANSIColors.RESET + red = traceback._ANSIColors.RED + magenta = traceback._ANSIColors.MAGENTA + boldm = traceback._ANSIColors.BOLD_MAGENTA + boldr = traceback._ANSIColors.BOLD_RED + reset = traceback._ANSIColors.RESET expected = "".join([ f' File {magenta}""{reset}, line {magenta}1{reset}\n', f' a {boldr}${reset} b\n', @@ -4332,15 +4332,15 @@ def foo(): self.fail("No exception thrown.") except Exception as e: with captured_output("stderr") as tbstderr: - with unittest.mock.patch('_colorize.can_colorize', return_value=True): + with unittest.mock.patch('traceback._can_colorize', return_value=True): exception_print(e) actual = tbstderr.getvalue().splitlines() - red = _colorize.ANSIColors.RED - boldr = _colorize.ANSIColors.BOLD_RED - magenta = _colorize.ANSIColors.MAGENTA - boldm = _colorize.ANSIColors.BOLD_MAGENTA - reset = _colorize.ANSIColors.RESET + red = traceback._ANSIColors.RED + boldr = traceback._ANSIColors.BOLD_RED + magenta = traceback._ANSIColors.MAGENTA + boldm = traceback._ANSIColors.BOLD_MAGENTA + reset = traceback._ANSIColors.RESET lno_foo = foo.__code__.co_firstlineno expected = ['Traceback (most recent call last):', f' File {magenta}"{__file__}"{reset}, ' @@ -4354,6 +4354,32 @@ def foo(): f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}'] self.assertEqual(actual, expected) + def test_colorized_detection_checks_for_environment_variables(self): + if sys.platform == "win32": + virtual_patching = unittest.mock.patch("nt._supports_virtual_terminal", return_value=True) + else: + virtual_patching = contextlib.nullcontext() + with virtual_patching: + with unittest.mock.patch("os.isatty") as isatty_mock: + isatty_mock.return_value = True + with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): + self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): + self.assertEqual(traceback._can_colorize(), True) + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): + self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): + self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): + self.assertEqual(traceback._can_colorize(), True) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): + self.assertEqual(traceback._can_colorize(), True) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): + self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): + self.assertEqual(traceback._can_colorize(), False) + isatty_mock.return_value = False + self.assertEqual(traceback._can_colorize(), False) if __name__ == "__main__": unittest.main() diff --git a/Lib/traceback.py b/Lib/traceback.py index 1922fd8caee762..054def57c21482 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1,5 +1,7 @@ """Extract, format and print information about Python stack traces.""" +import os +import io import collections.abc import itertools import linecache @@ -7,8 +9,6 @@ import textwrap import warnings from contextlib import suppress -import _colorize -from _colorize import ANSIColors __all__ = ['extract_stack', 'extract_tb', 'format_exception', 'format_exception_only', 'format_list', 'format_stack', @@ -17,11 +17,12 @@ 'FrameSummary', 'StackSummary', 'TracebackException', 'walk_stack', 'walk_tb'] - # # Formatting and printing lists of traceback lines. # +_COLORIZE = True + def print_list(extracted_list, file=None): """Print the list of tuples as returned by extract_tb() or extract_stack() as a formatted stack trace to the given file.""" @@ -132,10 +133,35 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ BUILTIN_EXCEPTION_LIMIT = object() +def _can_colorize(): + if sys.platform == "win32": + try: + import nt + if not nt._supports_virtual_terminal(): + return False + except (ImportError, AttributeError): + return False + + if os.environ.get("PYTHON_COLORS") == "0": + return False + if os.environ.get("PYTHON_COLORS") == "1": + return True + if "NO_COLOR" in os.environ: + return False + if not _COLORIZE: + return False + if "FORCE_COLOR" in os.environ: + return True + if os.environ.get("TERM") == "dumb": + return False + try: + return os.isatty(sys.stderr.fileno()) + except io.UnsupportedOperation: + return sys.stderr.isatty() def _print_exception_bltin(exc, /): file = sys.stderr if sys.stderr is not None else sys.__stderr__ - colorize = _colorize.can_colorize() + colorize = _can_colorize() return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) @@ -182,9 +208,9 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize= end_char = "\n" if insert_final_newline else "" if colorize: if value is None or not valuestr: - line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}{end_char}" + line = f"{_ANSIColors.BOLD_MAGENTA}{etype}{_ANSIColors.RESET}{end_char}" else: - line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}: {ANSIColors.MAGENTA}{valuestr}{ANSIColors.RESET}{end_char}" + line = f"{_ANSIColors.BOLD_MAGENTA}{etype}{_ANSIColors.RESET}: {_ANSIColors.MAGENTA}{valuestr}{_ANSIColors.RESET}{end_char}" else: if value is None or not valuestr: line = f"{etype}{end_char}" @@ -417,6 +443,17 @@ def _get_code_position(code, instruction_index): _RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c. +class _ANSIColors: + RED = '\x1b[31m' + BOLD_RED = '\x1b[1;31m' + MAGENTA = '\x1b[35m' + BOLD_MAGENTA = '\x1b[1;35m' + GREEN = "\x1b[32m" + BOLD_GREEN = "\x1b[1;32m" + GREY = '\x1b[90m' + RESET = '\x1b[0m' + YELLOW = "\x1b[33m" + class StackSummary(list): """A list of FrameSummary objects, representing a stack of frames.""" @@ -521,15 +558,15 @@ def format_frame_summary(self, frame_summary, **kwargs): filename = "" if colorize: row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format( - ANSIColors.MAGENTA, + _ANSIColors.MAGENTA, filename, - ANSIColors.RESET, - ANSIColors.MAGENTA, + _ANSIColors.RESET, + _ANSIColors.MAGENTA, frame_summary.lineno, - ANSIColors.RESET, - ANSIColors.MAGENTA, + _ANSIColors.RESET, + _ANSIColors.MAGENTA, frame_summary.name, - ANSIColors.RESET, + _ANSIColors.RESET, ) ) else: @@ -656,11 +693,11 @@ def output_line(lineno): for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]): caret_group = list(group) if color == "^": - colorized_line_parts.append(ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET) - colorized_carets_parts.append(ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET) + colorized_line_parts.append(_ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + _ANSIColors.RESET) + colorized_carets_parts.append(_ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + _ANSIColors.RESET) elif color == "~": - colorized_line_parts.append(ANSIColors.RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET) - colorized_carets_parts.append(ANSIColors.RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET) + colorized_line_parts.append(_ANSIColors.RED + "".join(char for char, _ in caret_group) + _ANSIColors.RESET) + colorized_carets_parts.append(_ANSIColors.RED + "".join(caret for _, caret in caret_group) + _ANSIColors.RESET) else: colorized_line_parts.append("".join(char for char, _ in caret_group)) colorized_carets_parts.append("".join(caret for _, caret in caret_group)) @@ -1236,12 +1273,12 @@ def _format_syntax_error(self, stype, **kwargs): if self.lineno is not None: if colorize: yield ' File {}"{}"{}, line {}{}{}\n'.format( - ANSIColors.MAGENTA, + _ANSIColors.MAGENTA, self.filename or "", - ANSIColors.RESET, - ANSIColors.MAGENTA, + _ANSIColors.RESET, + _ANSIColors.MAGENTA, self.lineno, - ANSIColors.RESET, + _ANSIColors.RESET, ) else: yield ' File "{}", line {}\n'.format( @@ -1281,11 +1318,11 @@ def _format_syntax_error(self, stype, **kwargs): # colorize from colno to end_colno ltext = ( ltext[:colno] + - ANSIColors.BOLD_RED + ltext[colno:end_colno] + ANSIColors.RESET + + _ANSIColors.BOLD_RED + ltext[colno:end_colno] + _ANSIColors.RESET + ltext[end_colno:] ) - start_color = ANSIColors.BOLD_RED - end_color = ANSIColors.RESET + start_color = _ANSIColors.BOLD_RED + end_color = _ANSIColors.RESET yield ' {}\n'.format(ltext) yield ' {}{}{}{}\n'.format( "".join(caretspace), @@ -1298,12 +1335,12 @@ def _format_syntax_error(self, stype, **kwargs): msg = self.msg or "" if colorize: yield "{}{}{}: {}{}{}{}\n".format( - ANSIColors.BOLD_MAGENTA, + _ANSIColors.BOLD_MAGENTA, stype, - ANSIColors.RESET, - ANSIColors.MAGENTA, + _ANSIColors.RESET, + _ANSIColors.MAGENTA, msg, - ANSIColors.RESET, + _ANSIColors.RESET, filename_suffix) else: yield "{}: {}{}\n".format(stype, msg, filename_suffix) diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h index b8fbb4f43434e7..ac9d91b5e12885 100644 --- a/Python/stdlib_module_names.h +++ b/Python/stdlib_module_names.h @@ -19,7 +19,6 @@ static const char* _Py_stdlib_module_names[] = { "_codecs_tw", "_collections", "_collections_abc", -"_colorize", "_compat_pickle", "_compression", "_contextvars", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy