This commit is contained in:
Waylon Walker 2022-03-31 20:20:07 -05:00
commit 38355d2442
No known key found for this signature in database
GPG key ID: 66E2BF2B4190EFE4
9083 changed files with 1225834 additions and 0 deletions

View file

@ -0,0 +1,11 @@
import os
provided_prefix = os.getenv('MYPY_TEST_PREFIX', None)
if provided_prefix:
PREFIX = provided_prefix
else:
this_file_dir = os.path.dirname(os.path.realpath(__file__))
PREFIX = os.path.dirname(os.path.dirname(this_file_dir))
# Location of test data files such as test case descriptions.
test_data_prefix = os.path.join(PREFIX, 'mypyc', 'test-data')

View file

@ -0,0 +1,77 @@
"""Test runner for data-flow analysis test cases."""
import os.path
from typing import Set
from mypy.test.data import DataDrivenTestCase
from mypy.test.config import test_temp_dir
from mypy.errors import CompileError
from mypyc.common import TOP_LEVEL_NAME
from mypyc.analysis import dataflow
from mypyc.transform import exceptions
from mypyc.ir.pprint import format_func, generate_names_for_ir
from mypyc.ir.ops import Value
from mypyc.ir.func_ir import all_values
from mypyc.test.testutil import (
ICODE_GEN_BUILTINS, use_custom_builtins, MypycDataSuite, build_ir_for_single_file,
assert_test_output
)
files = [
'analysis.test'
]
class TestAnalysis(MypycDataSuite):
files = files
base_path = test_temp_dir
optional_out = True
def run_case(self, testcase: DataDrivenTestCase) -> None:
"""Perform a data-flow analysis test case."""
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
try:
ir = build_ir_for_single_file(testcase.input)
except CompileError as e:
actual = e.messages
else:
actual = []
for fn in ir:
if (fn.name == TOP_LEVEL_NAME
and not testcase.name.endswith('_toplevel')):
continue
exceptions.insert_exception_handling(fn)
actual.extend(format_func(fn))
cfg = dataflow.get_cfg(fn.blocks)
args: Set[Value] = set(fn.arg_regs)
name = testcase.name
if name.endswith('_MaybeDefined'):
# Forward, maybe
analysis_result = dataflow.analyze_maybe_defined_regs(fn.blocks, cfg, args)
elif name.endswith('_Liveness'):
# Backward, maybe
analysis_result = dataflow.analyze_live_regs(fn.blocks, cfg)
elif name.endswith('_MustDefined'):
# Forward, must
analysis_result = dataflow.analyze_must_defined_regs(
fn.blocks, cfg, args,
regs=all_values(fn.arg_regs, fn.blocks))
elif name.endswith('_BorrowedArgument'):
# Forward, must
analysis_result = dataflow.analyze_borrowed_arguments(fn.blocks, cfg, args)
else:
assert False, 'No recognized _AnalysisName suffix in test case'
names = generate_names_for_ir(fn.arg_regs, fn.blocks)
for key in sorted(analysis_result.before.keys(),
key=lambda x: (x[0].label, x[1])):
pre = ', '.join(sorted(names[reg]
for reg in analysis_result.before[key]))
post = ', '.join(sorted(names[reg]
for reg in analysis_result.after[key]))
actual.append('%-8s %-23s %s' % ((key[0].label, key[1]),
'{%s}' % pre, '{%s}' % post))
assert_test_output(testcase, actual, 'Invalid source code output')

View file

@ -0,0 +1,40 @@
"""Test that C functions used in primitives are declared in a header such as CPy.h."""
import glob
import os
import re
import unittest
from mypyc.primitives import registry
from mypyc.primitives.registry import CFunctionDescription
class TestHeaderInclusion(unittest.TestCase):
def test_primitives_included_in_header(self) -> None:
base_dir = os.path.join(os.path.dirname(__file__), '..', 'lib-rt')
with open(os.path.join(base_dir, 'CPy.h')) as f:
header = f.read()
with open(os.path.join(base_dir, 'pythonsupport.h')) as f:
header += f.read()
def check_name(name: str) -> None:
if name.startswith('CPy'):
assert re.search(r'\b{}\b'.format(name), header), (
'"{}" is used in mypyc.primitives but not declared in CPy.h'.format(name))
for values in [registry.method_call_ops.values(),
registry.function_ops.values(),
registry.binary_ops.values(),
registry.unary_ops.values()]:
for ops in values:
if isinstance(ops, CFunctionDescription):
ops = [ops]
for op in ops:
check_name(op.c_function_name)
primitives_path = os.path.join(os.path.dirname(__file__), '..', 'primitives')
for fnam in glob.glob('{}/*.py'.format(primitives_path)):
with open(fnam) as f:
content = f.read()
for name in re.findall(r'c_function_name=["\'](CPy[A-Z_a-z0-9]+)', content):
check_name(name)

View file

@ -0,0 +1,72 @@
"""Test cases for invoking mypyc on the command line.
These are slow -- do not add test cases unless you have a very good reason to do so.
"""
import glob
import os
import os.path
import re
import subprocess
import sys
from mypy.test.data import DataDrivenTestCase
from mypy.test.config import test_temp_dir
from mypy.test.helpers import normalize_error_messages
from mypyc.test.testutil import MypycDataSuite, assert_test_output
files = [
'commandline.test',
]
base_path = os.path.join(os.path.dirname(__file__), '..', '..')
python3_path = sys.executable
class TestCommandLine(MypycDataSuite):
files = files
base_path = test_temp_dir
optional_out = True
def run_case(self, testcase: DataDrivenTestCase) -> None:
# Parse options from test case description (arguments must not have spaces)
text = '\n'.join(testcase.input)
m = re.search(r'# *cmd: *(.*)', text)
assert m is not None, 'Test case missing "# cmd: <files>" section'
args = m.group(1).split()
# Write main program to run (not compiled)
program = '_%s.py' % testcase.name
program_path = os.path.join(test_temp_dir, program)
with open(program_path, 'w') as f:
f.write(text)
out = b''
try:
# Compile program
cmd = subprocess.run([sys.executable, '-m', 'mypyc', *args],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd='tmp')
if 'ErrorOutput' in testcase.name or cmd.returncode != 0:
out += cmd.stdout
if cmd.returncode == 0:
# Run main program
out += subprocess.check_output(
[python3_path, program],
cwd='tmp')
finally:
suffix = 'pyd' if sys.platform == 'win32' else 'so'
so_paths = glob.glob('tmp/**/*.{}'.format(suffix), recursive=True)
for path in so_paths:
os.remove(path)
# Strip out 'tmp/' from error message paths in the testcase output,
# due to a mismatch between this test and mypy's test suite.
expected = [x.replace('tmp/', '') for x in testcase.output]
# Verify output
actual = normalize_error_messages(out.decode().splitlines())
assert_test_output(testcase, actual, 'Invalid output', expected=expected)

View file

@ -0,0 +1,33 @@
import unittest
from typing import Dict
from mypyc.codegen.emit import Emitter, EmitterContext
from mypyc.ir.ops import BasicBlock, Value, Register
from mypyc.ir.rtypes import int_rprimitive
from mypyc.namegen import NameGenerator
class TestEmitter(unittest.TestCase):
def setUp(self) -> None:
self.n = Register(int_rprimitive, 'n')
self.context = EmitterContext(NameGenerator([['mod']]))
def test_label(self) -> None:
emitter = Emitter(self.context, {})
assert emitter.label(BasicBlock(4)) == 'CPyL4'
def test_reg(self) -> None:
names: Dict[Value, str] = {self.n: "n"}
emitter = Emitter(self.context, names)
assert emitter.reg(self.n) == 'cpy_r_n'
def test_emit_line(self) -> None:
emitter = Emitter(self.context, {})
emitter.emit_line('line;')
emitter.emit_line('a {')
emitter.emit_line('f();')
emitter.emit_line('}')
assert emitter.fragments == ['line;\n',
'a {\n',
' f();\n',
'}\n']

View file

@ -0,0 +1,12 @@
import unittest
from mypyc.codegen.emitclass import slot_key
class TestEmitClass(unittest.TestCase):
def test_slot_key(self) -> None:
attrs = ['__add__', '__radd__', '__rshift__', '__rrshift__', '__setitem__', '__delitem__']
s = sorted(attrs, key=lambda x: slot_key(x))
# __delitem__ and reverse methods should come last.
assert s == [
'__add__', '__rshift__', '__setitem__', '__delitem__', '__radd__', '__rrshift__']

View file

