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