import pytest
from dataloader_rs import PyDataloader as DataLoader
from tests.py_dataloader_test_utils import FailingDs, ListDataset
def test_dataset_error_surfaces_in_iteration():
loader = DataLoader(FailingDs(8, fail_index=3), batch_size=4)
it = iter(loader)
with pytest.raises(RuntimeError, match="boom at index 3"):
next(it)
def test_collate_error_surfaces_in_iteration():
def bad_collate(_items):
raise RuntimeError("collate exploded")
loader = DataLoader(ListDataset(range(8)), batch_size=4, collate_fn=bad_collate)
it = iter(loader)
with pytest.raises(RuntimeError, match="collate exploded"):
next(it)
def test_dataset_error_after_success():
loader = DataLoader(FailingDs(12, fail_index=8), batch_size=4)
it = iter(loader)
batch0 = next(it) assert batch0 == [0, 1, 2, 3], "first batch must succeed"
batch1 = next(it) assert batch1 == [4, 5, 6, 7], "second batch must succeed"
with pytest.raises(RuntimeError, match="boom at index 8"):
next(it)
def test_dataset_error_parallel():
loader = DataLoader(FailingDs(16, fail_index=5), batch_size=4, num_workers=4)
raised = False
it = iter(loader)
for _ in range(4): try:
next(it)
except RuntimeError:
raised = True
break
assert raised, "a dataset error must surface as RuntimeError in parallel mode"
def test_collate_error_parallel():
def bad_collate(_items):
raise RuntimeError("parallel collate exploded")
loader = DataLoader(ListDataset(range(8)), batch_size=4, num_workers=2, collate_fn=bad_collate)
it = iter(loader)
with pytest.raises(RuntimeError, match="parallel collate exploded"):
next(it)
@pytest.mark.parametrize("num_workers", [0, 2])
def test_iteration_continues_after_an_exception(num_workers):
it = iter(DataLoader(FailingDs(12, fail_index=5), batch_size=4, num_workers=num_workers))
assert next(it) == [0, 1, 2, 3]
with pytest.raises(RuntimeError, match="boom at index 5"):
next(it)
assert len(it) == 1
assert next(it) == [8, 9, 10, 11]
with pytest.raises(StopIteration):
next(it)
def test_error_then_next_epoch_works():
fail_index = 3
loader = DataLoader(FailingDs(8, fail_index=fail_index), batch_size=4)
try:
for _ in loader:
pass
except RuntimeError:
pass
it = iter(loader)
with pytest.raises(RuntimeError, match=f"boom at index {fail_index}"):
next(it)
second = next(it)
assert second == [4, 5, 6, 7]
class _BadLenDs(ListDataset):
def __len__(self):
raise OSError("index file missing")
@pytest.mark.parametrize("num_workers", [0, 2])
def test_dataset_len_error_raises_from_iter(num_workers):
loader = DataLoader(_BadLenDs(range(4)), num_workers=num_workers)
with pytest.raises(OSError, match="index file missing"):
iter(loader)
def test_dataset_len_error_raises_from_len():
loader = DataLoader(_BadLenDs(range(4)))
with pytest.raises(OSError, match="index file missing"):
len(loader)
class _KeyErrorDs(ListDataset):
def __getitem__(self, idx):
if idx == 2:
raise KeyError(f"missing key {idx}")
return super().__getitem__(idx)
@pytest.mark.parametrize("num_workers", [0, 2])
def test_dataset_exception_type_is_preserved(num_workers):
loader = DataLoader(_KeyErrorDs(range(4)), batch_size=4, num_workers=num_workers)
with pytest.raises(KeyError, match="missing key 2"):
next(iter(loader))
@pytest.mark.parametrize("num_workers", [0, 2])
def test_collate_exception_type_is_preserved(num_workers):
def bad_collate(_items):
raise ValueError("bad shapes")
loader = DataLoader(
ListDataset(range(4)), batch_size=2, num_workers=num_workers, collate_fn=bad_collate
)
with pytest.raises(ValueError, match="bad shapes"):
next(iter(loader))
def test_non_iterable_sampler_raises_type_error():
with pytest.raises(TypeError):
DataLoader(ListDataset(range(4)), sampler=42)
class _RaisingSampler:
def __iter__(self):
yield 0
yield 1
raise ValueError("sampler exploded")
@pytest.mark.parametrize("num_workers", [0, 2])
def test_sampler_exception_surfaces_from_iter(num_workers):
loader = DataLoader(ListDataset(range(8)), sampler=_RaisingSampler(), num_workers=num_workers)
with pytest.raises(ValueError, match="sampler exploded"):
iter(loader)
@pytest.mark.parametrize("num_workers", [0, 2])
def test_sampler_non_integer_index_surfaces(num_workers):
loader = DataLoader(ListDataset(range(8)), sampler=[0, "x", 2], num_workers=num_workers)
with pytest.raises(TypeError):
iter(loader)
@pytest.mark.parametrize("num_workers", [0, 2])
def test_sampler_negative_index_surfaces(num_workers):
loader = DataLoader(ListDataset(range(8)), sampler=[0, -1, 2], num_workers=num_workers)
with pytest.raises(OverflowError):
iter(loader)
@pytest.mark.parametrize("num_workers", [0, 2])
def test_sampler_error_does_not_break_next_epoch(num_workers):
class FlakySampler:
fail = False
def __iter__(self):
if self.fail:
raise ValueError("this epoch fails")
return iter(range(4))
sampler = FlakySampler()
loader = DataLoader(
ListDataset(range(4)), sampler=sampler, batch_size=2, num_workers=num_workers
)
sampler.fail = True
with pytest.raises(ValueError, match="this epoch fails"):
iter(loader)
sampler.fail = False
assert list(loader) == [[0, 1], [2, 3]]