11"""Test suite for the sys.monitoring."""
22
33import collections
4+ import dis
45import functools
56import operator
67import sys
8+ import textwrap
79import types
810import unittest
911
@@ -506,7 +508,7 @@ def test_lines_single(self):
506508 sys .monitoring .set_events (TEST_TOOL , 0 )
507509 sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
508510 start = LineMonitoringTest .test_lines_single .__code__ .co_firstlineno
509- self .assertEqual (events , [start + 7 , 14 , start + 8 ])
511+ self .assertEqual (events , [start + 7 , 16 , start + 8 ])
510512 finally :
511513 sys .monitoring .set_events (TEST_TOOL , 0 )
512514 sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
@@ -524,7 +526,7 @@ def test_lines_loop(self):
524526 sys .monitoring .set_events (TEST_TOOL , 0 )
525527 sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
526528 start = LineMonitoringTest .test_lines_loop .__code__ .co_firstlineno
527- self .assertEqual (events , [start + 7 , 21 , 22 , 21 , 22 , 21 , start + 8 ])
529+ self .assertEqual (events , [start + 7 , 23 , 24 , 23 , 24 , 23 , start + 8 ])
528530 finally :
529531 sys .monitoring .set_events (TEST_TOOL , 0 )
530532 sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
@@ -546,7 +548,7 @@ def test_lines_two(self):
546548 sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
547549 sys .monitoring .register_callback (TEST_TOOL2 , E .LINE , None )
548550 start = LineMonitoringTest .test_lines_two .__code__ .co_firstlineno
549- expected = [start + 10 , 14 , start + 11 ]
551+ expected = [start + 10 , 16 , start + 11 ]
550552 self .assertEqual (events , expected )
551553 self .assertEqual (events2 , expected )
552554 finally :
@@ -1177,6 +1179,221 @@ def func():
11771179 ('return' , None ),
11781180 ('line' , 'check_events' , 11 )])
11791181
1182+ class TestLoadSuperAttr (CheckEvents ):
1183+ RECORDERS = CallRecorder , LineRecorder , CRaiseRecorder , CReturnRecorder
1184+
1185+ def _exec (self , co ):
1186+ d = {}
1187+ exec (co , d , d )
1188+ return d
1189+
1190+ def _exec_super (self , codestr , optimized = False ):
1191+ # The compiler checks for statically visible shadowing of the name
1192+ # `super`, and declines to emit `LOAD_SUPER_ATTR` if shadowing is found.
1193+ # So inserting `super = super` prevents the compiler from emitting
1194+ # `LOAD_SUPER_ATTR`, and allows us to test that monitoring events for
1195+ # `LOAD_SUPER_ATTR` are equivalent to those we'd get from the
1196+ # un-optimized `LOAD_GLOBAL super; CALL; LOAD_ATTR` form.
1197+ assignment = "x = 1" if optimized else "super = super"
1198+ codestr = f"{ assignment } \n { textwrap .dedent (codestr )} "
1199+ co = compile (codestr , "<string>" , "exec" )
1200+ # validate that we really do have a LOAD_SUPER_ATTR, only when optimized
1201+ self .assertEqual (self ._has_load_super_attr (co ), optimized )
1202+ return self ._exec (co )
1203+
1204+ def _has_load_super_attr (self , co ):
1205+ has = any (instr .opname == "LOAD_SUPER_ATTR" for instr in dis .get_instructions (co ))
1206+ if not has :
1207+ has = any (
1208+ isinstance (c , types .CodeType ) and self ._has_load_super_attr (c )
1209+ for c in co .co_consts
1210+ )
1211+ return has
1212+
1213+ def _super_method_call (self , optimized = False ):
1214+ codestr = """
1215+ class A:
1216+ def method(self, x):
1217+ return x
1218+
1219+ class B(A):
1220+ def method(self, x):
1221+ return super(
1222+ ).method(
1223+ x
1224+ )
1225+
1226+ b = B()
1227+ def f():
1228+ return b.method(1)
1229+ """
1230+ d = self ._exec_super (codestr , optimized )
1231+ expected = [
1232+ ('line' , 'check_events' , 10 ),
1233+ ('call' , 'f' , sys .monitoring .MISSING ),
1234+ ('line' , 'f' , 1 ),
1235+ ('call' , 'method' , d ["b" ]),
1236+ ('line' , 'method' , 1 ),
1237+ ('call' , 'super' , sys .monitoring .MISSING ),
1238+ ('C return' , 'super' , sys .monitoring .MISSING ),
1239+ ('line' , 'method' , 2 ),
1240+ ('line' , 'method' , 3 ),
1241+ ('line' , 'method' , 2 ),
1242+ ('call' , 'method' , 1 ),
1243+ ('line' , 'method' , 1 ),
1244+ ('line' , 'method' , 1 ),
1245+ ('line' , 'check_events' , 11 ),
1246+ ('call' , 'set_events' , 2 ),
1247+ ]
1248+ return d ["f" ], expected
1249+
1250+ def test_method_call (self ):
1251+ nonopt_func , nonopt_expected = self ._super_method_call (optimized = False )
1252+ opt_func , opt_expected = self ._super_method_call (optimized = True )
1253+
1254+ self .check_events (nonopt_func , recorders = self .RECORDERS , expected = nonopt_expected )
1255+ self .check_events (opt_func , recorders = self .RECORDERS , expected = opt_expected )
1256+
1257+ def _super_method_call_error (self , optimized = False ):
1258+ codestr = """
1259+ class A:
1260+ def method(self, x):
1261+ return x
1262+
1263+ class B(A):
1264+ def method(self, x):
1265+ return super(
1266+ x,
1267+ self,
1268+ ).method(
1269+ x
1270+ )
1271+
1272+ b = B()
1273+ def f():
1274+ try:
1275+ return b.method(1)
1276+ except TypeError:
1277+ pass
1278+ else:
1279+ assert False, "should have raised TypeError"
1280+ """
1281+ d = self ._exec_super (codestr , optimized )
1282+ expected = [
1283+ ('line' , 'check_events' , 10 ),
1284+ ('call' , 'f' , sys .monitoring .MISSING ),
1285+ ('line' , 'f' , 1 ),
1286+ ('line' , 'f' , 2 ),
1287+ ('call' , 'method' , d ["b" ]),
1288+ ('line' , 'method' , 1 ),
1289+ ('line' , 'method' , 2 ),
1290+ ('line' , 'method' , 3 ),
1291+ ('line' , 'method' , 1 ),
1292+ ('call' , 'super' , 1 ),
1293+ ('C raise' , 'super' , 1 ),
1294+ ('line' , 'f' , 3 ),
1295+ ('line' , 'f' , 4 ),
1296+ ('line' , 'check_events' , 11 ),
1297+ ('call' , 'set_events' , 2 ),
1298+ ]
1299+ return d ["f" ], expected
1300+
1301+ def test_method_call_error (self ):
1302+ nonopt_func , nonopt_expected = self ._super_method_call_error (optimized = False )
1303+ opt_func , opt_expected = self ._super_method_call_error (optimized = True )
1304+
1305+ self .check_events (nonopt_func , recorders = self .RECORDERS , expected = nonopt_expected )
1306+ self .check_events (opt_func , recorders = self .RECORDERS , expected = opt_expected )
1307+
1308+ def _super_attr (self , optimized = False ):
1309+ codestr = """
1310+ class A:
1311+ x = 1
1312+
1313+ class B(A):
1314+ def method(self):
1315+ return super(
1316+ ).x
1317+
1318+ b = B()
1319+ def f():
1320+ return b.method()
1321+ """
1322+ d = self ._exec_super (codestr , optimized )
1323+ expected = [
1324+ ('line' , 'check_events' , 10 ),
1325+ ('call' , 'f' , sys .monitoring .MISSING ),
1326+ ('line' , 'f' , 1 ),
1327+ ('call' , 'method' , d ["b" ]),
1328+ ('line' , 'method' , 1 ),
1329+ ('call' , 'super' , sys .monitoring .MISSING ),
1330+ ('C return' , 'super' , sys .monitoring .MISSING ),
1331+ ('line' , 'method' , 2 ),
1332+ ('line' , 'method' , 1 ),
1333+ ('line' , 'check_events' , 11 ),
1334+ ('call' , 'set_events' , 2 )
1335+ ]
1336+ return d ["f" ], expected
1337+
1338+ def test_attr (self ):
1339+ nonopt_func , nonopt_expected = self ._super_attr (optimized = False )
1340+ opt_func , opt_expected = self ._super_attr (optimized = True )
1341+
1342+ self .check_events (nonopt_func , recorders = self .RECORDERS , expected = nonopt_expected )
1343+ self .check_events (opt_func , recorders = self .RECORDERS , expected = opt_expected )
1344+
1345+ def test_vs_other_type_call (self ):
1346+ code_template = textwrap .dedent ("""
1347+ class C:
1348+ def method(self):
1349+ return {cls}().__repr__{call}
1350+ c = C()
1351+ def f():
1352+ return c.method()
1353+ """ )
1354+
1355+ def get_expected (name , call_method , ns ):
1356+ repr_arg = 0 if name == "int" else sys .monitoring .MISSING
1357+ return [
1358+ ('line' , 'check_events' , 10 ),
1359+ ('call' , 'f' , sys .monitoring .MISSING ),
1360+ ('line' , 'f' , 1 ),
1361+ ('call' , 'method' , ns ["c" ]),
1362+ ('line' , 'method' , 1 ),
1363+ ('call' , name , sys .monitoring .MISSING ),
1364+ ('C return' , name , sys .monitoring .MISSING ),
1365+ * (
1366+ [
1367+ ('call' , '__repr__' , repr_arg ),
1368+ ('C return' , '__repr__' , repr_arg ),
1369+ ] if call_method else []
1370+ ),
1371+ ('line' , 'check_events' , 11 ),
1372+ ('call' , 'set_events' , 2 ),
1373+ ]
1374+
1375+ for call_method in [True , False ]:
1376+ with self .subTest (call_method = call_method ):
1377+ call_str = "()" if call_method else ""
1378+ code_super = code_template .format (cls = "super" , call = call_str )
1379+ code_int = code_template .format (cls = "int" , call = call_str )
1380+ co_super = compile (code_super , '<string>' , 'exec' )
1381+ self .assertTrue (self ._has_load_super_attr (co_super ))
1382+ ns_super = self ._exec (co_super )
1383+ ns_int = self ._exec (code_int )
1384+
1385+ self .check_events (
1386+ ns_super ["f" ],
1387+ recorders = self .RECORDERS ,
1388+ expected = get_expected ("super" , call_method , ns_super )
1389+ )
1390+ self .check_events (
1391+ ns_int ["f" ],
1392+ recorders = self .RECORDERS ,
1393+ expected = get_expected ("int" , call_method , ns_int )
1394+ )
1395+
1396+
11801397class TestSetGetEvents (MonitoringTestBase , unittest .TestCase ):
11811398
11821399 def test_global (self ):
0 commit comments