1# Minimal tests for dis module 2 3import difflib 4import unittest 5import sys 6import xdis.std as dis 7import io 8import re 9 10class _C: 11 def __init__(self, x): 12 self.x = x == 1 13 14dis_c_instance_method = """\ 15%3d: 0 LOAD_FAST 1 (x) 16 3 LOAD_CONST 1 (1) 17 6 COMPARE_OP 2 (==) 18 9 LOAD_FAST 0 (self) 19 12 STORE_ATTR 0 (x) 20 15 LOAD_CONST 0 (None) 21 18 RETURN_VALUE 22 23""" % (_C.__init__.__code__.co_firstlineno + 1,) 24 25dis_c_instance_method_bytes = """\ 26 0 LOAD_FAST 1 (1) 27 3 LOAD_CONST 1 (1) 28 6 COMPARE_OP 2 (==) 29 9 LOAD_FAST 0 (0) 30 12 STORE_ATTR 0 (0) 31 15 LOAD_CONST 0 (0) 32 18 RETURN_VALUE 33 34""" 35 36def _f(a): 37 print(a) 38 return 1 39 40dis_f = """\ 41%3d: 0 LOAD_GLOBAL 0 (print) 42 3 LOAD_FAST 0 (a) 43 6 CALL_FUNCTION 1 (1 positional, 0 named) 44 9 POP_TOP 45 46%3d: 10 LOAD_CONST 1 (1) 47 13 RETURN_VALUE 48 49""" % (_f.__code__.co_firstlineno + 1, 50 _f.__code__.co_firstlineno + 2) 51 52 53dis_f_co_code = """\ 54 37: 0 LOAD_GLOBAL 0 (print) 55 3 LOAD_FAST 0 (a) 56 6 CALL_FUNCTION 1 (1 positional, 0 named) 57 9 POP_TOP 58 59 38: 10 LOAD_CONST 1 (1) 60 13 RETURN_VALUE 61 62""" 63 64 65def bug708901(): 66 for res in range(1, 67 10): 68 pass 69 70dis_bug708901 = """\ 71%3d: 0 SETUP_LOOP 23 (to 26) 72 3 LOAD_GLOBAL 0 (range) 73 6 LOAD_CONST 1 (1) 74 75%3d: 9 LOAD_CONST 2 (10) 76 12 CALL_FUNCTION 2 (2 positional, 0 named) 77 15 GET_ITER 78 >> 16 FOR_ITER 6 (to 25) 79 19 STORE_FAST 0 (res) 80 81%3d: 22 JUMP_ABSOLUTE 16 (to 16) 82 >> 25 POP_BLOCK 83 >> 26 LOAD_CONST 0 (None) 84 29 RETURN_VALUE 85 86""" % (bug708901.__code__.co_firstlineno + 1, 87 bug708901.__code__.co_firstlineno + 2, 88 bug708901.__code__.co_firstlineno + 3) 89 90 91def bug1333982(x=[]): 92 assert 0, ([s for s in x] + 93 1) 94 pass 95 96dis_bug1333982 = """\ 97%4d: 0 LOAD_CONST 1 (0) 98 3 POP_JUMP_IF_TRUE 92 (to 92) 99 6 LOAD_GLOBAL 0 (AssertionError) 100 9 LOAD_CONST 2 (<code object <listcomp> at 0x..., file "%s", line %d>) 101 12 LOAD_CONST 3 ('bug1333982.<locals>.<listcomp>') 102 15 MAKE_FUNCTION 0 (0 positional, 0 name and default, 0 annotations) 103 18 LOAD_FAST 0 (x) 104 21 GET_ITER 105 22 CALL_FUNCTION 1 (1 positional, 0 named) 106 107%4d: 25 LOAD_CONST 4 (1) 108 28 BINARY_ADD 109 29 CALL_FUNCTION 1 (1 positional, 0 named) 110 32 RAISE_VARARGS 1 111 112%4d: >> 35 LOAD_CONST 0 (None) 113 38 RETURN_VALUE 114 115""" % (bug1333982.__code__.co_firstlineno + 1, 116 __file__, 117 bug1333982.__code__.co_firstlineno + 1, 118 bug1333982.__code__.co_firstlineno + 2, 119 bug1333982.__code__.co_firstlineno + 3) 120 121_BIG_LINENO_FORMAT = """\ 122%3d: 0 LOAD_GLOBAL 0 (spam) 123 3 POP_TOP 124 4 LOAD_CONST 0 (None) 125 7 RETURN_VALUE 126 127""" 128 129dis_module_expected_results = """\ 130Disassembly of f: 131 4: 0 LOAD_CONST 0 (None) 132 3 RETURN_VALUE 133 134Disassembly of g: 135 5: 0 LOAD_CONST 0 (None) 136 3 RETURN_VALUE 137 138""" 139 140expr_str = "x + 1" 141 142dis_expr_str = """\ 143 1: 0 LOAD_NAME 0 (x) 144 3 LOAD_CONST 0 (1) 145 6 BINARY_ADD 146 7 RETURN_VALUE 147 148""" 149 150simple_stmt_str = "x = x + 1" 151 152dis_simple_stmt_str = """\ 153 1: 0 LOAD_NAME 0 (x) 154 3 LOAD_CONST 0 (1) 155 6 BINARY_ADD 156 7 STORE_NAME 0 (x) 157 10 LOAD_CONST 1 (None) 158 13 RETURN_VALUE 159 160""" 161 162compound_stmt_str = """\ 163x = 0 164while 1: 165 x += 1""" 166# Trailing newline has been deliberately omitted 167 168dis_compound_stmt_str = """\ 169 1: 0 LOAD_CONST 0 (0) 170 3 STORE_NAME 0 (x) 171 172 2: 6 SETUP_LOOP 13 (to 22) 173 174 3: >> 9 LOAD_NAME 0 (x) 175 12 LOAD_CONST 1 (1) 176 15 INPLACE_ADD 177 16 STORE_NAME 0 (x) 178 19 JUMP_ABSOLUTE 9 (to 9) 179 >> 22 LOAD_CONST 2 (None) 180 25 RETURN_VALUE 181 182""" 183 184if sys.version_info[0:2] == (3, 3): 185 from test.support import run_unittest, captured_stdout 186 class DisTests(unittest.TestCase): 187 188 def get_disassembly(self, func, lasti=-1, wrapper=True): 189 s = io.StringIO() 190 save_stdout = sys.stdout 191 sys.stdout = s 192 try: 193 if wrapper: 194 dis.dis(func) 195 else: 196 dis.disassemble(func, lasti) 197 finally: 198 sys.stdout = save_stdout 199 # Trim trailing blanks (if any). 200 return [line.rstrip() for line in s.getvalue().splitlines()] 201 202 def get_disassemble_as_string(self, func, lasti=-1): 203 return '\n'.join(self.get_disassembly(func, lasti, False)) 204 205 def do_disassembly_test(self, func, expected): 206 lines = self.get_disassembly(func) 207 expected = expected.splitlines() 208 if expected == lines: 209 return 210 else: 211 lines = [re.sub('0x[0-9A-Fa-f]+', '0x...', l) for l in lines] 212 if expected == lines: 213 return 214 self.fail( 215 "events did not match expectation:\n" + 216 "\n".join(difflib.ndiff(expected, 217 lines))) 218 219 def test_opmap(self): 220 self.assertEqual(dis.opmap["NOP"], 9) 221 self.assertIn(dis.opmap["LOAD_CONST"], dis.hasconst) 222 self.assertIn(dis.opmap["STORE_NAME"], dis.hasname) 223 224 def test_opname(self): 225 self.assertEqual(dis.opname[dis.opmap["LOAD_FAST"]], "LOAD_FAST") 226 227 def test_boundaries(self): 228 self.assertEqual(dis.opmap["EXTENDED_ARG"], dis.EXTENDED_ARG) 229 self.assertEqual(dis.opmap["STORE_NAME"], dis.HAVE_ARGUMENT) 230 231 def test_dis(self): 232 self.do_disassembly_test(_f, dis_f) 233 234 def test_bug_708901(self): 235 self.do_disassembly_test(bug708901, dis_bug708901) 236 237 def test_bug_1333982(self): 238 # This one is checking bytecodes generated for an `assert` statement, 239 # so fails if the tests are run with -O. Skip this test then. 240 if True: 241 self.skipTest('need asserts, run without -O') 242 return 243 244 self.do_disassembly_test(bug1333982, dis_bug1333982) 245 246 def test_big_linenos(self): 247 def func(count): 248 namespace = {} 249 func = "def foo():\n " + "".join(["\n "] * count + ["spam\n"]) 250 exec(func, namespace) 251 return namespace['foo'] 252 253 # Test all small ranges 254 for i in range(1, 300): 255 expected = _BIG_LINENO_FORMAT % (i + 2) 256 self.do_disassembly_test(func(i), expected) 257 258 # Test some larger ranges too 259 for i in range(300, 5000, 10): 260 expected = _BIG_LINENO_FORMAT % (i + 2) 261 self.do_disassembly_test(func(i), expected) 262 263 self.skipTest('Add ability to disassemble module') 264 # from test import dis_module 265 # self.do_disassembly_test(dis_module, dis_module_expected_results) 266 267 def test_disassemble_str(self): 268 self.do_disassembly_test(expr_str, dis_expr_str) 269 self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) 270 self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) 271 272 def test_disassemble_bytes(self): 273 self.do_disassembly_test(_f.__code__, dis_f_co_code) 274 275 def test_disassemble_method(self): 276 self.do_disassembly_test(_C(1).__init__, dis_c_instance_method) 277 278 def test_disassemble_method_bytes(self): 279 self.skipTest('Add ability to disassemble bytes') 280 # method_bytecode = _C(1).__init__.__code__.co_code 281 # self.do_disassembly_test(method_bytecode, dis_c_instance_method_bytes) 282 283 def test_dis_none(self): 284 try: 285 del sys.last_traceback 286 except AttributeError: 287 pass 288 self.assertRaises(TypeError, dis.dis, None) 289 290 def test_dis_traceback(self): 291 self.skipTest('Fix up ability to disassemble straceback') 292 return 293 try: 294 del sys.last_traceback 295 except AttributeError: 296 pass 297 298 try: 299 1/0 300 except Exception as e: 301 tb = e.__traceback__ 302 sys.last_traceback = tb 303 304 tb_dis = self.get_disassemble_as_string(tb.tb_frame.f_code, tb.tb_lasti) 305 self.do_disassembly_test(None, tb_dis) 306 307 def test_dis_object(self): 308 self.assertRaises(TypeError, dis.dis, object()) 309 310 code_info_code_info = """\ 311 Method Name: code_info 312 Filename: (.*) 313 Argument count: 1 314 Kw-only arguments: 0 315 Number of locals: 1 316 Stack size: 4 317 Flags: OPTIMIZED, NEWLOCALS, NOFREE 318 Constants: 319 0: %r 320 1: '__func__' 321 2: '__code__' 322 3: '<code_info>' 323 4: 'co_code' 324 5: "don't know how to disassemble %%s objects" 325 %sNames: 326 0: hasattr 327 1: __func__ 328 2: __code__ 329 3: isinstance 330 4: str 331 5: _try_compile 332 6: _format_code_info 333 7: TypeError 334 8: type 335 9: __name__ 336 Variable names: 337 0: x""" % (('Formatted details of methods, functions, or code.', ' 6: None\n') 338 if sys.flags.optimize < 2 else (None, '')) 339 340 # @staticmethod 341 # def tricky(x, y, z=True, *args, c, d, e=[], **kwds): 342 # def f(c=c): 343 # print(x, y, z, c, d, e, f) 344 # yield x, y, z, c, d, e, f 345 346 code_info_tricky = """\ 347 Method Name: tricky 348 Filename: (.*) 349 Argument count: 3 350 Kw-only arguments: 3 351 Number of locals: 8 352 Stack size: 7 353 Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR 354 Constants: 355 0: None 356 1: <code object f at (.*), file "(.*)", line (.*)> 357 2: 'tricky.<locals>.f' 358 Variable names: 359 0: x 360 1: y 361 2: z 362 3: c 363 4: d 364 5: e 365 6: args 366 7: kwds 367 Cell variables: 368 0: [edfxyz] 369 1: [edfxyz] 370 2: [edfxyz] 371 3: [edfxyz] 372 4: [edfxyz] 373 5: [edfxyz]""" 374 # NOTE: the order of the cell variables above depends on dictionary order! 375 376 # co_tricky_nested_f = tricky.__func__.__code__.co_consts[1] 377 378 code_info_tricky_nested_f = """\ 379 Method Name: f 380 Filename: (.*) 381 Argument count: 1 382 Kw-only arguments: 0 383 Number of locals: 1 384 Stack size: 8 385 Flags: OPTIMIZED, NEWLOCALS, NESTED 386 Constants: 387 0: None 388 Names: 389 0: print 390 Variable names: 391 0: c 392 Free variables: 393 0: [edfxyz] 394 1: [edfxyz] 395 2: [edfxyz] 396 3: [edfxyz] 397 4: [edfxyz] 398 5: [edfxyz]""" 399 400 code_info_expr_str = """\ 401 Name: <module> 402 Filename: <code_info> 403 Argument count: 0 404 Kw-only arguments: 0 405 Number of locals: 0 406 Stack size: 2 407 Flags: NOFREE 408 Constants: 409 0: 1 410 Names: 411 0: x""" 412 413 code_info_simple_stmt_str = """\ 414 Name: <module> 415 Filename: <code_info> 416 Argument count: 0 417 Kw-only arguments: 0 418 Number of locals: 0 419 Stack size: 2 420 Flags: NOFREE 421 Constants: 422 0: 1 423 1: None 424 Names: 425 0: x""" 426 427 code_info_compound_stmt_str = """\ 428 Name: <module> 429 Filename: <code_info> 430 Argument count: 0 431 Kw-only arguments: 0 432 Number of locals: 0 433 Stack size: 2 434 Flags: NOFREE 435 Constants: 436 0: 0 437 1: 1 438 2: None 439 Names: 440 0: x""" 441 442 class CodeInfoTests(unittest.TestCase): 443 test_pairs = [ 444 # (dis.code_info, code_info_code_info), 445 # (tricky, code_info_tricky), 446 # (co_tricky_nested_f, code_info_tricky_nested_f), 447 # (expr_str, code_info_expr_str), 448 # (simple_stmt_str, code_info_simple_stmt_str), 449 # (compound_stmt_str, code_info_compound_stmt_str), 450 ] 451 452 def test_code_info(self): 453 self.skipTest('Reconcile differences in format') 454 return 455 self.maxDiff = 1000 456 for x, expected in self.test_pairs: 457 self.assertRegex(dis.code_info(x), expected) 458 459 def test_show_code(self): 460 self.maxDiff = 1000 461 for x, expected in self.test_pairs: 462 with captured_stdout() as output: 463 dis.show_code(x) 464 self.assertRegex(output.getvalue(), expected+"\n") 465 466 def test_code_info_object(self): 467 self.assertRaises(TypeError, dis.code_info, object()) 468 469 def test_pretty_flags_no_flags(self): 470 self.assertEqual(dis.pretty_flags(0), '0x00000000 (0x0)') 471 472 def test_main(): 473 run_unittest(DisTests) 474 475 476if __name__ == "__main__": 477 unittest.main() 478