| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| """Tests for ffi.RecursiveEq / RecursiveLt / RecursiveLe / RecursiveGt / RecursiveGe.""" |
| |
| from __future__ import annotations |
| |
| import math |
| import struct |
| |
| import pytest |
| import tvm_ffi |
| import tvm_ffi.testing |
| from tvm_ffi._ffi_api import RecursiveEq, RecursiveGe, RecursiveGt, RecursiveLe, RecursiveLt |
| from tvm_ffi.testing import ( |
| TestCompare, |
| TestCustomCompare, |
| TestEqWithoutHash, |
| TestIntPair, |
| _TestCxxClassDerived, |
| _TestCxxClassDerivedDerived, |
| create_object, |
| ) |
| |
| |
| def _make_nan_from_payload(payload: int) -> float: |
| """Create a quiet NaN with a deterministic payload.""" |
| bits = 0x7FF8000000000000 | (payload & 0x0007FFFFFFFFFFFF) |
| return struct.unpack(">d", struct.pack(">Q", bits))[0] |
| |
| |
| # --------------------------------------------------------------------------- |
| # Primitives: int |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_int_eq() -> None: |
| assert RecursiveEq(42, 42) |
| assert not RecursiveEq(1, 2) |
| |
| |
| def test_int_ordering() -> None: |
| assert RecursiveLt(1, 2) |
| assert not RecursiveLt(2, 1) |
| assert not RecursiveLt(1, 1) |
| assert RecursiveLe(1, 2) |
| assert RecursiveLe(1, 1) |
| assert not RecursiveLe(2, 1) |
| assert RecursiveGt(2, 1) |
| assert not RecursiveGt(1, 2) |
| assert RecursiveGe(2, 1) |
| assert RecursiveGe(2, 2) |
| |
| |
| def test_int64_extremes_eq() -> None: |
| """Extreme int64 values compare equal to themselves.""" |
| i64_min = -(2**63) |
| i64_max = 2**63 - 1 |
| assert RecursiveEq(i64_max, i64_max) |
| assert RecursiveEq(i64_min, i64_min) |
| |
| |
| def test_int64_extremes_ordering() -> None: |
| """Exercise int64 boundary ordering where naive subtraction would overflow.""" |
| i64_min = -(2**63) |
| i64_max = 2**63 - 1 |
| assert RecursiveLt(i64_min, i64_max) |
| assert RecursiveGt(i64_max, i64_min) |
| assert not RecursiveGt(i64_min, i64_max) |
| assert not RecursiveLe(i64_max, i64_min) |
| # Cases that would give wrong results with naive Sign(a - b) |
| assert RecursiveLt(i64_min, 1) |
| assert RecursiveGt(i64_max, -1) |
| assert not RecursiveLt(i64_max, -1) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Primitives: float |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_float_eq() -> None: |
| assert RecursiveEq(3.14, 3.14) |
| assert not RecursiveEq(1.0, 2.0) |
| |
| |
| def test_float_ordering() -> None: |
| assert RecursiveLt(1.0, 2.0) |
| assert not RecursiveLt(2.0, 1.0) |
| assert RecursiveGe(2.0, 2.0) |
| |
| |
| def test_float_signed_zero() -> None: |
| assert RecursiveEq(-0.0, 0.0) |
| assert not RecursiveLt(-0.0, 0.0) |
| assert RecursiveLe(-0.0, 0.0) |
| assert RecursiveGe(-0.0, 0.0) |
| |
| |
| def test_float_infinity_ordering() -> None: |
| assert RecursiveLt(-math.inf, 0.0) |
| assert RecursiveLt(0.0, math.inf) |
| assert RecursiveLt(-math.inf, math.inf) |
| assert RecursiveGt(math.inf, -math.inf) |
| assert RecursiveEq(math.inf, math.inf) |
| assert RecursiveLt(1.0, math.inf) |
| |
| |
| # --------------------------------------------------------------------------- |
| # NaN handling |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_nan_eq() -> None: |
| """NaN == NaN under RecursiveEq (equality-only mode).""" |
| assert RecursiveEq(math.nan, math.nan) |
| |
| |
| def test_nan_ordering_raises() -> None: |
| """Ordering NaN values raises TypeError.""" |
| with pytest.raises(TypeError): |
| RecursiveLt(math.nan, 1.0) |
| with pytest.raises(TypeError): |
| RecursiveLt(1.0, math.nan) |
| with pytest.raises(TypeError): |
| RecursiveLe(math.nan, math.nan) |
| |
| |
| def test_nan_payloads_eq() -> None: |
| nan1 = _make_nan_from_payload(0x1) |
| nan2 = _make_nan_from_payload(0x2) |
| assert math.isnan(nan1) and math.isnan(nan2) |
| assert RecursiveEq(nan1, nan2) |
| |
| |
| def test_nan_payloads_in_nested_array() -> None: |
| nan1 = _make_nan_from_payload(0xA5) |
| nan2 = _make_nan_from_payload(0x5A) |
| a = tvm_ffi.Array([1.0, nan1, 2.0]) |
| b = tvm_ffi.Array([1.0, nan2, 2.0]) |
| assert RecursiveEq(a, b) |
| with pytest.raises(TypeError): |
| RecursiveLt(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Primitives: bool |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_bool_eq() -> None: |
| assert RecursiveEq(True, True) |
| assert RecursiveEq(False, False) |
| assert not RecursiveEq(True, False) |
| |
| |
| def test_bool_ordering() -> None: |
| assert RecursiveLt(False, True) |
| assert not RecursiveLt(True, False) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Primitives: string |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_string_eq() -> None: |
| assert RecursiveEq("hello", "hello") |
| assert not RecursiveEq("hello", "world") |
| |
| |
| def test_string_ordering() -> None: |
| assert RecursiveLt("abc", "abd") |
| assert RecursiveLt("abc", "abcd") |
| assert not RecursiveLt("abd", "abc") |
| |
| |
| def test_string_small_boundary_len7_len8() -> None: |
| small = "1234567" # SmallStr |
| large = "12345678" # heap-backed Str |
| assert RecursiveEq(small, "1234567") |
| assert RecursiveLt(small, large) |
| |
| |
| def test_string_embedded_nul() -> None: |
| assert RecursiveEq("a\x00b", "a\x00b") |
| assert RecursiveLt("a\x00b", "a\x00c") |
| |
| |
| # --------------------------------------------------------------------------- |
| # Primitives: bytes |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_bytes_eq() -> None: |
| assert RecursiveEq(b"hello", b"hello") |
| assert not RecursiveEq(b"hello", b"world") |
| |
| |
| def test_bytes_ordering() -> None: |
| assert RecursiveLt(b"abc", b"abd") |
| assert RecursiveLt(b"abc", b"abcd") |
| |
| |
| def test_bytes_small_boundary_len7_len8() -> None: |
| small = b"1234567" # SmallBytes |
| large = b"12345678" # heap-backed Bytes |
| assert RecursiveEq(small, b"1234567") |
| assert RecursiveLt(small, large) |
| |
| |
| def test_bytes_embedded_nul() -> None: |
| assert RecursiveEq(b"a\x00b", b"a\x00b") |
| assert RecursiveLt(b"a\x00b", b"a\x00c") |
| |
| |
| def test_bytes_high_bit() -> None: |
| """Document high-bit byte ordering behavior. |
| |
| Bytes::memncmp uses char comparison; whether char is signed or unsigned |
| is platform-dependent. On platforms where char is signed (most x86/arm64 |
| compilers), 0xff (-1) sorts before 0x00 (0). This is a known pre-existing |
| issue in Bytes::memncmp (see string.h), not specific to recursive_compare. |
| """ |
| # Just verify the two values are distinguishable (not equal) |
| assert not RecursiveEq(b"\x00", b"\xff") |
| |
| |
| # --------------------------------------------------------------------------- |
| # None |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_none_eq() -> None: |
| assert RecursiveEq(None, None) |
| assert not RecursiveEq(None, 42) |
| assert not RecursiveEq(42, None) |
| |
| |
| def test_none_ordering() -> None: |
| """None is less than any non-None value.""" |
| assert RecursiveLt(None, 42) |
| assert RecursiveLt(None, "s") |
| assert not RecursiveGt(None, 42) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Type mismatch |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_type_mismatch_eq() -> None: |
| """RecursiveEq returns False for different types (no throw).""" |
| assert not RecursiveEq(42, "hello") |
| assert not RecursiveEq(1, True) |
| assert not RecursiveEq(1.0, 1) |
| |
| |
| def test_type_mismatch_ordering_raises() -> None: |
| """Ordering different types raises TypeError.""" |
| with pytest.raises(TypeError): |
| RecursiveLt(42, "hello") |
| with pytest.raises(TypeError): |
| RecursiveGt(1.0, 1) |
| |
| |
| # --------------------------------------------------------------------------- |
| # DataType |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_dtype_eq() -> None: |
| assert RecursiveEq(tvm_ffi.dtype("float32"), tvm_ffi.dtype("float32")) |
| assert not RecursiveEq(tvm_ffi.dtype("float32"), tvm_ffi.dtype("float16")) |
| |
| |
| def test_dtype_ordering() -> None: |
| # float32 code=2 bits=32, float16 code=2 bits=16 -> float16 < float32 |
| assert RecursiveLt(tvm_ffi.dtype("float16"), tvm_ffi.dtype("float32")) |
| assert not RecursiveLt(tvm_ffi.dtype("float32"), tvm_ffi.dtype("float16")) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Device |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_device_eq() -> None: |
| assert RecursiveEq(tvm_ffi.Device("cpu", 0), tvm_ffi.Device("cpu", 0)) |
| assert not RecursiveEq(tvm_ffi.Device("cpu", 0), tvm_ffi.Device("cpu", 1)) |
| |
| |
| def test_device_ordering() -> None: |
| assert RecursiveLt(tvm_ffi.Device("cpu", 0), tvm_ffi.Device("cpu", 1)) |
| assert RecursiveLt(tvm_ffi.Device("cpu", 0), tvm_ffi.Device("cuda", 0)) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Containers: Array |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_array_eq() -> None: |
| a = tvm_ffi.Array([1, 2, 3]) |
| b = tvm_ffi.Array([1, 2, 3]) |
| c = tvm_ffi.Array([1, 2, 4]) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| def test_array_ordering() -> None: |
| a = tvm_ffi.Array([1, 2, 3]) |
| b = tvm_ffi.Array([1, 2, 4]) |
| c = tvm_ffi.Array([1, 2, 3, 0]) |
| assert RecursiveLt(a, b) |
| assert RecursiveLt(a, c) # shorter < longer when prefix matches |
| assert not RecursiveLt(b, a) |
| |
| |
| def test_array_empty() -> None: |
| empty = tvm_ffi.Array([]) |
| one = tvm_ffi.Array([1]) |
| assert RecursiveEq(empty, empty) |
| assert RecursiveLt(empty, one) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Containers: List |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_list_eq() -> None: |
| a = tvm_ffi.List([1, 2, 3]) |
| b = tvm_ffi.List([1, 2, 3]) |
| c = tvm_ffi.List([1, 2, 4]) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| def test_list_ordering() -> None: |
| a = tvm_ffi.List([1, 2]) |
| b = tvm_ffi.List([1, 3]) |
| assert RecursiveLt(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Shape |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_shape_eq() -> None: |
| a = tvm_ffi.Shape((2, 3, 4)) |
| b = tvm_ffi.Shape((2, 3, 4)) |
| c = tvm_ffi.Shape((2, 3, 5)) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| def test_shape_ordering() -> None: |
| a = tvm_ffi.Shape((2, 3, 4)) |
| b = tvm_ffi.Shape((2, 3, 5)) |
| c = tvm_ffi.Shape((2, 3, 4, 0)) |
| assert RecursiveLt(a, b) |
| assert RecursiveLt(a, c) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Map/Dict equality |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_map_eq() -> None: |
| a = tvm_ffi.Map({"x": 1, "y": 2}) |
| b = tvm_ffi.Map({"x": 1, "y": 2}) |
| c = tvm_ffi.Map({"x": 1, "y": 3}) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| def test_map_different_size() -> None: |
| a = tvm_ffi.Map({"x": 1}) |
| b = tvm_ffi.Map({"x": 1, "y": 2}) |
| assert not RecursiveEq(a, b) |
| |
| |
| def test_map_ordering_raises() -> None: |
| a = tvm_ffi.Map({"x": 1}) |
| b = tvm_ffi.Map({"x": 2}) |
| with pytest.raises(TypeError): |
| RecursiveLt(a, b) |
| |
| |
| def test_map_same_size_different_keys() -> None: |
| a = tvm_ffi.Map({"x": 1}) |
| b = tvm_ffi.Map({"y": 1}) |
| assert not RecursiveEq(a, b) |
| with pytest.raises(TypeError): |
| RecursiveLt(a, b) |
| |
| |
| def test_equal_maps_under_ordering() -> None: |
| """Two separate but equal maps pass Le/Ge without raising TypeError.""" |
| a = tvm_ffi.Map({"x": 1, "y": 2}) |
| b = tvm_ffi.Map({"x": 1, "y": 2}) |
| assert RecursiveLe(a, b) |
| assert RecursiveGe(a, b) |
| assert not RecursiveLt(a, b) |
| assert not RecursiveGt(a, b) |
| |
| |
| def test_dict_eq() -> None: |
| a = tvm_ffi.Dict({"x": 1, "y": 2}) |
| b = tvm_ffi.Dict({"x": 1, "y": 2}) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_dict_ordering_raises() -> None: |
| a = tvm_ffi.Dict({"x": 1}) |
| b = tvm_ffi.Dict({"x": 2}) |
| with pytest.raises(TypeError): |
| RecursiveLt(a, b) |
| |
| |
| def test_equal_dicts_under_ordering() -> None: |
| """Two separate but equal dicts pass Le/Ge without raising TypeError.""" |
| a = tvm_ffi.Dict({"x": 1, "y": 2}) |
| b = tvm_ffi.Dict({"x": 1, "y": 2}) |
| assert RecursiveLe(a, b) |
| assert RecursiveGe(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Reflected objects: TestIntPair |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_reflected_obj_eq() -> None: |
| a = TestIntPair(1, 2) |
| b = TestIntPair(1, 2) |
| c = TestIntPair(1, 3) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| def test_reflected_obj_ordering() -> None: |
| a = TestIntPair(1, 2) |
| b = TestIntPair(1, 3) |
| c = TestIntPair(2, 0) |
| assert RecursiveLt(a, b) # first field equal, second: 2 < 3 |
| assert RecursiveLt(a, c) # first field: 1 < 2 |
| |
| |
| # --------------------------------------------------------------------------- |
| # CompareOff flag: TestCompare |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_compare_off_ignored_field() -> None: |
| """ignored_field is excluded from comparison via Compare(false).""" |
| a = TestCompare(1, "x", 100) |
| b = TestCompare(1, "x", 999) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_compare_off_key_differs() -> None: |
| a = TestCompare(1, "x", 100) |
| b = TestCompare(2, "x", 100) |
| assert not RecursiveEq(a, b) |
| assert RecursiveLt(a, b) |
| |
| |
| def test_compare_off_name_differs() -> None: |
| a = TestCompare(1, "a", 100) |
| b = TestCompare(1, "b", 100) |
| assert not RecursiveEq(a, b) |
| assert RecursiveLt(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Same pointer fast path |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_same_pointer() -> None: |
| x = TestIntPair(42, 99) |
| assert RecursiveEq(x, x) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Different object types |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_different_obj_types_eq() -> None: |
| """RecursiveEq returns False for different object types.""" |
| a = TestIntPair(1, 2) |
| b = TestCompare(1, "x", 0) |
| assert not RecursiveEq(a, b) |
| |
| |
| def test_different_obj_types_ordering_raises() -> None: |
| a = TestIntPair(1, 2) |
| b = TestCompare(1, "x", 0) |
| with pytest.raises(TypeError): |
| RecursiveLt(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Nested objects |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_nested_objects_in_array() -> None: |
| a1 = TestIntPair(1, 2) |
| a2 = TestIntPair(3, 4) |
| b1 = TestIntPair(1, 2) |
| b2 = TestIntPair(3, 4) |
| arr_a = tvm_ffi.Array([a1, a2]) |
| arr_b = tvm_ffi.Array([b1, b2]) |
| assert RecursiveEq(arr_a, arr_b) |
| |
| |
| def test_nested_objects_in_array_differ() -> None: |
| a1 = TestIntPair(1, 2) |
| a2 = TestIntPair(3, 4) |
| b1 = TestIntPair(1, 2) |
| b2 = TestIntPair(3, 5) |
| arr_a = tvm_ffi.Array([a1, a2]) |
| arr_b = tvm_ffi.Array([b1, b2]) |
| assert not RecursiveEq(arr_a, arr_b) |
| assert RecursiveLt(arr_a, arr_b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Le / Ge / Gt derived operators |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_le_ge_gt() -> None: |
| assert RecursiveLe(1, 2) |
| assert RecursiveLe(2, 2) |
| assert not RecursiveLe(3, 2) |
| assert RecursiveGe(2, 1) |
| assert RecursiveGe(2, 2) |
| assert not RecursiveGe(1, 2) |
| assert RecursiveGt(2, 1) |
| assert not RecursiveGt(1, 1) |
| assert not RecursiveGt(0, 1) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Nested containers |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_array_of_arrays_eq() -> None: |
| a = tvm_ffi.Array([tvm_ffi.Array([1, 2]), tvm_ffi.Array([3, 4])]) |
| b = tvm_ffi.Array([tvm_ffi.Array([1, 2]), tvm_ffi.Array([3, 4])]) |
| c = tvm_ffi.Array([tvm_ffi.Array([1, 2]), tvm_ffi.Array([3, 5])]) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| def test_array_of_arrays_ordering() -> None: |
| a = tvm_ffi.Array([tvm_ffi.Array([1, 2]), tvm_ffi.Array([3, 4])]) |
| b = tvm_ffi.Array([tvm_ffi.Array([1, 2]), tvm_ffi.Array([3, 5])]) |
| assert RecursiveLt(a, b) # differ at depth-2: 4 < 5 |
| assert not RecursiveLt(b, a) |
| |
| |
| def test_list_of_lists_eq() -> None: |
| a = tvm_ffi.List([tvm_ffi.List([1, 2]), tvm_ffi.List([3])]) |
| b = tvm_ffi.List([tvm_ffi.List([1, 2]), tvm_ffi.List([3])]) |
| c = tvm_ffi.List([tvm_ffi.List([1, 2]), tvm_ffi.List([4])]) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| def test_array_of_shapes() -> None: |
| a = tvm_ffi.Array([tvm_ffi.Shape((1, 2)), tvm_ffi.Shape((3, 4))]) |
| b = tvm_ffi.Array([tvm_ffi.Shape((1, 2)), tvm_ffi.Shape((3, 4))]) |
| c = tvm_ffi.Array([tvm_ffi.Shape((1, 2)), tvm_ffi.Shape((3, 5))]) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| assert RecursiveLt(a, c) |
| |
| |
| def test_array_of_arrays_different_inner_lengths() -> None: |
| a = tvm_ffi.Array([tvm_ffi.Array([1, 2])]) |
| b = tvm_ffi.Array([tvm_ffi.Array([1, 2, 3])]) |
| assert not RecursiveEq(a, b) |
| assert RecursiveLt(a, b) # inner [1,2] < [1,2,3] |
| |
| |
| def test_three_level_nested_containers() -> None: |
| a = tvm_ffi.Array([tvm_ffi.Array([tvm_ffi.Array([1])])]) |
| b = tvm_ffi.Array([tvm_ffi.Array([tvm_ffi.Array([1])])]) |
| c = tvm_ffi.Array([tvm_ffi.Array([tvm_ffi.Array([2])])]) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| assert RecursiveLt(a, c) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Objects with container fields (TestObjectDerived) |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_object_with_array_field_eq() -> None: |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([10, 20, 30]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([10, 20, 30]), |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_object_with_array_field_differ() -> None: |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([10, 20]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([10, 21]), |
| ) |
| assert not RecursiveEq(a, b) |
| assert RecursiveLt(a, b) |
| |
| |
| def test_object_with_map_field_eq() -> None: |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({"a": 1, "b": 2}), |
| v_array=tvm_ffi.Array([]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({"a": 1, "b": 2}), |
| v_array=tvm_ffi.Array([]), |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_object_with_map_field_differ() -> None: |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({"a": 1}), |
| v_array=tvm_ffi.Array([]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({"a": 2}), |
| v_array=tvm_ffi.Array([]), |
| ) |
| assert not RecursiveEq(a, b) |
| |
| |
| def test_object_primitive_field_differ_short_circuits() -> None: |
| """First field (v_i64) differs; container fields are the same.""" |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({"k": 1}), |
| v_array=tvm_ffi.Array([1]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=2, |
| v_f64=2.0, |
| v_str="s", |
| v_map=tvm_ffi.Map({"k": 1}), |
| v_array=tvm_ffi.Array([1]), |
| ) |
| assert not RecursiveEq(a, b) |
| assert RecursiveLt(a, b) # ordering uses v_i64: 1 < 2 |
| |
| |
| # --------------------------------------------------------------------------- |
| # Objects nested inside object fields |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_object_array_of_objects() -> None: |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array( |
| [ |
| TestIntPair(1, 2), |
| TestIntPair(3, 4), |
| ] |
| ), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array( |
| [ |
| TestIntPair(1, 2), |
| TestIntPair(3, 4), |
| ] |
| ), |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_object_array_of_objects_differ() -> None: |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array( |
| [ |
| TestIntPair(1, 2), |
| TestIntPair(3, 4), |
| ] |
| ), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array( |
| [ |
| TestIntPair(1, 2), |
| TestIntPair(3, 5), |
| ] |
| ), |
| ) |
| assert not RecursiveEq(a, b) |
| assert RecursiveLt(a, b) |
| |
| |
| def test_object_map_with_object_values() -> None: |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map( |
| { |
| "x": TestIntPair(1, 2), |
| } |
| ), |
| v_array=tvm_ffi.Array([]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map( |
| { |
| "x": TestIntPair(1, 2), |
| } |
| ), |
| v_array=tvm_ffi.Array([]), |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_deep_object_in_object() -> None: |
| """TestObjectDerived.v_array contains another TestObjectDerived -> 3-level nesting.""" |
| inner_a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=1.0, |
| v_str="inner", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([42]), |
| ) |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="outer", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([inner_a]), |
| ) |
| inner_b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=1, |
| v_f64=1.0, |
| v_str="inner", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([42]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="outer", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([inner_b]), |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Inherited field comparison |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_inherited_fields_eq() -> None: |
| a = _TestCxxClassDerived(10, 20, 1.5, 2.5) |
| b = _TestCxxClassDerived(10, 20, 1.5, 2.5) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_inherited_fields_differ_in_base() -> None: |
| a = _TestCxxClassDerived(10, 20, 1.5, 2.5) |
| b = _TestCxxClassDerived(99, 20, 1.5, 2.5) |
| assert not RecursiveEq(a, b) |
| assert RecursiveLt(a, b) |
| |
| |
| def test_three_level_inheritance_eq_and_differ() -> None: |
| # Positional order: required (v_i64, v_i32, v_f64, v_bool), then optional (v_f32, v_str) |
| a = _TestCxxClassDerivedDerived(1, 2, 3.0, True, 4.0, "hi") |
| b = _TestCxxClassDerivedDerived(1, 2, 3.0, True, 4.0, "hi") |
| assert RecursiveEq(a, b) |
| c = _TestCxxClassDerivedDerived(1, 2, 3.0, False, 4.0, "hi") |
| assert not RecursiveEq(a, c) |
| |
| |
| # --------------------------------------------------------------------------- |
| # CompareOff with nesting |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_compare_off_inside_array() -> None: |
| a = tvm_ffi.Array( |
| [ |
| TestCompare(1, "x", 100), |
| TestCompare(2, "y", 200), |
| ] |
| ) |
| b = tvm_ffi.Array( |
| [ |
| TestCompare(1, "x", 999), |
| TestCompare(2, "y", 888), |
| ] |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_compare_off_inside_nested_object() -> None: |
| """TestObjectDerived.v_array contains TestCompare with different ignored_field.""" |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array( |
| [ |
| TestCompare(1, "n", 100), |
| ] |
| ), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array( |
| [ |
| TestCompare(1, "n", 999), |
| ] |
| ), |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # SmallStr / Str cross-variant in nested context |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_object_with_short_vs_long_string_field() -> None: |
| """SmallStr (<=7 bytes) vs Str (>7 bytes) stored in TestObjectDerived.v_str.""" |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="hi", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="a_very_long_string", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([]), |
| ) |
| assert not RecursiveEq(a, b) |
| |
| |
| def test_array_of_mixed_length_strings() -> None: |
| """Array mixing SmallStr (<=7 bytes) and Str (>7 bytes).""" |
| a = tvm_ffi.Array(["hi", "a_very_long_string", "ok"]) |
| b = tvm_ffi.Array(["hi", "a_very_long_string", "ok"]) |
| c = tvm_ffi.Array(["hi", "a_very_long_string", "zz"]) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| assert RecursiveLt(a, c) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Map / Dict with nested values |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_map_with_array_values_eq() -> None: |
| a = tvm_ffi.Map({"k": tvm_ffi.Array([1, 2])}) |
| b = tvm_ffi.Map({"k": tvm_ffi.Array([1, 2])}) |
| c = tvm_ffi.Map({"k": tvm_ffi.Array([1, 3])}) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| def test_dict_with_object_values_eq() -> None: |
| a = tvm_ffi.Dict( |
| { |
| "k": TestIntPair(1, 2), |
| } |
| ) |
| b = tvm_ffi.Dict( |
| { |
| "k": TestIntPair(1, 2), |
| } |
| ) |
| c = tvm_ffi.Dict( |
| { |
| "k": TestIntPair(1, 3), |
| } |
| ) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| |
| |
| # --------------------------------------------------------------------------- |
| # None in nested contexts |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_array_with_none_elements() -> None: |
| a = tvm_ffi.Array([None, 1, None]) |
| b = tvm_ffi.Array([None, 1, None]) |
| c = tvm_ffi.Array([None, 2, None]) |
| assert RecursiveEq(a, b) |
| assert not RecursiveEq(a, c) |
| assert RecursiveLt(a, c) # element 1: 1 < 2 |
| |
| |
| def test_object_with_none_in_array_field() -> None: |
| a = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([None, 1]), |
| ) |
| b = create_object( |
| "testing.TestObjectDerived", |
| v_i64=0, |
| v_f64=0.0, |
| v_str="", |
| v_map=tvm_ffi.Map({}), |
| v_array=tvm_ffi.Array([None, 1]), |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Cross-container and function-object edge cases |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_array_list_type_mismatch() -> None: |
| arr = tvm_ffi.Array([1, 2]) |
| lst = tvm_ffi.List([1, 2]) |
| assert not RecursiveEq(arr, lst) |
| with pytest.raises(TypeError): |
| RecursiveLt(arr, lst) |
| |
| |
| def test_map_dict_type_mismatch() -> None: |
| m = tvm_ffi.Map({"k": 1}) |
| d = tvm_ffi.Dict({"k": 1}) |
| assert not RecursiveEq(m, d) |
| with pytest.raises(TypeError): |
| RecursiveLt(m, d) |
| |
| |
| def test_function_objects_compare_equal() -> None: |
| """Function objects have no reflected fields, so distinct functions compare equal. |
| |
| This is by design: reflection-based comparison only considers reflected fields, |
| and Function has none. |
| """ |
| f_add_one = tvm_ffi.get_global_func("testing.add_one") |
| f_nop = tvm_ffi.get_global_func("testing.nop") |
| assert RecursiveEq(f_add_one, f_nop) |
| |
| |
| def test_function_same_pointer_eq() -> None: |
| """Same function object compared with itself returns True via pointer identity.""" |
| f = tvm_ffi.get_global_func("testing.add_one") |
| assert RecursiveEq(f, f) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Cycle safety: run in subprocess to avoid hanging/crashing pytest worker |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_cyclic_list_same_pointer_eq() -> None: |
| """Same cyclic list compared with itself returns True via pointer identity.""" |
| lst = tvm_ffi.List() |
| lst.append(lst) |
| assert RecursiveEq(lst, lst) |
| |
| |
| def test_cyclic_list_eq_returns_true() -> None: |
| """Two distinct cyclic lists are considered equal by RecursiveEq. |
| |
| In eq-only mode, the cycle detector treats a re-encountered (lhs, rhs) pair |
| as equal, allowing the recursion to terminate. |
| """ |
| a = tvm_ffi.List() |
| b = tvm_ffi.List() |
| a.append(a) |
| b.append(b) |
| assert RecursiveEq(a, b) |
| |
| |
| def test_cyclic_list_ordering_raises() -> None: |
| """Ordering two distinct cyclic lists raises ValueError.""" |
| a = tvm_ffi.List() |
| b = tvm_ffi.List() |
| a.append(a) |
| b.append(b) |
| with pytest.raises(ValueError, match="cyclic reference"): |
| RecursiveLt(a, b) |
| |
| |
| def test_cyclic_dict_eq_returns_true() -> None: |
| """Two distinct cyclic dicts are considered equal by RecursiveEq. |
| |
| In eq-only mode, the cycle detector treats a re-encountered (lhs, rhs) pair |
| as equal, allowing the recursion to terminate. |
| """ |
| a = tvm_ffi.Dict() |
| b = tvm_ffi.Dict() |
| a["self"] = a |
| b["self"] = b |
| assert RecursiveEq(a, b) |
| |
| |
| def test_cyclic_dict_ordering_raises() -> None: |
| """Ordering two distinct cyclic dicts raises ValueError.""" |
| a = tvm_ffi.Dict() |
| b = tvm_ffi.Dict() |
| a["self"] = a |
| b["self"] = b |
| with pytest.raises(ValueError, match="cyclic reference"): |
| RecursiveLt(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Comparator laws |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_ordering_laws_on_int_pairs() -> None: |
| """Verify ordering laws (trichotomy, antisymmetry, transitivity) on TestIntPair.""" |
| values = [ |
| TestIntPair(0, 0), |
| TestIntPair(0, 1), |
| TestIntPair(1, 0), |
| TestIntPair(1, 1), |
| ] |
| for a in values: |
| for b in values: |
| lt = RecursiveLt(a, b) |
| eq = RecursiveEq(a, b) |
| gt = RecursiveGt(a, b) |
| le = RecursiveLe(a, b) |
| ge = RecursiveGe(a, b) |
| # Trichotomy: exactly one of a < b, a == b, a > b |
| assert (lt + eq + gt) == 1, f"Trichotomy violated for ({a}, {b})" |
| # Consistency of derived operators |
| assert le == (lt or eq) |
| assert ge == (gt or eq) |
| assert lt == (not ge) |
| assert gt == (not le) |
| # Antisymmetry: (a <= b and b <= a) implies a == b |
| assert eq == (le and ge), f"Antisymmetry violated for ({a}, {b})" |
| # Transitivity for triplets |
| for a in values: |
| for b in values: |
| for c in values: |
| if RecursiveLt(a, b) and RecursiveLt(b, c): |
| assert RecursiveLt(a, c), f"Lt transitivity violated on ({a}, {b}, {c})" |
| if RecursiveLe(a, b) and RecursiveLe(b, c): |
| assert RecursiveLe(a, c), f"Le transitivity violated on ({a}, {b}, {c})" |
| |
| |
| # --------------------------------------------------------------------------- |
| # Deep nesting (iterative stack handles depth > 128) |
| # --------------------------------------------------------------------------- |
| |
| |
| def _make_nested_singleton_array(depth: int) -> object: |
| value: object = 0 |
| for _ in range(depth): |
| value = tvm_ffi.Array([value]) |
| return value |
| |
| |
| def test_depth_1000_nested_eq() -> None: |
| """Deep nested arrays compare correctly with iterative stack.""" |
| a = _make_nested_singleton_array(1000) |
| b = _make_nested_singleton_array(1000) |
| assert RecursiveEq(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Custom __ffi_eq__ / __ffi_compare__ hooks: TestCustomCompare |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_custom_eq_ignores_label() -> None: |
| """TestCustomCompare.__ffi_eq__ compares only `key`, ignoring `label`.""" |
| a = TestCustomCompare(42, "alpha") |
| b = TestCustomCompare(42, "beta") |
| assert RecursiveEq(a, b) |
| |
| |
| def test_custom_eq_different_key() -> None: |
| a = TestCustomCompare(1, "same") |
| b = TestCustomCompare(2, "same") |
| assert not RecursiveEq(a, b) |
| |
| |
| def test_custom_compare_ordering() -> None: |
| """Ordering uses __ffi_compare__ hook (key only).""" |
| a = TestCustomCompare(1, "zzz") |
| b = TestCustomCompare(2, "aaa") |
| assert RecursiveLt(a, b) |
| assert not RecursiveLt(b, a) |
| |
| |
| def test_custom_eq_in_container() -> None: |
| """Custom-hooked objects inside an Array.""" |
| a = tvm_ffi.Array( |
| [ |
| TestCustomCompare(1, "x"), |
| TestCustomCompare(2, "y"), |
| ] |
| ) |
| b = tvm_ffi.Array( |
| [ |
| TestCustomCompare(1, "different"), |
| TestCustomCompare(2, "labels"), |
| ] |
| ) |
| assert RecursiveEq(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # __ffi_eq__-only types: eq and ordering may diverge (no __ffi_compare__) |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_eq_only_type_eq_uses_hook() -> None: |
| """__ffi_eq__-only type: RecursiveEq uses the hook (compares only key).""" |
| a = TestEqWithoutHash(42, "alpha") |
| b = TestEqWithoutHash(42, "beta") |
| assert RecursiveEq(a, b) |
| |
| |
| def test_eq_only_type_ordering_uses_reflection() -> None: |
| """__ffi_eq__-only type: ordering falls back to field-by-field reflection. |
| |
| Without __ffi_compare__, ordering sees the differing `label` field even |
| though __ffi_eq__ ignores it. This is expected — register __ffi_compare__ |
| for consistent ordering semantics. |
| """ |
| a = TestEqWithoutHash(42, "alpha") |
| b = TestEqWithoutHash(42, "beta") |
| # Eq says equal (hook), but ordering sees label difference (reflection) |
| assert RecursiveEq(a, b) |
| assert RecursiveLt(a, b) # "alpha" < "beta" |
| |
| |
| # --------------------------------------------------------------------------- |
| # __ffi_compare__-equipped types: ordering consistency guaranteed |
| # --------------------------------------------------------------------------- |
| |
| |
| def test_custom_compare_ordering_consistency() -> None: |
| """TestCustomCompare has __ffi_compare__: Eq(a,b) implies not Lt/Gt and both Le/Ge.""" |
| a = TestCustomCompare(42, "alpha") |
| b = TestCustomCompare(42, "beta") |
| assert RecursiveEq(a, b) |
| assert not RecursiveLt(a, b) |
| assert not RecursiveGt(a, b) |
| assert RecursiveLe(a, b) |
| assert RecursiveGe(a, b) |
| |
| |
| # --------------------------------------------------------------------------- |
| # Custom __ffi_eq__ / __ffi_compare__ hooks via @py_class |
| # --------------------------------------------------------------------------- |
| import itertools as _itertools_cmp |
| from typing import Any as _Any_cmp |
| from typing import Callable as _Callable_cmp |
| |
| from tvm_ffi._ffi_api import RecursiveHash as _RecursiveHash_cmp |
| from tvm_ffi.core import Object as _Object_cmp |
| from tvm_ffi.dataclasses import py_class as _py_class_cmp |
| |
| _counter_cmp = _itertools_cmp.count() |
| |
| |
| def _unique_key_cmp(base: str) -> str: |
| return f"testing.cmp_pc.{base}_{next(_counter_cmp)}" |
| |
| |
| @_py_class_cmp(_unique_key_cmp("PyEqHash")) |
| class _PyEqHash(_Object_cmp): |
| key: int |
| label: str |
| |
| def __ffi_hash__(self, fn_hash: _Callable_cmp[..., _Any_cmp]) -> int: |
| return fn_hash(self.key) |
| |
| def __ffi_eq__(self, other: _PyEqHash, fn_eq: _Callable_cmp[..., _Any_cmp]) -> bool: |
| return fn_eq(self.key, other.key) |
| |
| |
| @_py_class_cmp(_unique_key_cmp("PyCmp")) |
| class _PyCmp(_Object_cmp): |
| key: int |
| label: str |
| |
| def __ffi_hash__(self, fn_hash: _Callable_cmp[..., _Any_cmp]) -> int: |
| return fn_hash(self.key) |
| |
| def __ffi_eq__(self, other: _PyCmp, fn_eq: _Callable_cmp[..., _Any_cmp]) -> bool: |
| return fn_eq(self.key, other.key) |
| |
| def __ffi_compare__(self, other: _PyCmp, fn_cmp: _Callable_cmp[..., _Any_cmp]) -> int: |
| return fn_cmp(self.key, other.key) |
| |
| |
| def test_py_class_custom_eq_ignores_label() -> None: |
| assert RecursiveEq(_PyEqHash(42, "alpha"), _PyEqHash(42, "beta")) |
| |
| |
| def test_py_class_custom_eq_different_key() -> None: |
| assert not RecursiveEq(_PyEqHash(1, "same"), _PyEqHash(2, "same")) |
| |
| |
| def test_py_class_custom_eq_hash_consistency() -> None: |
| a, b = _PyEqHash(42, "alpha"), _PyEqHash(42, "beta") |
| assert RecursiveEq(a, b) |
| assert _RecursiveHash_cmp(a) == _RecursiveHash_cmp(b) |
| |
| |
| def test_py_class_custom_compare_ordering() -> None: |
| a = _PyCmp(1, "zzz") |
| b = _PyCmp(2, "aaa") |
| assert RecursiveLt(a, b) |
| assert RecursiveLe(a, b) |
| assert not RecursiveGt(a, b) |
| assert not RecursiveGe(a, b) |
| |
| |
| def test_py_class_custom_compare_equal_keys() -> None: |
| a = _PyCmp(42, "alpha") |
| b = _PyCmp(42, "beta") |
| assert RecursiveEq(a, b) |
| assert RecursiveLe(a, b) |
| assert RecursiveGe(a, b) |
| assert not RecursiveLt(a, b) |
| assert not RecursiveGt(a, b) |