Files
2026-07-13 12:40:42 +08:00

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()