@ -0,0 +1,524 @@
import unittest
from typing import List, Optional
from mypy.backports import OrderedDict
from mypy.test.helpers import assert_string_arrays_equal
from mypyc.ir.ops import (
BasicBlock, Goto, Return, Integer, Assign, AssignMulti, IncRef, DecRef, Branch,
Call, Unbox, Box, TupleGet, GetAttr, SetAttr, Op, Value, CallC, IntOp, LoadMem,
GetElementPtr, LoadAddress, ComparisonOp, SetMem, Register, Unreachable
)
from mypyc.ir.rtypes import (
RTuple, RInstance, RType, RArray, int_rprimitive, bool_rprimitive, list_rprimitive,
dict_rprimitive, object_rprimitive, c_int_rprimitive, short_int_rprimitive, int32_rprimitive,
int64_rprimitive, RStruct, pointer_rprimitive
)
from mypyc.ir.func_ir import FuncIR, FuncDecl, RuntimeArg, FuncSignature
from mypyc.ir.class_ir import ClassIR
from mypyc.ir.pprint import generate_names_for_ir
from mypyc.irbuild.vtable import compute_vtable
from mypyc.codegen.emit import Emitter, EmitterContext
from mypyc.codegen.emitfunc import generate_native_function, FunctionEmitterVisitor
from mypyc.primitives.registry import binary_ops
from mypyc.primitives.misc_ops import none_object_op
from mypyc.primitives.list_ops import list_get_item_op, list_set_item_op, list_append_op
from mypyc.primitives.dict_ops import (
dict_new_op, dict_update_op, dict_get_item_op, dict_set_item_op
)
from mypyc.primitives.int_ops import int_neg_op
from mypyc.subtype import is_subtype
from mypyc.namegen import NameGenerator
class TestFunctionEmitterVisitor(unittest.TestCase):
"""Test generation of fragments of C from individual IR ops."""
def setUp(self) -> None:
self.registers: List[Register] = []
def add_local(name: str, rtype: RType) -> Register:
reg = Register(rtype, name)
self.registers.append(reg)
return reg
self.n = add_local('n', int_rprimitive)
self.m = add_local('m', int_rprimitive)
self.k = add_local('k', int_rprimitive)
self.l = add_local('l', list_rprimitive) # noqa
self.ll = add_local('ll', list_rprimitive)
self.o = add_local('o', object_rprimitive)
self.o2 = add_local('o2', object_rprimitive)
self.d = add_local('d', dict_rprimitive)
self.b = add_local('b', bool_rprimitive)
self.s1 = add_local('s1', short_int_rprimitive)
self.s2 = add_local('s2', short_int_rprimitive)
self.i32 = add_local('i32', int32_rprimitive)
self.i32_1 = add_local('i32_1', int32_rprimitive)
self.i64 = add_local('i64', int64_rprimitive)
self.i64_1 = add_local('i64_1', int64_rprimitive)
self.ptr = add_local('ptr', pointer_rprimitive)
self.t = add_local('t', RTuple([int_rprimitive, bool_rprimitive]))
self.tt = add_local(
'tt', RTuple([RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive]))
ir = ClassIR('A', 'mod')
ir.attributes = OrderedDict([('x', bool_rprimitive), ('y', int_rprimitive)])
compute_vtable(ir)
ir.mro = [ir]
self.r = add_local('r', RInstance(ir))
self.context = EmitterContext(NameGenerator([['mod']]))
def test_goto(self) -> None:
self.assert_emit(Goto(BasicBlock(2)),
"goto CPyL2;")
def test_goto_next_block(self) -> None:
next_block = BasicBlock(2)
self.assert_emit(Goto(next_block), "", next_block=next_block)
def test_return(self) -> None:
self.assert_emit(Return(self.m),
"return cpy_r_m;")
def test_integer(self) -> None:
self.assert_emit(Assign(self.n, Integer(5)),
"cpy_r_n = 10;")
self.assert_emit(Assign(self.i32, Integer(5, c_int_rprimitive)),
"cpy_r_i32 = 5;")
def test_tuple_get(self) -> None:
self.assert_emit(TupleGet(self.t, 1, 0), 'cpy_r_r0 = cpy_r_t.f1;')
def test_load_None(self) -> None:
self.assert_emit(LoadAddress(none_object_op.type, none_object_op.src, 0),
"cpy_r_r0 = (PyObject *)&_Py_NoneStruct;")
def test_assign_int(self) -> None:
self.assert_emit(Assign(self.m, self.n),
"cpy_r_m = cpy_r_n;")
def test_int_add(self) -> None:
self.assert_emit_binary_op(
'+', self.n, self.m, self.k,
"cpy_r_r0 = CPyTagged_Add(cpy_r_m, cpy_r_k);")
def test_int_sub(self) -> None:
self.assert_emit_binary_op(
'-', self.n, self.m, self.k,
"cpy_r_r0 = CPyTagged_Subtract(cpy_r_m, cpy_r_k);")
def test_int_neg(self) -> None:
self.assert_emit(CallC(int_neg_op.c_function_name, [self.m], int_neg_op.return_type,
int_neg_op.steals, int_neg_op.is_borrowed, int_neg_op.is_borrowed,
int_neg_op.error_kind, 55),
"cpy_r_r0 = CPyTagged_Negate(cpy_r_m);")
def test_branch(self) -> None:
self.assert_emit(Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL),
"""if (cpy_r_b) {
goto CPyL8;
} else
goto CPyL9;
""")
b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL)
b.negated = True
self.assert_emit(b,
"""if (!cpy_r_b) {
goto CPyL8;
} else
goto CPyL9;
""")
def test_branch_no_else(self) -> None:
next_block = BasicBlock(9)
b = Branch(self.b, BasicBlock(8), next_block, Branch.BOOL)
self.assert_emit(b,
"""if (cpy_r_b) goto CPyL8;""",
next_block=next_block)
next_block = BasicBlock(9)
b = Branch(self.b, BasicBlock(8), next_block, Branch.BOOL)
b.negated = True
self.assert_emit(b,
"""if (!cpy_r_b) goto CPyL8;""",
next_block=next_block)
def test_branch_no_else_negated(self) -> None:
next_block = BasicBlock(1)
b = Branch(self.b, next_block, BasicBlock(2), Branch.BOOL)
self.assert_emit(b,
"""if (!cpy_r_b) goto CPyL2;""",
next_block=next_block)
next_block = BasicBlock(1)
b = Branch(self.b, next_block, BasicBlock(2), Branch.BOOL)
b.negated = True
self.assert_emit(b,
"""if (cpy_r_b) goto CPyL2;""",
next_block=next_block)
def test_branch_is_error(self) -> None:
b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR)
self.assert_emit(b,
"""if (cpy_r_b == 2) {
goto CPyL8;
} else
goto CPyL9;
""")
b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR)
b.negated = True
self.assert_emit(b,
"""if (cpy_r_b != 2) {
goto CPyL8;
} else
goto CPyL9;
""")
def test_branch_is_error_next_block(self) -> None:
next_block = BasicBlock(8)
b = Branch(self.b, next_block, BasicBlock(9), Branch.IS_ERROR)
self.assert_emit(b,
"""if (cpy_r_b != 2) goto CPyL9;""",
next_block=next_block)
b = Branch(self.b, next_block, BasicBlock(9), Branch.IS_ERROR)
b.negated = True
self.assert_emit(b,
"""if (cpy_r_b == 2) goto CPyL9;""",
next_block=next_block)
def test_branch_rare(self) -> None:
self.assert_emit(Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL, rare=True),
"""if (unlikely(cpy_r_b)) {
goto CPyL8;
} else
goto CPyL9;
""")
next_block = BasicBlock(9)
self.assert_emit(Branch(self.b, BasicBlock(8), next_block, Branch.BOOL, rare=True),
"""if (unlikely(cpy_r_b)) goto CPyL8;""",
next_block=next_block)
next_block = BasicBlock(8)
b = Branch(self.b, next_block, BasicBlock(9), Branch.BOOL, rare=True)
self.assert_emit(b,
"""if (likely(!cpy_r_b)) goto CPyL9;""",
next_block=next_block)
next_block = BasicBlock(8)
b = Branch(self.b, next_block, BasicBlock(9), Branch.BOOL, rare=True)
b.negated = True
self.assert_emit(b,
"""if (likely(cpy_r_b)) goto CPyL9;""",
next_block=next_block)
def test_call(self) -> None:
decl = FuncDecl('myfn', None, 'mod',
FuncSignature([RuntimeArg('m', int_rprimitive)], int_rprimitive))
self.assert_emit(Call(decl, [self.m], 55),
"cpy_r_r0 = CPyDef_myfn(cpy_r_m);")
def test_call_two_args(self) -> None:
decl = FuncDecl('myfn', None, 'mod',
FuncSignature([RuntimeArg('m', int_rprimitive),
RuntimeArg('n', int_rprimitive)],
int_rprimitive))
self.assert_emit(Call(decl, [self.m, self.k], 55),
"cpy_r_r0 = CPyDef_myfn(cpy_r_m, cpy_r_k);")
def test_inc_ref(self) -> None:
self.assert_emit(IncRef(self.o), "CPy_INCREF(cpy_r_o);")
self.assert_emit(IncRef(self.o), "CPy_INCREF(cpy_r_o);", rare=True)
def test_dec_ref(self) -> None:
self.assert_emit(DecRef(self.o), "CPy_DECREF(cpy_r_o);")
self.assert_emit(DecRef(self.o), "CPy_DecRef(cpy_r_o);", rare=True)
def test_inc_ref_int(self) -> None:
self.assert_emit(IncRef(self.m), "CPyTagged_INCREF(cpy_r_m);")
self.assert_emit(IncRef(self.m), "CPyTagged_IncRef(cpy_r_m);", rare=True)
def test_dec_ref_int(self) -> None:
self.assert_emit(DecRef(self.m), "CPyTagged_DECREF(cpy_r_m);")
self.assert_emit(DecRef(self.m), "CPyTagged_DecRef(cpy_r_m);", rare=True)
def test_dec_ref_tuple(self) -> None:
self.assert_emit(DecRef(self.t), 'CPyTagged_DECREF(cpy_r_t.f0);')
def test_dec_ref_tuple_nested(self) -> None:
self.assert_emit(DecRef(self.tt), 'CPyTagged_DECREF(cpy_r_tt.f0.f0);')
def test_list_get_item(self) -> None:
self.assert_emit(CallC(list_get_item_op.c_function_name, [self.m, self.k],
list_get_item_op.return_type, list_get_item_op.steals,
list_get_item_op.is_borrowed, list_get_item_op.error_kind, 55),
"""cpy_r_r0 = CPyList_GetItem(cpy_r_m, cpy_r_k);""")
def test_list_set_item(self) -> None:
self.assert_emit(CallC(list_set_item_op.c_function_name, [self.l, self.n, self.o],
list_set_item_op.return_type, list_set_item_op.steals,
list_set_item_op.is_borrowed, list_set_item_op.error_kind, 55),
"""cpy_r_r0 = CPyList_SetItem(cpy_r_l, cpy_r_n, cpy_r_o);""")
def test_box(self) -> None:
self.assert_emit(Box(self.n),
"""cpy_r_r0 = CPyTagged_StealAsObject(cpy_r_n);""")
def test_unbox(self) -> None:
self.assert_emit(Unbox(self.m, int_rprimitive, 55),
"""if (likely(PyLong_Check(cpy_r_m)))
cpy_r_r0 = CPyTagged_FromObject(cpy_r_m);
else {
CPy_TypeError("int", cpy_r_m); cpy_r_r0 = CPY_INT_TAG;
}
""")
def test_list_append(self) -> None:
self.assert_emit(CallC(list_append_op.c_function_name, [self.l, self.o],
list_append_op.return_type, list_append_op.steals,
list_append_op.is_borrowed, list_append_op.error_kind, 1),
"""cpy_r_r0 = PyList_Append(cpy_r_l, cpy_r_o);""")
def test_get_attr(self) -> None:
self.assert_emit(
GetAttr(self.r, 'y', 1),
"""cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_y;
if (unlikely(((mod___AObject *)cpy_r_r)->_y == CPY_INT_TAG)) {
PyErr_SetString(PyExc_AttributeError, "attribute 'y' of 'A' undefined");
} else {
CPyTagged_INCREF(((mod___AObject *)cpy_r_r)->_y);
}
""")
def test_get_attr_non_refcounted(self) -> None:
self.assert_emit(
GetAttr(self.r, 'x', 1),
"""cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_x;
if (unlikely(((mod___AObject *)cpy_r_r)->_x == 2)) {
PyErr_SetString(PyExc_AttributeError, "attribute 'x' of 'A' undefined");
}
""")
def test_set_attr(self) -> None:
self.assert_emit(
SetAttr(self.r, 'y', self.m, 1),
"""if (((mod___AObject *)cpy_r_r)->_y != CPY_INT_TAG) {
CPyTagged_DECREF(((mod___AObject *)cpy_r_r)->_y);
}
((mod___AObject *)cpy_r_r)->_y = cpy_r_m;
cpy_r_r0 = 1;
""")
def test_dict_get_item(self) -> None:
self.assert_emit(CallC(dict_get_item_op.c_function_name, [self.d, self.o2],
dict_get_item_op.return_type, dict_get_item_op.steals,
dict_get_item_op.is_borrowed, dict_get_item_op.error_kind, 1),
"""cpy_r_r0 = CPyDict_GetItem(cpy_r_d, cpy_r_o2);""")
def test_dict_set_item(self) -> None:
self.assert_emit(CallC(dict_set_item_op.c_function_name, [self.d, self.o, self.o2],
dict_set_item_op.return_type, dict_set_item_op.steals,
dict_set_item_op.is_borrowed, dict_set_item_op.error_kind, 1),
"""cpy_r_r0 = CPyDict_SetItem(cpy_r_d, cpy_r_o, cpy_r_o2);""")
def test_dict_update(self) -> None:
self.assert_emit(CallC(dict_update_op.c_function_name, [self.d, self.o],
dict_update_op.return_type, dict_update_op.steals,
dict_update_op.is_borrowed, dict_update_op.error_kind, 1),
"""cpy_r_r0 = CPyDict_Update(cpy_r_d, cpy_r_o);""")
def test_new_dict(self) -> None:
self.assert_emit(CallC(dict_new_op.c_function_name, [], dict_new_op.return_type,
dict_new_op.steals, dict_new_op.is_borrowed,
dict_new_op.error_kind, 1),
"""cpy_r_r0 = PyDict_New();""")
def test_dict_contains(self) -> None:
self.assert_emit_binary_op(
'in', self.b, self.o, self.d,
"""cpy_r_r0 = PyDict_Contains(cpy_r_d, cpy_r_o);""")
def test_int_op(self) -> None:
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.ADD, 1),
"""cpy_r_r0 = cpy_r_s1 + cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.SUB, 1),
"""cpy_r_r0 = cpy_r_s1 - cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.MUL, 1),
"""cpy_r_r0 = cpy_r_s1 * cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.DIV, 1),
"""cpy_r_r0 = cpy_r_s1 / cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.MOD, 1),
"""cpy_r_r0 = cpy_r_s1 % cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.AND, 1),
"""cpy_r_r0 = cpy_r_s1 & cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.OR, 1),
"""cpy_r_r0 = cpy_r_s1 | cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.XOR, 1),
"""cpy_r_r0 = cpy_r_s1 ^ cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.LEFT_SHIFT, 1),
"""cpy_r_r0 = cpy_r_s1 << cpy_r_s2;""")
self.assert_emit(IntOp(short_int_rprimitive, self.s1, self.s2, IntOp.RIGHT_SHIFT, 1),
"""cpy_r_r0 = cpy_r_s1 >> cpy_r_s2;""")
def test_comparison_op(self) -> None:
# signed
self.assert_emit(ComparisonOp(self.s1, self.s2, ComparisonOp.SLT, 1),
"""cpy_r_r0 = (Py_ssize_t)cpy_r_s1 < (Py_ssize_t)cpy_r_s2;""")
self.assert_emit(ComparisonOp(self.i32, self.i32_1, ComparisonOp.SLT, 1),
"""cpy_r_r0 = cpy_r_i32 < cpy_r_i32_1;""")
self.assert_emit(ComparisonOp(self.i64, self.i64_1, ComparisonOp.SLT, 1),
"""cpy_r_r0 = cpy_r_i64 < cpy_r_i64_1;""")
# unsigned
self.assert_emit(ComparisonOp(self.s1, self.s2, ComparisonOp.ULT, 1),
"""cpy_r_r0 = cpy_r_s1 < cpy_r_s2;""")
self.assert_emit(ComparisonOp(self.i32, self.i32_1, ComparisonOp.ULT, 1),
"""cpy_r_r0 = (uint32_t)cpy_r_i32 < (uint32_t)cpy_r_i32_1;""")
self.assert_emit(ComparisonOp(self.i64, self.i64_1, ComparisonOp.ULT, 1),
"""cpy_r_r0 = (uint64_t)cpy_r_i64 < (uint64_t)cpy_r_i64_1;""")
# object type
self.assert_emit(ComparisonOp(self.o, self.o2, ComparisonOp.EQ, 1),
"""cpy_r_r0 = cpy_r_o == cpy_r_o2;""")
self.assert_emit(ComparisonOp(self.o, self.o2, ComparisonOp.NEQ, 1),
"""cpy_r_r0 = cpy_r_o != cpy_r_o2;""")
def test_load_mem(self) -> None:
self.assert_emit(LoadMem(bool_rprimitive, self.ptr),
"""cpy_r_r0 = *(char *)cpy_r_ptr;""")
def test_set_mem(self) -> None:
self.assert_emit(SetMem(bool_rprimitive, self.ptr, self.b),
"""*(char *)cpy_r_ptr = cpy_r_b;""")
def test_get_element_ptr(self) -> None:
r = RStruct("Foo", ["b", "i32", "i64"], [bool_rprimitive,
int32_rprimitive, int64_rprimitive])
self.assert_emit(GetElementPtr(self.o, r, "b"),
"""cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->b;""")
self.assert_emit(GetElementPtr(self.o, r, "i32"),
"""cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->i32;""")
self.assert_emit(GetElementPtr(self.o, r, "i64"),
"""cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->i64;""")
def test_load_address(self) -> None:
self.assert_emit(LoadAddress(object_rprimitive, "PyDict_Type"),
"""cpy_r_r0 = (PyObject *)&PyDict_Type;""")
def test_assign_multi(self) -> None:
t = RArray(object_rprimitive, 2)
a = Register(t, 'a')
self.registers.append(a)
self.assert_emit(AssignMulti(a, [self.o, self.o2]),
"""PyObject *cpy_r_a[2] = {cpy_r_o, cpy_r_o2};""")
def test_long_unsigned(self) -> None:
a = Register(int64_rprimitive, 'a')
self.assert_emit(Assign(a, Integer(1 << 31, int64_rprimitive)),
"""cpy_r_a = 2147483648ULL;""")
self.assert_emit(Assign(a, Integer((1 << 31) - 1, int64_rprimitive)),
"""cpy_r_a = 2147483647;""")
def test_long_signed(self) -> None:
a = Register(int64_rprimitive, 'a')
self.assert_emit(Assign(a, Integer(-(1 << 31) + 1, int64_rprimitive)),
"""cpy_r_a = -2147483647;""")
self.assert_emit(Assign(a, Integer(-(1 << 31), int64_rprimitive)),
"""cpy_r_a = -2147483648LL;""")
def assert_emit(self,
op: Op,
expected: str,
next_block: Optional[BasicBlock] = None,
*,
rare: bool = False) -> None:
block = BasicBlock(0)
block.ops.append(op)
value_names = generate_names_for_ir(self.registers, [block])
emitter = Emitter(self.context, value_names)
declarations = Emitter(self.context, value_names)
emitter.fragments = []
declarations.fragments = []
visitor = FunctionEmitterVisitor(emitter, declarations, 'prog.py', 'prog')
visitor.next_block = next_block
visitor.rare = rare
op.accept(visitor)
frags = declarations.fragments + emitter.fragments
actual_lines = [line.strip(' ') for line in frags]
assert all(line.endswith('\n') for line in actual_lines)
actual_lines = [line.rstrip('\n') for line in actual_lines]
if not expected.strip():
expected_lines = []
else:
expected_lines = expected.rstrip().split('\n')
expected_lines = [line.strip(' ') for line in expected_lines]
assert_string_arrays_equal(expected_lines, actual_lines,
msg='Generated code unexpected')
def assert_emit_binary_op(self,
op: str,
dest: Value,
left: Value,
right: Value,
expected: str) -> None:
if op in binary_ops:
ops = binary_ops[op]
for desc in ops:
if (is_subtype(left.type, desc.arg_types[0])
and is_subtype(right.type, desc.arg_types[1])):
args = [left, right]
if desc.ordering is not None:
args = [args[i] for i in desc.ordering]
self.assert_emit(CallC(desc.c_function_name, args, desc.return_type,
desc.steals, desc.is_borrowed,
desc.error_kind, 55), expected)
return
else:
assert False, 'Could not find matching op'
class TestGenerateFunction(unittest.TestCase):
def setUp(self) -> None:
self.arg = RuntimeArg('arg', int_rprimitive)
self.reg = Register(int_rprimitive, 'arg')
self.block = BasicBlock(0)
def test_simple(self) -> None:
self.block.ops.append(Return(self.reg))
fn = FuncIR(FuncDecl('myfunc', None, 'mod', FuncSignature([self.arg], int_rprimitive)),
[self.reg],
[self.block])
value_names = generate_names_for_ir(fn.arg_regs, fn.blocks)
emitter = Emitter(EmitterContext(NameGenerator([['mod']])), value_names)
generate_native_function(fn, emitter, 'prog.py', 'prog')
result = emitter.fragments
assert_string_arrays_equal(
[
'CPyTagged CPyDef_myfunc(CPyTagged cpy_r_arg) {\n',
'CPyL0: ;\n',
' return cpy_r_arg;\n',
'}\n',
],
result, msg='Generated code invalid')
def test_register(self) -> None:
reg = Register(int_rprimitive)
op = Assign(reg, Integer(5))
self.block.ops.append(op)
self.block.ops.append(Unreachable())
fn = FuncIR(FuncDecl('myfunc', None, 'mod', FuncSignature([self.arg], list_rprimitive)),
[self.reg],
[self.block])
value_names = generate_names_for_ir(fn.arg_regs, fn.blocks)
emitter = Emitter(EmitterContext(NameGenerator([['mod']])), value_names)
generate_native_function(fn, emitter, 'prog.py', 'prog')
result = emitter.fragments
assert_string_arrays_equal(
[
'PyObject *CPyDef_myfunc(CPyTagged cpy_r_arg) {\n',
' CPyTagged cpy_r_r0;\n',
'CPyL0: ;\n',
' cpy_r_r0 = 10;\n',
' CPy_Unreachable();\n',
'}\n',
],
result, msg='Generated code invalid')

