-
-
Notifications
You must be signed in to change notification settings - Fork 29.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-91462: Make lltrace output human-readable. #91463
Changes from all commits
d64552c
e07db47
a5d1cee
1cb8700
00d8e30
e39716a
a063749
2308d63
2b1fd5b
37a751b
f417858
b8f99d3
c69022c
e2b4bed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,112 @@ | ||
import os | ||
import dis | ||
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 | ||
|
||
def example(): | ||
x = [] | ||
for i in range(1): | ||
x.append(i) | ||
x = "this is" | ||
y = "an example" | ||
print(x, y) | ||
|
||
Py_DEBUG = hasattr(sys, 'gettotalrefcount') | ||
|
||
@unittest.skipUnless(Py_DEBUG, "lltrace requires Py_DEBUG") | ||
class TestLLTrace(unittest.TestCase): | ||
|
||
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 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This tripped me over! :-) It almost seems like a bug that the feature is called LLTRACE but the dunder is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to be a decade-old typo introduced here: 3c1e481 We could maybe accept both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eh, just change to |
||
trace_me() | ||
del __ltrace__ | ||
dont_trace_2() | ||
""") | ||
self.assertIn("GET_ITER", stdout) | ||
self.assertIn("FOR_ITER", stdout) | ||
self.assertIn("UNARY_POSITIVE", stdout) | ||
self.assertIn("POP_TOP", stdout) | ||
self.assertNotIn("BINARY_OP", stdout) | ||
self.assertNotIn("UNARY_NEGATIVE", stdout) | ||
gvanrossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
self.assertIn("'trace_me' in module '__main__'", stdout) | ||
self.assertNotIn("dont_trace_1", stdout) | ||
self.assertNotIn("'dont_trace_2' in module", stdout) | ||
gvanrossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def test_lltrace_different_module(self): | ||
stdout = self.run_code(""" | ||
from test import test_lltrace | ||
test_lltrace.__ltrace__ = 1 | ||
test_lltrace.example() | ||
""") | ||
self.assertIn("'example' in module 'test.test_lltrace'", stdout) | ||
self.assertIn('LOAD_CONST', stdout) | ||
self.assertIn('FOR_ITER', stdout) | ||
self.assertIn('this is an example', stdout) | ||
|
||
# check that offsets match the output of dis.dis() | ||
instr_map = {i.offset: i for i in dis.get_instructions(example)} | ||
for line in stdout.splitlines(): | ||
offset, colon, opname_oparg = line.partition(":") | ||
if not colon: | ||
continue | ||
offset = int(offset) | ||
opname_oparg = opname_oparg.split() | ||
if len(opname_oparg) == 2: | ||
opname, oparg = opname_oparg | ||
oparg = int(oparg) | ||
else: | ||
(opname,) = opname_oparg | ||
oparg = None | ||
self.assertEqual(instr_map[offset].opname, opname) | ||
self.assertEqual(instr_map[offset].arg, oparg) | ||
|
||
def test_lltrace_does_not_crash_on_subscript_operator(self): | ||
# If this test fails, it will reproduce a crash reported as | ||
# bpo-34113. The crash happened at the command line console of | ||
# debug Python builds with __ltrace__ enabled (only possible in console), | ||
# when the internal Python stack was negatively adjusted | ||
with open(os_helper.TESTFN, 'w', encoding='utf-8') as fd: | ||
self.addCleanup(os_helper.unlink, os_helper.TESTFN) | ||
fd.write(textwrap.dedent("""\ | ||
stdout = self.run_code(""" | ||
import code | ||
|
||
console = code.InteractiveConsole() | ||
console.push('__ltrace__ = 1') | ||
console.push('a = [1, 2, 3]') | ||
console.push('a[0] = 1') | ||
print('unreachable if bug exists') | ||
""")) | ||
|
||
assert_python_ok(os_helper.TESTFN) | ||
""") | ||
self.assertIn("unreachable if bug exists", stdout) | ||
|
||
if __name__ == "__main__": | ||
unittest.main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Make the interpreter's low-level tracing (lltrace) feature output more readable by displaying opcode names (rather than just numbers), and by displaying stack contents before each opcode. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Surprised there isn't already a utility for this.)