GH-92236: Remove spurious "line" event when starting coroutine or generator. (GH-92722)

This commit is contained in:
Mark Shannon 2022-05-13 11:24:45 +01:00 committed by GitHub
parent db388df1d9
commit 22a1db378c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 0 deletions

View File

@ -7,6 +7,7 @@ import difflib
import gc import gc
from functools import wraps from functools import wraps
import asyncio import asyncio
from test.support import import_helper
support.requires_working_socket(module=True) support.requires_working_socket(module=True)
@ -1473,6 +1474,58 @@ class TraceTestCase(unittest.TestCase):
(3, 'return'), (3, 'return'),
(1, 'return')]) (1, 'return')])
@support.cpython_only
def test_no_line_event_after_creating_generator(self):
# Spurious line events before call events only show up with C tracer
# Skip this test if the _testcapi module isn't available.
_testcapi = import_helper.import_module('_testcapi')
def gen():
yield 1
def func():
for _ in (
gen()
):
pass
EXPECTED_EVENTS = [
(0, 'call'),
(2, 'line'),
(1, 'line'),
(-3, 'call'),
(-2, 'line'),
(-2, 'return'),
(4, 'line'),
(1, 'line'),
(-2, 'call'),
(-2, 'return'),
(1, 'return'),
]
# C level events should be the same as expected and the same as Python level.
events = []
# Turning on and off tracing must be on same line to avoid unwanted LINE events.
_testcapi.settrace_to_record(events); func(); sys.settrace(None)
start_line = func.__code__.co_firstlineno
events = [
(line-start_line, EVENT_NAMES[what])
for (what, line, arg) in events
]
self.assertEqual(events, EXPECTED_EVENTS)
self.run_and_compare(func, EXPECTED_EVENTS)
EVENT_NAMES = [
'call',
'exception',
'line',
'return'
]
class SkipLineEventsTraceTestCase(TraceTestCase): class SkipLineEventsTraceTestCase(TraceTestCase):
"""Repeat the trace tests, but with per-line events skipped""" """Repeat the trace tests, but with per-line events skipped"""

View File

@ -0,0 +1,2 @@
Remove spurious "LINE" event when starting a generator or coroutine, visible
tracing functions implemented in C.

View File

@ -5787,6 +5787,51 @@ test_code_api(PyObject *self, PyObject *Py_UNUSED(args))
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static int
record_func(PyObject *obj, PyFrameObject *f, int what, PyObject *arg)
{
assert(PyList_Check(obj));
PyObject *what_obj = NULL;
PyObject *line_obj = NULL;
PyObject *tuple = NULL;
int res = -1;
what_obj = PyLong_FromLong(what);
if (what_obj == NULL) {
goto error;
}
int line = PyFrame_GetLineNumber(f);
line_obj = PyLong_FromLong(line);
if (line_obj == NULL) {
goto error;
}
tuple = PyTuple_Pack(3, what_obj, line_obj, arg);
if (tuple == NULL) {
goto error;
}
PyTuple_SET_ITEM(tuple, 0, what_obj);
if (PyList_Append(obj, tuple)) {
goto error;
}
res = 0;
error:
Py_XDECREF(what_obj);
Py_XDECREF(line_obj);
Py_XDECREF(tuple);
return res;
}
static PyObject *
settrace_to_record(PyObject *self, PyObject *list)
{
if (!PyList_Check(list)) {
PyErr_SetString(PyExc_TypeError, "argument must be a list");
return NULL;
}
PyEval_SetTrace(record_func, list);
Py_RETURN_NONE;
}
static PyObject *negative_dictoffset(PyObject *, PyObject *); static PyObject *negative_dictoffset(PyObject *, PyObject *);
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *); static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*); static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*);
@ -6076,6 +6121,7 @@ static PyMethodDef TestMethods[] = {
{"frame_getlasti", frame_getlasti, METH_O, NULL}, {"frame_getlasti", frame_getlasti, METH_O, NULL},
{"get_feature_macros", get_feature_macros, METH_NOARGS, NULL}, {"get_feature_macros", get_feature_macros, METH_NOARGS, NULL},
{"test_code_api", test_code_api, METH_NOARGS, NULL}, {"test_code_api", test_code_api, METH_NOARGS, NULL},
{"settrace_to_record", settrace_to_record, METH_O, NULL},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };

View File

@ -5680,6 +5680,12 @@ handle_eval_breaker:
TRACE_FUNCTION_ENTRY(); TRACE_FUNCTION_ENTRY();
DTRACE_FUNCTION_ENTRY(); DTRACE_FUNCTION_ENTRY();
break; break;
case POP_TOP:
if (_Py_OPCODE(next_instr[-1]) == RETURN_GENERATOR) {
/* Frame not fully initialized */
break;
}
/* fall through */
default: default:
/* line-by-line tracing support */ /* line-by-line tracing support */
if (PyDTrace_LINE_ENABLED()) { if (PyDTrace_LINE_ENABLED()) {