View file

@ -0,0 +1,53 @@
import unittest
from typing import List
from mypy.test.helpers import assert_string_arrays_equal
from mypyc.codegen.emit import Emitter, EmitterContext, ReturnHandler
from mypyc.codegen.emitwrapper import generate_arg_check
from mypyc.ir.rtypes import list_rprimitive, int_rprimitive
from mypyc.namegen import NameGenerator
class TestArgCheck(unittest.TestCase):
def setUp(self) -> None:
self.context = EmitterContext(NameGenerator([['mod']]))
def test_check_list(self) -> None:
emitter = Emitter(self.context)
generate_arg_check('x', list_rprimitive, emitter, ReturnHandler('NULL'))
lines = emitter.fragments
self.assert_lines([
'PyObject *arg_x;',
'if (likely(PyList_Check(obj_x)))',
' arg_x = obj_x;',
'else {',
' CPy_TypeError("list", obj_x); return NULL;',
'}',
], lines)
def test_check_int(self) -> None:
emitter = Emitter(self.context)
generate_arg_check('x', int_rprimitive, emitter, ReturnHandler('NULL'))
generate_arg_check('y', int_rprimitive, emitter, ReturnHandler('NULL'), optional=True)
lines = emitter.fragments
self.assert_lines([
'CPyTagged arg_x;',
'if (likely(PyLong_Check(obj_x)))',
' arg_x = CPyTagged_BorrowFromObject(obj_x);',
'else {',
' CPy_TypeError("int", obj_x); return NULL;',
'}',
'CPyTagged arg_y;',
'if (obj_y == NULL) {',
' arg_y = CPY_INT_TAG;',
'} else if (likely(PyLong_Check(obj_y)))',
' arg_y = CPyTagged_BorrowFromObject(obj_y);',
'else {',
' CPy_TypeError("int", obj_y); return NULL;',
'}',
], lines)
def assert_lines(self, expected: List[str], actual: List[str]) -> None:
actual = [line.rstrip('\n') for line in actual]
assert_string_arrays_equal(expected, actual, 'Invalid output')

