mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Align canonical compiler fixtures and generator frame state with final 3.15
Migrate only CI-red canonical test modules and their required syntax-warning fixture through update_lib from CPython d220409a. Preserve generated migration marker output. Clear created generator frames using actual owner state, publish closed state before warning callbacks, and expose the final read-only gi_state contract. Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
1 parent
a2dacaf4bd
commit
476bb74f3c
10 files changed
+1239
-513
No files matched your search
Vendored
+22
-13
@@ -87,7 +87,7 @@ cellvars: ()
|
|||||||
freevars: ()
|
freevars: ()
|
||||||
nlocals: 0
|
nlocals: 0
|
||||||
flags: 67108867
|
flags: 67108867
|
||||||
consts: ("'doc string'", 'None')
|
consts: ("'doc string'",)
|
||||||
|
|
||||||
>>> def keywordonly_args(a,b,*,k1):
|
>>> def keywordonly_args(a,b,*,k1):
|
||||||
... return a,b,k1
|
... return a,b,k1
|
||||||
@@ -161,7 +161,7 @@ cellvars: ()
|
|||||||
freevars: ()
|
freevars: ()
|
||||||
nlocals: 3
|
nlocals: 3
|
||||||
flags: 67108995
|
flags: 67108995
|
||||||
consts: ("'This is a docstring from async function'", 'None')
|
consts: ("'This is a docstring from async function'",)
|
||||||
|
|
||||||
>>> def no_docstring(x, y, z):
|
>>> def no_docstring(x, y, z):
|
||||||
... return x + "hello" + y + z + "world"
|
... return x + "hello" + y + z + "world"
|
||||||
@@ -423,14 +423,6 @@ class CodeTest(unittest.TestCase):
|
|||||||
new_code = code = func.__code__.replace(co_linetable=b'')
|
new_code = code = func.__code__.replace(co_linetable=b'')
|
||||||
self.assertEqual(list(new_code.co_lines()), [])
|
self.assertEqual(list(new_code.co_lines()), [])
|
||||||
|
|
||||||
@unittest.expectedFailure # TODO: RUSTPYTHON; co_lnotab intentionally not implemented (deprecated since 3.12)
|
|
||||||
def test_co_lnotab_is_deprecated(self): # TODO: remove in 3.14
|
|
||||||
def func():
|
|
||||||
pass
|
|
||||||
|
|
||||||
with self.assertWarns(DeprecationWarning):
|
|
||||||
func.__code__.co_lnotab
|
|
||||||
|
|
||||||
@unittest.skipIf(_testinternalcapi is None, '_testinternalcapi is missing')
|
@unittest.skipIf(_testinternalcapi is None, '_testinternalcapi is missing')
|
||||||
def test_returns_only_none(self):
|
def test_returns_only_none(self):
|
||||||
value = True
|
value = True
|
||||||
@@ -540,7 +532,7 @@ class CodeTest(unittest.TestCase):
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
("PUSH_EXC_INFO", None),
|
("PUSH_EXC_INFO", None),
|
||||||
("LOAD_CONST", None), # artificial 'None'
|
("LOAD_COMMON_CONSTANT", None), # artificial 'None'
|
||||||
("STORE_NAME", "e"), # XX: we know the location for this
|
("STORE_NAME", "e"), # XX: we know the location for this
|
||||||
("DELETE_NAME", "e"),
|
("DELETE_NAME", "e"),
|
||||||
("RERAISE", 1),
|
("RERAISE", 1),
|
||||||
@@ -1165,6 +1157,22 @@ class CodeTest(unittest.TestCase):
|
|||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
_testinternalcapi.verify_stateless_code(func)
|
_testinternalcapi.verify_stateless_code(func)
|
||||||
|
|
||||||
|
def test_code_richcompare_raise_exception(self):
|
||||||
|
class BadStr(str):
|
||||||
|
def __eq__(self, _):
|
||||||
|
raise RuntimeError("Poison!")
|
||||||
|
|
||||||
|
__hash__ = str.__hash__
|
||||||
|
|
||||||
|
c1 = compile("pass", "test", "exec")
|
||||||
|
c2 = c1.replace(co_name=BadStr("poison"))
|
||||||
|
c3 = compile("pass", "poison", "exec")
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
c2 == c3
|
||||||
|
|
||||||
|
@unittest.expectedFailure # TODO: RUSTPYTHON; co_lnotab intentionally not implemented (deprecated since 3.12)
|
||||||
|
def test_co_lnotab_is_deprecated(self):
|
||||||
|
return super().test_co_lnotab_is_deprecated()
|
||||||
|
|
||||||
def isinterned(s):
|
def isinterned(s):
|
||||||
return s is sys.intern(('_' + s + '_')[1:-1])
|
return s is sys.intern(('_' + s + '_')[1:-1])
|
||||||
@@ -1438,6 +1446,7 @@ class CodeLocationTest(unittest.TestCase):
|
|||||||
co_code=bytes(
|
co_code=bytes(
|
||||||
[
|
[
|
||||||
dis.opmap["RESUME"], 0,
|
dis.opmap["RESUME"], 0,
|
||||||
|
dis.opmap["CACHE"], 0,
|
||||||
dis.opmap["LOAD_COMMON_CONSTANT"], 0,
|
dis.opmap["LOAD_COMMON_CONSTANT"], 0,
|
||||||
dis.opmap["RAISE_VARARGS"], 1,
|
dis.opmap["RAISE_VARARGS"], 1,
|
||||||
]
|
]
|
||||||
@@ -1446,7 +1455,7 @@ class CodeLocationTest(unittest.TestCase):
|
|||||||
[
|
[
|
||||||
(1 << 7)
|
(1 << 7)
|
||||||
| (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
|
| (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
|
||||||
| (3 - 1),
|
| (4 - 1),
|
||||||
0,
|
0,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
@@ -1454,7 +1463,7 @@ class CodeLocationTest(unittest.TestCase):
|
|||||||
self.assertRaises(AssertionError, f)
|
self.assertRaises(AssertionError, f)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
list(f.__code__.co_positions()),
|
list(f.__code__.co_positions()),
|
||||||
3 * [(42, 42, None, None)],
|
4 * [(42, 42, None, None)],
|
||||||
)
|
)
|
||||||
|
|
||||||
@cpython_only
|
@cpython_only
|
||||||
|
|||||||
Vendored
+50
-9
@@ -724,8 +724,7 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
|
|
||||||
@support.cpython_only
|
@support.cpython_only
|
||||||
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
|
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
|
||||||
@support.run_with_limited_c_stack(
|
@support.skip_if_huge_c_stack(100_000 if sys.platform == "android" else 500_000)
|
||||||
100_000 if sys.platform == "android" else 500_000)
|
|
||||||
@support.skip_emscripten_stack_overflow()
|
@support.skip_emscripten_stack_overflow()
|
||||||
def test_compiler_recursion_limit(self):
|
def test_compiler_recursion_limit(self):
|
||||||
# Compiler frames are small
|
# Compiler frames are small
|
||||||
@@ -841,8 +840,10 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
return "unused"
|
return "unused"
|
||||||
|
|
||||||
self.assertEqual(f.__code__.co_consts,
|
if f.__doc__ is None:
|
||||||
(f.__doc__, "used"))
|
self.assertEqual(f.__code__.co_consts, (True, "used"))
|
||||||
|
else:
|
||||||
|
self.assertEqual(f.__code__.co_consts, (f.__doc__, "used"))
|
||||||
|
|
||||||
@support.cpython_only
|
@support.cpython_only
|
||||||
def test_remove_unused_consts_no_docstring(self):
|
def test_remove_unused_consts_no_docstring(self):
|
||||||
@@ -887,6 +888,10 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
def f1():
|
def f1():
|
||||||
"docstring"
|
"docstring"
|
||||||
return 42
|
return 42
|
||||||
|
|
||||||
|
if f1.__doc__ is None:
|
||||||
|
self.assertEqual(f1.__code__.co_consts, (42,))
|
||||||
|
else:
|
||||||
self.assertEqual(f1.__code__.co_consts, (f1.__doc__,))
|
self.assertEqual(f1.__code__.co_consts, (f1.__doc__,))
|
||||||
|
|
||||||
# This is a regression test for a CPython specific peephole optimizer
|
# This is a regression test for a CPython specific peephole optimizer
|
||||||
@@ -1294,7 +1299,7 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
x
|
x
|
||||||
in
|
in
|
||||||
y)
|
y)
|
||||||
genexp_lines = [0, 4, 2, 0, 4]
|
genexp_lines = [4, 0, 4, 2, 0, 4]
|
||||||
|
|
||||||
genexp_code = return_genexp.__code__.co_consts[0]
|
genexp_code = return_genexp.__code__.co_consts[0]
|
||||||
code_lines = self.get_code_lines(genexp_code)
|
code_lines = self.get_code_lines(genexp_code)
|
||||||
@@ -1629,6 +1634,17 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
def f():
|
def f():
|
||||||
a if (1 if b else c) else d
|
a if (1 if b else c) else d
|
||||||
|
|
||||||
|
def test_lineno_propagation_empty_blocks(self):
|
||||||
|
# Smoke test. See gh-138714.
|
||||||
|
def f():
|
||||||
|
while name:
|
||||||
|
try:
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
1 if 1 else 1
|
||||||
|
|
||||||
def test_global_declaration_in_except_used_in_else(self):
|
def test_global_declaration_in_except_used_in_else(self):
|
||||||
# See gh-111123
|
# See gh-111123
|
||||||
code = textwrap.dedent("""\
|
code = textwrap.dedent("""\
|
||||||
@@ -1733,6 +1749,30 @@ class TestSpecifics(unittest.TestCase):
|
|||||||
self.assertEqual(wm.category, SyntaxWarning)
|
self.assertEqual(wm.category, SyntaxWarning)
|
||||||
self.assertIn("\"is\" with 'int' literal", str(wm.message))
|
self.assertIn("\"is\" with 'int' literal", str(wm.message))
|
||||||
|
|
||||||
|
def test_filter_syntax_warnings_by_module(self):
|
||||||
|
filename = support.findfile('test_import/data/syntax_warnings.py')
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
source = f.read()
|
||||||
|
module_re = r'test\.test_import\.data\.syntax_warnings\z'
|
||||||
|
with warnings.catch_warnings(record=True) as wlog:
|
||||||
|
warnings.simplefilter('error')
|
||||||
|
warnings.filterwarnings('always', module=module_re)
|
||||||
|
compile(source, filename, 'exec')
|
||||||
|
self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
|
||||||
|
for wm in wlog:
|
||||||
|
self.assertEqual(wm.filename, filename)
|
||||||
|
self.assertIs(wm.category, SyntaxWarning)
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as wlog:
|
||||||
|
warnings.simplefilter('error')
|
||||||
|
warnings.filterwarnings('always', module=r'package\.module\z')
|
||||||
|
warnings.filterwarnings('error', module=module_re)
|
||||||
|
compile(source, filename, 'exec', module='package.module')
|
||||||
|
self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
|
||||||
|
for wm in wlog:
|
||||||
|
self.assertEqual(wm.filename, filename)
|
||||||
|
self.assertIs(wm.category, SyntaxWarning)
|
||||||
|
|
||||||
@support.subTests('src', [
|
@support.subTests('src', [
|
||||||
textwrap.dedent("""
|
textwrap.dedent("""
|
||||||
def f():
|
def f():
|
||||||
@@ -2419,8 +2459,8 @@ class TestSourcePositions(unittest.TestCase):
|
|||||||
for i, pos in enumerate(positions):
|
for i, pos in enumerate(positions):
|
||||||
with self.subTest(i=i, pos=pos):
|
with self.subTest(i=i, pos=pos):
|
||||||
start_line, end_line, start_col, end_col = pos
|
start_line, end_line, start_col, end_col = pos
|
||||||
if i == 0 and start_col == end_col == 0:
|
if i <= 1:
|
||||||
# ignore the RESUME in the beginning
|
# ignore the RESUME and CACHE in the beginning
|
||||||
continue
|
continue
|
||||||
self.assertEqual(start_line, 1)
|
self.assertEqual(start_line, 1)
|
||||||
self.assertEqual(end_line, 1)
|
self.assertEqual(end_line, 1)
|
||||||
@@ -2446,12 +2486,13 @@ class TestSourcePositions(unittest.TestCase):
|
|||||||
start_line, end_line, _, _ = instr.positions
|
start_line, end_line, _, _ = instr.positions
|
||||||
self.assertEqual(start_line, end_line)
|
self.assertEqual(start_line, end_line)
|
||||||
|
|
||||||
# Expect four `LOAD_CONST None` instructions:
|
# Expect four `None`-loading instructions:
|
||||||
# three for the no-exception __exit__ call, and one for the return.
|
# three for the no-exception __exit__ call, and one for the return.
|
||||||
# They should all have the locations of the context manager ('xyz').
|
# They should all have the locations of the context manager ('xyz').
|
||||||
|
|
||||||
load_none = [instr for instr in dis.get_instructions(f) if
|
load_none = [instr for instr in dis.get_instructions(f) if
|
||||||
instr.opname == 'LOAD_CONST' and instr.argval is None]
|
instr.opname in ('LOAD_CONST', 'LOAD_COMMON_CONSTANT')
|
||||||
|
and instr.argval is None]
|
||||||
return_value = [instr for instr in dis.get_instructions(f) if
|
return_value = [instr for instr in dis.get_instructions(f) if
|
||||||
instr.opname == 'RETURN_VALUE']
|
instr.opname == 'RETURN_VALUE']
|
||||||
|
|
||||||
|
|||||||
Vendored
+328
-288
File diff suppressed because it is too large.
Load diff
Vendored
+15
-17
@@ -357,26 +357,25 @@ class ModifyUnderlyingIterableTest(unittest.TestCase):
|
|||||||
yield x
|
yield x
|
||||||
return gen(range(10))
|
return gen(range(10))
|
||||||
|
|
||||||
def process_tests(self, get_generator, is_expr):
|
def process_tests(self, get_generator, changes_iterable):
|
||||||
err_iterator = "'.*' object is not an iterator"
|
if changes_iterable:
|
||||||
err_iterable = "'.*' object is not iterable"
|
|
||||||
for obj in self.iterables:
|
for obj in self.iterables:
|
||||||
g_obj = get_generator(obj)
|
g_obj = get_generator(obj)
|
||||||
with self.subTest(g_obj=g_obj, obj=obj):
|
with self.subTest(g_obj=g_obj, obj=obj):
|
||||||
if is_expr:
|
|
||||||
self.assertRaisesRegex(TypeError, err_iterator, list, g_obj)
|
|
||||||
else:
|
|
||||||
self.assertListEqual(list(g_obj), list(obj))
|
self.assertListEqual(list(g_obj), list(obj))
|
||||||
|
|
||||||
g_iter = get_generator(iter(obj))
|
g_iter = get_generator(iter(obj))
|
||||||
with self.subTest(g_iter=g_iter, obj=obj):
|
with self.subTest(g_iter=g_iter, obj=obj):
|
||||||
self.assertListEqual(list(g_iter), list(obj))
|
self.assertListEqual(list(g_iter), list(obj))
|
||||||
|
|
||||||
|
err_regex = "'.*' object is not iterable"
|
||||||
for obj in self.non_iterables:
|
for obj in self.non_iterables:
|
||||||
g_obj = get_generator(obj)
|
g_obj = get_generator(obj)
|
||||||
with self.subTest(g_obj=g_obj):
|
with self.subTest(g_obj=g_obj):
|
||||||
err = err_iterator if is_expr else err_iterable
|
if changes_iterable:
|
||||||
self.assertRaisesRegex(TypeError, err, list, g_obj)
|
self.assertRaisesRegex(TypeError, err_regex, list, g_obj)
|
||||||
|
else:
|
||||||
|
next(g_obj)
|
||||||
|
|
||||||
def test_modify_f_locals(self):
|
def test_modify_f_locals(self):
|
||||||
def modify_f_locals(g, local, obj):
|
def modify_f_locals(g, local, obj):
|
||||||
@@ -389,22 +388,22 @@ class ModifyUnderlyingIterableTest(unittest.TestCase):
|
|||||||
def get_generator_genfunc(obj):
|
def get_generator_genfunc(obj):
|
||||||
return modify_f_locals(self.genfunc(), 'it', obj)
|
return modify_f_locals(self.genfunc(), 'it', obj)
|
||||||
|
|
||||||
self.process_tests(get_generator_genexpr, True)
|
self.process_tests(get_generator_genexpr, False)
|
||||||
self.process_tests(get_generator_genfunc, False)
|
self.process_tests(get_generator_genfunc, True)
|
||||||
|
|
||||||
def test_new_gen_from_gi_code(self):
|
def test_new_gen_from_gi_code(self):
|
||||||
def new_gen_from_gi_code(g, obj):
|
def new_gen_from_gi_code(g, obj):
|
||||||
generator_func = types.FunctionType(g.gi_code, {})
|
generator_func = types.FunctionType(g.gi_code, {})
|
||||||
return generator_func(obj)
|
return generator_func(obj)
|
||||||
|
|
||||||
def get_generator_genexpr(obj):
|
for obj in self.non_iterables:
|
||||||
return new_gen_from_gi_code(self.genexpr(), obj)
|
with self.assertRaises(TypeError):
|
||||||
|
new_gen_from_gi_code(self.genexpr(), obj)
|
||||||
|
|
||||||
def get_generator_genfunc(obj):
|
def get_generator_genfunc(obj):
|
||||||
return new_gen_from_gi_code(self.genfunc(), obj)
|
return new_gen_from_gi_code(self.genfunc(), obj)
|
||||||
|
|
||||||
self.process_tests(get_generator_genexpr, True)
|
self.process_tests(get_generator_genfunc, True)
|
||||||
self.process_tests(get_generator_genfunc, False)
|
|
||||||
|
|
||||||
|
|
||||||
class ExceptionTest(unittest.TestCase):
|
class ExceptionTest(unittest.TestCase):
|
||||||
@@ -1367,7 +1366,7 @@ From the Iterators list, about the types of these things.
|
|||||||
>>> type(i)
|
>>> type(i)
|
||||||
<class 'generator'>
|
<class 'generator'>
|
||||||
>>> [s for s in dir(i) if not s.startswith('_')]
|
>>> [s for s in dir(i) if not s.startswith('_')]
|
||||||
['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw']
|
['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_state', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw']
|
||||||
>>> from test.support import HAVE_DOCSTRINGS
|
>>> from test.support import HAVE_DOCSTRINGS
|
||||||
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
|
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
|
||||||
Implement next(self).
|
Implement next(self).
|
||||||
@@ -2878,8 +2877,7 @@ __test__ = {"tut": tutorial_tests,
|
|||||||
}
|
}
|
||||||
|
|
||||||
def load_tests(loader, tests, pattern):
|
def load_tests(loader, tests, pattern):
|
||||||
from test.support.rustpython import DocTestChecker # TODO: RUSTPYTHON
|
tests.addTest(doctest.DocTestSuite())
|
||||||
tests.addTest(doctest.DocTestSuite(checker=DocTestChecker())) # TODO: RUSTPYTHON
|
|
||||||
return tests
|
return tests
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# Syntax warnings emitted in different parts of the Python compiler.
|
||||||
|
|
||||||
|
# Parser/lexer/lexer.c
|
||||||
|
x = 1or 0 # line 4
|
||||||
|
|
||||||
|
# Parser/tokenizer/helpers.c
|
||||||
|
'\z' # line 7
|
||||||
|
|
||||||
|
# Parser/string_parser.c
|
||||||
|
'\400' # line 10
|
||||||
|
|
||||||
|
# _PyCompile_Warn() in Python/codegen.c
|
||||||
|
assert(x, 'message') # line 13
|
||||||
|
x is 1 # line 14
|
||||||
|
|
||||||
|
# _PyErr_EmitSyntaxWarning() in Python/ast_preprocess.c
|
||||||
|
def f():
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
return 42 # line 21
|
||||||
Vendored
+441
-115
@@ -12,16 +12,7 @@ import types
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import test.support
|
import test.support
|
||||||
from test.support import requires_specialization_ft, script_helper
|
from test.support import import_helper, requires_specialization, script_helper
|
||||||
|
|
||||||
try:
|
|
||||||
import _testcapi
|
|
||||||
except ImportError:
|
|
||||||
_testcapi = None
|
|
||||||
try:
|
|
||||||
import _testinternalcapi
|
|
||||||
except ImportError:
|
|
||||||
_testinternalcapi = None
|
|
||||||
|
|
||||||
PAIR = (0,1)
|
PAIR = (0,1)
|
||||||
|
|
||||||
@@ -202,13 +193,10 @@ INSTRUMENTED_EVENTS = [
|
|||||||
(E.BRANCH, "branch"),
|
(E.BRANCH, "branch"),
|
||||||
]
|
]
|
||||||
|
|
||||||
EXCEPT_EVENTS = [
|
SIMPLE_EVENTS = INSTRUMENTED_EVENTS + [
|
||||||
(E.RAISE, "raise"),
|
(E.RAISE, "raise"),
|
||||||
(E.PY_UNWIND, "unwind"),
|
|
||||||
(E.EXCEPTION_HANDLED, "exception_handled"),
|
(E.EXCEPTION_HANDLED, "exception_handled"),
|
||||||
]
|
(E.PY_UNWIND, "unwind"),
|
||||||
|
|
||||||
SIMPLE_EVENTS = INSTRUMENTED_EVENTS + EXCEPT_EVENTS + [
|
|
||||||
(E.C_RAISE, "c_raise"),
|
(E.C_RAISE, "c_raise"),
|
||||||
(E.C_RETURN, "c_return"),
|
(E.C_RETURN, "c_return"),
|
||||||
]
|
]
|
||||||
@@ -744,18 +732,6 @@ class TestDisable(MonitoringTestBase, unittest.TestCase):
|
|||||||
sys.monitoring.register_callback(TEST_TOOL, event, None)
|
sys.monitoring.register_callback(TEST_TOOL, event, None)
|
||||||
|
|
||||||
|
|
||||||
def test_disable_illegal_events(self):
|
|
||||||
for event, name in EXCEPT_EVENTS:
|
|
||||||
try:
|
|
||||||
counter = CounterWithDisable()
|
|
||||||
counter.disable = True
|
|
||||||
sys.monitoring.register_callback(TEST_TOOL, event, counter)
|
|
||||||
sys.monitoring.set_events(TEST_TOOL, event)
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
self.raise_handle_reraise()
|
|
||||||
finally:
|
|
||||||
sys.monitoring.set_events(TEST_TOOL, 0)
|
|
||||||
sys.monitoring.register_callback(TEST_TOOL, event, None)
|
|
||||||
|
|
||||||
|
|
||||||
class ExceptionRecorder:
|
class ExceptionRecorder:
|
||||||
@@ -879,7 +855,6 @@ class ExceptionMonitoringTest(CheckEvents):
|
|||||||
|
|
||||||
self.check_events(func1, [("raise", KeyError)])
|
self.check_events(func1, [("raise", KeyError)])
|
||||||
|
|
||||||
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
|
|
||||||
def test_implicit_stop_iteration(self):
|
def test_implicit_stop_iteration(self):
|
||||||
"""Generators are documented as raising a StopIteration
|
"""Generators are documented as raising a StopIteration
|
||||||
when they terminate.
|
when they terminate.
|
||||||
@@ -889,6 +864,8 @@ class ExceptionMonitoringTest(CheckEvents):
|
|||||||
This test checks that both paths record an equivalent event.
|
This test checks that both paths record an equivalent event.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_testinternalcapi = import_helper.import_module("_testinternalcapi")
|
||||||
|
|
||||||
def gen():
|
def gen():
|
||||||
yield 1
|
yield 1
|
||||||
return 2
|
return 2
|
||||||
@@ -1054,9 +1031,10 @@ class ExceptionMonitoringTest(CheckEvents):
|
|||||||
)
|
)
|
||||||
self.assertEqual(events[0], ("throw", IndexError))
|
self.assertEqual(events[0], ("throw", IndexError))
|
||||||
|
|
||||||
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
|
@requires_specialization
|
||||||
@requires_specialization_ft
|
|
||||||
def test_no_unwind_for_shim_frame(self):
|
def test_no_unwind_for_shim_frame(self):
|
||||||
|
_testinternalcapi = import_helper.import_module("_testinternalcapi")
|
||||||
|
|
||||||
class ValueErrorRaiser:
|
class ValueErrorRaiser:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
@@ -1222,19 +1200,20 @@ class TestLineAndInstructionEvents(CheckEvents):
|
|||||||
line3 = 3
|
line3 = 3
|
||||||
|
|
||||||
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
||||||
('line', 'get_events', 10),
|
("line", "get_events", 10),
|
||||||
('line', 'func1', 1),
|
("line", "func1", 1),
|
||||||
('instruction', 'func1', 2),
|
("instruction", "func1", 4),
|
||||||
('instruction', 'func1', 4),
|
("instruction", "func1", 6),
|
||||||
('line', 'func1', 2),
|
("line", "func1", 2),
|
||||||
('instruction', 'func1', 6),
|
("instruction", "func1", 8),
|
||||||
('instruction', 'func1', 8),
|
("instruction", "func1", 10),
|
||||||
('line', 'func1', 3),
|
("line", "func1", 3),
|
||||||
('instruction', 'func1', 10),
|
("instruction", "func1", 12),
|
||||||
('instruction', 'func1', 12),
|
("instruction", "func1", 14),
|
||||||
('instruction', 'func1', 14),
|
("instruction", "func1", 16),
|
||||||
('instruction', 'func1', 16),
|
("instruction", "func1", 18),
|
||||||
('line', 'get_events', 11)])
|
("line", "get_events", 11),
|
||||||
|
])
|
||||||
|
|
||||||
def test_c_call(self):
|
def test_c_call(self):
|
||||||
|
|
||||||
@@ -1244,22 +1223,23 @@ class TestLineAndInstructionEvents(CheckEvents):
|
|||||||
line3 = 3
|
line3 = 3
|
||||||
|
|
||||||
self.check_events(func2, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
self.check_events(func2, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
||||||
('line', 'get_events', 10),
|
("line", "get_events", 10),
|
||||||
('line', 'func2', 1),
|
("line", "func2", 1),
|
||||||
('instruction', 'func2', 2),
|
("instruction", "func2", 4),
|
||||||
('instruction', 'func2', 4),
|
("instruction", "func2", 6),
|
||||||
('line', 'func2', 2),
|
("line", "func2", 2),
|
||||||
('instruction', 'func2', 6),
|
("instruction", "func2", 8),
|
||||||
('instruction', 'func2', 8),
|
("instruction", "func2", 10),
|
||||||
('instruction', 'func2', 28),
|
("instruction", "func2", 30),
|
||||||
('instruction', 'func2', 30),
|
("instruction", "func2", 32),
|
||||||
('instruction', 'func2', 38),
|
("instruction", "func2", 40),
|
||||||
('line', 'func2', 3),
|
("line", "func2", 3),
|
||||||
('instruction', 'func2', 40),
|
("instruction", "func2", 42),
|
||||||
('instruction', 'func2', 42),
|
("instruction", "func2", 44),
|
||||||
('instruction', 'func2', 44),
|
("instruction", "func2", 46),
|
||||||
('instruction', 'func2', 46),
|
("instruction", "func2", 48),
|
||||||
('line', 'get_events', 11)])
|
("line", "get_events", 11),
|
||||||
|
])
|
||||||
|
|
||||||
def test_try_except(self):
|
def test_try_except(self):
|
||||||
|
|
||||||
@@ -1272,28 +1252,29 @@ class TestLineAndInstructionEvents(CheckEvents):
|
|||||||
line = 6
|
line = 6
|
||||||
|
|
||||||
self.check_events(func3, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
self.check_events(func3, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
||||||
('line', 'get_events', 10),
|
("line", "get_events", 10),
|
||||||
('line', 'func3', 1),
|
("line", "func3", 1),
|
||||||
('instruction', 'func3', 2),
|
("instruction", "func3", 4),
|
||||||
('line', 'func3', 2),
|
("line", "func3", 2),
|
||||||
('instruction', 'func3', 4),
|
("instruction", "func3", 6),
|
||||||
('instruction', 'func3', 6),
|
("instruction", "func3", 8),
|
||||||
('line', 'func3', 3),
|
("line", "func3", 3),
|
||||||
('instruction', 'func3', 8),
|
("instruction", "func3", 10),
|
||||||
('instruction', 'func3', 18),
|
("instruction", "func3", 20),
|
||||||
('instruction', 'func3', 20),
|
("instruction", "func3", 22),
|
||||||
('line', 'func3', 4),
|
("line", "func3", 4),
|
||||||
('instruction', 'func3', 22),
|
("instruction", "func3", 24),
|
||||||
('line', 'func3', 5),
|
("line", "func3", 5),
|
||||||
('instruction', 'func3', 24),
|
("instruction", "func3", 26),
|
||||||
('instruction', 'func3', 26),
|
("instruction", "func3", 28),
|
||||||
('instruction', 'func3', 28),
|
("instruction", "func3", 30),
|
||||||
('line', 'func3', 6),
|
("line", "func3", 6),
|
||||||
('instruction', 'func3', 30),
|
("instruction", "func3", 32),
|
||||||
('instruction', 'func3', 32),
|
("instruction", "func3", 34),
|
||||||
('instruction', 'func3', 34),
|
("instruction", "func3", 36),
|
||||||
('instruction', 'func3', 36),
|
("instruction", "func3", 38),
|
||||||
('line', 'get_events', 11)])
|
("line", "get_events", 11),
|
||||||
|
])
|
||||||
|
|
||||||
def test_with_restart(self):
|
def test_with_restart(self):
|
||||||
def func1():
|
def func1():
|
||||||
@@ -1304,16 +1285,16 @@ class TestLineAndInstructionEvents(CheckEvents):
|
|||||||
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
||||||
('line', 'get_events', 10),
|
('line', 'get_events', 10),
|
||||||
('line', 'func1', 1),
|
('line', 'func1', 1),
|
||||||
('instruction', 'func1', 2),
|
|
||||||
('instruction', 'func1', 4),
|
('instruction', 'func1', 4),
|
||||||
('line', 'func1', 2),
|
|
||||||
('instruction', 'func1', 6),
|
('instruction', 'func1', 6),
|
||||||
|
('line', 'func1', 2),
|
||||||
('instruction', 'func1', 8),
|
('instruction', 'func1', 8),
|
||||||
('line', 'func1', 3),
|
|
||||||
('instruction', 'func1', 10),
|
('instruction', 'func1', 10),
|
||||||
|
('line', 'func1', 3),
|
||||||
('instruction', 'func1', 12),
|
('instruction', 'func1', 12),
|
||||||
('instruction', 'func1', 14),
|
('instruction', 'func1', 14),
|
||||||
('instruction', 'func1', 16),
|
('instruction', 'func1', 16),
|
||||||
|
('instruction', 'func1', 18),
|
||||||
('line', 'get_events', 11)])
|
('line', 'get_events', 11)])
|
||||||
|
|
||||||
sys.monitoring.restart_events()
|
sys.monitoring.restart_events()
|
||||||
@@ -1321,16 +1302,16 @@ class TestLineAndInstructionEvents(CheckEvents):
|
|||||||
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [
|
||||||
('line', 'get_events', 10),
|
('line', 'get_events', 10),
|
||||||
('line', 'func1', 1),
|
('line', 'func1', 1),
|
||||||
('instruction', 'func1', 2),
|
|
||||||
('instruction', 'func1', 4),
|
('instruction', 'func1', 4),
|
||||||
('line', 'func1', 2),
|
|
||||||
('instruction', 'func1', 6),
|
('instruction', 'func1', 6),
|
||||||
|
('line', 'func1', 2),
|
||||||
('instruction', 'func1', 8),
|
('instruction', 'func1', 8),
|
||||||
('line', 'func1', 3),
|
|
||||||
('instruction', 'func1', 10),
|
('instruction', 'func1', 10),
|
||||||
|
('line', 'func1', 3),
|
||||||
('instruction', 'func1', 12),
|
('instruction', 'func1', 12),
|
||||||
('instruction', 'func1', 14),
|
('instruction', 'func1', 14),
|
||||||
('instruction', 'func1', 16),
|
('instruction', 'func1', 16),
|
||||||
|
('instruction', 'func1', 18),
|
||||||
('line', 'get_events', 11)])
|
('line', 'get_events', 11)])
|
||||||
|
|
||||||
def test_turn_off_only_instruction(self):
|
def test_turn_off_only_instruction(self):
|
||||||
@@ -1378,10 +1359,10 @@ class TestInstallIncrementally(MonitoringTestBase, unittest.TestCase):
|
|||||||
line1 = 1
|
line1 = 1
|
||||||
|
|
||||||
MUST_INCLUDE_LI = [
|
MUST_INCLUDE_LI = [
|
||||||
('instruction', 'func1', 2),
|
|
||||||
('line', 'func1', 2),
|
|
||||||
('instruction', 'func1', 4),
|
('instruction', 'func1', 4),
|
||||||
('instruction', 'func1', 6)]
|
('line', 'func1', 2),
|
||||||
|
('instruction', 'func1', 6),
|
||||||
|
('instruction', 'func1', 8)]
|
||||||
|
|
||||||
def test_line_then_instruction(self):
|
def test_line_then_instruction(self):
|
||||||
recorders = [ LineRecorder, InstructionRecorder ]
|
recorders = [ LineRecorder, InstructionRecorder ]
|
||||||
@@ -1398,11 +1379,11 @@ class TestInstallIncrementally(MonitoringTestBase, unittest.TestCase):
|
|||||||
len(())
|
len(())
|
||||||
|
|
||||||
MUST_INCLUDE_CI = [
|
MUST_INCLUDE_CI = [
|
||||||
('instruction', 'func2', 2),
|
('instruction', 'func2', 4),
|
||||||
('call', 'func2', sys.monitoring.MISSING),
|
('call', 'func2', sys.monitoring.MISSING),
|
||||||
('call', 'len', ()),
|
('call', 'len', ()),
|
||||||
('instruction', 'func2', 12),
|
('instruction', 'func2', 14),
|
||||||
('instruction', 'func2', 14)]
|
('instruction', 'func2', 16)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1486,8 +1467,334 @@ class TestLocalEvents(MonitoringTestBase, unittest.TestCase):
|
|||||||
('line', 'func3', 6)])
|
('line', 'func3', 6)])
|
||||||
|
|
||||||
def test_set_non_local_event(self):
|
def test_set_non_local_event(self):
|
||||||
|
# C_RETURN/C_RAISE are ancillary (derived) events — not settable as local
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.RAISE)
|
sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.C_RETURN)
|
||||||
|
|
||||||
|
def test_local_reraise(self):
|
||||||
|
"""RERAISE fires as a local event only for the instrumented code object."""
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
raise
|
||||||
|
|
||||||
|
events = set()
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
events.add(code.co_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE)
|
||||||
|
try:
|
||||||
|
foo()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
bar() # should NOT trigger the callback
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(events, {'foo'})
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None)
|
||||||
|
|
||||||
|
def test_local_reraise_disable(self):
|
||||||
|
"""Returning DISABLE from a RERAISE callback disables it for that code object."""
|
||||||
|
|
||||||
|
call_count = 0
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
nonlocal call_count
|
||||||
|
call_count += 1
|
||||||
|
return sys.monitoring.DISABLE
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE)
|
||||||
|
try:
|
||||||
|
foo()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(call_count, 1)
|
||||||
|
try:
|
||||||
|
foo()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(call_count, 1) # not fired again — disabled
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None)
|
||||||
|
|
||||||
|
def test_local_py_throw(self):
|
||||||
|
"""PY_THROW fires as a local event only for the instrumented code object."""
|
||||||
|
|
||||||
|
def gen_foo():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
|
||||||
|
def gen_bar():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
|
||||||
|
events = []
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
events.append(code.co_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, E.PY_THROW)
|
||||||
|
|
||||||
|
g = gen_foo()
|
||||||
|
next(g)
|
||||||
|
try:
|
||||||
|
g.throw(RuntimeError("test"))
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
h = gen_bar()
|
||||||
|
next(h)
|
||||||
|
try:
|
||||||
|
h.throw(RuntimeError("test")) # should NOT trigger the callback
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(events, ['gen_foo'])
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None)
|
||||||
|
|
||||||
|
def test_local_py_throw_disable(self):
|
||||||
|
"""Returning DISABLE from a PY_THROW callback disables it for that code object."""
|
||||||
|
|
||||||
|
call_count = 0
|
||||||
|
|
||||||
|
def gen_foo():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
nonlocal call_count
|
||||||
|
call_count += 1
|
||||||
|
return sys.monitoring.DISABLE
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, E.PY_THROW)
|
||||||
|
|
||||||
|
g = gen_foo()
|
||||||
|
next(g)
|
||||||
|
try:
|
||||||
|
g.throw(RuntimeError("test"))
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(call_count, 1)
|
||||||
|
|
||||||
|
g2 = gen_foo()
|
||||||
|
next(g2)
|
||||||
|
try:
|
||||||
|
g2.throw(RuntimeError("test"))
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(call_count, 1) # not fired again — disabled
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None)
|
||||||
|
|
||||||
|
def test_local_raise(self):
|
||||||
|
"""RAISE fires as a local event only for the instrumented code object."""
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
events = []
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
events.append(code.co_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE)
|
||||||
|
foo()
|
||||||
|
bar() # should NOT trigger the callback
|
||||||
|
self.assertEqual(events, ['foo'])
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None)
|
||||||
|
|
||||||
|
def test_local_raise_disable(self):
|
||||||
|
"""Returning DISABLE from a RAISE callback disables it for that code object."""
|
||||||
|
|
||||||
|
call_count = 0
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
nonlocal call_count
|
||||||
|
call_count += 1
|
||||||
|
return sys.monitoring.DISABLE
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE)
|
||||||
|
foo()
|
||||||
|
self.assertEqual(call_count, 1)
|
||||||
|
foo()
|
||||||
|
self.assertEqual(call_count, 1) # not fired again — disabled
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None)
|
||||||
|
|
||||||
|
def test_local_exception_handled(self):
|
||||||
|
"""EXCEPTION_HANDLED fires as a local event only for the instrumented code object."""
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
events = []
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
events.append(code.co_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.EXCEPTION_HANDLED)
|
||||||
|
foo()
|
||||||
|
bar() # should NOT trigger the callback
|
||||||
|
self.assertEqual(events, ['foo'])
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, None)
|
||||||
|
|
||||||
|
def test_local_exception_handled_disable(self):
|
||||||
|
"""Returning DISABLE from an EXCEPTION_HANDLED callback disables it for that code object."""
|
||||||
|
|
||||||
|
call_count = 0
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
try:
|
||||||
|
raise RuntimeError("test")
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
nonlocal call_count
|
||||||
|
call_count += 1
|
||||||
|
return sys.monitoring.DISABLE
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.EXCEPTION_HANDLED)
|
||||||
|
foo()
|
||||||
|
self.assertEqual(call_count, 1)
|
||||||
|
foo()
|
||||||
|
self.assertEqual(call_count, 1) # not fired again — disabled
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, None)
|
||||||
|
|
||||||
|
def test_local_py_unwind(self):
|
||||||
|
"""PY_UNWIND fires as a local event only for the instrumented code object."""
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
raise RuntimeError("test")
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
raise RuntimeError("test")
|
||||||
|
|
||||||
|
events = []
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
events.append(code.co_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.PY_UNWIND)
|
||||||
|
|
||||||
|
try:
|
||||||
|
foo()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
bar() # should NOT trigger the callback
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(events, ['foo'])
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None)
|
||||||
|
|
||||||
|
def test_local_py_unwind_disable(self):
|
||||||
|
"""Returning DISABLE from a PY_UNWIND callback disables it for that code object."""
|
||||||
|
|
||||||
|
call_count = 0
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
raise RuntimeError("test")
|
||||||
|
|
||||||
|
def callback(code, offset, exc):
|
||||||
|
nonlocal call_count
|
||||||
|
call_count += 1
|
||||||
|
return sys.monitoring.DISABLE
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback)
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.PY_UNWIND)
|
||||||
|
|
||||||
|
try:
|
||||||
|
foo()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(call_count, 1) # fired once
|
||||||
|
|
||||||
|
try:
|
||||||
|
foo()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(call_count, 1) # not fired again — disabled by DISABLE return
|
||||||
|
|
||||||
|
finally:
|
||||||
|
sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
|
||||||
|
sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None)
|
||||||
|
|
||||||
def line_from_offset(code, offset):
|
def line_from_offset(code, offset):
|
||||||
for start, end, line in code.co_lines():
|
for start, end, line in code.co_lines():
|
||||||
@@ -1617,11 +1924,11 @@ class TestBranchAndJumpEvents(CheckEvents):
|
|||||||
('branch right', 'whilefunc', 1, 3)])
|
('branch right', 'whilefunc', 1, 3)])
|
||||||
|
|
||||||
self.check_events(func, recorders = BRANCH_OFFSET_RECORDERS, expected = [
|
self.check_events(func, recorders = BRANCH_OFFSET_RECORDERS, expected = [
|
||||||
('branch left', 'func', 28, 32),
|
('branch left', 'func', 32, 36),
|
||||||
('branch right', 'func', 44, 58),
|
('branch right', 'func', 48, 62),
|
||||||
('branch left', 'func', 28, 32),
|
('branch left', 'func', 32, 36),
|
||||||
('branch left', 'func', 44, 50),
|
('branch left', 'func', 48, 54),
|
||||||
('branch right', 'func', 28, 70)])
|
('branch right', 'func', 32, 74)])
|
||||||
|
|
||||||
def test_except_star(self):
|
def test_except_star(self):
|
||||||
|
|
||||||
@@ -1648,8 +1955,8 @@ class TestBranchAndJumpEvents(CheckEvents):
|
|||||||
('branch', 'func', 4, 4),
|
('branch', 'func', 4, 4),
|
||||||
('line', 'func', 5),
|
('line', 'func', 5),
|
||||||
('line', 'meth', 1),
|
('line', 'meth', 1),
|
||||||
('jump', 'func', 5, '[offset=120]'),
|
('jump', 'func', 5, '[offset=122]'),
|
||||||
('branch', 'func', '[offset=124]', '[offset=130]'),
|
('branch', 'func', '[offset=126]', '[offset=132]'),
|
||||||
('line', 'get_events', 11)])
|
('line', 'get_events', 11)])
|
||||||
|
|
||||||
self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [
|
self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [
|
||||||
@@ -1663,8 +1970,8 @@ class TestBranchAndJumpEvents(CheckEvents):
|
|||||||
('line', 'func', 5),
|
('line', 'func', 5),
|
||||||
('line', 'meth', 1),
|
('line', 'meth', 1),
|
||||||
('return', 'meth', None),
|
('return', 'meth', None),
|
||||||
('jump', 'func', 5, '[offset=120]'),
|
('jump', 'func', 5, '[offset=122]'),
|
||||||
('branch', 'func', '[offset=124]', '[offset=130]'),
|
('branch', 'func', '[offset=126]', '[offset=132]'),
|
||||||
('return', 'func', None),
|
('return', 'func', None),
|
||||||
('line', 'get_events', 11)])
|
('line', 'get_events', 11)])
|
||||||
|
|
||||||
@@ -1676,8 +1983,8 @@ class TestBranchAndJumpEvents(CheckEvents):
|
|||||||
n += 1
|
n += 1
|
||||||
return None
|
return None
|
||||||
|
|
||||||
in_loop = ('branch left', 'foo', 10, 16)
|
in_loop = ('branch left', 'foo', 12, 18)
|
||||||
exit_loop = ('branch right', 'foo', 10, 40)
|
exit_loop = ('branch right', 'foo', 12, 42)
|
||||||
self.check_events(foo, recorders = BRANCH_OFFSET_RECORDERS, expected = [
|
self.check_events(foo, recorders = BRANCH_OFFSET_RECORDERS, expected = [
|
||||||
in_loop,
|
in_loop,
|
||||||
in_loop,
|
in_loop,
|
||||||
@@ -2150,8 +2457,8 @@ class TestRegressions(MonitoringTestBase, unittest.TestCase):
|
|||||||
finally:
|
finally:
|
||||||
sys.monitoring.set_events(TEST_TOOL, 0)
|
sys.monitoring.set_events(TEST_TOOL, 0)
|
||||||
|
|
||||||
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
|
|
||||||
def test_108390(self):
|
def test_108390(self):
|
||||||
|
_testinternalcapi = import_helper.import_module("_testinternalcapi")
|
||||||
|
|
||||||
class Foo:
|
class Foo:
|
||||||
def __init__(self, set_event):
|
def __init__(self, set_event):
|
||||||
@@ -2209,6 +2516,21 @@ class TestRegressions(MonitoringTestBase, unittest.TestCase):
|
|||||||
sys.monitoring.restart_events()
|
sys.monitoring.restart_events()
|
||||||
sys.monitoring.set_events(0, 0)
|
sys.monitoring.set_events(0, 0)
|
||||||
|
|
||||||
|
def test_134879(self):
|
||||||
|
# gh-134789
|
||||||
|
# Specialized FOR_ITER not incrementing index
|
||||||
|
def foo():
|
||||||
|
t = 0
|
||||||
|
for i in [1,2,3,4]:
|
||||||
|
t += i
|
||||||
|
self.assertEqual(t, 10)
|
||||||
|
|
||||||
|
sys.monitoring.use_tool_id(0, "test")
|
||||||
|
self.addCleanup(sys.monitoring.free_tool_id, 0)
|
||||||
|
sys.monitoring.set_local_events(0, foo.__code__, E.BRANCH_LEFT | E.BRANCH_RIGHT)
|
||||||
|
foo()
|
||||||
|
sys.monitoring.set_local_events(0, foo.__code__, 0)
|
||||||
|
|
||||||
|
|
||||||
class TestOptimizer(MonitoringTestBase, unittest.TestCase):
|
class TestOptimizer(MonitoringTestBase, unittest.TestCase):
|
||||||
|
|
||||||
@@ -2227,8 +2549,9 @@ class TestOptimizer(MonitoringTestBase, unittest.TestCase):
|
|||||||
|
|
||||||
class TestTier2Optimizer(CheckEvents):
|
class TestTier2Optimizer(CheckEvents):
|
||||||
|
|
||||||
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
|
|
||||||
def test_monitoring_already_opimized_loop(self):
|
def test_monitoring_already_opimized_loop(self):
|
||||||
|
_testinternalcapi = import_helper.import_module("_testinternalcapi")
|
||||||
|
|
||||||
def test_func(recorder):
|
def test_func(recorder):
|
||||||
set_events = sys.monitoring.set_events
|
set_events = sys.monitoring.set_events
|
||||||
line = E.LINE
|
line = E.LINE
|
||||||
@@ -2262,23 +2585,24 @@ class TestMonitoringAtShutdown(unittest.TestCase):
|
|||||||
script_helper.run_test_script(script)
|
script_helper.run_test_script(script)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(_testcapi, "requires _testcapi")
|
|
||||||
class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
|
class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
|
||||||
|
|
||||||
class Scope:
|
class Scope:
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
|
self._testcapi = import_helper.import_module("_testcapi")
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
_testcapi.monitoring_enter_scope(*self.args)
|
self._testcapi.monitoring_enter_scope(*self.args)
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
_testcapi.monitoring_exit_scope()
|
self._testcapi.monitoring_exit_scope()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCApiEventGeneration, self).setUp()
|
super(TestCApiEventGeneration, self).setUp()
|
||||||
|
|
||||||
capi = _testcapi
|
self._testcapi = import_helper.import_module("_testcapi")
|
||||||
|
capi = self._testcapi
|
||||||
|
|
||||||
self.codelike = capi.CodeLike(2)
|
self.codelike = capi.CodeLike(2)
|
||||||
|
|
||||||
@@ -2347,7 +2671,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
|
|||||||
def test_fire_event(self):
|
def test_fire_event(self):
|
||||||
for expected, event, function, *args in self.cases:
|
for expected, event, function, *args in self.cases:
|
||||||
offset = 0
|
offset = 0
|
||||||
self.codelike = _testcapi.CodeLike(1)
|
self.codelike = self._testcapi.CodeLike(1)
|
||||||
with self.subTest(function.__name__):
|
with self.subTest(function.__name__):
|
||||||
args_ = (self.codelike, offset) + tuple(args)
|
args_ = (self.codelike, offset) + tuple(args)
|
||||||
self.check_event_count(event, function, args_, expected)
|
self.check_event_count(event, function, args_, expected)
|
||||||
@@ -2358,7 +2682,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
|
|||||||
continue
|
continue
|
||||||
assert args and isinstance(args[-1], BaseException)
|
assert args and isinstance(args[-1], BaseException)
|
||||||
offset = 0
|
offset = 0
|
||||||
self.codelike = _testcapi.CodeLike(1)
|
self.codelike = self._testcapi.CodeLike(1)
|
||||||
with self.subTest(function.__name__):
|
with self.subTest(function.__name__):
|
||||||
args_ = (self.codelike, offset) + tuple(args[:-1]) + (None,)
|
args_ = (self.codelike, offset) + tuple(args[:-1]) + (None,)
|
||||||
evt = int(math.log2(event))
|
evt = int(math.log2(event))
|
||||||
@@ -2368,7 +2692,7 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
|
|||||||
def test_fire_event_failing_callback(self):
|
def test_fire_event_failing_callback(self):
|
||||||
for expected, event, function, *args in self.cases:
|
for expected, event, function, *args in self.cases:
|
||||||
offset = 0
|
offset = 0
|
||||||
self.codelike = _testcapi.CodeLike(1)
|
self.codelike = self._testcapi.CodeLike(1)
|
||||||
with self.subTest(function.__name__):
|
with self.subTest(function.__name__):
|
||||||
args_ = (self.codelike, offset) + tuple(args)
|
args_ = (self.codelike, offset) + tuple(args)
|
||||||
exc = OSError(42)
|
exc = OSError(42)
|
||||||
@@ -2418,12 +2742,14 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
|
|||||||
def test_disable_event(self):
|
def test_disable_event(self):
|
||||||
for expected, event, function, *args in self.cases:
|
for expected, event, function, *args in self.cases:
|
||||||
offset = 0
|
offset = 0
|
||||||
self.codelike = _testcapi.CodeLike(2)
|
self.codelike = self._testcapi.CodeLike(2)
|
||||||
with self.subTest(function.__name__):
|
with self.subTest(function.__name__):
|
||||||
args_ = (self.codelike, 0) + tuple(args)
|
args_ = (self.codelike, 0) + tuple(args)
|
||||||
self.check_disable(event, function, args_, expected)
|
self.check_disable(event, function, args_, expected)
|
||||||
|
|
||||||
def test_enter_scope_two_events(self):
|
def test_enter_scope_two_events(self):
|
||||||
|
_testcapi = self._testcapi
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield_counter = CounterWithDisable()
|
yield_counter = CounterWithDisable()
|
||||||
unwind_counter = CounterWithDisable()
|
unwind_counter = CounterWithDisable()
|
||||||
|
|||||||
Vendored
+312
-60
@@ -13,7 +13,7 @@ except ImportError:
|
|||||||
|
|
||||||
from test import support
|
from test import support
|
||||||
from test.support.bytecode_helper import (
|
from test.support.bytecode_helper import (
|
||||||
BytecodeTestCase, CfgOptimizationTestCase, CompilationStepTestCase)
|
BytecodeTestCase, CfgOptimizationTestCase)
|
||||||
|
|
||||||
|
|
||||||
def compile_pattern_with_fast_locals(pattern):
|
def compile_pattern_with_fast_locals(pattern):
|
||||||
@@ -106,7 +106,7 @@ class TestTranforms(BytecodeTestCase):
|
|||||||
self.check_lnotab(code)
|
self.check_lnotab(code)
|
||||||
|
|
||||||
def test_global_as_constant(self):
|
def test_global_as_constant(self):
|
||||||
# LOAD_GLOBAL None/True/False --> LOAD_CONST None/True/False
|
# LOAD_GLOBAL None/True/False --> LOAD_COMMON_CONSTANT None/True/False
|
||||||
def f():
|
def f():
|
||||||
x = None
|
x = None
|
||||||
x = None
|
x = None
|
||||||
@@ -121,7 +121,7 @@ class TestTranforms(BytecodeTestCase):
|
|||||||
for func, elem in ((f, None), (g, True), (h, False)):
|
for func, elem in ((f, None), (g, True), (h, False)):
|
||||||
with self.subTest(func=func):
|
with self.subTest(func=func):
|
||||||
self.assertNotInBytecode(func, 'LOAD_GLOBAL')
|
self.assertNotInBytecode(func, 'LOAD_GLOBAL')
|
||||||
self.assertInBytecode(func, 'LOAD_CONST', elem)
|
self.assertInBytecode(func, 'LOAD_COMMON_CONSTANT', elem)
|
||||||
self.check_lnotab(func)
|
self.check_lnotab(func)
|
||||||
|
|
||||||
def f():
|
def f():
|
||||||
@@ -129,7 +129,7 @@ class TestTranforms(BytecodeTestCase):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
self.assertNotInBytecode(f, 'LOAD_GLOBAL')
|
self.assertNotInBytecode(f, 'LOAD_GLOBAL')
|
||||||
self.assertInBytecode(f, 'LOAD_CONST', None)
|
self.assertInBytecode(f, 'LOAD_COMMON_CONSTANT', None)
|
||||||
self.check_lnotab(f)
|
self.check_lnotab(f)
|
||||||
|
|
||||||
def test_while_one(self):
|
def test_while_one(self):
|
||||||
@@ -146,12 +146,13 @@ class TestTranforms(BytecodeTestCase):
|
|||||||
|
|
||||||
def test_pack_unpack(self):
|
def test_pack_unpack(self):
|
||||||
for line, elem in (
|
for line, elem in (
|
||||||
('a, = a,', 'LOAD_CONST',),
|
('a, = a,', None),
|
||||||
('a, b = a, b', 'SWAP',),
|
('a, b = a, b', 'SWAP',),
|
||||||
('a, b, c = a, b, c', 'SWAP',),
|
('a, b, c = a, b, c', 'SWAP',),
|
||||||
):
|
):
|
||||||
with self.subTest(line=line):
|
with self.subTest(line=line):
|
||||||
code = compile(line,'','single')
|
code = compile(line,'','single')
|
||||||
|
if elem is not None:
|
||||||
self.assertInBytecode(code, elem)
|
self.assertInBytecode(code, elem)
|
||||||
self.assertNotInBytecode(code, 'BUILD_TUPLE')
|
self.assertNotInBytecode(code, 'BUILD_TUPLE')
|
||||||
self.assertNotInBytecode(code, 'UNPACK_SEQUENCE')
|
self.assertNotInBytecode(code, 'UNPACK_SEQUENCE')
|
||||||
@@ -174,10 +175,10 @@ class TestTranforms(BytecodeTestCase):
|
|||||||
# Long tuples should be folded too.
|
# Long tuples should be folded too.
|
||||||
code = compile(repr(tuple(range(10000))),'','single')
|
code = compile(repr(tuple(range(10000))),'','single')
|
||||||
self.assertNotInBytecode(code, 'BUILD_TUPLE')
|
self.assertNotInBytecode(code, 'BUILD_TUPLE')
|
||||||
# One LOAD_CONST for the tuple, one for the None return value
|
# One LOAD_CONST for the tuple; None return value uses LOAD_COMMON_CONSTANT
|
||||||
load_consts = [instr for instr in dis.get_instructions(code)
|
load_consts = [instr for instr in dis.get_instructions(code)
|
||||||
if instr.opname == 'LOAD_CONST']
|
if instr.opname == 'LOAD_CONST']
|
||||||
self.assertEqual(len(load_consts), 2)
|
self.assertEqual(len(load_consts), 1)
|
||||||
self.check_lnotab(code)
|
self.check_lnotab(code)
|
||||||
|
|
||||||
# Bug 1053819: Tuple of constants misidentified when presented with:
|
# Bug 1053819: Tuple of constants misidentified when presented with:
|
||||||
@@ -283,11 +284,11 @@ class TestTranforms(BytecodeTestCase):
|
|||||||
('-0.0', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
|
('-0.0', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
|
||||||
('-(1.0-1.0)', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
|
('-(1.0-1.0)', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
|
||||||
('-0.5', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.5),
|
('-0.5', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.5),
|
||||||
('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -1),
|
('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_COMMON_CONSTANT', -1),
|
||||||
('---""', 'UNARY_NEGATIVE', None, False, None, None),
|
('---""', 'UNARY_NEGATIVE', None, False, None, None),
|
||||||
('~~~1', 'UNARY_INVERT', None, True, 'LOAD_CONST', -2),
|
('~~~1', 'UNARY_INVERT', None, True, 'LOAD_CONST', -2),
|
||||||
('~~~""', 'UNARY_INVERT', None, False, None, None),
|
('~~~""', 'UNARY_INVERT', None, False, None, None),
|
||||||
('not not True', 'UNARY_NOT', None, True, 'LOAD_CONST', True),
|
('not not True', 'UNARY_NOT', None, True, 'LOAD_COMMON_CONSTANT', True),
|
||||||
('not not x', 'UNARY_NOT', None, True, 'LOAD_NAME', 'x'), # this should be optimized regardless of constant or not
|
('not not x', 'UNARY_NOT', None, True, 'LOAD_NAME', 'x'), # this should be optimized regardless of constant or not
|
||||||
('+++1', 'CALL_INTRINSIC_1', intrinsic_positive, True, 'LOAD_SMALL_INT', 1),
|
('+++1', 'CALL_INTRINSIC_1', intrinsic_positive, True, 'LOAD_SMALL_INT', 1),
|
||||||
('---x', 'UNARY_NEGATIVE', None, False, None, None),
|
('---x', 'UNARY_NEGATIVE', None, False, None, None),
|
||||||
@@ -326,7 +327,7 @@ class TestTranforms(BytecodeTestCase):
|
|||||||
('1 + 2', 'NB_ADD', True, 'LOAD_SMALL_INT', 3),
|
('1 + 2', 'NB_ADD', True, 'LOAD_SMALL_INT', 3),
|
||||||
('1 + 2 + 3', 'NB_ADD', True, 'LOAD_SMALL_INT', 6),
|
('1 + 2 + 3', 'NB_ADD', True, 'LOAD_SMALL_INT', 6),
|
||||||
('1 + ""', 'NB_ADD', False, None, None),
|
('1 + ""', 'NB_ADD', False, None, None),
|
||||||
('1 - 2', 'NB_SUBTRACT', True, 'LOAD_CONST', -1),
|
('1 - 2', 'NB_SUBTRACT', True, 'LOAD_COMMON_CONSTANT', -1),
|
||||||
('1 - 2 - 3', 'NB_SUBTRACT', True, 'LOAD_CONST', -4),
|
('1 - 2 - 3', 'NB_SUBTRACT', True, 'LOAD_CONST', -4),
|
||||||
('1 - ""', 'NB_SUBTRACT', False, None, None),
|
('1 - ""', 'NB_SUBTRACT', False, None, None),
|
||||||
('2 * 2', 'NB_MULTIPLY', True, 'LOAD_SMALL_INT', 4),
|
('2 * 2', 'NB_MULTIPLY', True, 'LOAD_SMALL_INT', 4),
|
||||||
@@ -734,23 +735,27 @@ class TestTranforms(BytecodeTestCase):
|
|||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
'not all arguments converted during string formatting'):
|
'not all arguments converted during string formatting'):
|
||||||
eval("'%s' % (x, y)", {'x': 1, 'y': 2})
|
eval("'%s' % (x, y)", {'x': 1, 'y': 2})
|
||||||
with self.assertRaisesRegex(ValueError, 'incomplete format'):
|
with self.assertRaisesRegex(ValueError, 'stray % at position 2'):
|
||||||
eval("'%s%' % (x,)", {'x': 1234})
|
eval("'%s%' % (x,)", {'x': 1234})
|
||||||
with self.assertRaisesRegex(ValueError, 'incomplete format'):
|
with self.assertRaisesRegex(ValueError, 'stray % at position 4'):
|
||||||
eval("'%s%%%' % (x,)", {'x': 1234})
|
eval("'%s%%%' % (x,)", {'x': 1234})
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
'not enough arguments for format string'):
|
'not enough arguments for format string'):
|
||||||
eval("'%s%z' % (x,)", {'x': 1234})
|
eval("'%s%z' % (x,)", {'x': 1234})
|
||||||
with self.assertRaisesRegex(ValueError, 'unsupported format character'):
|
with self.assertRaisesRegex(ValueError,
|
||||||
|
'unsupported format %z at position 2'):
|
||||||
eval("'%s%z' % (x, 5)", {'x': 1234})
|
eval("'%s%z' % (x, 5)", {'x': 1234})
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
'format argument 1: %d requires a real number, not str'):
|
'format argument 1: %d requires a real number, not str'):
|
||||||
eval("'%d' % (x,)", {'x': '1234'})
|
eval("'%d' % (x,)", {'x': '1234'})
|
||||||
with self.assertRaisesRegex(TypeError, 'an integer is required, not float'):
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
'format argument 1: %x requires an integer, not float'):
|
||||||
eval("'%x' % (x,)", {'x': 1234.56})
|
eval("'%x' % (x,)", {'x': 1234.56})
|
||||||
with self.assertRaisesRegex(TypeError, 'an integer is required, not str'):
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
'format argument 1: %x requires an integer, not str'):
|
||||||
eval("'%x' % (x,)", {'x': '1234'})
|
eval("'%x' % (x,)", {'x': '1234'})
|
||||||
with self.assertRaisesRegex(TypeError, 'must be real number, not str'):
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
'format argument 1: %f requires a real number, not str'):
|
||||||
eval("'%f' % (x,)", {'x': '1234'})
|
eval("'%f' % (x,)", {'x': '1234'})
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
'not enough arguments for format string'):
|
'not enough arguments for format string'):
|
||||||
@@ -1118,6 +1123,13 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
|
|||||||
self.assertInBytecode(f, "LOAD_FAST_BORROW")
|
self.assertInBytecode(f, "LOAD_FAST_BORROW")
|
||||||
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
|
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
|
||||||
|
|
||||||
|
def test_import_from_doesnt_clobber_load_fast_borrow(self):
|
||||||
|
def f(self):
|
||||||
|
if x: pass
|
||||||
|
self.x
|
||||||
|
from shutil import ExecError
|
||||||
|
print(ExecError)
|
||||||
|
self.assertInBytecode(f, "LOAD_FAST_BORROW", "self")
|
||||||
|
|
||||||
class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
||||||
|
|
||||||
@@ -1507,7 +1519,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_SMALL_INT', 1, 0),
|
('LOAD_SMALL_INT', 1, 0),
|
||||||
('LOAD_SMALL_INT', 2, 0),
|
('LOAD_SMALL_INT', 2, 0),
|
||||||
('BUILD_LIST', 2, 0),
|
('BUILD_LIST', 2, 0),
|
||||||
('GET_ITER', None, 0),
|
('GET_ITER', 0, 0),
|
||||||
start := self.Label(),
|
start := self.Label(),
|
||||||
('FOR_ITER', end := self.Label(), 0),
|
('FOR_ITER', end := self.Label(), 0),
|
||||||
('STORE_FAST', 0, 0),
|
('STORE_FAST', 0, 0),
|
||||||
@@ -1520,7 +1532,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
]
|
]
|
||||||
after = [
|
after = [
|
||||||
('LOAD_CONST', 1, 0),
|
('LOAD_CONST', 1, 0),
|
||||||
('GET_ITER', None, 0),
|
('GET_ITER', 0, 0),
|
||||||
start := self.Label(),
|
start := self.Label(),
|
||||||
('FOR_ITER', end := self.Label(), 0),
|
('FOR_ITER', end := self.Label(), 0),
|
||||||
('STORE_FAST', 0, 0),
|
('STORE_FAST', 0, 0),
|
||||||
@@ -1528,7 +1540,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
end,
|
end,
|
||||||
('END_FOR', None, 0),
|
('END_FOR', None, 0),
|
||||||
('POP_ITER', None, 0),
|
('POP_ITER', None, 0),
|
||||||
('LOAD_CONST', 0, 0),
|
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
|
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
|
||||||
@@ -1538,7 +1550,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_SMALL_INT', 1, 0),
|
('LOAD_SMALL_INT', 1, 0),
|
||||||
('LOAD_NAME', 0, 0),
|
('LOAD_NAME', 0, 0),
|
||||||
('BUILD_LIST', 2, 0),
|
('BUILD_LIST', 2, 0),
|
||||||
('GET_ITER', None, 0),
|
('GET_ITER', 0, 0),
|
||||||
start := self.Label(),
|
start := self.Label(),
|
||||||
('FOR_ITER', end := self.Label(), 0),
|
('FOR_ITER', end := self.Label(), 0),
|
||||||
('STORE_FAST', 0, 0),
|
('STORE_FAST', 0, 0),
|
||||||
@@ -1553,7 +1565,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_SMALL_INT', 1, 0),
|
('LOAD_SMALL_INT', 1, 0),
|
||||||
('LOAD_NAME', 0, 0),
|
('LOAD_NAME', 0, 0),
|
||||||
('BUILD_TUPLE', 2, 0),
|
('BUILD_TUPLE', 2, 0),
|
||||||
('GET_ITER', None, 0),
|
('GET_ITER', 0, 0),
|
||||||
start := self.Label(),
|
start := self.Label(),
|
||||||
('FOR_ITER', end := self.Label(), 0),
|
('FOR_ITER', end := self.Label(), 0),
|
||||||
('STORE_FAST', 0, 0),
|
('STORE_FAST', 0, 0),
|
||||||
@@ -1561,7 +1573,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
end,
|
end,
|
||||||
('END_FOR', None, 0),
|
('END_FOR', None, 0),
|
||||||
('POP_ITER', None, 0),
|
('POP_ITER', None, 0),
|
||||||
('LOAD_CONST', 0, 0),
|
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
|
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
|
||||||
@@ -1572,7 +1584,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_SMALL_INT', 1, 0),
|
('LOAD_SMALL_INT', 1, 0),
|
||||||
('LOAD_SMALL_INT', 2, 0),
|
('LOAD_SMALL_INT', 2, 0),
|
||||||
('BUILD_SET', 2, 0),
|
('BUILD_SET', 2, 0),
|
||||||
('GET_ITER', None, 0),
|
('GET_ITER', 0, 0),
|
||||||
start := self.Label(),
|
start := self.Label(),
|
||||||
('FOR_ITER', end := self.Label(), 0),
|
('FOR_ITER', end := self.Label(), 0),
|
||||||
('STORE_FAST', 0, 0),
|
('STORE_FAST', 0, 0),
|
||||||
@@ -1585,7 +1597,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
]
|
]
|
||||||
after = [
|
after = [
|
||||||
('LOAD_CONST', 1, 0),
|
('LOAD_CONST', 1, 0),
|
||||||
('GET_ITER', None, 0),
|
('GET_ITER', 0, 0),
|
||||||
start := self.Label(),
|
start := self.Label(),
|
||||||
('FOR_ITER', end := self.Label(), 0),
|
('FOR_ITER', end := self.Label(), 0),
|
||||||
('STORE_FAST', 0, 0),
|
('STORE_FAST', 0, 0),
|
||||||
@@ -1593,7 +1605,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
end,
|
end,
|
||||||
('END_FOR', None, 0),
|
('END_FOR', None, 0),
|
||||||
('POP_ITER', None, 0),
|
('POP_ITER', None, 0),
|
||||||
('LOAD_CONST', 0, 0),
|
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})])
|
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})])
|
||||||
@@ -1604,7 +1616,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_SMALL_INT', 1, 0),
|
('LOAD_SMALL_INT', 1, 0),
|
||||||
('LOAD_NAME', 0, 0),
|
('LOAD_NAME', 0, 0),
|
||||||
('BUILD_SET', 2, 0),
|
('BUILD_SET', 2, 0),
|
||||||
('GET_ITER', None, 0),
|
('GET_ITER', 0, 0),
|
||||||
start := self.Label(),
|
start := self.Label(),
|
||||||
('FOR_ITER', end := self.Label(), 0),
|
('FOR_ITER', end := self.Label(), 0),
|
||||||
('STORE_FAST', 0, 0),
|
('STORE_FAST', 0, 0),
|
||||||
@@ -1615,7 +1627,22 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_CONST', 0, 0),
|
('LOAD_CONST', 0, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None])
|
expected = [
|
||||||
|
('LOAD_SMALL_INT', 1, 0),
|
||||||
|
('LOAD_NAME', 0, 0),
|
||||||
|
('BUILD_SET', 2, 0),
|
||||||
|
('GET_ITER', 0, 0),
|
||||||
|
start := self.Label(),
|
||||||
|
('FOR_ITER', end := self.Label(), 0),
|
||||||
|
('STORE_FAST', 0, 0),
|
||||||
|
('JUMP', start, 0),
|
||||||
|
end,
|
||||||
|
('END_FOR', None, 0),
|
||||||
|
('POP_ITER', None, 0),
|
||||||
|
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||||
|
('RETURN_VALUE', None, 0),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None])
|
||||||
|
|
||||||
def test_optimize_literal_list_contains(self):
|
def test_optimize_literal_list_contains(self):
|
||||||
# x in [1, 2] ==> x in (1, 2)
|
# x in [1, 2] ==> x in (1, 2)
|
||||||
@@ -1634,7 +1661,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_CONST', 1, 0),
|
('LOAD_CONST', 1, 0),
|
||||||
('CONTAINS_OP', 0, 0),
|
('CONTAINS_OP', 0, 0),
|
||||||
('POP_TOP', None, 0),
|
('POP_TOP', None, 0),
|
||||||
('LOAD_CONST', 0, 0),
|
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
|
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
|
||||||
@@ -1657,7 +1684,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('BUILD_TUPLE', 2, 0),
|
('BUILD_TUPLE', 2, 0),
|
||||||
('CONTAINS_OP', 0, 0),
|
('CONTAINS_OP', 0, 0),
|
||||||
('POP_TOP', None, 0),
|
('POP_TOP', None, 0),
|
||||||
('LOAD_CONST', 0, 0),
|
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
|
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
|
||||||
@@ -1679,7 +1706,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_CONST', 1, 0),
|
('LOAD_CONST', 1, 0),
|
||||||
('CONTAINS_OP', 0, 0),
|
('CONTAINS_OP', 0, 0),
|
||||||
('POP_TOP', None, 0),
|
('POP_TOP', None, 0),
|
||||||
('LOAD_CONST', 0, 0),
|
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})])
|
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})])
|
||||||
@@ -1696,7 +1723,17 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('LOAD_CONST', 0, 0),
|
('LOAD_CONST', 0, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None])
|
expected = [
|
||||||
|
('LOAD_NAME', 0, 0),
|
||||||
|
('LOAD_SMALL_INT', 1, 0),
|
||||||
|
('LOAD_NAME', 1, 0),
|
||||||
|
('BUILD_SET', 2, 0),
|
||||||
|
('CONTAINS_OP', 0, 0),
|
||||||
|
('POP_TOP', None, 0),
|
||||||
|
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||||
|
('RETURN_VALUE', None, 0),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None])
|
||||||
|
|
||||||
def test_optimize_unary_not(self):
|
def test_optimize_unary_not(self):
|
||||||
# test folding
|
# test folding
|
||||||
@@ -1707,10 +1744,10 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
after = [
|
after = [
|
||||||
('LOAD_CONST', 1, 0),
|
('LOAD_COMMON_CONSTANT', 10, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False])
|
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
|
||||||
|
|
||||||
# test cancel out
|
# test cancel out
|
||||||
before = [
|
before = [
|
||||||
@@ -1758,7 +1795,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
after = [
|
after = [
|
||||||
('LOAD_CONST', 0, 0),
|
('LOAD_COMMON_CONSTANT', 9, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
|
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
|
||||||
@@ -1774,10 +1811,10 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
after = [
|
after = [
|
||||||
('LOAD_CONST', 1, 0),
|
('LOAD_COMMON_CONSTANT', 10, 0),
|
||||||
('RETURN_VALUE', None, 0),
|
('RETURN_VALUE', None, 0),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False])
|
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
|
||||||
|
|
||||||
# test cancel out & eliminate to bool (to bool stays as we are not iterating to a fixed point)
|
# test cancel out & eliminate to bool (to bool stays as we are not iterating to a fixed point)
|
||||||
before = [
|
before = [
|
||||||
@@ -2393,7 +2430,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
("LOAD_FAST", 1, 4),
|
("LOAD_FAST", 1, 4),
|
||||||
("LIST_EXTEND", 1, 5),
|
("LIST_EXTEND", 1, 5),
|
||||||
("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6),
|
("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6),
|
||||||
("GET_ITER", None, 7),
|
("GET_ITER", 0, 7),
|
||||||
top := self.Label(),
|
top := self.Label(),
|
||||||
("FOR_ITER", end := self.Label(), 8),
|
("FOR_ITER", end := self.Label(), 8),
|
||||||
("STORE_FAST", 2, 9),
|
("STORE_FAST", 2, 9),
|
||||||
@@ -2411,7 +2448,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
("LOAD_FAST_BORROW", 1, 4),
|
("LOAD_FAST_BORROW", 1, 4),
|
||||||
("LIST_EXTEND", 1, 5),
|
("LIST_EXTEND", 1, 5),
|
||||||
("NOP", None, 6), # ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6),
|
("NOP", None, 6), # ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6),
|
||||||
("GET_ITER", None, 7),
|
("GET_ITER", 0, 7),
|
||||||
top := self.Label(),
|
top := self.Label(),
|
||||||
("FOR_ITER", end := self.Label(), 8),
|
("FOR_ITER", end := self.Label(), 8),
|
||||||
("STORE_FAST", 2, 9),
|
("STORE_FAST", 2, 9),
|
||||||
@@ -2419,7 +2456,7 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
end,
|
end,
|
||||||
("END_FOR", None, 11),
|
("END_FOR", None, 11),
|
||||||
("POP_TOP", None, 12),
|
("POP_TOP", None, 12),
|
||||||
("LOAD_CONST", 0, 13),
|
("LOAD_COMMON_CONSTANT", 7, 13),
|
||||||
("RETURN_VALUE", None, 14),
|
("RETURN_VALUE", None, 14),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(insts, expected_insts, consts=[None])
|
self.cfg_optimization_test(insts, expected_insts, consts=[None])
|
||||||
@@ -2433,6 +2470,168 @@ class DirectCfgOptimizerTests(CfgOptimizationTestCase):
|
|||||||
self.assertEqual(b, [3, 2, 1, 0])
|
self.assertEqual(b, [3, 2, 1, 0])
|
||||||
self.assertEqual(items, [])
|
self.assertEqual(items, [])
|
||||||
|
|
||||||
|
def test_fold_constant_big_list_for_iter(self):
|
||||||
|
# for x in [c1, c2, ..., cN] (N > 30) should fold to LOAD_CONST tuple
|
||||||
|
consts = 35
|
||||||
|
before = (
|
||||||
|
[("BUILD_LIST", 0, 1)] +
|
||||||
|
[("LOAD_CONST", 0, 2), ("LIST_APPEND", 1, 3)] * consts +
|
||||||
|
[("GET_ITER", 0, 4),
|
||||||
|
top := self.Label(),
|
||||||
|
("FOR_ITER", end := self.Label(), 5),
|
||||||
|
("STORE_FAST", 0, 6),
|
||||||
|
("JUMP", top, 7),
|
||||||
|
end,
|
||||||
|
("END_FOR", None, 8),
|
||||||
|
("POP_ITER", None, 9),
|
||||||
|
("LOAD_CONST", 0, 10),
|
||||||
|
("RETURN_VALUE", None, 11)]
|
||||||
|
)
|
||||||
|
after = [
|
||||||
|
("LOAD_CONST", 1, 3),
|
||||||
|
("GET_ITER", 0, 4),
|
||||||
|
top := self.Label(),
|
||||||
|
("FOR_ITER", end := self.Label(), 5),
|
||||||
|
("STORE_FAST", 0, 6),
|
||||||
|
("JUMP", top, 7),
|
||||||
|
end,
|
||||||
|
("END_FOR", None, 8),
|
||||||
|
("POP_ITER", None, 9),
|
||||||
|
("LOAD_CONST", 0, 10),
|
||||||
|
("RETURN_VALUE", None, 11),
|
||||||
|
]
|
||||||
|
result_const = tuple(["test"] * consts)
|
||||||
|
self.cfg_optimization_test(before, after, consts=["test"],
|
||||||
|
expected_consts=["test", result_const])
|
||||||
|
|
||||||
|
def test_fold_constant_big_set_for_iter(self):
|
||||||
|
# for x in {c1, c2, ..., cN} (N > 30) should fold to LOAD_CONST frozenset
|
||||||
|
before = [
|
||||||
|
("BUILD_SET", 0, 1),
|
||||||
|
("LOAD_SMALL_INT", 1, 2), ("SET_ADD", 1, 3),
|
||||||
|
("LOAD_SMALL_INT", 2, 4), ("SET_ADD", 1, 5),
|
||||||
|
("LOAD_SMALL_INT", 3, 6), ("SET_ADD", 1, 7),
|
||||||
|
("GET_ITER", 0, 8),
|
||||||
|
top := self.Label(),
|
||||||
|
("FOR_ITER", end := self.Label(), 9),
|
||||||
|
("STORE_FAST", 0, 10),
|
||||||
|
("JUMP", top, 11),
|
||||||
|
end,
|
||||||
|
("END_FOR", None, 12),
|
||||||
|
("POP_ITER", None, 13),
|
||||||
|
("LOAD_CONST", 0, 14),
|
||||||
|
("RETURN_VALUE", None, 15),
|
||||||
|
]
|
||||||
|
after = [
|
||||||
|
("LOAD_CONST", 1, 7),
|
||||||
|
("GET_ITER", 0, 8),
|
||||||
|
top := self.Label(),
|
||||||
|
("FOR_ITER", end := self.Label(), 9),
|
||||||
|
("STORE_FAST", 0, 10),
|
||||||
|
("JUMP", top, 11),
|
||||||
|
end,
|
||||||
|
("END_FOR", None, 12),
|
||||||
|
("POP_ITER", None, 13),
|
||||||
|
("LOAD_CONST", 0, 14),
|
||||||
|
("RETURN_VALUE", None, 15),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(before, after, consts=["test"],
|
||||||
|
expected_consts=["test", frozenset({1, 2, 3})])
|
||||||
|
|
||||||
|
def test_fold_constant_list_to_tuple_for_iter(self):
|
||||||
|
INTRINSIC_LIST_TO_TUPLE = 6
|
||||||
|
before = [
|
||||||
|
("BUILD_LIST", 0, 1),
|
||||||
|
("LOAD_SMALL_INT", 1, 2), ("LIST_APPEND", 1, 3),
|
||||||
|
("LOAD_SMALL_INT", 2, 4), ("LIST_APPEND", 1, 5),
|
||||||
|
("LOAD_SMALL_INT", 3, 6), ("LIST_APPEND", 1, 7),
|
||||||
|
("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 8),
|
||||||
|
("GET_ITER", 0, 9),
|
||||||
|
top := self.Label(),
|
||||||
|
("FOR_ITER", end := self.Label(), 10),
|
||||||
|
("STORE_FAST", 0, 11),
|
||||||
|
("JUMP", top, 12),
|
||||||
|
end,
|
||||||
|
("END_FOR", None, 13),
|
||||||
|
("POP_ITER", None, 14),
|
||||||
|
("LOAD_CONST", 0, 15),
|
||||||
|
("RETURN_VALUE", None, 16),
|
||||||
|
]
|
||||||
|
after = [
|
||||||
|
("LOAD_CONST", 1, 8),
|
||||||
|
("GET_ITER", 0, 9),
|
||||||
|
top := self.Label(),
|
||||||
|
("FOR_ITER", end := self.Label(), 10),
|
||||||
|
("STORE_FAST", 0, 11),
|
||||||
|
("JUMP", top, 12),
|
||||||
|
end,
|
||||||
|
("END_FOR", None, 13),
|
||||||
|
("POP_ITER", None, 14),
|
||||||
|
("LOAD_CONST", 0, 15),
|
||||||
|
("RETURN_VALUE", None, 16),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(before, after, consts=["test"],
|
||||||
|
expected_consts=["test", (1, 2, 3)])
|
||||||
|
|
||||||
|
def test_fold_constant_big_list_contains_op(self):
|
||||||
|
# x in [c1, c2, ..., cN] (N > 30) should fold to LOAD_CONST tuple
|
||||||
|
before = [
|
||||||
|
("LOAD_FAST", 0, 1),
|
||||||
|
("BUILD_LIST", 0, 2),
|
||||||
|
("LOAD_SMALL_INT", 1, 3), ("LIST_APPEND", 1, 4),
|
||||||
|
("LOAD_SMALL_INT", 2, 5), ("LIST_APPEND", 1, 6),
|
||||||
|
("LOAD_SMALL_INT", 3, 7), ("LIST_APPEND", 1, 8),
|
||||||
|
("CONTAINS_OP", 0, 9),
|
||||||
|
("RETURN_VALUE", None, 10),
|
||||||
|
]
|
||||||
|
after = [
|
||||||
|
("LOAD_FAST_BORROW", 0, 1),
|
||||||
|
("LOAD_CONST", 1, 8),
|
||||||
|
("CONTAINS_OP", 0, 9),
|
||||||
|
("RETURN_VALUE", None, 10),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(before, after, consts=[None],
|
||||||
|
expected_consts=[None, (1, 2, 3)])
|
||||||
|
|
||||||
|
def test_fold_constant_big_set_contains_op(self):
|
||||||
|
# x in {c1, c2, ..., cN} (N > 30) should fold to LOAD_CONST frozenset
|
||||||
|
before = [
|
||||||
|
("LOAD_FAST", 0, 1),
|
||||||
|
("BUILD_SET", 0, 2),
|
||||||
|
("LOAD_SMALL_INT", 1, 3), ("SET_ADD", 1, 4),
|
||||||
|
("LOAD_SMALL_INT", 2, 5), ("SET_ADD", 1, 6),
|
||||||
|
("LOAD_SMALL_INT", 3, 7), ("SET_ADD", 1, 8),
|
||||||
|
("CONTAINS_OP", 0, 9),
|
||||||
|
("RETURN_VALUE", None, 10),
|
||||||
|
]
|
||||||
|
after = [
|
||||||
|
("LOAD_FAST_BORROW", 0, 1),
|
||||||
|
("LOAD_CONST", 1, 8),
|
||||||
|
("CONTAINS_OP", 0, 9),
|
||||||
|
("RETURN_VALUE", None, 10),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(before, after, consts=[None],
|
||||||
|
expected_consts=[None, frozenset({1, 2, 3})])
|
||||||
|
|
||||||
|
def test_no_fold_big_list_for_iter_with_non_const(self):
|
||||||
|
same = [
|
||||||
|
("BUILD_LIST", 0, 1),
|
||||||
|
("LOAD_SMALL_INT", 1, 2), ("LIST_APPEND", 1, 3),
|
||||||
|
("LOAD_FAST_BORROW", 0, 4), ("LIST_APPEND", 1, 5),
|
||||||
|
("LOAD_SMALL_INT", 3, 6), ("LIST_APPEND", 1, 7),
|
||||||
|
("GET_ITER", 0, 8),
|
||||||
|
top := self.Label(),
|
||||||
|
("FOR_ITER", end := self.Label(), 9),
|
||||||
|
("STORE_FAST", 1, 10),
|
||||||
|
("JUMP", top, 11),
|
||||||
|
end,
|
||||||
|
("END_FOR", None, 12),
|
||||||
|
("POP_ITER", None, 13),
|
||||||
|
("LOAD_CONST", 0, 14),
|
||||||
|
("RETURN_VALUE", None, 15),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(same, same, consts=["test"])
|
||||||
|
|
||||||
|
|
||||||
class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
||||||
def make_bb(self, insts):
|
def make_bb(self, insts):
|
||||||
@@ -2443,7 +2642,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
maxconst = max(maxconst, arg)
|
maxconst = max(maxconst, arg)
|
||||||
consts = [None for _ in range(maxconst + 1)]
|
consts = [None for _ in range(maxconst + 1)]
|
||||||
return insts + [
|
return insts + [
|
||||||
("LOAD_CONST", 0, last_loc + 1),
|
("LOAD_COMMON_CONSTANT", 7, last_loc + 1),
|
||||||
("RETURN_VALUE", None, last_loc + 2),
|
("RETURN_VALUE", None, last_loc + 2),
|
||||||
], consts
|
], consts
|
||||||
|
|
||||||
@@ -2474,7 +2673,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
]
|
]
|
||||||
expected = [
|
expected = [
|
||||||
("LOAD_FAST_BORROW", 0, 1),
|
("LOAD_FAST_BORROW", 0, 1),
|
||||||
("LOAD_CONST", 1, 2),
|
("LOAD_COMMON_CONSTANT", 7, 2),
|
||||||
("SWAP", 2, 3),
|
("SWAP", 2, 3),
|
||||||
("POP_TOP", None, 4),
|
("POP_TOP", None, 4),
|
||||||
]
|
]
|
||||||
@@ -2512,7 +2711,13 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
("STORE_FAST", 0, 3),
|
("STORE_FAST", 0, 3),
|
||||||
("POP_TOP", None, 4),
|
("POP_TOP", None, 4),
|
||||||
]
|
]
|
||||||
self.check(insts, insts)
|
expected = [
|
||||||
|
("LOAD_FAST", 0, 1),
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 2),
|
||||||
|
("STORE_FAST", 0, 3),
|
||||||
|
("POP_TOP", None, 4),
|
||||||
|
]
|
||||||
|
self.check(insts, expected)
|
||||||
|
|
||||||
insts = [
|
insts = [
|
||||||
("LOAD_FAST", 0, 1),
|
("LOAD_FAST", 0, 1),
|
||||||
@@ -2521,7 +2726,14 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
||||||
("POP_TOP", None, 5),
|
("POP_TOP", None, 5),
|
||||||
]
|
]
|
||||||
self.check(insts, insts)
|
expected = [
|
||||||
|
("LOAD_FAST", 0, 1),
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 2),
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 3),
|
||||||
|
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
||||||
|
("POP_TOP", None, 5),
|
||||||
|
]
|
||||||
|
self.check(insts, expected)
|
||||||
|
|
||||||
insts = [
|
insts = [
|
||||||
("LOAD_FAST", 0, 1),
|
("LOAD_FAST", 0, 1),
|
||||||
@@ -2542,7 +2754,12 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
("LOAD_CONST", 0, 3),
|
("LOAD_CONST", 0, 3),
|
||||||
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
||||||
]
|
]
|
||||||
self.check(insts, insts)
|
expected = [
|
||||||
|
("LOAD_FAST", 0, 1),
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 3),
|
||||||
|
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
||||||
|
]
|
||||||
|
self.check(insts, expected)
|
||||||
|
|
||||||
def test_consume_no_inputs(self):
|
def test_consume_no_inputs(self):
|
||||||
insts = [
|
insts = [
|
||||||
@@ -2587,7 +2804,19 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
("LOAD_CONST", 0, 7),
|
("LOAD_CONST", 0, 7),
|
||||||
("RETURN_VALUE", None, 8),
|
("RETURN_VALUE", None, 8),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(insts, insts, consts=[None])
|
expected = [
|
||||||
|
("LOAD_FAST", 0, 1),
|
||||||
|
top := self.Label(),
|
||||||
|
("FOR_ITER", end := self.Label(), 2),
|
||||||
|
("STORE_FAST", 2, 3),
|
||||||
|
("JUMP", top, 4),
|
||||||
|
end,
|
||||||
|
("END_FOR", None, 5),
|
||||||
|
("POP_TOP", None, 6),
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 7),
|
||||||
|
("RETURN_VALUE", None, 8),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||||
|
|
||||||
def test_load_attr(self):
|
def test_load_attr(self):
|
||||||
insts = [
|
insts = [
|
||||||
@@ -2656,10 +2885,10 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
("LOAD_FAST", 0, 1),
|
("LOAD_FAST", 0, 1),
|
||||||
("LOAD_FAST_BORROW", 1, 2),
|
("LOAD_FAST_BORROW", 1, 2),
|
||||||
("SEND", end := self.Label(), 3),
|
("SEND", end := self.Label(), 3),
|
||||||
("LOAD_CONST", 0, 4),
|
("LOAD_COMMON_CONSTANT", 7, 4),
|
||||||
("RETURN_VALUE", None, 5),
|
("RETURN_VALUE", None, 5),
|
||||||
end,
|
end,
|
||||||
("LOAD_CONST", 0, 6),
|
("LOAD_COMMON_CONSTANT", 7, 6),
|
||||||
("RETURN_VALUE", None, 7)
|
("RETURN_VALUE", None, 7)
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(insts, expected, consts=[None])
|
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||||
@@ -2697,7 +2926,15 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
("LOAD_CONST", 0, 5),
|
("LOAD_CONST", 0, 5),
|
||||||
("RETURN_VALUE", None, 6)
|
("RETURN_VALUE", None, 6)
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(insts, insts, consts=[None])
|
expected = [
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 1),
|
||||||
|
("LOAD_FAST", 0, 2),
|
||||||
|
("SET_FUNCTION_ATTRIBUTE", 2, 3),
|
||||||
|
("STORE_FAST", 1, 4),
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 5),
|
||||||
|
("RETURN_VALUE", None, 6)
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||||
|
|
||||||
insts = [
|
insts = [
|
||||||
("LOAD_CONST", 0, 1),
|
("LOAD_CONST", 0, 1),
|
||||||
@@ -2706,7 +2943,7 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
("RETURN_VALUE", None, 4)
|
("RETURN_VALUE", None, 4)
|
||||||
]
|
]
|
||||||
expected = [
|
expected = [
|
||||||
("LOAD_CONST", 0, 1),
|
("LOAD_COMMON_CONSTANT", 7, 1),
|
||||||
("LOAD_FAST_BORROW", 0, 2),
|
("LOAD_FAST_BORROW", 0, 2),
|
||||||
("SET_FUNCTION_ATTRIBUTE", 2, 3),
|
("SET_FUNCTION_ATTRIBUTE", 2, 3),
|
||||||
("RETURN_VALUE", None, 4)
|
("RETURN_VALUE", None, 4)
|
||||||
@@ -2714,22 +2951,37 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
|
|||||||
self.cfg_optimization_test(insts, expected, consts=[None])
|
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||||
|
|
||||||
def test_get_yield_from_iter(self):
|
def test_get_yield_from_iter(self):
|
||||||
# GET_YIELD_FROM_ITER may leave its operand on the stack
|
|
||||||
insts = [
|
insts = [
|
||||||
("LOAD_FAST", 0, 1),
|
("LOAD_FAST", 0, 1),
|
||||||
("GET_YIELD_FROM_ITER", None, 2),
|
("GET_ITER", 1, 2),
|
||||||
("LOAD_CONST", 0, 3),
|
("PUSH_NULL", None, 3),
|
||||||
|
("LOAD_CONST", 0, 4),
|
||||||
send := self.Label(),
|
send := self.Label(),
|
||||||
("SEND", end := self.Label(), 5),
|
("SEND", end := self.Label(), 6),
|
||||||
("YIELD_VALUE", 1, 6),
|
("YIELD_VALUE", 1, 7),
|
||||||
("RESUME", 2, 7),
|
("RESUME", 2, 8),
|
||||||
("JUMP", send, 8),
|
("JUMP", send, 9),
|
||||||
end,
|
end,
|
||||||
("END_SEND", None, 9),
|
("END_SEND", None, 10),
|
||||||
("LOAD_CONST", 0, 10),
|
("LOAD_CONST", 0, 11),
|
||||||
("RETURN_VALUE", None, 11),
|
("RETURN_VALUE", None, 12),
|
||||||
]
|
]
|
||||||
self.cfg_optimization_test(insts, insts, consts=[None])
|
expected = [
|
||||||
|
("LOAD_FAST", 0, 1),
|
||||||
|
("GET_ITER", 1, 2),
|
||||||
|
("PUSH_NULL", None, 3),
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 4),
|
||||||
|
send := self.Label(),
|
||||||
|
("SEND", end := self.Label(), 6),
|
||||||
|
("YIELD_VALUE", 1, 7),
|
||||||
|
("RESUME", 2, 8),
|
||||||
|
("JUMP", send, 9),
|
||||||
|
end,
|
||||||
|
("END_SEND", None, 10),
|
||||||
|
("LOAD_COMMON_CONSTANT", 7, 11),
|
||||||
|
("RETURN_VALUE", None, 12),
|
||||||
|
]
|
||||||
|
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||||
|
|
||||||
def test_push_exc_info(self):
|
def test_push_exc_info(self):
|
||||||
insts = [
|
insts = [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
class::PyClassImpl,
|
class::PyClassImpl,
|
||||||
frame::{FrameObject, FrameOwner},
|
frame::{FrameObject, FrameOwner},
|
||||||
function::PySetterValue,
|
function::PySetterValue,
|
||||||
types::{Destructor, Representable},
|
types::Representable,
|
||||||
};
|
};
|
||||||
use core::sync::atomic::Ordering::Relaxed;
|
use core::sync::atomic::Ordering::Relaxed;
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
@@ -515,19 +515,19 @@ impl Py<FrameObject> {
|
|||||||
);
|
);
|
||||||
match owner {
|
match owner {
|
||||||
FrameOwner::Generator => {
|
FrameOwner::Generator => {
|
||||||
// FRAME_SUSPENDED (lasti > 0) cannot be cleared. FRAME_CREATED
|
// RETURN_GENERATOR executes the creation preamble, so lasti
|
||||||
// and finished frames go through the owner finalizer.
|
// no longer distinguishes a created frame from a suspended one.
|
||||||
if self.lasti() != 0 {
|
|
||||||
return Err(vm.new_runtime_error("cannot clear a suspended frame"));
|
|
||||||
}
|
|
||||||
if let Some(owner) = self.iframe().generator.to_owned() {
|
if let Some(owner) = self.iframe().generator.to_owned() {
|
||||||
if let Some(coro) = owner.downcast_ref::<PyCoroutine>() {
|
let coro = if let Some(coro) = owner.downcast_ref::<PyCoroutine>() {
|
||||||
let _ = PyCoroutine::del(coro, vm);
|
coro.as_coro()
|
||||||
} else if let Some(async_gen) = owner.downcast_ref::<PyAsyncGen>() {
|
} else if let Some(async_gen) = owner.downcast_ref::<PyAsyncGen>() {
|
||||||
let _ = PyAsyncGen::del(async_gen, vm);
|
async_gen.as_coro()
|
||||||
} else if let Some(generator) = owner.downcast_ref::<PyGenerator>() {
|
} else if let Some(generator) = owner.downcast_ref::<PyGenerator>() {
|
||||||
let _ = PyGenerator::del(generator, vm);
|
generator.as_coro()
|
||||||
}
|
} else {
|
||||||
|
unreachable!("generator-owned frame has no coroutine owner")
|
||||||
|
};
|
||||||
|
coro.clear_frame(&owner, vm)?;
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,19 @@ impl Py<PyGenerator> {
|
|||||||
self.inner.running()
|
self.inner.running()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pygetset]
|
||||||
|
fn gi_state(&self) -> &'static str {
|
||||||
|
if self.inner.closed() {
|
||||||
|
"GEN_CLOSED"
|
||||||
|
} else if self.inner.running() {
|
||||||
|
"GEN_RUNNING"
|
||||||
|
} else if self.inner.started() {
|
||||||
|
"GEN_SUSPENDED"
|
||||||
|
} else {
|
||||||
|
"GEN_CREATED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pygetset]
|
#[pygetset]
|
||||||
fn gi_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
|
fn gi_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
|
||||||
self.inner.code()
|
self.inner.code()
|
||||||
|
|||||||
@@ -495,6 +495,32 @@ impl Coro {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear a created generator frame without resuming its body.
|
||||||
|
pub(crate) fn clear_frame(&self, jen: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
|
||||||
|
if self.closed.load() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if self.running.compare_exchange(false, true).is_err() {
|
||||||
|
return Err(vm.new_runtime_error("cannot clear an executing frame"));
|
||||||
|
}
|
||||||
|
let claim = RunningGuard(self);
|
||||||
|
if self.closed.load() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if self.started() {
|
||||||
|
return Err(vm.new_runtime_error("cannot clear a suspended frame"));
|
||||||
|
}
|
||||||
|
self.closed.store(true);
|
||||||
|
// Closed owners cannot resume. Publish that state and release the
|
||||||
|
// running claim before a warning hook can observe or clear us again.
|
||||||
|
drop(claim);
|
||||||
|
if jen.class().is(vm.ctx.types.coroutine_type) {
|
||||||
|
crate::warn::warn_unawaited_coroutine(jen, &self.qualname(), vm);
|
||||||
|
}
|
||||||
|
self.clear_except_code();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the frame has run at all, i.e. is stopped at a `yield` rather
|
/// Whether the frame has run at all, i.e. is stopped at a `yield` rather
|
||||||
/// than at its start.
|
/// than at its start.
|
||||||
pub(crate) fn started(&self) -> bool {
|
pub(crate) fn started(&self) -> bool {
|
||||||
|
|||||||
Reference in new issue
Block a user