338 lines
11 KiB
Python
338 lines
11 KiB
Python
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed 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.
|
|
|
|
import unittest
|
|
|
|
import numpy as np
|
|
|
|
import paddle
|
|
from paddle.testing import assert_close
|
|
|
|
|
|
class TestAssertClose(unittest.TestCase):
|
|
def setUp(self):
|
|
paddle.set_device("cpu")
|
|
|
|
def test_scalars_exact_match(self):
|
|
assert_close(1, 1)
|
|
assert_close(1.0, 1.0)
|
|
assert_close(True, True)
|
|
assert_close(None, None)
|
|
assert_close(1 + 2j, 1 + 2j)
|
|
|
|
def test_scalars_mismatch(self):
|
|
with self.assertRaisesRegex(AssertionError, "Scalars are not equal!"):
|
|
assert_close(1, 2)
|
|
with self.assertRaisesRegex(AssertionError, "Booleans mismatch"):
|
|
assert_close(True, False)
|
|
with self.assertRaisesRegex(AssertionError, "None mismatch"):
|
|
assert_close(None, 1)
|
|
|
|
def test_scalars_tolerances(self):
|
|
assert_close(1.0, 1.0 + 1e-9)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
assert_close(1.0, 1.1)
|
|
|
|
assert_close(1.0, 1.1, atol=0.2, rtol=0.0)
|
|
with self.assertRaises(AssertionError):
|
|
assert_close(1.0, 1.1, atol=0.05, rtol=0.0)
|
|
|
|
def test_numpy_scalars(self):
|
|
assert_close(np.float32(1.0), np.float32(1.0))
|
|
assert_close(np.int32(1), np.int32(1))
|
|
assert_close(np.bool_(True), np.bool_(True))
|
|
|
|
assert_close(np.float64(1.0), 1.0)
|
|
|
|
def test_tensor_exact_match(self):
|
|
t1 = paddle.to_tensor([1.0, 2.0, 3.0])
|
|
t2 = paddle.to_tensor([1.0, 2.0, 3.0])
|
|
assert_close(t1, t2)
|
|
|
|
def test_tensor_tolerances(self):
|
|
t1 = paddle.to_tensor([1.0, 2.0, 3.0], dtype='float32')
|
|
t2 = t1 + 1e-6
|
|
assert_close(t1, t2)
|
|
|
|
t3 = t1 + 1e-4
|
|
with self.assertRaisesRegex(
|
|
AssertionError, "Tensor-likes are not close"
|
|
):
|
|
assert_close(t1, t3)
|
|
|
|
assert_close(t1, t3, atol=1e-3, rtol=0.0)
|
|
|
|
def test_tensor_shape_mismatch(self):
|
|
t1 = paddle.zeros([2, 2])
|
|
t2 = paddle.zeros([2, 3])
|
|
with self.assertRaisesRegex(AssertionError, "shape"):
|
|
assert_close(t1, t2)
|
|
|
|
def test_tensor_dtype_check(self):
|
|
t_float32 = paddle.to_tensor([1.0], dtype='float32')
|
|
t_float64 = paddle.to_tensor([1.0], dtype='float64')
|
|
|
|
with self.assertRaisesRegex(AssertionError, "dtype"):
|
|
assert_close(t_float32, t_float64)
|
|
|
|
assert_close(t_float32, t_float64, check_dtype=False)
|
|
|
|
def test_tensor_device_check(self):
|
|
t1 = paddle.to_tensor([1.0])
|
|
if paddle.device.is_compiled_with_cuda():
|
|
t_gpu = t1.to("gpu")
|
|
with self.assertRaisesRegex(AssertionError, "device"):
|
|
assert_close(t1, t_gpu)
|
|
|
|
assert_close(t1, t_gpu, check_device=False)
|
|
else:
|
|
assert_close(t1, t1)
|
|
|
|
def test_nan_handling(self):
|
|
val_nan = float('nan')
|
|
t_nan = paddle.to_tensor([val_nan])
|
|
|
|
with self.assertRaises(AssertionError):
|
|
assert_close(val_nan, val_nan)
|
|
with self.assertRaises(AssertionError):
|
|
assert_close(t_nan, t_nan)
|
|
|
|
assert_close(val_nan, val_nan, equal_nan=True)
|
|
assert_close(t_nan, t_nan, equal_nan=True)
|
|
|
|
def test_sequences(self):
|
|
l1 = [paddle.to_tensor(1.0), 2.0]
|
|
l2 = [paddle.to_tensor(1.0), 2.0]
|
|
assert_close(l1, l2)
|
|
|
|
with self.assertRaisesRegex(
|
|
AssertionError, "length of the sequences mismatch"
|
|
):
|
|
assert_close([1], [1, 2])
|
|
|
|
with self.assertRaisesRegex(AssertionError, "Scalars are not equal!"):
|
|
assert_close([1], [2])
|
|
|
|
def test_mappings(self):
|
|
d1 = {"a": 1, "b": paddle.to_tensor(2.0)}
|
|
d2 = {"a": 1, "b": paddle.to_tensor(2.0)}
|
|
assert_close(d1, d2)
|
|
|
|
d3 = {"a": 1, "c": 2.0}
|
|
with self.assertRaisesRegex(
|
|
AssertionError, "keys of the mappings do not match"
|
|
):
|
|
assert_close(d1, d3)
|
|
|
|
def test_nested_structure_error_msg(self):
|
|
actual = {"data": [{"val": 10}]}
|
|
expected = {"data": [{"val": 20}]}
|
|
|
|
try:
|
|
assert_close(actual, expected)
|
|
except AssertionError as e:
|
|
msg = str(e)
|
|
self.assertIn("data", msg)
|
|
self.assertIn("val", msg)
|
|
self.assertIn("['data']", msg)
|
|
|
|
def test_tensor_mismatch_msg_details(self):
|
|
t1 = paddle.to_tensor([[1.0, 2.0], [3.0, 4.0]])
|
|
t2 = paddle.to_tensor([[1.0, 2.0], [3.0, 5.0]])
|
|
|
|
try:
|
|
assert_close(t1, t2)
|
|
except AssertionError as e:
|
|
msg = str(e)
|
|
self.assertIn("Mismatched elements: 1 / 4", msg)
|
|
self.assertIn("Greatest absolute difference: 1.0", msg)
|
|
self.assertIn("at index (1, 1)", msg)
|
|
|
|
def test_msg_override(self):
|
|
with self.assertRaisesRegex(AssertionError, "My custom error"):
|
|
assert_close(1, 2, msg="My custom error")
|
|
|
|
def test_unsupported_types(self):
|
|
class A:
|
|
pass
|
|
|
|
class B:
|
|
pass
|
|
|
|
with self.assertRaises(TypeError):
|
|
assert_close(A(), B())
|
|
|
|
def test_complex_numbers(self):
|
|
c1 = 1 + 1j
|
|
c2 = 1 + 1j + 1e-10j
|
|
c3 = 1 + 2j
|
|
|
|
assert_close(c1, c2)
|
|
with self.assertRaises(AssertionError):
|
|
assert_close(c1, c3)
|
|
|
|
def test_tolerance_validation_logic(self):
|
|
with self.assertRaisesRegex(
|
|
ValueError,
|
|
"Both 'rtol' and 'atol' must be either specified or omitted",
|
|
):
|
|
assert_close(1.0, 1.0, rtol=1e-5)
|
|
|
|
with self.assertRaisesRegex(
|
|
ValueError,
|
|
"Both 'rtol' and 'atol' must be either specified or omitted",
|
|
):
|
|
assert_close(1.0, 1.0, atol=1e-5)
|
|
|
|
def test_msg_callable(self):
|
|
def custom_formatter(orig_msg):
|
|
return f"PREFIX -> {orig_msg} <- SUFFIX"
|
|
|
|
with self.assertRaisesRegex(
|
|
AssertionError, "PREFIX -> Scalars are not equal!"
|
|
):
|
|
assert_close(1, 2, msg=custom_formatter)
|
|
|
|
def test_zero_dim_tensor_mismatch(self):
|
|
t1 = paddle.to_tensor(1.0)
|
|
t2 = paddle.to_tensor(2.0)
|
|
|
|
with self.assertRaisesRegex(AssertionError, "Scalars"):
|
|
assert_close(t1, t2)
|
|
|
|
try:
|
|
assert_close(t1, t2)
|
|
except AssertionError as e:
|
|
self.assertNotIn("Tensor-likes", str(e))
|
|
|
|
def test_type_promotion_logic(self):
|
|
t_real = paddle.to_tensor([1.0], dtype='float32')
|
|
t_complex = paddle.to_tensor([1.0 + 0j], dtype='complex64')
|
|
assert_close(t_real, t_complex, check_dtype=False)
|
|
|
|
t_c64 = paddle.to_tensor([1 + 1j], dtype='complex64')
|
|
t_c128 = paddle.to_tensor([1 + 1j], dtype='complex128')
|
|
assert_close(t_c64, t_c128, check_dtype=False)
|
|
|
|
def test_object_pair_broken_eq(self):
|
|
from paddle.testing._comparison import ErrorMeta, ObjectPair
|
|
|
|
class BrokenObj:
|
|
def __eq__(self, other):
|
|
raise RuntimeError("Comparison crashed internal error!")
|
|
|
|
def __repr__(self):
|
|
return "BrokenObj"
|
|
|
|
obj = BrokenObj()
|
|
pair = ObjectPair(obj, obj)
|
|
|
|
try:
|
|
pair.compare()
|
|
except ErrorMeta as e:
|
|
actual_error = e.to_error()
|
|
|
|
self.assertIsInstance(actual_error, ValueError)
|
|
self.assertIn(
|
|
"failed with:\nComparison crashed internal error!",
|
|
str(actual_error),
|
|
)
|
|
else:
|
|
self.fail("ObjectPair.compare() should have raised an ErrorMeta")
|
|
|
|
def test_pair_repr_and_extra_repr(self):
|
|
from paddle.testing._comparison import NumberPair, ObjectPair, Pair
|
|
|
|
obj_pair = ObjectPair(actual=10, expected=20, id=("test_id",))
|
|
rep_str = repr(obj_pair)
|
|
|
|
self.assertIn("ObjectPair(", rep_str)
|
|
self.assertIn("id=('test_id',),", rep_str)
|
|
self.assertIn("actual=10,", rep_str)
|
|
self.assertIn("expected=20,", rep_str)
|
|
|
|
num_pair = NumberPair(1.0, 1.0, rtol=0.5, atol=0.1)
|
|
rep_str_num = repr(num_pair)
|
|
|
|
self.assertIn("NumberPair(", rep_str_num)
|
|
self.assertIn("rtol=0.5,", rep_str_num)
|
|
self.assertIn("atol=0.1,", rep_str_num)
|
|
|
|
class MockTuplePair(Pair):
|
|
def compare(self):
|
|
pass
|
|
|
|
def extra_repr(self):
|
|
return [("custom_key", "custom_value")]
|
|
|
|
mock_pair = MockTuplePair("act", "exp")
|
|
rep_str_mock = repr(mock_pair)
|
|
|
|
self.assertIn("MockTuplePair(", rep_str_mock)
|
|
self.assertIn("custom_key=custom_value,", rep_str_mock)
|
|
|
|
def test_static_graph_variable(self):
|
|
paddle.enable_static()
|
|
try:
|
|
main_prog = paddle.static.Program()
|
|
startup_prog = paddle.static.Program()
|
|
|
|
with paddle.static.program_guard(main_prog, startup_prog):
|
|
x = paddle.static.data(name='x', shape=[2, 2], dtype='float32')
|
|
y = paddle.static.data(name='y', shape=[2, 2], dtype='float32')
|
|
|
|
assert_close(x, y)
|
|
with self.assertRaisesRegex(
|
|
AssertionError, "Python types do not match"
|
|
):
|
|
assert_close(x, 1)
|
|
|
|
z = paddle.static.data(name='z', shape=[2, 2], dtype='int32')
|
|
assert_close(x, z, check_dtype=False)
|
|
with self.assertRaisesRegex(
|
|
AssertionError,
|
|
"The values for attribute dtype do not match",
|
|
):
|
|
assert_close(x, z)
|
|
|
|
w = paddle.static.data(name='w', shape=[2, 3], dtype='float32')
|
|
with self.assertRaisesRegex(
|
|
AssertionError,
|
|
"The values for attribute shape do not match",
|
|
):
|
|
assert_close(x, w)
|
|
|
|
v = paddle.static.data(
|
|
name='v', shape=[2, 2, 1], dtype='float32'
|
|
)
|
|
with self.assertRaisesRegex(
|
|
AssertionError,
|
|
"The values for attribute shape do not match",
|
|
):
|
|
assert_close(x, v)
|
|
|
|
dynamic_x = paddle.static.data(
|
|
name='dynamic_x', shape=[-1, 2], dtype='float32'
|
|
)
|
|
assert_close(x, dynamic_x)
|
|
assert_close(dynamic_x, x)
|
|
|
|
finally:
|
|
paddle.disable_static()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|