Files
paddlepaddle--paddle/test/ai_edited_test/test_ai_dataset_utils.py
T
2026-07-13 12:40:42 +08:00

434 lines
16 KiB
Python

# Copyright (c) 2024 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.
# [AUTO-GENERATED] Unit test for paddle.io.dataloader.dataset
# 自动生成的单测,覆盖 paddle.io.dataloader.dataset 模块中未覆盖的代码
# Target: cover uncovered lines 323-336, 340-344, 389-410, 453-462, 588, 629-630, 677-710
# in paddle/python/paddle/io/dataloader/dataset.py
# 目标:覆盖 dataset.py 中 TensorDataset 的创建和使用、to_list 辅助函数、
# ComposeDataset 的初始化和错误检查、ChainDataset 的初始化和迭代、
# random_split 的长度校验错误、_accumulate 空列表路径、
# ConcatDataset 的创建和负索引
"""
This test covers the following modules and code paths:
这个测试覆盖以下模块和代码路径:
1. TensorDataset - creation, __getitem__, __len__ (lines 322-336)
TensorDataset - 创建、索引和长度
2. to_list() helper function - all three branches: None, list/tuple, scalar (lines 340-344)
to_list() 辅助函数 - 三个分支:None、列表/元组、标量
3. ComposeDataset - init validation and __getitem__ (lines 389-410)
ComposeDataset - 初始化校验和索引取值
4. ChainDataset - init validation and __iter__ (lines 453-462)
ChainDataset - 初始化校验和迭代
5. random_split - sum-of-lengths mismatch error (line 588)
random_split - 长度总和不匹配错误
6. _accumulate - empty iterable early return (lines 629-630)
_accumulate - 空可迭代对象的提前返回
7. ConcatDataset - cumsum, init, __len__, negative indexing (lines 677-710)
ConcatDataset - 累加和、初始化、长度、负索引
"""
import unittest
import numpy as np
import paddle
from paddle.io import (
ChainDataset,
ComposeDataset,
ConcatDataset,
Dataset,
IterableDataset,
TensorDataset,
random_split,
)
from paddle.io.dataloader.dataset import _accumulate
# Helper datasets
# 辅助数据集类
class SimpleMapDataset(Dataset):
"""A simple map-style dataset for testing.
用于测试的简单映射式数据集。
"""
def __init__(self, num_samples, return_type='tuple'):
self.num_samples = num_samples
self.return_type = return_type
def __getitem__(self, idx):
if self.return_type == 'tuple':
return (
np.array([idx], dtype='float32'),
np.array([idx * 2], dtype='int64'),
)
elif self.return_type == 'scalar':
return np.array([idx], dtype='float32')
elif self.return_type == 'list':
return [
np.array([idx], dtype='float32'),
np.array([idx * 2], dtype='int64'),
]
def __len__(self):
return self.num_samples
class SimpleIterableDataset(IterableDataset):
"""A simple iterable dataset for testing.
用于测试的简单可迭代数据集。
"""
def __init__(self, num_samples):
self.num_samples = num_samples
def __iter__(self):
for i in range(self.num_samples):
yield np.array([i], dtype='float32')
class TestTensorDataset(unittest.TestCase):
"""Test TensorDataset creation, __getitem__, and __len__.
测试 TensorDataset 的创建、索引取值和长度。
覆盖 paddle/python/paddle/io/dataloader/dataset.py 第 322-336 行。
"""
def test_tensor_dataset_basic(self):
"""TensorDataset should store and index tensors correctly.
TensorDataset 应正确存储和索引张量。
"""
data = paddle.to_tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
labels = paddle.to_tensor([0, 1, 2])
dataset = TensorDataset([data, labels])
self.assertEqual(len(dataset), 3)
item = dataset[0]
self.assertEqual(len(item), 2)
np.testing.assert_allclose(item[0].numpy(), [1.0, 2.0])
np.testing.assert_allclose(item[1].numpy(), 0)
def test_tensor_dataset_all_items(self):
"""TensorDataset should return correct items for all indices.
TensorDataset 应对所有索引返回正确的元素。
"""
data = paddle.arange(0, 15, dtype='float32').reshape([5, 3])
labels = paddle.arange(0, 5, dtype='int64')
dataset = TensorDataset([data, labels])
for i in range(len(dataset)):
item_data, item_label = dataset[i]
np.testing.assert_allclose(item_data.numpy(), data[i].numpy())
np.testing.assert_allclose(item_label.numpy(), labels[i].numpy())
def test_tensor_dataset_single_tensor(self):
"""TensorDataset should work with a single tensor.
TensorDataset 应支持仅一个张量的情况。
"""
data = paddle.to_tensor([[1.0], [2.0], [3.0]])
dataset = TensorDataset([data])
self.assertEqual(len(dataset), 3)
item = dataset[1]
self.assertEqual(len(item), 1)
np.testing.assert_allclose(item[0].numpy(), [2.0])
def test_tensor_dataset_shape_mismatch(self):
"""TensorDataset should raise AssertionError for shape mismatch.
当张量第一维大小不一致时应抛出 AssertionError。
"""
data = paddle.to_tensor([[1.0], [2.0], [3.0]])
labels = paddle.to_tensor([0, 1])
with self.assertRaises(AssertionError):
TensorDataset([data, labels])
class TestToListFunction(unittest.TestCase):
"""Test the to_list() helper function used by ComposeDataset.
测试 ComposeDataset 使用的 to_list() 辅助函数。
覆盖 paddle/python/paddle/io/dataloader/dataset.py 第 340-344 行。
"""
def test_compose_with_tuple_return(self):
"""ComposeDataset with datasets returning tuples calls to_list with tuple.
当数据集返回元组时,to_list 接收到元组并转换为列表。
"""
ds1 = SimpleMapDataset(5, return_type='tuple')
ds2 = SimpleMapDataset(5, return_type='tuple')
dataset = ComposeDataset([ds1, ds2])
item = dataset[0]
# ds1 returns (data, label), ds2 returns (data, label) => 4 items
self.assertEqual(len(item), 4)
def test_compose_with_scalar_return(self):
"""ComposeDataset with datasets returning scalars calls to_list with scalar.
当数据集返回标量时,to_list 接收到标量并包装为列表(覆盖第 344 行)。
"""
ds1 = SimpleMapDataset(5, return_type='scalar')
ds2 = SimpleMapDataset(5, return_type='scalar')
dataset = ComposeDataset([ds1, ds2])
item = dataset[0]
# Each dataset returns a single value, wrapped in list => 2 items
self.assertEqual(len(item), 2)
def test_compose_with_list_return(self):
"""ComposeDataset with datasets returning lists calls to_list with list.
当数据集返回列表时,to_list 接收到列表并转换(覆盖第 342-343 行)。
"""
ds1 = SimpleMapDataset(5, return_type='list')
ds2 = SimpleMapDataset(5, return_type='list')
dataset = ComposeDataset([ds1, ds2])
item = dataset[0]
self.assertEqual(len(item), 4)
class TestComposeDatasetValidation(unittest.TestCase):
"""Test ComposeDataset initialization validation.
测试 ComposeDataset 初始化校验。
覆盖 paddle/python/paddle/io/dataloader/dataset.py 第 389-401 行。
"""
def test_empty_datasets(self):
"""ComposeDataset should raise AssertionError for empty datasets.
空数据集列表应抛出 AssertionError。
"""
with self.assertRaises(AssertionError):
ComposeDataset([])
def test_non_dataset(self):
"""ComposeDataset should raise AssertionError for non-Dataset items.
非 Dataset 对象应抛出 AssertionError。
"""
with self.assertRaises(AssertionError):
ComposeDataset(["not_a_dataset"])
def test_iterable_dataset_rejected(self):
"""ComposeDataset should reject IterableDataset.
应拒绝 IterableDataset。
"""
with self.assertRaises(AssertionError):
ComposeDataset([SimpleIterableDataset(10)])
def test_length_mismatch(self):
"""ComposeDataset should raise AssertionError for length-mismatched datasets.
长度不匹配的数据集应抛出 AssertionError。
"""
with self.assertRaises(AssertionError):
ComposeDataset([SimpleMapDataset(10), SimpleMapDataset(5)])
def test_compose_len(self):
"""ComposeDataset.__len__ should delegate to first dataset.
ComposeDataset 的长度应等于第一个子数据集的长度。
"""
dataset = ComposeDataset([SimpleMapDataset(7), SimpleMapDataset(7)])
self.assertEqual(len(dataset), 7)
class TestChainDatasetValidation(unittest.TestCase):
"""Test ChainDataset initialization and iteration.
测试 ChainDataset 的初始化校验和迭代。
覆盖 paddle/python/paddle/io/dataloader/dataset.py 第 453-462 行。
"""
def test_empty_datasets(self):
"""ChainDataset should raise AssertionError for empty datasets.
空数据集列表应抛出 AssertionError。
"""
with self.assertRaises(AssertionError):
ChainDataset([])
def test_non_iterable_dataset(self):
"""ChainDataset should raise AssertionError for non-IterableDataset.
非 IterableDataset 应抛出 AssertionError。
"""
with self.assertRaises(AssertionError):
ChainDataset([SimpleMapDataset(10)])
def test_chain_iteration(self):
"""ChainDataset should iterate through all datasets sequentially.
ChainDataset 应按顺序迭代所有数据集。
"""
ds1 = SimpleIterableDataset(3)
ds2 = SimpleIterableDataset(4)
chain = ChainDataset([ds1, ds2])
items = []
for item in chain:
items.append(item)
self.assertEqual(len(items), 7)
# First 3 items from ds1 (0,1,2), next 4 from ds2 (0,1,2,3)
np.testing.assert_allclose(items[0], [0])
np.testing.assert_allclose(items[2], [2])
np.testing.assert_allclose(items[3], [0])
np.testing.assert_allclose(items[6], [3])
class TestRandomSplitLengthError(unittest.TestCase):
"""Test random_split raises ValueError for mismatched lengths.
测试 random_split 在长度不匹配时抛出 ValueError。
覆盖 paddle/python/paddle/io/dataloader/dataset.py 第 587-590 行。
"""
def test_lengths_sum_mismatch(self):
"""random_split should raise ValueError when sum(lengths) != len(dataset).
当 lengths 之和不等于数据集长度时应抛出 ValueError。
"""
dataset = SimpleMapDataset(10)
with self.assertRaises(ValueError):
random_split(dataset, [3, 3])
def test_lengths_sum_too_large(self):
"""random_split should raise ValueError when sum exceeds dataset length.
当 lengths 之和超过数据集长度时应抛出 ValueError。
"""
dataset = SimpleMapDataset(5)
with self.assertRaises(ValueError):
random_split(dataset, [3, 5])
def test_valid_random_split(self):
"""random_split should work when sum(lengths) == len(dataset).
当 lengths 之和等于数据集长度时应正常工作。
"""
dataset = SimpleMapDataset(10)
splits = random_split(dataset, [3, 7])
self.assertEqual(len(splits), 2)
self.assertEqual(len(splits[0]), 3)
self.assertEqual(len(splits[1]), 7)
class TestAccumulateEmpty(unittest.TestCase):
"""Test _accumulate with empty iterable.
测试 _accumulate 处理空可迭代对象。
覆盖 paddle/python/paddle/io/dataloader/dataset.py 第 629-630 行。
"""
def test_accumulate_empty_list(self):
"""_accumulate with empty list should return empty.
空列表应返回空结果。
"""
result = list(_accumulate([]))
self.assertEqual(result, [])
def test_accumulate_normal(self):
"""_accumulate with normal list should return running totals.
正常列表应返回累积和。
"""
result = list(_accumulate([1, 2, 3, 4, 5]))
self.assertEqual(result, [1, 3, 6, 10, 15])
class TestConcatDataset(unittest.TestCase):
"""Test ConcatDataset creation, negative indexing, and validation.
测试 ConcatDataset 的创建、负索引和校验。
覆盖 paddle/python/paddle/io/dataloader/dataset.py 第 677-710 行。
"""
def test_concat_basic(self):
"""ConcatDataset should concatenate multiple datasets.
ConcatDataset 应正确连接多个数据集。
"""
ds1 = SimpleMapDataset(5)
ds2 = SimpleMapDataset(3)
concat = ConcatDataset([ds1, ds2])
self.assertEqual(len(concat), 8)
def test_concat_negative_index(self):
"""ConcatDataset should support negative indexing.
ConcatDataset 应支持负索引。
覆盖第 699-704 行。
"""
ds1 = SimpleMapDataset(5, return_type='scalar')
ds2 = SimpleMapDataset(3, return_type='scalar')
concat = ConcatDataset([ds1, ds2])
# Last item should be from ds2, index 2
last_item = concat[-1]
np.testing.assert_allclose(last_item, [2])
# Second to last item
second_last = concat[-2]
np.testing.assert_allclose(second_last, [1])
def test_concat_negative_index_out_of_bounds(self):
"""ConcatDataset should raise ValueError for out-of-bounds negative index.
超出范围的负索引应抛出 ValueError。
覆盖第 700-703 行。
"""
ds1 = SimpleMapDataset(3)
ds2 = SimpleMapDataset(2)
concat = ConcatDataset([ds1, ds2])
with self.assertRaises(ValueError):
concat[-10]
def test_concat_first_dataset_index(self):
"""ConcatDataset should correctly index into first dataset.
ConcatDataset 应正确索引到第一个子数据集。
覆盖第 706-707 行 (dataset_idx == 0 分支)。
"""
ds1 = SimpleMapDataset(5, return_type='scalar')
ds2 = SimpleMapDataset(3, return_type='scalar')
concat = ConcatDataset([ds1, ds2])
item = concat[2]
np.testing.assert_allclose(item, [2])
def test_concat_second_dataset_index(self):
"""ConcatDataset should correctly index into second dataset.
ConcatDataset 应正确索引到第二个子数据集。
覆盖第 708-709 行 (dataset_idx > 0 分支)。
"""
ds1 = SimpleMapDataset(5, return_type='scalar')
ds2 = SimpleMapDataset(3, return_type='scalar')
concat = ConcatDataset([ds1, ds2])
# Index 5 should be the first item in ds2
item = concat[5]
np.testing.assert_allclose(item, [0])
def test_concat_empty_raises(self):
"""ConcatDataset should raise AssertionError for empty datasets.
空数据集列表应抛出 AssertionError。
"""
with self.assertRaises(AssertionError):
ConcatDataset([])
def test_concat_iterable_rejected(self):
"""ConcatDataset should reject IterableDataset.
应拒绝 IterableDataset。
"""
with self.assertRaises(AssertionError):
ConcatDataset([SimpleIterableDataset(10)])
def test_concat_cumsum(self):
"""ConcatDataset.cumsum should compute correct cumulative sizes.
ConcatDataset.cumsum 应计算正确的累积大小。
覆盖第 677-682 行。
"""
ds1 = SimpleMapDataset(5)
ds2 = SimpleMapDataset(3)
ds3 = SimpleMapDataset(7)
result = ConcatDataset.cumsum([ds1, ds2, ds3])
self.assertEqual(result, [5, 8, 15])
if __name__ == '__main__':
unittest.main()