diff --git a/Lib/test/test_lltrace.py b/Lib/test/test_lltrace.py index 06e33f4c4c2f3859cddc810c24432ff125f8ba53..b5b0c10d404be9f00a8e494b2c73aaf815a10be1 100644 --- a/Lib/test/test_lltrace.py +++ b/Lib/test/test_lltrace.py @@ -1,11 +1,16 @@ -import os +import opcode +import re +import sys import textwrap import unittest -from test.support import os_helper +from test.support import os_helper, verbose from test.support.script_helper import assert_python_ok +Py_DEBUG = hasattr(sys, 'gettotalrefcount') + +@unittest.skipUnless(Py_DEBUG, "lltrace requires Py_DEBUG") class TestLLTrace(unittest.TestCase): def test_lltrace_does_not_crash_on_subscript_operator(self): @@ -27,5 +32,67 @@ def test_lltrace_does_not_crash_on_subscript_operator(self): assert_python_ok(os_helper.TESTFN) + def run_code(self, code): + code = textwrap.dedent(code).strip() + with open(os_helper.TESTFN, 'w', encoding='utf-8') as fd: + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + fd.write(code) + status, stdout, stderr = assert_python_ok(os_helper.TESTFN) + self.assertEqual(stderr, b"") + self.assertEqual(status, 0) + result = stdout.decode('utf-8') + if verbose: + print("\n\n--- code ---") + print(code) + print("\n--- stdout ---") + print(result) + print() + return result + + def check_op(self, op, stdout, present): + op = opcode.opmap[op] + regex = re.compile(f': {op}($|, )', re.MULTILINE) + if present: + self.assertTrue(regex.search(stdout), + f'": {op}" not found in: {stdout}') + else: + self.assertFalse(regex.search(stdout), + f'": {op}" found in: {stdout}') + + def check_op_in(self, op, stdout): + self.check_op(op, stdout, True) + + def check_op_not_in(self, op, stdout): + self.check_op(op, stdout, False) + + def test_lltrace(self): + stdout = self.run_code(""" + def dont_trace_1(): + a = "a" + a = 10 * a + def trace_me(): + for i in range(3): + +i + def dont_trace_2(): + x = 42 + y = -x + dont_trace_1() + __ltrace__ = 1 + trace_me() + del __ltrace__ + dont_trace_2() + """) + self.check_op_in("GET_ITER", stdout) + self.check_op_in("FOR_ITER", stdout) + self.check_op_in("UNARY_POSITIVE", stdout) + self.check_op_in("POP_TOP", stdout) + + # before: dont_trace_1() is not traced + self.check_op_not_in("BINARY_MULTIPLY", stdout) + + # after: dont_trace_2() is not traced + self.check_op_not_in("UNARY_NEGATIVE", stdout) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-25-04-07-22.gh-issue-91924.-UyO4q.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-25-04-07-22.gh-issue-91924.-UyO4q.rst new file mode 100644 index 0000000000000000000000000000000000000000..3986ad8aa8d951d0cb6c2a5ce1a4814d8d06439f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-25-04-07-22.gh-issue-91924.-UyO4q.rst @@ -0,0 +1,2 @@ +Fix ``__ltrace__`` debug feature if the stdout encoding is not UTF-8. Patch +by Victor Stinner. diff --git a/Python/ceval.c b/Python/ceval.c index 21674e0be13240d67db569ca335df7d69c94de79..9a193c994d12ec67eb4dfd2e341601464ce90b67 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -5377,6 +5377,8 @@ prtrace(PyThreadState *tstate, PyObject *v, const char *str) } printf("\n"); PyErr_Restore(type, value, traceback); + // gh-91924: PyObject_Print() can indirectly set lltrace to 0 + lltrace = 1; return 1; } #endif