Re-implementing some things that were broken. Also, if a typing error is found, and we detect an infinite loop, we return the errors instead, as that's probably what causing the loop anyhow.
487 lines
11 KiB
Python
487 lines
11 KiB
Python
import pytest
|
|
|
|
from phasm.exceptions import TypingError
|
|
from phasm.type3.entry import Type3Exception
|
|
|
|
from ..helpers import Suite
|
|
from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, COMPLETE_NUMERIC_TYPES, TYPE_MAP
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
|
def test_expr_constant_int(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 13
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 13 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
def test_expr_constant_float(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 32.125
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 32.125 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
def test_expr_constant_literal_does_not_fit():
|
|
code_py = """
|
|
@exported
|
|
def testEntry() -> u8:
|
|
return 1000
|
|
"""
|
|
|
|
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
|
Suite(code_py).run_code()
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
|
def test_module_constant_int(type_):
|
|
code_py = f"""
|
|
CONSTANT: {type_} = 13
|
|
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return CONSTANT
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 13 == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
def test_module_constant_float(type_):
|
|
code_py = f"""
|
|
CONSTANT: {type_} = 32.125
|
|
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return CONSTANT
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 32.125 == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.skip('Awaiting result of Type3 experiment')
|
|
def test_module_constant_entanglement():
|
|
code_py = """
|
|
CONSTANT: u8 = 1000
|
|
|
|
@exported
|
|
def testEntry() -> u32:
|
|
return 14
|
|
"""
|
|
|
|
with pytest.raises(TypingError, match='u8.*1000'):
|
|
Suite(code_py).run_code()
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation
|
|
def test_logical_left_shift(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 << 3
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 80 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ['u32', 'u64'])
|
|
def test_logical_right_shift_left_bit_zero(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 >> 3
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 1 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
def test_logical_right_shift_left_bit_one():
|
|
code_py = """
|
|
@exported
|
|
def testEntry() -> u32:
|
|
return 4294967295 >> 16
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 0xFFFF == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
def test_bitwise_or_uint(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 | 3
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 11 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
def test_bitwise_or_inv_type():
|
|
code_py = """
|
|
@exported
|
|
def testEntry() -> f64:
|
|
return 10.0 | 3.0
|
|
"""
|
|
|
|
with pytest.raises(Type3Exception, match='f64 does not implement the BitWiseOperation type class'):
|
|
Suite(code_py).run_code()
|
|
|
|
@pytest.mark.integration_test
|
|
def test_bitwise_or_type_mismatch():
|
|
code_py = """
|
|
CONSTANT1: u32 = 3
|
|
CONSTANT2: u64 = 3
|
|
|
|
@exported
|
|
def testEntry() -> u64:
|
|
return CONSTANT1 | CONSTANT2
|
|
"""
|
|
|
|
with pytest.raises(Type3Exception, match='u64 must be u32 instead'):
|
|
Suite(code_py).run_code()
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
def test_bitwise_xor(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 ^ 3
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 9 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
def test_bitwise_and(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 & 3
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 2 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
def test_addition_int(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 + 3
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 13 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
def test_addition_float(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 32.0 + 0.125
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 32.125 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
def test_subtraction_int(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 - 3
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 7 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
def test_subtraction_float(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 100.0 - 67.875
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 32.125 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.skip('TODO: Runtimes return a signed value, which is difficult to test')
|
|
@pytest.mark.parametrize('type_', ('u32', 'u64')) # FIXME: u8
|
|
def test_subtraction_underflow(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 - 11
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 0 < result.returned_value
|
|
|
|
# TODO: Multiplication
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
def test_division_int(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 / 3
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 3 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
def test_division_float(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10.0 / 8.0
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 1.25 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
def test_division_zero_let_it_crash_int(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10 / 0
|
|
"""
|
|
|
|
# WebAssembly dictates that integer division is a partial operator (e.g. unreachable for 0)
|
|
# https://www.w3.org/TR/wasm-core-1/#-hrefop-idiv-umathrmidiv_u_n-i_1-i_2
|
|
with pytest.raises(Exception):
|
|
Suite(code_py).run_code()
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
def test_division_zero_let_it_crash_float(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return 10.0 / 0.0
|
|
"""
|
|
|
|
# WebAssembly dictates that float division follows the IEEE rules
|
|
# https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2
|
|
result = Suite(code_py).run_code()
|
|
assert float('+inf') == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ['f32', 'f64'])
|
|
def test_builtins_sqrt(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return sqrt(25.0)
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 5 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
|
|
def test_function_argument(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry(a: {type_}) -> {type_}:
|
|
return a
|
|
"""
|
|
|
|
result = Suite(code_py).run_code(125)
|
|
|
|
assert 125 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.skip('TODO')
|
|
def test_explicit_positive_number():
|
|
code_py = """
|
|
@exported
|
|
def testEntry() -> i32:
|
|
return +523
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 523 == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.skip('TODO')
|
|
def test_explicit_negative_number():
|
|
code_py = """
|
|
@exported
|
|
def testEntry() -> i32:
|
|
return -19
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert -19 == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
def test_call_no_args():
|
|
code_py = """
|
|
def helper() -> i32:
|
|
return 19
|
|
|
|
@exported
|
|
def testEntry() -> i32:
|
|
return helper()
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 19 == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
def test_call_pre_defined():
|
|
code_py = """
|
|
def helper(left: i32) -> i32:
|
|
return left
|
|
|
|
@exported
|
|
def testEntry() -> i32:
|
|
return helper(13)
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 13 == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
def test_call_post_defined():
|
|
code_py = """
|
|
@exported
|
|
def testEntry() -> i32:
|
|
return helper(10, 3)
|
|
|
|
def helper(left: i32, right: i32) -> i32:
|
|
return left - right
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 7 == result.returned_value
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
def test_call_with_expression_int(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return helper(10 + 20, 3 + 5)
|
|
|
|
def helper(left: {type_}, right: {type_}) -> {type_}:
|
|
return left - right
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 22 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
def test_call_with_expression_float(type_):
|
|
code_py = f"""
|
|
@exported
|
|
def testEntry() -> {type_}:
|
|
return helper(10.078125 + 90.046875, 63.0 + 5.0)
|
|
|
|
def helper(left: {type_}, right: {type_}) -> {type_}:
|
|
return left - right
|
|
"""
|
|
|
|
result = Suite(code_py).run_code()
|
|
|
|
assert 32.125 == result.returned_value
|
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
|
|
@pytest.mark.integration_test
|
|
def test_call_invalid_return_type():
|
|
code_py = """
|
|
def helper() -> i64:
|
|
return 19
|
|
|
|
@exported
|
|
def testEntry() -> i32:
|
|
return helper()
|
|
"""
|
|
|
|
with pytest.raises(Type3Exception, match=r'i64 must be i32 instead'):
|
|
Suite(code_py).run_code()
|
|
|
|
@pytest.mark.integration_test
|
|
def test_call_invalid_arg_type():
|
|
code_py = """
|
|
def helper(left: u8) -> u8:
|
|
return left
|
|
|
|
@exported
|
|
def testEntry() -> u8:
|
|
return helper(500)
|
|
"""
|
|
|
|
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
|
Suite(code_py).run_code()
|