View file

@ -0,0 +1,56 @@
"""Test runner for exception handling transform test cases.
The transform inserts exception handling branch operations to IR.
"""
import os.path
from mypy.test.config import test_temp_dir
from mypy.test.data import DataDrivenTestCase
from mypy.errors import CompileError
from mypyc.common import TOP_LEVEL_NAME
from mypyc.ir.pprint import format_func
from mypyc.transform.uninit import insert_uninit_checks
from mypyc.transform.exceptions import insert_exception_handling
from mypyc.transform.refcount import insert_ref_count_opcodes
from mypyc.test.testutil import (
ICODE_GEN_BUILTINS, use_custom_builtins, MypycDataSuite, build_ir_for_single_file,
assert_test_output, remove_comment_lines
)
from mypyc.analysis.blockfreq import frequently_executed_blocks
files = [
'exceptions.test',
'exceptions-freq.test',
]
class TestExceptionTransform(MypycDataSuite):
files = files
base_path = test_temp_dir
def run_case(self, testcase: DataDrivenTestCase) -> None:
"""Perform a runtime checking transformation test case."""
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
expected_output = remove_comment_lines(testcase.output)
try:
ir = build_ir_for_single_file(testcase.input)
except CompileError as e:
actual = e.messages
else:
actual = []
for fn in ir:
if (fn.name == TOP_LEVEL_NAME
and not testcase.name.endswith('_toplevel')):
continue
insert_uninit_checks(fn)
insert_exception_handling(fn)
insert_ref_count_opcodes(fn)
actual.extend(format_func(fn))
if testcase.name.endswith('_freq'):
common = frequently_executed_blocks(fn.blocks[0])
actual.append('hot blocks: %s' % sorted(b.label for b in common))
assert_test_output(testcase, actual, 'Invalid source code output',
expected_output)

View file

@ -0,0 +1,47 @@
"""Test cases that run tests as subprocesses."""
from typing import List
import os
import subprocess
import sys
import unittest
base_dir = os.path.join(os.path.dirname(__file__), '..', '..')
class TestExternal(unittest.TestCase):
# TODO: Get this to work on Windows.
# (Or don't. It is probably not a good use of time.)
@unittest.skipIf(sys.platform.startswith("win"), "rt tests don't work on windows")
def test_c_unit_test(self) -> None:
"""Run C unit tests in a subprocess."""
# Build Google Test, the C++ framework we use for testing C code.
# The source code for Google Test is copied to this repository.
cppflags: List[str] = []
env = os.environ.copy()
if sys.platform == 'darwin':
cppflags += ['-mmacosx-version-min=10.10', '-stdlib=libc++']
env['CPPFLAGS'] = ' '.join(cppflags)
subprocess.check_call(
['make', 'libgtest.a'],
env=env,
cwd=os.path.join(base_dir, 'mypyc', 'external', 'googletest', 'make'))
# Build Python wrapper for C unit tests.
env = os.environ.copy()
env['CPPFLAGS'] = ' '.join(cppflags)
status = subprocess.check_call(
[sys.executable, 'setup.py', 'build_ext', '--inplace'],
env=env,
cwd=os.path.join(base_dir, 'mypyc', 'lib-rt'))
# Run C unit tests.
env = os.environ.copy()
if 'GTEST_COLOR' not in os.environ:
env['GTEST_COLOR'] = 'yes' # Use fancy colors
status = subprocess.call([sys.executable, '-c',
'import sys, test_capi; sys.exit(test_capi.run_tests())'],
env=env,
cwd=os.path.join(base_dir, 'mypyc', 'lib-rt'))
if status != 0:
raise AssertionError("make test: C unit test failure")

View file

@ -0,0 +1,71 @@
"""Test cases for IR generation."""
import os.path
from mypy.test.config import test_temp_dir
from mypy.test.data import DataDrivenTestCase
from mypy.errors import CompileError
from mypyc.common import TOP_LEVEL_NAME
from mypyc.ir.pprint import format_func
from mypyc.test.testutil import (
ICODE_GEN_BUILTINS, use_custom_builtins, MypycDataSuite, build_ir_for_single_file,
assert_test_output, remove_comment_lines, replace_word_size,
infer_ir_build_options_from_test_name
)
files = [
'irbuild-basic.test',
'irbuild-int.test',
'irbuild-lists.test',
'irbuild-tuple.test',
'irbuild-dict.test',
'irbuild-set.test',
'irbuild-str.test',
'irbuild-bytes.test',
'irbuild-statements.test',
'irbuild-nested.test',
'irbuild-classes.test',
'irbuild-optional.test',
'irbuild-any.test',
'irbuild-generics.test',
'irbuild-try.test',
'irbuild-strip-asserts.test',
'irbuild-vectorcall.test',
'irbuild-unreachable.test',
'irbuild-isinstance.test',
'irbuild-dunders.test',
'irbuild-singledispatch.test',
'irbuild-constant-fold.test',
]
class TestGenOps(MypycDataSuite):
files = files
base_path = test_temp_dir
optional_out = True
def run_case(self, testcase: DataDrivenTestCase) -> None:
"""Perform a runtime checking transformation test case."""
options = infer_ir_build_options_from_test_name(testcase.name)
if options is None:
# Skipped test case
return
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
expected_output = remove_comment_lines(testcase.output)
expected_output = replace_word_size(expected_output)
name = testcase.name
try:
ir = build_ir_for_single_file(testcase.input, options)
except CompileError as e:
actual = e.messages
else:
actual = []
for fn in ir:
if (fn.name == TOP_LEVEL_NAME
and not name.endswith('_toplevel')):
continue
actual.extend(format_func(fn))
assert_test_output(testcase, actual, 'Invalid source code output',
expected_output)

View file

@ -0,0 +1,216 @@
import unittest
from typing import List, Optional
from mypyc.analysis.ircheck import check_func_ir, FnError, can_coerce_to
from mypyc.ir.class_ir import ClassIR
from mypyc.ir.rtypes import (
none_rprimitive, str_rprimitive, int32_rprimitive, int64_rprimitive,
RType, RUnion, bytes_rprimitive, RInstance, object_rprimitive
)
from mypyc.ir.ops import (
BasicBlock, Op, Return, Integer, Goto, Register, LoadLiteral, Assign
)
from mypyc.ir.func_ir import FuncIR, FuncDecl, FuncSignature
from mypyc.ir.pprint import format_func
def assert_has_error(fn: FuncIR, error: FnError) -> None:
errors = check_func_ir(fn)
assert errors == [error]
def assert_no_errors(fn: FuncIR) -> None:
assert not check_func_ir(fn)
NONE_VALUE = Integer(0, rtype=none_rprimitive)
class TestIrcheck(unittest.TestCase):
def setUp(self) -> None:
self.label = 0
def basic_block(self, ops: List[Op]) -> BasicBlock:
self.label += 1
block = BasicBlock(self.label)
block.ops = ops
return block
def func_decl(self, name: str, ret_type: Optional[RType] = None) -> FuncDecl:
if ret_type is None:
ret_type = none_rprimitive
return FuncDecl(
name=name,
class_name=None,
module_name="module",
sig=FuncSignature(
args=[],
ret_type=ret_type,
),
)
def test_valid_fn(self) -> None:
assert_no_errors(
FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[],
blocks=[
self.basic_block(
ops=[
Return(value=NONE_VALUE),
]
)
],
)
)
def test_block_not_terminated_empty_block(self) -> None:
block = self.basic_block([])
fn = FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[],
blocks=[block],
)
assert_has_error(fn, FnError(source=block, desc="Block not terminated"))
def test_valid_goto(self) -> None:
block_1 = self.basic_block([Return(value=NONE_VALUE)])
block_2 = self.basic_block([Goto(label=block_1)])
fn = FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[],
blocks=[block_1, block_2],
)
assert_no_errors(fn)
def test_invalid_goto(self) -> None:
block_1 = self.basic_block([Return(value=NONE_VALUE)])
goto = Goto(label=block_1)
block_2 = self.basic_block([goto])
fn = FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[],
# block_1 omitted
blocks=[block_2],
)
assert_has_error(
fn, FnError(source=goto, desc="Invalid control operation target: 1")
)
def test_invalid_register_source(self) -> None:
ret = Return(
value=Register(
type=none_rprimitive,
name="r1",
)
)
block = self.basic_block([ret])
fn = FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[],
blocks=[block],
)
assert_has_error(
fn, FnError(source=ret, desc="Invalid op reference to register r1")
)
def test_invalid_op_source(self) -> None:
ret = Return(
value=LoadLiteral(
value="foo",
rtype=str_rprimitive,
)
)
block = self.basic_block([ret])
fn = FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[],
blocks=[block],
)
assert_has_error(
fn,
FnError(source=ret, desc="Invalid op reference to op of type LoadLiteral"),
)
def test_invalid_return_type(self) -> None:
ret = Return(value=Integer(value=5, rtype=int32_rprimitive))
fn = FuncIR(
decl=self.func_decl(name="func_1", ret_type=int64_rprimitive),
arg_regs=[],
blocks=[self.basic_block([ret])],
)
assert_has_error(
fn,
FnError(
source=ret, desc="Cannot coerce source type int32 to dest type int64"
),
)
def test_invalid_assign(self) -> None:
arg_reg = Register(type=int64_rprimitive, name="r1")
assign = Assign(dest=arg_reg, src=Integer(value=5, rtype=int32_rprimitive))
ret = Return(value=NONE_VALUE)
fn = FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[arg_reg],
blocks=[self.basic_block([assign, ret])],
)
assert_has_error(
fn,
FnError(
source=assign, desc="Cannot coerce source type int32 to dest type int64"
),
)
def test_can_coerce_to(self) -> None:
cls = ClassIR(name="Cls", module_name="cls")
valid_cases = [
(int64_rprimitive, int64_rprimitive),
(str_rprimitive, str_rprimitive),
(str_rprimitive, object_rprimitive),
(object_rprimitive, str_rprimitive),
(RUnion([bytes_rprimitive, str_rprimitive]), str_rprimitive),
(str_rprimitive, RUnion([bytes_rprimitive, str_rprimitive])),
(RInstance(cls), object_rprimitive),
]
invalid_cases = [
(int64_rprimitive, int32_rprimitive),
(RInstance(cls), str_rprimitive),
(str_rprimitive, bytes_rprimitive),
]
for src, dest in valid_cases:
assert can_coerce_to(src, dest)
for src, dest in invalid_cases:
assert not can_coerce_to(src, dest)
def test_duplicate_op(self) -> None:
arg_reg = Register(type=int32_rprimitive, name="r1")
assign = Assign(dest=arg_reg, src=Integer(value=5, rtype=int32_rprimitive))
block = self.basic_block([assign, assign, Return(value=NONE_VALUE)])
fn = FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[],
blocks=[block],
)
assert_has_error(fn, FnError(source=assign, desc="Func has a duplicate op"))
def test_pprint(self) -> None:
block_1 = self.basic_block([Return(value=NONE_VALUE)])
goto = Goto(label=block_1)
block_2 = self.basic_block([goto])
fn = FuncIR(
decl=self.func_decl(name="func_1"),
arg_regs=[],
# block_1 omitted
blocks=[block_2],
)
errors = [(goto, "Invalid control operation target: 1")]
formatted = format_func(fn, errors)
assert formatted == [
"def func_1():",
"L0:",
" goto L1",
" ERR: Invalid control operation target: 1",
]

