diff --git a/tests/integration/test_lang/test_bits.py b/tests/integration/test_lang/test_bits.py new file mode 100644 index 0000000..dfe21db --- /dev/null +++ b/tests/integration/test_lang/test_bits.py @@ -0,0 +1,115 @@ +import pytest + +from phasm.type3.entry import Type3Exception + +from ..helpers import Suite +from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, TYPE_MAP + +@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 +""" + + # Check with wasmtime, as other engines don't mind if the type + # doesn't match. They'll complain when: (>>) : u32 -> u64 -> u32 + result = Suite(code_py).run_code(runtime='wasmtime') + + 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) diff --git a/tests/integration/test_lang/test_bytes.py b/tests/integration/test_lang/test_bytes.py index 6449831..e336bf6 100644 --- a/tests/integration/test_lang/test_bytes.py +++ b/tests/integration/test_lang/test_bytes.py @@ -17,7 +17,7 @@ def testEntry(f: bytes) -> u32: assert 24 == result.returned_value @pytest.mark.integration_test -def test_bytes_index(): +def test_bytes_index_ok(): code_py = """ @exported def testEntry(f: bytes) -> u8: @@ -41,30 +41,12 @@ def testEntry(f: bytes, g: bytes) -> u8: assert 0 == result.returned_value @pytest.mark.integration_test -def test_function_call_element_ok(): +def test_bytes_index_invalid_type(): code_py = """ @exported -def testEntry(f: bytes) -> u8: - return helper(f[0]) - -def helper(x: u8) -> u8: - return x -""" - - result = Suite(code_py).run_code(b'Short') - - assert 83 == result.returned_value - -@pytest.mark.integration_test -def test_function_call_element_type_mismatch(): - code_py = """ -@exported -def testEntry(f: bytes) -> u64: - return helper(f[0]) - -def helper(x: u64) -> u64: - return x +def testEntry(f: bytes, g: bytes) -> u64: + return f[50] """ with pytest.raises(Type3Exception, match=r'u64 must be u8 instead'): - Suite(code_py).run_code() + Suite(code_py).run_code(b'Short', b'Long' * 100) diff --git a/tests/integration/test_lang/test_floating.py b/tests/integration/test_lang/test_floating.py new file mode 100644 index 0000000..263ca80 --- /dev/null +++ b/tests/integration/test_lang/test_floating.py @@ -0,0 +1,17 @@ +import pytest + +from ..helpers import Suite + +@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 isinstance(result.returned_value, float) diff --git a/tests/integration/test_lang/test_fractional.py b/tests/integration/test_lang/test_fractional.py new file mode 100644 index 0000000..3ad8a41 --- /dev/null +++ b/tests/integration/test_lang/test_fractional.py @@ -0,0 +1,33 @@ +import pytest + +from ..helpers import Suite + +TYPE_LIST = ['f32', 'f64'] + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', TYPE_LIST) +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 isinstance(result.returned_value, float) + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', TYPE_LIST) +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 diff --git a/tests/integration/test_lang/test_function_calls.py b/tests/integration/test_lang/test_function_calls.py new file mode 100644 index 0000000..27a4678 --- /dev/null +++ b/tests/integration/test_lang/test_function_calls.py @@ -0,0 +1,33 @@ +import pytest + +from ..helpers import Suite + +@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 diff --git a/tests/integration/test_lang/test_interface.py b/tests/integration/test_lang/test_imports.py similarity index 100% rename from tests/integration/test_lang/test_interface.py rename to tests/integration/test_lang/test_imports.py diff --git a/tests/integration/test_lang/test_integral.py b/tests/integration/test_lang/test_integral.py new file mode 100644 index 0000000..63486da --- /dev/null +++ b/tests/integration/test_lang/test_integral.py @@ -0,0 +1,33 @@ +import pytest + +from ..helpers import Suite + +TYPE_LIST = ['u32', 'u64', 'i32', 'i64'] + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', TYPE_LIST) +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 isinstance(result.returned_value, int) + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', TYPE_LIST) +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() diff --git a/tests/integration/test_lang/test_literals.py b/tests/integration/test_lang/test_literals.py new file mode 100644 index 0000000..99163bb --- /dev/null +++ b/tests/integration/test_lang/test_literals.py @@ -0,0 +1,17 @@ +import pytest + +from phasm.type3.entry import Type3Exception + +from ..helpers import Suite +from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, TYPE_MAP + +@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() diff --git a/tests/integration/test_lang/test_num.py b/tests/integration/test_lang/test_num.py new file mode 100644 index 0000000..7685f0a --- /dev/null +++ b/tests/integration/test_lang/test_num.py @@ -0,0 +1,112 @@ +import pytest + +from phasm.type3.entry import Type3Exception + +from ..helpers import Suite +from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, TYPE_MAP + +@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_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) diff --git a/tests/integration/test_lang/test_primitives.py b/tests/integration/test_lang/test_primitives.py deleted file mode 100644 index 6458d77..0000000 --- a/tests/integration/test_lang/test_primitives.py +++ /dev/null @@ -1,360 +0,0 @@ -import pytest - -from phasm.type3.entry import Type3Exception - -from ..helpers import Suite -from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, TYPE_MAP - -@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_', ['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 -""" - - # Check with wasmtime, as other engines don't mind if the type - # doesn't match. They'll complain when: (>>) : u32 -> u64 -> u32 - result = Suite(code_py).run_code(runtime='wasmtime') - - 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 -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()