1
1
"""Test suite for the sys.monitoring."""
2
2
3
3
import collections
4
+ import dis
4
5
import functools
5
6
import operator
6
7
import sys
8
+ import textwrap
7
9
import types
8
10
import unittest
9
11
@@ -506,7 +508,7 @@ def test_lines_single(self):
506
508
sys .monitoring .set_events (TEST_TOOL , 0 )
507
509
sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
508
510
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 ])
510
512
finally :
511
513
sys .monitoring .set_events (TEST_TOOL , 0 )
512
514
sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
@@ -524,7 +526,7 @@ def test_lines_loop(self):
524
526
sys .monitoring .set_events (TEST_TOOL , 0 )
525
527
sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
526
528
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 ])
528
530
finally :
529
531
sys .monitoring .set_events (TEST_TOOL , 0 )
530
532
sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
@@ -546,7 +548,7 @@ def test_lines_two(self):
546
548
sys .monitoring .register_callback (TEST_TOOL , E .LINE , None )
547
549
sys .monitoring .register_callback (TEST_TOOL2 , E .LINE , None )
548
550
start = LineMonitoringTest .test_lines_two .__code__ .co_firstlineno
549
- expected = [start + 10 , 14 , start + 11 ]
551
+ expected = [start + 10 , 16 , start + 11 ]
550
552
self .assertEqual (events , expected )
551
553
self .assertEqual (events2 , expected )
552
554
finally :
@@ -1177,6 +1179,221 @@ def func():
1177
1179
('return' , None ),
1178
1180
('line' , 'check_events' , 11 )])
1179
1181
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
+
1180
1397
class TestSetGetEvents (MonitoringTestBase , unittest .TestCase ):
1181
1398
1182
1399
def test_global (self ):
0 commit comments