Home | History | Annotate | Download | only in test
      1 import dis
      2 import sys
      3 from cStringIO import StringIO
      4 import unittest
      5 
      6 def disassemble(func):
      7     f = StringIO()
      8     tmp = sys.stdout
      9     sys.stdout = f
     10     dis.dis(func)
     11     sys.stdout = tmp
     12     result = f.getvalue()
     13     f.close()
     14     return result
     15 
     16 def dis_single(line):
     17     return disassemble(compile(line, '', 'single'))
     18 
     19 class TestTranforms(unittest.TestCase):
     20 
     21     def test_unot(self):
     22         # UNARY_NOT POP_JUMP_IF_FALSE  -->  POP_JUMP_IF_TRUE
     23         def unot(x):
     24             if not x == 2:
     25                 del x
     26         asm = disassemble(unot)
     27         for elem in ('UNARY_NOT', 'POP_JUMP_IF_FALSE'):
     28             self.assertNotIn(elem, asm)
     29         self.assertIn('POP_JUMP_IF_TRUE', asm)
     30 
     31     def test_elim_inversion_of_is_or_in(self):
     32         for line, elem in (
     33             ('not a is b', '(is not)',),
     34             ('not a in b', '(not in)',),
     35             ('not a is not b', '(is)',),
     36             ('not a not in b', '(in)',),
     37             ):
     38             asm = dis_single(line)
     39             self.assertIn(elem, asm)
     40 
     41     def test_none_as_constant(self):
     42         # LOAD_GLOBAL None  -->  LOAD_CONST None
     43         def f(x):
     44             None
     45             return x
     46         asm = disassemble(f)
     47         for elem in ('LOAD_GLOBAL',):
     48             self.assertNotIn(elem, asm)
     49         for elem in ('LOAD_CONST', '(None)'):
     50             self.assertIn(elem, asm)
     51         def f():
     52             'Adding a docstring made this test fail in Py2.5.0'
     53             return None
     54         self.assertIn('LOAD_CONST', disassemble(f))
     55         self.assertNotIn('LOAD_GLOBAL', disassemble(f))
     56 
     57     def test_while_one(self):
     58         # Skip over:  LOAD_CONST trueconst  POP_JUMP_IF_FALSE xx
     59         def f():
     60             while 1:
     61                 pass
     62             return list
     63         asm = disassemble(f)
     64         for elem in ('LOAD_CONST', 'POP_JUMP_IF_FALSE'):
     65             self.assertNotIn(elem, asm)
     66         for elem in ('JUMP_ABSOLUTE',):
     67             self.assertIn(elem, asm)
     68 
     69     def test_pack_unpack(self):
     70         for line, elem in (
     71             ('a, = a,', 'LOAD_CONST',),
     72             ('a, b = a, b', 'ROT_TWO',),
     73             ('a, b, c = a, b, c', 'ROT_THREE',),
     74             ):
     75             asm = dis_single(line)
     76             self.assertIn(elem, asm)
     77             self.assertNotIn('BUILD_TUPLE', asm)
     78             self.assertNotIn('UNPACK_TUPLE', asm)
     79 
     80     def test_folding_of_tuples_of_constants(self):
     81         for line, elem in (
     82             ('a = 1,2,3', '((1, 2, 3))'),
     83             ('("a","b","c")', "(('a', 'b', 'c'))"),
     84             ('a,b,c = 1,2,3', '((1, 2, 3))'),
     85             ('(None, 1, None)', '((None, 1, None))'),
     86             ('((1, 2), 3, 4)', '(((1, 2), 3, 4))'),
     87             ):
     88             asm = dis_single(line)
     89             self.assertIn(elem, asm)
     90             self.assertNotIn('BUILD_TUPLE', asm)
     91 
     92         # Bug 1053819:  Tuple of constants misidentified when presented with:
     93         # . . . opcode_with_arg 100   unary_opcode   BUILD_TUPLE 1  . . .
     94         # The following would segfault upon compilation
     95         def crater():
     96             (~[
     97                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     98                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     99                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    100                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    101                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    102                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    103                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    104                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    105                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    106                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    107             ],)
    108 
    109     def test_folding_of_binops_on_constants(self):
    110         for line, elem in (
    111             ('a = 2+3+4', '(9)'),                   # chained fold
    112             ('"@"*4', "('@@@@')"),                  # check string ops
    113             ('a="abc" + "def"', "('abcdef')"),      # check string ops
    114             ('a = 3**4', '(81)'),                   # binary power
    115             ('a = 3*4', '(12)'),                    # binary multiply
    116             ('a = 13//4', '(3)'),                   # binary floor divide
    117             ('a = 14%4', '(2)'),                    # binary modulo
    118             ('a = 2+3', '(5)'),                     # binary add
    119             ('a = 13-4', '(9)'),                    # binary subtract
    120             ('a = (12,13)[1]', '(13)'),             # binary subscr
    121             ('a = 13 << 2', '(52)'),                # binary lshift
    122             ('a = 13 >> 2', '(3)'),                 # binary rshift
    123             ('a = 13 & 7', '(5)'),                  # binary and
    124             ('a = 13 ^ 7', '(10)'),                 # binary xor
    125             ('a = 13 | 7', '(15)'),                 # binary or
    126             ):
    127             asm = dis_single(line)
    128             self.assertIn(elem, asm, asm)
    129             self.assertNotIn('BINARY_', asm)
    130 
    131         # Verify that unfoldables are skipped
    132         asm = dis_single('a=2+"b"')
    133         self.assertIn('(2)', asm)
    134         self.assertIn("('b')", asm)
    135 
    136         # Verify that large sequences do not result from folding
    137         asm = dis_single('a="x"*1000')
    138         self.assertIn('(1000)', asm)
    139 
    140     def test_binary_subscr_on_unicode(self):
    141         # unicode strings don't get optimized
    142         asm = dis_single('u"foo"[0]')
    143         self.assertNotIn("(u'f')", asm)
    144         self.assertIn('BINARY_SUBSCR', asm)
    145         asm = dis_single('u"\u0061\uffff"[1]')
    146         self.assertNotIn("(u'\\uffff')", asm)
    147         self.assertIn('BINARY_SUBSCR', asm)
    148 
    149         # out of range
    150         asm = dis_single('u"fuu"[10]')
    151         self.assertIn('BINARY_SUBSCR', asm)
    152         # non-BMP char (see #5057)
    153         asm = dis_single('u"\U00012345"[0]')
    154         self.assertIn('BINARY_SUBSCR', asm)
    155         asm = dis_single('u"\U00012345abcdef"[3]')
    156         self.assertIn('BINARY_SUBSCR', asm)
    157 
    158 
    159     def test_folding_of_unaryops_on_constants(self):
    160         for line, elem in (
    161             ('`1`', "('1')"),                       # unary convert
    162             ('-0.5', '(-0.5)'),                     # unary negative
    163             ('~-2', '(1)'),                         # unary invert
    164         ):
    165             asm = dis_single(line)
    166             self.assertIn(elem, asm, asm)
    167             self.assertNotIn('UNARY_', asm)
    168 
    169         # Verify that unfoldables are skipped
    170         for line, elem in (
    171             ('-"abc"', "('abc')"),                  # unary negative
    172             ('~"abc"', "('abc')"),                  # unary invert
    173         ):
    174             asm = dis_single(line)
    175             self.assertIn(elem, asm, asm)
    176             self.assertIn('UNARY_', asm)
    177 
    178     def test_elim_extra_return(self):
    179         # RETURN LOAD_CONST None RETURN  -->  RETURN
    180         def f(x):
    181             return x
    182         asm = disassemble(f)
    183         self.assertNotIn('LOAD_CONST', asm)
    184         self.assertNotIn('(None)', asm)
    185         self.assertEqual(asm.split().count('RETURN_VALUE'), 1)
    186 
    187     def test_elim_jump_to_return(self):
    188         # JUMP_FORWARD to RETURN -->  RETURN
    189         def f(cond, true_value, false_value):
    190             return true_value if cond else false_value
    191         asm = disassemble(f)
    192         self.assertNotIn('JUMP_FORWARD', asm)
    193         self.assertNotIn('JUMP_ABSOLUTE', asm)
    194         self.assertEqual(asm.split().count('RETURN_VALUE'), 2)
    195 
    196     def test_elim_jump_after_return1(self):
    197         # Eliminate dead code: jumps immediately after returns can't be reached
    198         def f(cond1, cond2):
    199             if cond1: return 1
    200             if cond2: return 2
    201             while 1:
    202                 return 3
    203             while 1:
    204                 if cond1: return 4
    205                 return 5
    206             return 6
    207         asm = disassemble(f)
    208         self.assertNotIn('JUMP_FORWARD', asm)
    209         self.assertNotIn('JUMP_ABSOLUTE', asm)
    210         self.assertEqual(asm.split().count('RETURN_VALUE'), 6)
    211 
    212     def test_elim_jump_after_return2(self):
    213         # Eliminate dead code: jumps immediately after returns can't be reached
    214         def f(cond1, cond2):
    215             while 1:
    216                 if cond1: return 4
    217         asm = disassemble(f)
    218         self.assertNotIn('JUMP_FORWARD', asm)
    219         # There should be one jump for the while loop.
    220         self.assertEqual(asm.split().count('JUMP_ABSOLUTE'), 1)
    221         self.assertEqual(asm.split().count('RETURN_VALUE'), 2)
    222 
    223 
    224 def test_main(verbose=None):
    225     import sys
    226     from test import test_support
    227     test_classes = (TestTranforms,)
    228 
    229     with test_support.check_py3k_warnings(
    230             ("backquote not supported", SyntaxWarning)):
    231         test_support.run_unittest(*test_classes)
    232 
    233         # verify reference counting
    234         if verbose and hasattr(sys, "gettotalrefcount"):
    235             import gc
    236             counts = [None] * 5
    237             for i in xrange(len(counts)):
    238                 test_support.run_unittest(*test_classes)
    239                 gc.collect()
    240                 counts[i] = sys.gettotalrefcount()
    241             print counts
    242 
    243 if __name__ == "__main__":
    244     test_main(verbose=True)
    245