View file

@ -0,0 +1,87 @@
"""Test code geneneration for literals."""
import unittest
from mypyc.codegen.literals import (
Literals, format_str_literal, _encode_str_values, _encode_bytes_values, _encode_int_values
)
class TestLiterals(unittest.TestCase):
def test_format_str_literal(self) -> None:
assert format_str_literal('') == b'\x00'
assert format_str_literal('xyz') == b'\x03xyz'
assert format_str_literal('x' * 127) == b'\x7f' + b'x' * 127
assert format_str_literal('x' * 128) == b'\x81\x00' + b'x' * 128
assert format_str_literal('x' * 131) == b'\x81\x03' + b'x' * 131
def test_encode_str_values(self) -> None:
assert _encode_str_values({}) == [b'']
assert _encode_str_values({'foo': 0}) == [b'\x01\x03foo', b'']
assert _encode_str_values({'foo': 0, 'b': 1}) == [b'\x02\x03foo\x01b', b'']
assert _encode_str_values({'foo': 0, 'x' * 70: 1}) == [
b'\x01\x03foo',
bytes([1, 70]) + b'x' * 70,
b''
]
assert _encode_str_values({'y' * 100: 0}) == [
bytes([1, 100]) + b'y' * 100,
b''
]
def test_encode_bytes_values(self) -> None:
assert _encode_bytes_values({}) == [b'']
assert _encode_bytes_values({b'foo': 0}) == [b'\x01\x03foo', b'']
assert _encode_bytes_values({b'foo': 0, b'b': 1}) == [b'\x02\x03foo\x01b', b'']
assert _encode_bytes_values({b'foo': 0, b'x' * 70: 1}) == [
b'\x01\x03foo',
bytes([1, 70]) + b'x' * 70,
b''
]
assert _encode_bytes_values({b'y' * 100: 0}) == [
bytes([1, 100]) + b'y' * 100,
b''
]
def test_encode_int_values(self) -> None:
assert _encode_int_values({}) == [b'']
assert _encode_int_values({123: 0}) == [b'\x01123', b'']
assert _encode_int_values({123: 0, 9: 1}) == [b'\x02123\x009', b'']
assert _encode_int_values({123: 0, 45: 1, 5 * 10**70: 2}) == [
b'\x02123\x0045',
b'\x015' + b'0' * 70,
b''
]
assert _encode_int_values({6 * 10**100: 0}) == [
b'\x016' + b'0' * 100,
b''
]
def test_simple_literal_index(self) -> None:
lit = Literals()
lit.record_literal(1)
lit.record_literal('y')
lit.record_literal(True)
lit.record_literal(None)
lit.record_literal(False)
assert lit.literal_index(None) == 0
assert lit.literal_index(False) == 1
assert lit.literal_index(True) == 2
assert lit.literal_index('y') == 3
assert lit.literal_index(1) == 4
def test_tuple_literal(self) -> None:
lit = Literals()
lit.record_literal((1, 'y', None, (b'a', 'b')))
lit.record_literal((b'a', 'b'))
lit.record_literal(())
assert lit.literal_index((b'a', 'b')) == 7
assert lit.literal_index((1, 'y', None, (b'a', 'b'))) == 8
assert lit.literal_index(()) == 9
print(lit.encoded_tuple_values())
assert lit.encoded_tuple_values() == [
'3', # Number of tuples
'2', '5', '4', # First tuple (length=2)
'4', '6', '3', '0', '7', # Second tuple (length=4)
'0', # Third tuple (length=0)
]

View file

@ -0,0 +1,40 @@
import unittest
from mypyc.namegen import (
NameGenerator, exported_name, candidate_suffixes, make_module_translation_map
)
class TestNameGen(unittest.TestCase):
def test_candidate_suffixes(self) -> None:
assert candidate_suffixes('foo') == ['', 'foo.']
assert candidate_suffixes('foo.bar') == ['', 'bar.', 'foo.bar.']
def test_exported_name(self) -> None:
assert exported_name('foo') == 'foo'
assert exported_name('foo.bar') == 'foo___bar'
def test_make_module_translation_map(self) -> None:
assert make_module_translation_map(
['foo', 'bar']) == {'foo': 'foo.', 'bar': 'bar.'}
assert make_module_translation_map(
['foo.bar', 'foo.baz']) == {'foo.bar': 'bar.', 'foo.baz': 'baz.'}
assert make_module_translation_map(
['zar', 'foo.bar', 'foo.baz']) == {'foo.bar': 'bar.',
'foo.baz': 'baz.',
'zar': 'zar.'}
assert make_module_translation_map(
['foo.bar', 'fu.bar', 'foo.baz']) == {'foo.bar': 'foo.bar.',
'fu.bar': 'fu.bar.',
'foo.baz': 'baz.'}
def test_name_generator(self) -> None:
g = NameGenerator([['foo', 'foo.zar']])
assert g.private_name('foo', 'f') == 'foo___f'
assert g.private_name('foo', 'C.x.y') == 'foo___C___x___y'
assert g.private_name('foo', 'C.x.y') == 'foo___C___x___y'
assert g.private_name('foo.zar', 'C.x.y') == 'zar___C___x___y'
assert g.private_name('foo', 'C.x_y') == 'foo___C___x_y'
assert g.private_name('foo', 'C_x_y') == 'foo___C_x_y'
assert g.private_name('foo', 'C_x_y') == 'foo___C_x_y'
assert g.private_name('foo', '___') == 'foo______3_'

View file

@ -0,0 +1,41 @@
import unittest
from typing import List
from mypyc.ir.ops import BasicBlock, Register, Op, Integer, IntOp, Unreachable, Assign
from mypyc.ir.rtypes import int_rprimitive
from mypyc.ir.pprint import generate_names_for_ir
def register(name: str) -> Register:
return Register(int_rprimitive, 'foo', is_arg=True)
def make_block(ops: List[Op]) -> BasicBlock:
block = BasicBlock()
block.ops.extend(ops)
return block
class TestGenerateNames(unittest.TestCase):
def test_empty(self) -> None:
assert generate_names_for_ir([], []) == {}
def test_arg(self) -> None:
reg = register('foo')
assert generate_names_for_ir([reg], []) == {reg: 'foo'}
def test_int_op(self) -> None:
n1 = Integer(2)
n2 = Integer(4)
op1 = IntOp(int_rprimitive, n1, n2, IntOp.ADD)
op2 = IntOp(int_rprimitive, op1, n2, IntOp.ADD)
block = make_block([op1, op2, Unreachable()])
assert generate_names_for_ir([], [block]) == {op1: 'r0', op2: 'r1'}
def test_assign(self) -> None:
reg = register('foo')
n = Integer(2)
op1 = Assign(reg, n)
op2 = Assign(reg, n)
block = make_block([op1, op2])
assert generate_names_for_ir([reg], [block]) == {reg: 'foo'}

View file

@ -0,0 +1,42 @@
"""Unit tests for RArray types."""
import unittest
from mypyc.common import PLATFORM_SIZE
from mypyc.ir.rtypes import (
RArray, int_rprimitive, bool_rprimitive, compute_rtype_alignment, compute_rtype_size
)
class TestRArray(unittest.TestCase):
def test_basics(self) -> None:
a = RArray(int_rprimitive, 10)
assert a.item_type == int_rprimitive
assert a.length == 10
def test_str_conversion(self) -> None:
a = RArray(int_rprimitive, 10)
assert str(a) == 'int[10]'
assert repr(a) == '<RArray <RPrimitive builtins.int>[10]>'
def test_eq(self) -> None:
a = RArray(int_rprimitive, 10)
assert a == RArray(int_rprimitive, 10)
assert a != RArray(bool_rprimitive, 10)
assert a != RArray(int_rprimitive, 9)
def test_hash(self) -> None:
assert hash(RArray(int_rprimitive, 10)) == hash(RArray(int_rprimitive, 10))
assert hash(RArray(bool_rprimitive, 5)) == hash(RArray(bool_rprimitive, 5))
def test_alignment(self) -> None:
a = RArray(int_rprimitive, 10)
assert compute_rtype_alignment(a) == PLATFORM_SIZE
b = RArray(bool_rprimitive, 55)
assert compute_rtype_alignment(b) == 1
def test_size(self) -> None:
a = RArray(int_rprimitive, 9)
assert compute_rtype_size(a) == 9 * PLATFORM_SIZE
b = RArray(bool_rprimitive, 3)
assert compute_rtype_size(b) == 3

View file

