Repository URL to install this package:
|
Version:
2022.2.8 ▾
|
pytype
/
blocks_test.py
|
|---|
"""Tests for blocks.py.
To create test cases, you can disassemble source code with the help of the dis
module. For example, in Python 3.7, this snippet:
import dis
import opcode
def f(): return None
bytecode = dis.Bytecode(f)
for x in bytecode.codeobj.co_code:
print(f'{x} ({opcode.opname[x]})')
prints:
100 (LOAD_CONST)
0 (<0>)
83 (RETURN_VALUE)
0 (<0>)
"""
from pytype import blocks
from pytype.pyc import opcodes
from pytype.pyc import pyc
from pytype.tests import test_utils
import unittest
class BaseBlocksTest(unittest.TestCase, test_utils.MakeCodeMixin):
"""A base class for implementing tests testing blocks.py."""
# These tests check disassembled bytecode, which varies from version to
# version, so we fix the test version.
python_version = (3, 7)
class OrderingTest(BaseBlocksTest):
"""Tests for order_code in blocks.py."""
def _order_code(self, code):
"""Helper function to disassemble and then order code."""
disassembled_code = pyc.visit(code, blocks.DisCodeVisitor())
return blocks.order_code(disassembled_code, self.python_version)
def test_trivial(self):
# Disassembled from:
# | return None
o = test_utils.Py37Opcodes
co = self.make_code([
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="trivial")
ordered_code = self._order_code(co)
b0, = ordered_code.order
self.assertEqual(len(b0.code), 2)
self.assertCountEqual([], b0.incoming)
self.assertCountEqual([], b0.outgoing)
def test_has_opcode(self):
# Disassembled from:
# | return None
o = test_utils.Py37Opcodes
co = self.make_code([
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="trivial")
ordered_code = self._order_code(co)
self.assertTrue(ordered_code.has_opcode(opcodes.LOAD_CONST))
self.assertTrue(ordered_code.has_opcode(opcodes.RETURN_VALUE))
self.assertFalse(ordered_code.has_opcode(opcodes.POP_TOP))
def test_yield(self):
# Disassembled from:
# | yield 1
# | yield None
o = test_utils.Py37Opcodes
co = self.make_code([
# b0:
o.LOAD_CONST, 0,
o.YIELD_VALUE, 0,
# b1:
o.POP_TOP, 0,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="yield")
ordered_code = self._order_code(co)
self.assertEqual(ordered_code.co_name, "yield")
b0, b1 = ordered_code.order
self.assertCountEqual(b0.outgoing, [b1])
self.assertCountEqual(b1.incoming, [b0])
self.assertCountEqual(b0.incoming, [])
self.assertCountEqual(b1.outgoing, [])
def test_triangle(self):
# Disassembled from:
# | x = y
# | if y > 1:
# | x -= 2
# | return x
o = test_utils.Py37Opcodes
co = self.make_code([
# b0:
o.LOAD_GLOBAL, 0,
o.STORE_FAST, 0,
o.LOAD_GLOBAL, 0,
o.LOAD_CONST, 1,
o.COMPARE_OP, 4,
o.POP_JUMP_IF_FALSE, 20,
# b1:
o.LOAD_FAST, 0,
o.LOAD_CONST, 2,
o.INPLACE_SUBTRACT, 0,
o.STORE_FAST, 0,
# b2:
o.LOAD_FAST, 0,
o.RETURN_VALUE, 0,
], name="triangle")
ordered_code = self._order_code(co)
self.assertEqual(ordered_code.co_name, "triangle")
b0, b1, b2 = ordered_code.order
self.assertCountEqual(b0.incoming, [])
self.assertCountEqual(b0.outgoing, [b1, b2])
self.assertCountEqual(b1.incoming, [b0])
self.assertCountEqual(b1.outgoing, [b2])
self.assertCountEqual(b2.incoming, [b0, b1])
self.assertCountEqual(b2.outgoing, [])
def test_diamond(self):
# Disassembled from:
# | x = y
# | if y > 1:
# | x -= 2
# | else:
# | x += 2
# | return x
o = test_utils.Py37Opcodes
co = self.make_code([
# b0:
o.LOAD_GLOBAL, 0,
o.STORE_FAST, 0,
o.LOAD_GLOBAL, 0,
o.LOAD_CONST, 1,
o.COMPARE_OP, 4,
o.POP_JUMP_IF_FALSE, 22,
# b1:
o.LOAD_FAST, 0,
o.LOAD_CONST, 2,
o.INPLACE_SUBTRACT, 0,
o.STORE_FAST, 0,
o.JUMP_FORWARD, 8,
# b2:
o.LOAD_FAST, 0,
o.LOAD_CONST, 2,
o.INPLACE_ADD, 0,
o.STORE_FAST, 0,
# b3:
o.LOAD_FAST, 0,
o.RETURN_VALUE, 0,
], name="diamond")
ordered_code = self._order_code(co)
self.assertEqual(ordered_code.co_name, "diamond")
b0, b1, b2, b3 = ordered_code.order
self.assertCountEqual(b0.incoming, [])
self.assertCountEqual(b0.outgoing, [b1, b2])
self.assertCountEqual(b1.incoming, [b0])
self.assertCountEqual(b1.outgoing, [b3])
self.assertCountEqual(b2.incoming, [b0])
self.assertCountEqual(b2.outgoing, [b3])
self.assertCountEqual(b3.incoming, [b1, b2])
self.assertCountEqual(b3.outgoing, [])
def test_raise(self):
# Disassembled from:
# | raise ValueError()
# | return 1
o = test_utils.Py37Opcodes
co = self.make_code([
# b0:
o.LOAD_GLOBAL, 0,
o.CALL_FUNCTION, 0,
o.RAISE_VARARGS, 1,
o.LOAD_CONST, 1,
o.RETURN_VALUE, 0, # dead.
], name="raise")
ordered_code = self._order_code(co)
self.assertEqual(ordered_code.co_name, "raise")
b0, b1 = ordered_code.order
self.assertEqual(len(b0.code), 2)
self.assertCountEqual(b0.incoming, [])
self.assertCountEqual(b0.outgoing, [b1])
self.assertCountEqual(b1.incoming, [b0])
self.assertCountEqual(b1.outgoing, [])
def test_call(self):
# Disassembled from:
# | f()
o = test_utils.Py37Opcodes
co = self.make_code([
# b0:
o.LOAD_GLOBAL, 0,
o.CALL_FUNCTION, 0,
# b1:
o.POP_TOP, 0,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="call")
ordered_code = self._order_code(co)
b0, b1 = ordered_code.order
self.assertEqual(len(b0.code), 2)
self.assertEqual(len(b1.code), 3)
self.assertCountEqual(b0.outgoing, [b1])
def test_finally(self):
# Disassembled from:
# | try:
# | pass
# | finally:
# | pass
o = test_utils.Py37Opcodes
co = self.make_code([
# b0:
o.SETUP_FINALLY, 4,
o.POP_BLOCK, 0,
# b1:
o.LOAD_CONST, 0,
# b2:
o.END_FINALLY, 0,
# b3:
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="finally")
ordered_code = self._order_code(co)
b0, b1, b2, b3 = ordered_code.order
self.assertEqual(len(b0.code), 2)
self.assertEqual(len(b1.code), 1)
self.assertEqual(len(b2.code), 1)
self.assertEqual(len(b3.code), 2)
self.assertCountEqual(b0.outgoing, [b1, b2])
def test_except(self):
# Disassembled from:
# | try:
# | pass
# | except:
# | pass
o = test_utils.Py37Opcodes
co = self.make_code([
# b0:
o.SETUP_EXCEPT, 4,
o.POP_BLOCK, 0,
# b1:
o.JUMP_FORWARD, 12,
# b2:
o.POP_TOP, 0,
o.POP_TOP, 0,
o.POP_TOP, 0,
o.POP_EXCEPT, 0,
o.JUMP_FORWARD, 2,
# b3:
o.END_FINALLY, 0,
# b4:
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="except")
ordered_code = self._order_code(co)
b0, b1, b2, b3 = ordered_code.order
self.assertEqual(len(b0.code), 2)
self.assertEqual(len(b1.code), 1)
self.assertEqual(len(b2.code), 5)
self.assertEqual(len(b3.code), 2)
self.assertCountEqual([b1, b2], b0.outgoing)
self.assertCountEqual([b3], b1.outgoing)
self.assertCountEqual([b3], b2.outgoing)
def test_return(self):
# Disassembled from:
# | return None
# | return None
o = test_utils.Py37Opcodes
co = self.make_code([
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0, # dead.
o.LOAD_CONST, 1, # dead.
o.RETURN_VALUE, 0, # dead.
], name="return")
ordered_code = self._order_code(co)
b0, = ordered_code.order
self.assertEqual(len(b0.code), 2)
def test_with(self):
# Disassembled from:
# | with None:
# | pass
o = test_utils.Py37Opcodes
co = self.make_code([
# b0:
o.LOAD_CONST, 0,
o.SETUP_WITH, 6,
o.POP_TOP, 0,
o.POP_BLOCK, 0,
# b1:
o.LOAD_CONST, 0,
# b2:
o.WITH_CLEANUP_START, 0,
# b3:
o.WITH_CLEANUP_FINISH, 0,
o.END_FINALLY, 0,
# b4:
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="with")
ordered_code = self._order_code(co)
b0, b1, b2, b3, b4 = ordered_code.order
self.assertEqual(len(b0.code), 4)
self.assertEqual(len(b1.code), 1)
self.assertEqual(len(b2.code), 1)
self.assertEqual(len(b3.code), 2)
self.assertEqual(len(b4.code), 2)
class BlockStackTest(BaseBlocksTest):
"""Test the add_pop_block_targets function."""
def test_finally(self):
# Disassembled from:
# | try:
# | pass
# | finally:
# | pass
o = test_utils.Py37Opcodes
co = self.make_code([
o.SETUP_FINALLY, 4,
o.POP_BLOCK, 0,
o.LOAD_CONST, 0,
o.END_FINALLY, 0,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="finally")
bytecode = opcodes.dis(co.co_code, python_version=self.python_version)
blocks.add_pop_block_targets(bytecode, self.python_version)
# END_FINALLY == SETUP_FINALLY.target
self.assertEqual(bytecode[3], bytecode[0].target)
# END_FINALLY == POP_BLOCK.block_target
self.assertEqual(bytecode[3], bytecode[1].block_target)
def test_except(self):
# Disassembled from:
# | try:
# | pass
# | except:
# | pass
o = test_utils.Py37Opcodes
co = self.make_code([
o.SETUP_EXCEPT, 4,
o.POP_BLOCK, 0,
o.JUMP_FORWARD, 12,
o.POP_TOP, 0,
o.POP_TOP, 0,
o.POP_TOP, 0,
o.JUMP_FORWARD, 2,
o.END_FINALLY, 0,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="except")
bytecode = opcodes.dis(co.co_code, python_version=self.python_version)
blocks.add_pop_block_targets(bytecode, self.python_version)
# POP_TOP == SETUP_EXCEPT.target
self.assertEqual(bytecode[3], bytecode[0].target)
# POP_TOP == POP_BLOCK.block_target
self.assertEqual(bytecode[3], bytecode[1].block_target)
def test_with(self):
# Disassembled from:
# | with None:
# | pass
o = test_utils.Py37Opcodes
co = self.make_code([
o.LOAD_CONST, 0,
o.SETUP_WITH, 6,
o.POP_TOP, 0,
o.POP_BLOCK, 0,
o.LOAD_CONST, 0,
o.WITH_CLEANUP_START, 0,
o.WITH_CLEANUP_FINISH, 0,
o.END_FINALLY, 0,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
], name="with")
bytecode = opcodes.dis(co.co_code, python_version=self.python_version)
blocks.add_pop_block_targets(bytecode, self.python_version)
# WITH_CLEANUP_START == SETUP_WITH.target
self.assertEqual(bytecode[5], bytecode[1].target)
# WITH_CLEANUP_START == POP_BLOCK.block_target
self.assertEqual(bytecode[5], bytecode[3].block_target)
def test_loop(self):
# Disassembled from:
# | while []:
# | break
o = test_utils.Py37Opcodes
co = self.make_code([
o.SETUP_LOOP, 10,
o.BUILD_LIST, 0,
o.POP_JUMP_IF_FALSE, 10,
o.BREAK_LOOP, 0,
o.JUMP_ABSOLUTE, 2,
o.POP_BLOCK, 0,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
])
bytecode = opcodes.dis(co.co_code, python_version=self.python_version)
blocks.add_pop_block_targets(bytecode, self.python_version)
# LOAD_CONST == SETUP_LOOP.target
self.assertEqual(bytecode[6], bytecode[0].target)
# POP_BLOCK == POP_JUMP_IF_FALSE.target
self.assertEqual(bytecode[5], bytecode[2].target)
# BUILD_LIST == JUMP_ABSOLUTE.target
self.assertEqual(bytecode[1], bytecode[4].target)
# LOAD_CONST == POP_BLOCK.block_target
self.assertEqual(bytecode[6], bytecode[5].block_target)
def test_break(self):
# Disassembled from:
# | while True:
# | if []:
# | break
o = test_utils.Py37Opcodes
co = self.make_code([
o.SETUP_LOOP, 10,
o.BUILD_LIST, 0,
o.POP_JUMP_IF_FALSE, 2,
o.BREAK_LOOP, 0,
o.JUMP_ABSOLUTE, 2,
o.POP_BLOCK, 0,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
])
bytecode = opcodes.dis(co.co_code, python_version=self.python_version)
blocks.add_pop_block_targets(bytecode, self.python_version)
# LOAD_CONST == SETUP_LOOP.target
self.assertEqual(bytecode[6], bytecode[0].target)
# LOAD_CONST == BREAK_LOOP.block_target
self.assertEqual(bytecode[6], bytecode[3].block_target)
# BUILD_LIST == POP_JUMP_IF_FALSE.target
self.assertEqual(bytecode[1], bytecode[2].target)
# BUILD_LIST == JUMP_ABSOLUTE.target
self.assertEqual(bytecode[1], bytecode[4].target)
def test_continue(self):
# Disassembled from:
# | while True:
# | try:
# | continue
# | except:
# | pass
o = test_utils.Py37Opcodes
co = self.make_code([
o.SETUP_LOOP, 24,
o.SETUP_EXCEPT, 6,
o.CONTINUE_LOOP, 2,
o.POP_BLOCK, 0,
o.JUMP_ABSOLUTE, 2,
o.POP_TOP, 0,
o.POP_TOP, 0,
o.POP_TOP, 0,
o.POP_EXCEPT, 0,
o.JUMP_ABSOLUTE, 2,
o.END_FINALLY, 0,
o.JUMP_ABSOLUTE, 2,
o.POP_BLOCK, 0,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
])
bytecode = opcodes.dis(co.co_code, python_version=self.python_version)
blocks.add_pop_block_targets(bytecode, self.python_version)
# LOAD_CONST == SETUP_LOOP.target
self.assertEqual(bytecode[13], bytecode[0].target)
# POP_TOP == SETUP_EXCEPT.target
self.assertEqual(bytecode[5], bytecode[1].target)
# SETUP_EXCEPT == CONTINUE_LOOP.target
self.assertEqual(bytecode[1], bytecode[2].target)
# SETUP_EXCEPT == JUMP_ABSOLUTE.target
self.assertEqual(bytecode[1], bytecode[4].target)
# SETUP_EXCEPT == JUMP_ABSOLUTE.target
self.assertEqual(bytecode[1], bytecode[9].target)
# SETUP_EXCEPT == JUMP_ABSOLUTE.target
self.assertEqual(bytecode[1], bytecode[11].target)
def test_apply_typecomments(self):
# Disassembly + type comment map from
# a = 1; b = 2 # type: float
# The type comment should only apply to b.
o = test_utils.Py37Opcodes
co = self.make_code([
o.LOAD_CONST, 1,
o.STORE_FAST, 0,
o.LOAD_CONST, 2,
o.STORE_FAST, 1,
o.LOAD_CONST, 0,
o.RETURN_VALUE, 0,
])
ordered_code = blocks.merge_annotations(
blocks.process_code(co, self.python_version), {1: "float"})
bytecode = ordered_code.order[0].code
self.assertIsNone(bytecode[1].annotation)
self.assertEqual(bytecode[3].annotation, "float")
if __name__ == "__main__":
unittest.main()