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'i32.*i64'): 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()