@ -0,0 +1,57 @@
"""Test runner for reference count opcode insertion transform test cases.
The transform inserts needed reference count increment/decrement
operations to IR.
"""
import os.path
from mypy.test.config import test_temp_dir
from mypy.test.data import DataDrivenTestCase
from mypy.errors import CompileError
from mypyc.common import TOP_LEVEL_NAME
from mypyc.ir.pprint import format_func
from mypyc.transform.refcount import insert_ref_count_opcodes
from mypyc.transform.uninit import insert_uninit_checks
from mypyc.test.testutil import (
ICODE_GEN_BUILTINS, use_custom_builtins, MypycDataSuite, build_ir_for_single_file,
assert_test_output, remove_comment_lines, replace_word_size,
infer_ir_build_options_from_test_name
)
files = [
'refcount.test'
]
class TestRefCountTransform(MypycDataSuite):
files = files
base_path = test_temp_dir
optional_out = True
def run_case(self, testcase: DataDrivenTestCase) -> None:
"""Perform a runtime checking transformation test case."""
options = infer_ir_build_options_from_test_name(testcase.name)
if options is None:
# Skipped test case
return
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase):
expected_output = remove_comment_lines(testcase.output)
expected_output = replace_word_size(expected_output)
try:
ir = build_ir_for_single_file(testcase.input, options)
except CompileError as e:
actual = e.messages
else:
actual = []
for fn in ir:
if (fn.name == TOP_LEVEL_NAME
and not testcase.name.endswith('_toplevel')):
continue
insert_uninit_checks(fn)
insert_ref_count_opcodes(fn)
actual.extend(format_func(fn))
assert_test_output(testcase, actual, 'Invalid source code output',
expected_output)

View file

@ -0,0 +1,410 @@
"""Test cases for building an C extension and running it."""
import ast
import glob
import os.path
import platform
import re
import subprocess
import contextlib
import shutil
import sys
from typing import Any, Iterator, List, cast
from mypy import build
from mypy.test.data import DataDrivenTestCase
from mypy.test.config import test_temp_dir
from mypy.errors import CompileError
from mypy.options import Options
from mypy.test.helpers import assert_module_equivalence, perform_file_operations
from mypyc.codegen import emitmodule
from mypyc.options import CompilerOptions
from mypyc.errors import Errors
from mypyc.build import construct_groups
from mypyc.test.testutil import (
ICODE_GEN_BUILTINS, TESTUTIL_PATH,
use_custom_builtins, MypycDataSuite, assert_test_output,
show_c, fudge_dir_mtimes,
)
from mypyc.test.test_serialization import check_serialization_roundtrip
files = [
'run-misc.test',
'run-functions.test',
'run-integers.test',
'run-floats.test',
'run-bools.test',
'run-strings.test',
'run-bytes.test',
'run-tuples.test',
'run-lists.test',
'run-dicts.test',
'run-sets.test',
'run-primitives.test',
'run-loops.test',
'run-exceptions.test',
'run-imports.test',
'run-classes.test',
'run-traits.test',
'run-generators.test',
'run-multimodule.test',
'run-bench.test',
'run-mypy-sim.test',
'run-dunders.test',
'run-singledispatch.test',
'run-attrs.test',
]
if sys.version_info >= (3, 7):
files.append('run-python37.test')
if sys.version_info >= (3, 8):
files.append('run-python38.test')
setup_format = """\
from setuptools import setup
from mypyc.build import mypycify
setup(name='test_run_output',
ext_modules=mypycify({}, separate={}, skip_cgen_input={!r}, strip_asserts=False,
multi_file={}, opt_level='{}'),
)
"""
WORKDIR = 'build'
def run_setup(script_name: str, script_args: List[str]) -> bool:
"""Run a setup script in a somewhat controlled environment.
This is adapted from code in distutils and our goal here is that is
faster to not need to spin up a python interpreter to run it.
We had to fork it because the real run_setup swallows errors
and KeyboardInterrupt with no way to recover them (!).
The real version has some extra features that we removed since
we weren't using them.
Returns whether the setup succeeded.
"""
save_argv = sys.argv.copy()
g = {'__file__': script_name}
try:
try:
sys.argv[0] = script_name
sys.argv[1:] = script_args
with open(script_name, 'rb') as f:
exec(f.read(), g)
finally:
sys.argv = save_argv
except SystemExit as e:
# typeshed reports code as being an int but that is wrong
code = cast(Any, e).code
# distutils converts KeyboardInterrupt into a SystemExit with
# "interrupted" as the argument. Convert it back so that
# pytest will exit instead of just failing the test.
if code == "interrupted":
raise KeyboardInterrupt from e
return code == 0 or code is None
return True
@contextlib.contextmanager
def chdir_manager(target: str) -> Iterator[None]:
dir = os.getcwd()
os.chdir(target)
try:
yield
finally:
os.chdir(dir)
class TestRun(MypycDataSuite):
"""Test cases that build a C extension and run code."""
files = files
base_path = test_temp_dir
optional_out = True
multi_file = False
separate = False # If True, using separate (incremental) compilation
def run_case(self, testcase: DataDrivenTestCase) -> None:
# setup.py wants to be run from the root directory of the package, which we accommodate
# by chdiring into tmp/
with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase), (
chdir_manager('tmp')):
self.run_case_inner(testcase)
def run_case_inner(self, testcase: DataDrivenTestCase) -> None:
if not os.path.isdir(WORKDIR): # (one test puts something in build...)
os.mkdir(WORKDIR)
text = '\n'.join(testcase.input)
with open('native.py', 'w', encoding='utf-8') as f:
f.write(text)
with open('interpreted.py', 'w', encoding='utf-8') as f:
f.write(text)
shutil.copyfile(TESTUTIL_PATH, 'testutil.py')
step = 1
self.run_case_step(testcase, step)
steps = testcase.find_steps()
if steps == [[]]:
steps = []
for operations in steps:
# To make sure that any new changes get picked up as being
# new by distutils, shift the mtime of all of the
# generated artifacts back by a second.
fudge_dir_mtimes(WORKDIR, -1)
step += 1
with chdir_manager('..'):
perform_file_operations(operations)
self.run_case_step(testcase, step)
def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> None:
bench = testcase.config.getoption('--bench', False) and 'Benchmark' in testcase.name
options = Options()
options.use_builtins_fixtures = True
options.show_traceback = True
options.strict_optional = True
# N.B: We try to (and ought to!) run with the current
# version of python, since we are going to link and run
# against the current version of python.
# But a lot of the tests use type annotations so we can't say it is 3.5.
options.python_version = max(sys.version_info[:2], (3, 6))
options.export_types = True
options.preserve_asts = True
options.incremental = self.separate
# Avoid checking modules/packages named 'unchecked', to provide a way
# to test interacting with code we don't have types for.
options.per_module_options['unchecked.*'] = {'follow_imports': 'error'}
source = build.BuildSource('native.py', 'native', None)
sources = [source]
module_names = ['native']
module_paths = ['native.py']
# Hard code another module name to compile in the same compilation unit.
to_delete = []
for fn, text in testcase.files:
fn = os.path.relpath(fn, test_temp_dir)
if os.path.basename(fn).startswith('other') and fn.endswith('.py'):
name = fn.split('.')[0].replace(os.sep, '.')
module_names.append(name)
sources.append(build.BuildSource(fn, name, None))
to_delete.append(fn)
module_paths.append(fn)
shutil.copyfile(fn,
os.path.join(os.path.dirname(fn), name + '_interpreted.py'))
for source in sources:
options.per_module_options.setdefault(source.module, {})['mypyc'] = True
separate = (self.get_separate('\n'.join(testcase.input), incremental_step) if self.separate
else False)
groups = construct_groups(sources, separate, len(module_names) > 1)
try:
compiler_options = CompilerOptions(multi_file=self.multi_file, separate=self.separate)
result = emitmodule.parse_and_typecheck(
sources=sources,
options=options,
compiler_options=compiler_options,
groups=groups,
alt_lib_path='.')
errors = Errors()
ir, cfiles = emitmodule.compile_modules_to_c(
result,
compiler_options=compiler_options,
errors=errors,
groups=groups,
)
if errors.num_errors:
errors.flush_errors()
assert False, "Compile error"
except CompileError as e:
for line in e.messages:
print(fix_native_line_number(line, testcase.file, testcase.line))
assert False, 'Compile error'
# Check that serialization works on this IR. (Only on the first
# step because the the returned ir only includes updated code.)
if incremental_step == 1:
check_serialization_roundtrip(ir)
opt_level = int(os.environ.get('MYPYC_OPT_LEVEL', 0))
debug_level = int(os.environ.get('MYPYC_DEBUG_LEVEL', 0))
setup_file = os.path.abspath(os.path.join(WORKDIR, 'setup.py'))
# We pass the C file information to the build script via setup.py unfortunately
with open(setup_file, 'w', encoding='utf-8') as f:
f.write(setup_format.format(module_paths,
separate,
cfiles,
self.multi_file,
opt_level,
debug_level))
if not run_setup(setup_file, ['build_ext', '--inplace']):
if testcase.config.getoption('--mypyc-showc'):
show_c(cfiles)
assert False, "Compilation failed"
# Assert that an output file got created
suffix = 'pyd' if sys.platform == 'win32' else 'so'
assert glob.glob('native.*.{}'.format(suffix))
driver_path = 'driver.py'
if not os.path.isfile(driver_path):
# No driver.py provided by test case. Use the default one
# (mypyc/test-data/driver/driver.py) that calls each
# function named test_*.
default_driver = os.path.join(
os.path.dirname(__file__), '..', 'test-data', 'driver', 'driver.py')
shutil.copy(default_driver, driver_path)
env = os.environ.copy()
env['MYPYC_RUN_BENCH'] = '1' if bench else '0'
# XXX: This is an ugly hack.
if 'MYPYC_RUN_GDB' in os.environ:
if platform.system() == 'Darwin':
subprocess.check_call(['lldb', '--', sys.executable, driver_path], env=env)
assert False, ("Test can't pass in lldb mode. (And remember to pass -s to "
"pytest)")
elif platform.system() == 'Linux':
subprocess.check_call(['gdb', '--args', sys.executable, driver_path], env=env)
assert False, ("Test can't pass in gdb mode. (And remember to pass -s to "
"pytest)")
else:
assert False, 'Unsupported OS'
proc = subprocess.Popen([sys.executable, driver_path], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, env=env)
output = proc.communicate()[0].decode('utf8')
outlines = output.splitlines()
if testcase.config.getoption('--mypyc-showc'):
show_c(cfiles)
if proc.returncode != 0:
print()
print('*** Exit status: %d' % proc.returncode)
# Verify output.
if bench:
print('Test output:')
print(output)
else:
if incremental_step == 1:
msg = 'Invalid output'
expected = testcase.output
else:
msg = 'Invalid output (step {})'.format(incremental_step)
expected = testcase.output2.get(incremental_step, [])
if not expected:
# Tweak some line numbers, but only if the expected output is empty,
# as tweaked output might not match expected output.
outlines = [fix_native_line_number(line, testcase.file, testcase.line)
for line in outlines]
assert_test_output(testcase, outlines, msg, expected)
if incremental_step > 1 and options.incremental:
suffix = '' if incremental_step == 2 else str(incremental_step - 1)
expected_rechecked = testcase.expected_rechecked_modules.get(incremental_step - 1)
if expected_rechecked is not None:
assert_module_equivalence(
'rechecked' + suffix,
expected_rechecked, result.manager.rechecked_modules)
expected_stale = testcase.expected_stale_modules.get(incremental_step - 1)
if expected_stale is not None:
assert_module_equivalence(
'stale' + suffix,
expected_stale, result.manager.stale_modules)
assert proc.returncode == 0
def get_separate(self, program_text: str,
incremental_step: int) -> Any:
template = r'# separate{}: (\[.*\])$'
m = re.search(template.format(incremental_step), program_text, flags=re.MULTILINE)
if not m:
m = re.search(template.format(''), program_text, flags=re.MULTILINE)
if m:
return ast.literal_eval(m.group(1))
else:
return True
class TestRunMultiFile(TestRun):
"""Run the main multi-module tests in multi-file compilation mode.
In multi-file mode each module gets compiled into a separate C file,
but all modules (C files) are compiled together.
"""
multi_file = True
test_name_suffix = '_multi'
files = [
'run-multimodule.test',
'run-mypy-sim.test',
]
class TestRunSeparate(TestRun):
"""Run the main multi-module tests in separate compilation mode.
In this mode there are multiple compilation groups, which are compiled
incrementally. Each group is compiled to a separate C file, and these C
files are compiled separately.
Each compiled module is placed into a separate compilation group, unless
overridden by a special comment. Consider this example:
# separate: [(["other.py", "other_b.py"], "stuff")]
This puts other.py and other_b.py into a compilation group named "stuff".
Any files not mentioned in the comment will get single-file groups.
"""
separate = True
test_name_suffix = '_separate'
files = [
'run-multimodule.test',
'run-mypy-sim.test',
]
def fix_native_line_number(message: str, fnam: str, delta: int) -> str:
"""Update code locations in test case output to point to the .test file.
The description of the test case is written to native.py, and line numbers
in test case output often are relative to native.py. This translates the
line numbers to be relative to the .test file that contains the test case
description, and also updates the file name to the .test file name.
Args:
message: message to update
fnam: path of the .test file
delta: line number of the beginning of the test case in the .test file
Returns updated message (or original message if we couldn't find anything).
"""
fnam = os.path.basename(fnam)
message = re.sub(r'native\.py:([0-9]+):',
lambda m: '%s:%d:' % (fnam, int(m.group(1)) + delta),
message)
message = re.sub(r'"native.py", line ([0-9]+),',
lambda m: '"%s", line %d,' % (fnam, int(m.group(1)) + delta),
message)
return message

