Skip to content

Commit 9d7ed67

Browse files
authored
Improve test assertions to show actual values on failure (#1616)
Replace assertion patterns that don't show values on failure with NumPy and PyTorch testing utilities. Migrate from `self.assertTrue(np.all(...))` and `self.assertTrue(torch.allclose(...))` to `np.testing.assert_array_equal`, `np.testing.assert_array_compare`, `np.testing.assert_allclose`, and `torch.testing.assert_close`. These testing utilities automatically include summaries of actual vs expected values in their error messages, making test failures much easier to debug.
1 parent 9d6d236 commit 9d7ed67

16 files changed

Lines changed: 95 additions & 83 deletions

tests/cuda/buffer_transfer_test.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_transfer_buffer_to_cuda(self, media_type: str) -> None:
6969
self.assertTrue(cuda_tensor.is_cuda)
7070
self.assertEqual(cuda_tensor.device, torch.device(f"cuda:{DEFAULT_CUDA}"))
7171

72-
self.assertTrue(torch.allclose(cpu_tensor, cuda_tensor.cpu()))
72+
torch.testing.assert_close(cpu_tensor, cuda_tensor.cpu())
7373

7474
@parameterized.expand(
7575
[
@@ -128,7 +128,7 @@ def test() -> None:
128128
cuda_tensor = spdl.io.to_torch(cuda_buffer)
129129
self.assertTrue(cuda_tensor.is_cuda)
130130
self.assertEqual(cuda_tensor.device, torch.device(f"cuda:{DEFAULT_CUDA}"))
131-
self.assertTrue(torch.allclose(cpu_tensor, cuda_tensor.cpu()))
131+
torch.testing.assert_close(cpu_tensor, cuda_tensor.cpu())
132132

133133
print("Asserting deleter was not yet called")
134134
self.assertFalse(deleter_called)
@@ -161,7 +161,7 @@ def test(array) -> None:
161161
self.assertEqual(tensor.dtype, dtypes[array.dtype])
162162
self.assertEqual(tensor.shape, torch.Size(array.shape))
163163
self.assertEqual(tensor.device, torch.device(device))
164-
self.assertTrue(torch.allclose(tensor, torch.from_numpy(array).to(device)))
164+
torch.testing.assert_close(tensor, torch.from_numpy(array).to(device))
165165

166166
for dtype in [np.uint8, np.int32, np.int64]:
167167
max_val = np.iinfo(dtype).max
@@ -182,7 +182,7 @@ def test(cpu_tensor) -> None:
182182
self.assertEqual(cuda_tensor.dtype, cpu_tensor.dtype)
183183
self.assertEqual(cuda_tensor.shape, cpu_tensor.shape)
184184
self.assertEqual(cuda_tensor.device, device)
185-
self.assertTrue(torch.allclose(cuda_tensor, cpu_tensor.to(device)))
185+
torch.testing.assert_close(cuda_tensor, cpu_tensor.to(device))
186186

187187
for dtype in [np.uint8, np.int32, np.int64]:
188188
max_val = np.iinfo(dtype).max
@@ -205,7 +205,7 @@ def test_array_transfer_non_contiguous_torch(self) -> None:
205205
self.assertEqual(cuda_tensor.dtype, cpu_tensor.dtype)
206206
self.assertEqual(cuda_tensor.shape, cpu_tensor.shape)
207207
self.assertEqual(cuda_tensor.device, device)
208-
self.assertTrue(torch.allclose(cuda_tensor, cpu_tensor.to(device)))
208+
torch.testing.assert_close(cuda_tensor, cpu_tensor.to(device))
209209

210210
def test_array_transfer_non_contiguous_numpy(self) -> None:
211211
"""passing noncontiguous array/tensor to transfer_buffer works"""
@@ -224,7 +224,7 @@ def test_array_transfer_non_contiguous_numpy(self) -> None:
224224
self.assertEqual(tensor.dtype, torch.int64)
225225
self.assertEqual(tensor.shape, torch.Size(arr.shape))
226226
self.assertEqual(tensor.device, torch.device(device))
227-
self.assertTrue(torch.allclose(tensor, torch.from_numpy(arr).to(device)))
227+
torch.testing.assert_close(tensor, torch.from_numpy(arr).to(device))
228228

229229
def test_array_transfer_smoke_test(self) -> None:
230230
"""smoke test for transferring multiple arrays concurrently"""
@@ -253,4 +253,4 @@ def test_transfer_cpu(self) -> None:
253253
self.assertEqual(ref.dtype, cpu_tensor.dtype)
254254
self.assertEqual(ref.shape, cpu_tensor.shape)
255255
self.assertEqual(ref.device, cpu_tensor.device)
256-
self.assertTrue(torch.allclose(ref, cpu_tensor))
256+
torch.testing.assert_close(ref, cpu_tensor)

tests/cuda/nvjpeg_decode_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ def _test(data, pix_fmt):
4444
rgb_tensor = _test(sample.path, "rgb")
4545
bgr_tensor = _test(sample.path, "bgr")
4646

47-
self.assertTrue(torch.equal(rgb_tensor[0], bgr_tensor[2]))
48-
self.assertTrue(torch.equal(rgb_tensor[1], bgr_tensor[1]))
49-
self.assertTrue(torch.equal(rgb_tensor[2], bgr_tensor[0]))
47+
torch.testing.assert_close(rgb_tensor[0], bgr_tensor[2])
48+
torch.testing.assert_close(rgb_tensor[1], bgr_tensor[1])
49+
torch.testing.assert_close(rgb_tensor[2], bgr_tensor[0])
5050

5151
def test_decode_rubbish(self) -> None:
5252
"""When decoding fails, it should raise an error instead of segfault then,

tests/cuda/pin_memory_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def test_pin_memory_convert_array(self) -> None:
8888
self.assertEqual(tensor.shape, (10,))
8989
self.assertEqual(tensor.dtype, torch.int64)
9090
self.assertEqual(tensor.device, torch.device("cuda:0"))
91-
self.assertTrue((vals == tensor.cpu().numpy()).all())
91+
np.testing.assert_array_equal(tensor.cpu().numpy(), vals, strict=True)
9292

9393
def test_pin_memory_convert_array_invalid_size(self) -> None:
9494
"""convert_array fails if storage is small."""

tests/cuda/transfer_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ def test_gpu_transfer(self) -> None:
2020
cuda = transfer_tensor(ref)
2121
print(cuda)
2222
self.assertEqual(cuda.device.type, "cuda")
23-
self.assertTrue(torch.equal(cuda, ref.cuda()))
23+
torch.testing.assert_close(cuda, ref.cuda())

tests/dataloader/sampler_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ def test_weighted_sampling(self) -> None:
462462
print(f"{ref=}")
463463
print(f"{hyp=}")
464464

465-
self.assertTrue(np.allclose(hyp, ref, atol=1e-3))
465+
np.testing.assert_allclose(hyp, ref, rtol=1e-5, atol=1e-3)
466466

467467

468468
class TestDistributedSamplerEmbedShuffle(unittest.TestCase):

tests/io/array_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,20 @@ def test_load_npy_integral(
4848

4949
data = _dump_npy(ref)
5050
recon = spdl.io.load_npy(data)
51-
self.assertTrue(np.array_equal(recon, ref))
51+
np.testing.assert_array_equal(recon, ref, strict=True)
5252

5353
# Use bytearray to check if the change to the original is refrected to the recon
5454
# (which means that the recon is referring to the original, no copy)
5555
data = bytearray(data)
5656
print(f"{id(data)=}")
5757
print(f"{id(recon.data.obj)=}")
5858
recon = spdl.io.load_npy(data)
59-
self.assertTrue(np.array_equal(recon, ref))
59+
np.testing.assert_array_equal(recon, ref, strict=True)
6060

6161
self.assertTrue(np.any(recon))
6262
# Fill zeros. The header is cleared too, but it's already parsed, so not an issue.
6363
data[:] = b"\x00" * len(data)
64-
self.assertFalse(np.any(recon))
64+
np.testing.assert_array_equal(recon, 0)
6565

6666
@parameterized.expand(
6767
[
@@ -81,20 +81,20 @@ def test_load_npy_float(self, dtype: type[np.floating]) -> None:
8181

8282
data = _dump_npy(ref)
8383
recon = spdl.io.load_npy(data)
84-
self.assertTrue(np.array_equal(recon, ref))
84+
np.testing.assert_array_equal(recon, ref, strict=True)
8585

8686
# Use bytearray to check if the change to the original is refrected to the recon
8787
# (which means that the recon is referring to the original, no copy)
8888
data = bytearray(data)
8989
print(f"{id(data)=}")
9090
print(f"{id(recon.data.obj)=}")
9191
recon = spdl.io.load_npy(data)
92-
self.assertTrue(np.array_equal(recon, ref))
92+
np.testing.assert_array_equal(recon, ref, strict=True)
9393

9494
self.assertTrue(np.any(recon))
9595
# Fill zeros. The header is cleared too, but it's already parsed, so not an issue.
9696
data[:] = b"\x00" * len(data)
97-
self.assertFalse(np.any(recon))
97+
np.testing.assert_array_equal(recon, 0)
9898

9999

100100
##############################################################################

tests/io/async_test.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,14 @@ def _decode(num_frames=None):
9191
arr1 = _decode(num_frames=num_frames)
9292
self.assertEqual(arr1.dtype, np.int16)
9393
self.assertEqual(arr1.shape, (num_frames, 1))
94-
self.assertTrue(np.all(arr1 == arr0[:num_frames]))
94+
np.testing.assert_array_equal(arr1, arr0[:num_frames], strict=True)
9595

9696
num_frames = 32000
9797
arr2 = _decode(num_frames=num_frames)
9898
self.assertEqual(arr2.dtype, np.int16)
9999
self.assertEqual(arr2.shape, (num_frames, 1))
100-
self.assertTrue(np.all(arr2[:16000] == arr0))
101-
self.assertTrue(np.all(arr2[16000:] == 0))
100+
np.testing.assert_array_equal(arr2[:16000], arr0, strict=True)
101+
np.testing.assert_array_equal(arr2[16000:], 0)
102102

103103
def test_decode_audio_many_channels_6(self) -> None:
104104
"""Can decode audio with more than 6 channels.
@@ -229,28 +229,30 @@ def _decode(pix_fmt="rgb24", **kwargs):
229229
arr1 = _decode(num_frames=num_frames)
230230
self.assertEqual(arr1.dtype, np.uint8)
231231
self.assertEqual(arr1.shape, (num_frames, 240, 320, 3))
232-
self.assertTrue(np.all(arr1 == arr0[:num_frames]))
232+
np.testing.assert_array_equal(arr1, arr0[:num_frames], strict=True)
233233

234234
num_frames = 100
235235
arr2 = _decode(num_frames=num_frames)
236236
self.assertEqual(arr2.dtype, np.uint8)
237237
self.assertEqual(arr2.shape, (num_frames, 240, 320, 3))
238-
self.assertTrue(np.all(arr2[:50] == arr0))
239-
self.assertTrue(np.all(arr2[50:] == arr2[50]))
238+
np.testing.assert_array_equal(arr2[:50], arr0, strict=True)
239+
np.testing.assert_array_equal(
240+
arr2[50:], np.broadcast_to(arr2[50], arr2[50:].shape), strict=True
241+
)
240242

241243
num_frames = 100
242244
arr2 = _decode(num_frames=num_frames, pad_mode="black")
243245
self.assertEqual(arr2.dtype, np.uint8)
244246
self.assertEqual(arr2.shape, (num_frames, 240, 320, 3))
245-
self.assertTrue(np.all(arr2[:50] == arr0))
246-
self.assertTrue(np.all(arr2[50:] == 0))
247+
np.testing.assert_array_equal(arr2[:50], arr0, strict=True)
248+
np.testing.assert_array_equal(arr2[50:], 0)
247249

248250
num_frames = 100
249251
arr2 = _decode(num_frames=num_frames, pad_mode="white")
250252
self.assertEqual(arr2.dtype, np.uint8)
251253
self.assertEqual(arr2.shape, (num_frames, 240, 320, 3))
252-
self.assertTrue(np.all(arr2[:50] == arr0))
253-
self.assertTrue(np.all(arr2[50:] == 255))
254+
np.testing.assert_array_equal(arr2[:50], arr0, strict=True)
255+
np.testing.assert_array_equal(arr2[50:], 255)
254256

255257
def test_decode_video_frame_rate_pts(self) -> None:
256258
"""Applying frame rate outputs correct PTS."""
@@ -267,7 +269,7 @@ def test_decode_video_frame_rate_pts(self) -> None:
267269
pts = frames.get_timestamps()
268270
print(pts_ref, pts)
269271

270-
self.assertTrue(np.all(pts_ref[::2] == pts))
272+
np.testing.assert_array_equal(pts_ref[::2], pts, strict=True)
271273

272274

273275
class TestConvertFrames(unittest.TestCase):

tests/io/frames_clone_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def test_clone_frames(self, media_type: str) -> None:
5656
array1 = _load_from_frames(frames1)
5757
array2 = _load_from_frames(frames2)
5858

59-
self.assertTrue(np.all(array1 == array2))
59+
np.testing.assert_array_equal(array1, array2, strict=True)
6060

6161
@parameterized.expand(
6262
[
@@ -113,4 +113,4 @@ def test_clone_frames_multi(self, media_type: str) -> None:
113113
arrays = [_load_from_frames(c) for c in clones]
114114

115115
for i in range(N):
116-
self.assertTrue(np.all(array == arrays[i]))
116+
np.testing.assert_array_equal(array, arrays[i], strict=True)

tests/io/image_decoding_test.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
# pyre-unsafe
88

9+
import operator
910
import unittest
1011

1112
import numpy as np
@@ -21,6 +22,18 @@ def _load_image(src, filter_desc="format=pix_fmts=rgb24"):
2122
return spdl.io.to_numpy(spdl.io.load_image(src, filter_desc=filter_desc))
2223

2324

25+
def _assert_all_ge(x, val) -> None:
26+
np.testing.assert_array_compare(
27+
operator.ge, x, val, header=f"Arrays are not all >= {val}"
28+
)
29+
30+
31+
def _assert_all_le(x, val) -> None:
32+
np.testing.assert_array_compare(
33+
operator.le, x, val, header=f"Arrays are not all <= {val}"
34+
)
35+
36+
2437
class TestDecodeImage(unittest.TestCase):
2538
def test_decode_image_gray16_native(self) -> None:
2639
"""Can decode gray16 PNG image (16be) as-is"""
@@ -44,8 +57,8 @@ def test_decode_image_gray16_native(self) -> None:
4457

4558
ffmpeg8 = get_ffmpeg_versions()["libavutil"][0] >= 60
4659

47-
self.assertTrue(np.all(hyp[..., :32] == 65535 if ffmpeg8 else 65022))
48-
self.assertTrue(np.all(hyp[..., 32:] == 0 if ffmpeg8 else 256))
60+
np.testing.assert_array_equal(hyp[..., :32], 65535 if ffmpeg8 else 65022)
61+
np.testing.assert_array_equal(hyp[..., 32:], 0 if ffmpeg8 else 256)
4962

5063
def test_decode_image_16be_rgb24(self) -> None:
5164
"""Can decode gray16 PNG image (16be) as rgb24"""
@@ -67,8 +80,8 @@ def test_decode_image_16be_rgb24(self) -> None:
6780
ref = load_ref_image(sample.path, shape)
6881
np.testing.assert_array_equal(hyp, ref, strict=True)
6982

70-
self.assertTrue(np.all(hyp[:, :32, :] == 255))
71-
self.assertTrue(np.all(hyp[:, 32:, :] == 0))
83+
np.testing.assert_array_equal(hyp[:, :32, :], 255)
84+
np.testing.assert_array_equal(hyp[:, 32:, :], 0)
7285

7386
def test_decode_image_yuvj422_native(self) -> None:
7487
"""Can decode yuvj422p JPEG image as-is."""
@@ -153,17 +166,17 @@ def test_decode_image_yuvj420p_as_rgb24_edge_values(self) -> None:
153166

154167
red, green, blue = hyp[:, :width], hyp[:, width:2*width], hyp[:, 2*width:]
155168

156-
self.assertTrue(np.all(red[..., 0] >= 254))
157-
self.assertTrue(np.all(red[..., 1] <= 1))
158-
self.assertTrue(np.all(red[..., 2] == 0))
169+
_assert_all_ge(red[..., 0], 254)
170+
_assert_all_le(red[..., 1], 1)
171+
np.testing.assert_array_equal(red[..., 2], 0)
159172

160-
self.assertTrue(np.all(green[..., 0] == 0))
161-
self.assertTrue(np.all(green[..., 1] >= 253))
162-
self.assertTrue(np.all(green[..., 2] <= 1))
173+
np.testing.assert_array_equal(green[..., 0], 0)
174+
_assert_all_ge(green[..., 1], 253)
175+
_assert_all_le(green[..., 2], 1)
163176

164-
self.assertTrue(np.all(blue[..., 0] <= 1))
165-
self.assertTrue(np.all(blue[..., 1] <= 1))
166-
self.assertTrue(np.all(blue[..., 2] >= 254))
177+
_assert_all_le(blue[..., 0], 1)
178+
_assert_all_le(blue[..., 1], 1)
179+
_assert_all_ge(blue[..., 2], 254)
167180

168181
def test_decode_image_yuvj444p_native(self) -> None:
169182
"""Can decode yuvj444p JPEG image as-is."""
@@ -335,19 +348,19 @@ def test_load_image_batch_native_edge_values(self) -> None:
335348

336349
left, middle, right = arr[..., :w, :], arr[..., w:-w, :], arr[..., -w:, :]
337350
# Red
338-
self.assertTrue(np.all(left[..., 0] >= 252))
339-
self.assertTrue(np.all(left[..., 1] == 0))
340-
self.assertTrue(np.all(left[..., 2] == 0))
351+
_assert_all_ge(left[..., 0], 252)
352+
np.testing.assert_array_equal(left[..., 1], 0)
353+
np.testing.assert_array_equal(left[..., 2], 0)
341354

342355
# Green
343-
self.assertTrue(np.all(middle[..., 0] == 0))
344-
self.assertTrue(np.all(middle[..., 1] >= 253))
345-
self.assertTrue(np.all(middle[..., 2] == 0))
356+
np.testing.assert_array_equal(middle[..., 0], 0)
357+
_assert_all_ge(middle[..., 1], 253)
358+
np.testing.assert_array_equal(middle[..., 2], 0)
346359

347360
# Blue
348-
self.assertTrue(np.all(right[..., 0] == 0))
349-
self.assertTrue(np.all(right[..., 1] == 0))
350-
self.assertTrue(np.all(right[..., 2] >= 253))
361+
np.testing.assert_array_equal(right[..., 0], 0)
362+
np.testing.assert_array_equal(right[..., 1], 0)
363+
_assert_all_ge(right[..., 2], 253)
351364

352365
def test_batch_decode_image_handle_failure(self) -> None:
353366
"""load_image_batch dismisses failures when strict=False."""

0 commit comments

Comments
 (0)