View file

@ -0,0 +1,104 @@
"""Functions to check that serialization round-tripped properly."""
# This file is named test_serialization.py even though it doesn't
# contain its own tests so that pytest will rewrite the asserts...
from typing import Any, Dict, Tuple
from mypy.backports import OrderedDict
from collections.abc import Iterable
from mypyc.ir.ops import DeserMaps
from mypyc.ir.rtypes import RType
from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature
from mypyc.ir.class_ir import ClassIR
from mypyc.ir.module_ir import ModuleIR, deserialize_modules
from mypyc.sametype import is_same_type, is_same_signature
def get_dict(x: Any) -> Dict[str, Any]:
if hasattr(x, '__mypyc_attrs__'):
return {k: getattr(x, k) for k in x.__mypyc_attrs__ if hasattr(x, k)}
else:
return dict(x.__dict__)
def get_function_dict(x: FuncIR) -> Dict[str, Any]:
"""Get a dict of function attributes safe to compare across serialization"""
d = get_dict(x)
d.pop('blocks', None)
d.pop('env', None)
return d
def assert_blobs_same(x: Any, y: Any, trail: Tuple[Any, ...]) -> None:
"""Compare two blobs of IR as best we can.
FuncDecls, FuncIRs, and ClassIRs are compared by fullname to avoid
infinite recursion.
(More detailed comparisons should be done manually.)
Types and signatures are compared using mypyc.sametype.
Containers are compared recursively.
Anything else is compared with ==.
The `trail` argument is used in error messages.
"""
assert type(x) is type(y), ("Type mismatch at {}".format(trail), type(x), type(y))
if isinstance(x, (FuncDecl, FuncIR, ClassIR)):
assert x.fullname == y.fullname, "Name mismatch at {}".format(trail)
elif isinstance(x, OrderedDict):
assert len(x.keys()) == len(y.keys()), "Keys mismatch at {}".format(trail)
for (xk, xv), (yk, yv) in zip(x.items(), y.items()):
assert_blobs_same(xk, yk, trail + ("keys",))
assert_blobs_same(xv, yv, trail + (xk,))
elif isinstance(x, dict):
assert x.keys() == y.keys(), "Keys mismatch at {}".format(trail)
for k in x.keys():
assert_blobs_same(x[k], y[k], trail + (k,))
elif isinstance(x, Iterable) and not isinstance(x, str):
for i, (xv, yv) in enumerate(zip(x, y)):
assert_blobs_same(xv, yv, trail + (i,))
elif isinstance(x, RType):
assert is_same_type(x, y), "RType mismatch at {}".format(trail)
elif isinstance(x, FuncSignature):
assert is_same_signature(x, y), "Signature mismatch at {}".format(trail)
else:
assert x == y, "Value mismatch at {}".format(trail)
def assert_modules_same(ir1: ModuleIR, ir2: ModuleIR) -> None:
"""Assert that two module IRs are the same (*).
* Or rather, as much as we care about preserving across
serialization. We drop the actual IR bodies of functions but try
to preserve everything else.
"""
assert ir1.fullname == ir2.fullname
assert ir1.imports == ir2.imports
for cls1, cls2 in zip(ir1.classes, ir2.classes):
assert_blobs_same(get_dict(cls1), get_dict(cls2), (ir1.fullname, cls1.fullname))
for fn1, fn2 in zip(ir1.functions, ir2.functions):
assert_blobs_same(get_function_dict(fn1), get_function_dict(fn2),
(ir1.fullname, fn1.fullname))
assert_blobs_same(get_dict(fn1.decl), get_dict(fn2.decl),
(ir1.fullname, fn1.fullname))
assert_blobs_same(ir1.final_names, ir2.final_names, (ir1.fullname, 'final_names'))
def check_serialization_roundtrip(irs: Dict[str, ModuleIR]) -> None:
"""Check that we can serialize modules out and deserialize them to the same thing."""
serialized = {k: ir.serialize() for k, ir in irs.items()}
ctx = DeserMaps({}, {})
irs2 = deserialize_modules(serialized, ctx)
assert irs.keys() == irs2.keys()
for k in irs:
assert_modules_same(irs[k], irs2[k])

View file

@ -0,0 +1,115 @@
import unittest
from mypyc.ir.rtypes import (
RStruct, bool_rprimitive, int64_rprimitive, int32_rprimitive, object_rprimitive,
int_rprimitive
)
from mypyc.rt_subtype import is_runtime_subtype
class TestStruct(unittest.TestCase):
def test_struct_offsets(self) -> None:
# test per-member alignment
r = RStruct("", [], [bool_rprimitive, int32_rprimitive, int64_rprimitive])
assert r.size == 16
assert r.offsets == [0, 4, 8]
# test final alignment
r1 = RStruct("", [], [bool_rprimitive, bool_rprimitive])
assert r1.size == 2
assert r1.offsets == [0, 1]
r2 = RStruct("", [], [int32_rprimitive, bool_rprimitive])
r3 = RStruct("", [], [int64_rprimitive, bool_rprimitive])
assert r2.offsets == [0, 4]
assert r3.offsets == [0, 8]
assert r2.size == 8
assert r3.size == 16
r4 = RStruct("", [], [bool_rprimitive, bool_rprimitive,
bool_rprimitive, int32_rprimitive])
assert r4.size == 8
assert r4.offsets == [0, 1, 2, 4]
# test nested struct
r5 = RStruct("", [], [bool_rprimitive, r])
assert r5.offsets == [0, 8]
assert r5.size == 24
r6 = RStruct("", [], [int32_rprimitive, r5])
assert r6.offsets == [0, 8]
assert r6.size == 32
# test nested struct with alignment less than 8
r7 = RStruct("", [], [bool_rprimitive, r4])
assert r7.offsets == [0, 4]
assert r7.size == 12
def test_struct_str(self) -> None:
r = RStruct("Foo", ["a", "b"],
[bool_rprimitive, object_rprimitive])
assert str(r) == "Foo{a:bool, b:object}"
assert repr(r) == "<RStruct Foo{a:<RPrimitive builtins.bool>, " \
"b:<RPrimitive builtins.object>}>"
r1 = RStruct("Bar", ["c"], [int32_rprimitive])
assert str(r1) == "Bar{c:int32}"
assert repr(r1) == "<RStruct Bar{c:<RPrimitive int32>}>"
r2 = RStruct("Baz", [], [])
assert str(r2) == "Baz{}"
assert repr(r2) == "<RStruct Baz{}>"
def test_runtime_subtype(self) -> None:
# right type to check with
r = RStruct("Foo", ["a", "b"],
[bool_rprimitive, int_rprimitive])
# using the exact same fields
r1 = RStruct("Foo", ["a", "b"],
[bool_rprimitive, int_rprimitive])
# names different
r2 = RStruct("Bar", ["c", "b"],
[bool_rprimitive, int_rprimitive])
# name different
r3 = RStruct("Baz", ["a", "b"],
[bool_rprimitive, int_rprimitive])
# type different
r4 = RStruct("FooBar", ["a", "b"],
[bool_rprimitive, int32_rprimitive])
# number of types different
r5 = RStruct("FooBarBaz", ["a", "b", "c"],
[bool_rprimitive, int_rprimitive, bool_rprimitive])
assert is_runtime_subtype(r1, r) is True
assert is_runtime_subtype(r2, r) is False
assert is_runtime_subtype(r3, r) is False
assert is_runtime_subtype(r4, r) is False
assert is_runtime_subtype(r5, r) is False
def test_eq_and_hash(self) -> None:
r = RStruct("Foo", ["a", "b"],
[bool_rprimitive, int_rprimitive])
# using the exact same fields
r1 = RStruct("Foo", ["a", "b"],
[bool_rprimitive, int_rprimitive])
assert hash(r) == hash(r1)
assert r == r1
# different name
r2 = RStruct("Foq", ["a", "b"],
[bool_rprimitive, int_rprimitive])
assert hash(r) != hash(r2)
assert r != r2
# different names
r3 = RStruct("Foo", ["a", "c"],
[bool_rprimitive, int_rprimitive])
assert hash(r) != hash(r3)
assert r != r3
# different type
r4 = RStruct("Foo", ["a", "b"],
[bool_rprimitive, int_rprimitive, bool_rprimitive])
assert hash(r) != hash(r4)
assert r != r4

View file

@ -0,0 +1,27 @@
"""Test cases for is_subtype and is_runtime_subtype."""
import unittest
from mypyc.ir.rtypes import bit_rprimitive, bool_rprimitive, int_rprimitive
from mypyc.subtype import is_subtype
from mypyc.rt_subtype import is_runtime_subtype
class TestSubtype(unittest.TestCase):
def test_bit(self) -> None:
assert is_subtype(bit_rprimitive, bool_rprimitive)
assert is_subtype(bit_rprimitive, int_rprimitive)
def test_bool(self) -> None:
assert not is_subtype(bool_rprimitive, bit_rprimitive)
assert is_subtype(bool_rprimitive, int_rprimitive)
class TestRuntimeSubtype(unittest.TestCase):
def test_bit(self) -> None:
assert is_runtime_subtype(bit_rprimitive, bool_rprimitive)
assert not is_runtime_subtype(bit_rprimitive, int_rprimitive)
def test_bool(self) -> None:
assert not is_runtime_subtype(bool_rprimitive, bit_rprimitive)
assert not is_runtime_subtype(bool_rprimitive, int_rprimitive)

View file

@ -0,0 +1,23 @@
import unittest
from mypyc.ir.rtypes import (
RTuple, object_rprimitive, int_rprimitive, bool_rprimitive, list_rprimitive,
RInstance, RUnion,
)
from mypyc.ir.class_ir import ClassIR
class TestTupleNames(unittest.TestCase):
def setUp(self) -> None:
self.inst_a = RInstance(ClassIR('A', '__main__'))
self.inst_b = RInstance(ClassIR('B', '__main__'))
def test_names(self) -> None:
assert RTuple([int_rprimitive, int_rprimitive]).unique_id == "T2II"
assert RTuple([list_rprimitive, object_rprimitive, self.inst_a]).unique_id == "T3OOO"
assert RTuple([list_rprimitive, object_rprimitive, self.inst_b]).unique_id == "T3OOO"
assert RTuple([]).unique_id == "T0"
assert RTuple([RTuple([]),
RTuple([int_rprimitive, int_rprimitive])]).unique_id == "T2T0T2II"
assert RTuple([bool_rprimitive,
RUnion([bool_rprimitive, int_rprimitive])]).unique_id == "T2CO"

View file

@ -0,0 +1,266 @@
"""Helpers for writing tests"""
import contextlib
import os
import os.path
import re
import shutil
from typing import List, Callable, Iterator, Optional, Tuple
from mypy import build
from mypy.errors import CompileError
from mypy.options import Options
from mypy.test.data import DataSuite, DataDrivenTestCase
from mypy.test.config import test_temp_dir
from mypy.test.helpers import assert_string_arrays_equal
from mypyc.options import CompilerOptions
from mypyc.analysis.ircheck import assert_func_ir_valid
from mypyc.ir.func_ir import FuncIR
from mypyc.errors import Errors
from mypyc.irbuild.main import build_ir
from mypyc.irbuild.mapper import Mapper
from mypyc.test.config import test_data_prefix
from mypyc.common import IS_32_BIT_PLATFORM, PLATFORM_SIZE
# The builtins stub used during icode generation test cases.
ICODE_GEN_BUILTINS = os.path.join(test_data_prefix, 'fixtures/ir.py')
# The testutil support library
TESTUTIL_PATH = os.path.join(test_data_prefix, 'fixtures/testutil.py')
class MypycDataSuite(DataSuite):
# Need to list no files, since this will be picked up as a suite of tests
files: List[str] = []
data_prefix = test_data_prefix
def builtins_wrapper(func: Callable[[DataDrivenTestCase], None],
path: str) -> Callable[[DataDrivenTestCase], None]:
"""Decorate a function that implements a data-driven test case to copy an
alternative builtins module implementation in place before performing the
test case. Clean up after executing the test case.
"""
return lambda testcase: perform_test(func, path, testcase)
@contextlib.contextmanager
def use_custom_builtins(builtins_path: str, testcase: DataDrivenTestCase) -> Iterator[None]:
for path, _ in testcase.files:
if os.path.basename(path) == 'builtins.pyi':
default_builtins = False
break
else:
# Use default builtins.
builtins = os.path.abspath(os.path.join(test_temp_dir, 'builtins.pyi'))
shutil.copyfile(builtins_path, builtins)
default_builtins = True
# Actually perform the test case.
try:
yield None
finally:
if default_builtins:
# Clean up.
os.remove(builtins)
def perform_test(func: Callable[[DataDrivenTestCase], None],
builtins_path: str, testcase: DataDrivenTestCase) -> None:
for path, _ in testcase.files:
if os.path.basename(path) == 'builtins.py':
default_builtins = False
break
else:
# Use default builtins.
builtins = os.path.join(test_temp_dir, 'builtins.py')
shutil.copyfile(builtins_path, builtins)
default_builtins = True
# Actually perform the test case.
func(testcase)
if default_builtins:
# Clean up.
os.remove(builtins)
def build_ir_for_single_file(input_lines: List[str],
compiler_options: Optional[CompilerOptions] = None) -> List[FuncIR]:
program_text = '\n'.join(input_lines)
# By default generate IR compatible with the earliest supported Python C API.
# If a test needs more recent API features, this should be overridden.
compiler_options = compiler_options or CompilerOptions(capi_version=(3, 5))
options = Options()
options.show_traceback = True
options.use_builtins_fixtures = True
options.strict_optional = True
options.python_version = (3, 6)
options.export_types = True
options.preserve_asts = True
options.per_module_options['__main__'] = {'mypyc': True}
source = build.BuildSource('main', '__main__', program_text)
# Construct input as a single single.
# Parse and type check the input program.
result = build.build(sources=[source],
options=options,
alt_lib_path=test_temp_dir)
if result.errors:
raise CompileError(result.errors)
errors = Errors()
modules = build_ir(
[result.files['__main__']], result.graph, result.types,
Mapper({'__main__': None}),
compiler_options, errors)
if errors.num_errors:
raise CompileError(errors.new_messages())
module = list(modules.values())[0]
for fn in module.functions:
assert_func_ir_valid(fn)
return module.functions
def update_testcase_output(testcase: DataDrivenTestCase, output: List[str]) -> None:
# TODO: backport this to mypy
assert testcase.old_cwd is not None, "test was not properly set up"
testcase_path = os.path.join(testcase.old_cwd, testcase.file)
with open(testcase_path) as f:
data_lines = f.read().splitlines()
# We can't rely on the test line numbers to *find* the test, since
# we might fix multiple tests in a run. So find it by the case
# header. Give up if there are multiple tests with the same name.
test_slug = '[case {}]'.format(testcase.name)
if data_lines.count(test_slug) != 1:
return
start_idx = data_lines.index(test_slug)
stop_idx = start_idx + 11
while stop_idx < len(data_lines) and not data_lines[stop_idx].startswith('[case '):
stop_idx += 1
test = data_lines[start_idx:stop_idx]
out_start = test.index('[out]')
test[out_start + 1:] = output
data_lines[start_idx:stop_idx] = test + ['']
data = '\n'.join(data_lines)
with open(testcase_path, 'w') as f:
print(data, file=f)
def assert_test_output(testcase: DataDrivenTestCase,
actual: List[str],
message: str,
expected: Optional[List[str]] = None,
formatted: Optional[List[str]] = None) -> None:
__tracebackhide__ = True
expected_output = expected if expected is not None else testcase.output
if expected_output != actual and testcase.config.getoption('--update-data', False):
update_testcase_output(testcase, actual)
assert_string_arrays_equal(
expected_output, actual,
'{} ({}, line {})'.format(message, testcase.file, testcase.line))
def get_func_names(expected: List[str]) -> List[str]:
res = []
for s in expected:
m = re.match(r'def ([_a-zA-Z0-9.*$]+)\(', s)
if m:
res.append(m.group(1))
return res
def remove_comment_lines(a: List[str]) -> List[str]:
"""Return a copy of array with comments removed.
Lines starting with '--' (but not with '---') are removed.
"""
r = []
for s in a:
if s.strip().startswith('--') and not s.strip().startswith('---'):
pass
else:
r.append(s)
return r
def print_with_line_numbers(s: str) -> None:
lines = s.splitlines()
for i, line in enumerate(lines):
print('%-4d %s' % (i + 1, line))
def heading(text: str) -> None:
print('=' * 20 + ' ' + text + ' ' + '=' * 20)
def show_c(cfiles: List[List[Tuple[str, str]]]) -> None:
heading('Generated C')
for group in cfiles:
for cfile, ctext in group:
print('== {} =='.format(cfile))
print_with_line_numbers(ctext)
heading('End C')
def fudge_dir_mtimes(dir: str, delta: int) -> None:
for dirpath, _, filenames in os.walk(dir):
for name in filenames:
path = os.path.join(dirpath, name)
new_mtime = os.stat(path).st_mtime + delta
os.utime(path, times=(new_mtime, new_mtime))
def replace_word_size(text: List[str]) -> List[str]:
"""Replace WORDSIZE with platform specific word sizes"""
result = []
for line in text:
index = line.find('WORD_SIZE')
if index != -1:
# get 'WORDSIZE*n' token
word_size_token = line[index:].split()[0]
n = int(word_size_token[10:])
replace_str = str(PLATFORM_SIZE * n)
result.append(line.replace(word_size_token, replace_str))
else:
result.append(line)
return result
def infer_ir_build_options_from_test_name(name: str) -> Optional[CompilerOptions]:
"""Look for magic substrings in test case name to set compiler options.
Return None if the test case should be skipped (always pass).
Supported naming conventions:
*_64bit*:
Run test case only on 64-bit platforms
*_32bit*:
Run test caseonly on 32-bit platforms
*_python3_8* (or for any Python version):
Use Python 3.8+ C API features (default: lowest supported version)
*StripAssert*:
Don't generate code for assert statements
"""
# If this is specific to some bit width, always pass if platform doesn't match.
if '_64bit' in name and IS_32_BIT_PLATFORM:
return None
if '_32bit' in name and not IS_32_BIT_PLATFORM:
return None
options = CompilerOptions(strip_asserts='StripAssert' in name,
capi_version=(3, 5))
# A suffix like _python3.8 is used to set the target C API version.
m = re.search(r'_python([3-9]+)_([0-9]+)(_|\b)', name)
if m:
options.capi_version = (int(m.group(1)), int(m.group(2)))
elif '_py' in name or '_Python' in name:
assert False, 'Invalid _py* suffix (should be _pythonX_Y): {}'.format(name)
return options