Files
2026-07-13 13:17:40 +08:00

177 lines
6.0 KiB
Python

import os
from typing import Any, Dict
import pyarrow
import pytest
from pyarrow.fs import LocalFileSystem
import ray
from ray.data.block import BlockAccessor
from ray.data.datasource import BlockBasedFileDatasink, RowBasedFileDatasink
class FlakyOutputStream:
def __init__(self, stream: pyarrow.NativeFile, num_attempts: int):
self._stream = stream
self._num_attempts = num_attempts
def __enter__(self):
return self._stream.__enter__()
def __exit__(self, exc_type, exc_value, traceback):
if self._num_attempts < 2:
raise RuntimeError("AWS Error NETWORK_CONNECTION")
self._stream.__exit__(exc_type, exc_value, traceback)
def test_flaky_block_based_open_output_stream(ray_start_regular_shared, tmp_path):
class FlakyCSVDatasink(BlockBasedFileDatasink):
def __init__(self, path: str):
super().__init__(path)
self._num_attempts = 0
self._filesystem = LocalFileSystem()
def open_output_stream(self, path: str) -> "pyarrow.NativeFile":
stream = self._filesystem.open_output_stream(path)
flaky_stream = FlakyOutputStream(stream, self._num_attempts)
self._num_attempts += 1
return flaky_stream
def write_block_to_file(self, block: BlockAccessor, file: "pyarrow.NativeFile"):
block.to_pandas().to_csv(file)
ds = ray.data.range(100)
ds.write_datasink(FlakyCSVDatasink(tmp_path))
expected_values = list(range(100))
written_values = [row["id"] for row in ray.data.read_csv(tmp_path).take_all()]
assert sorted(written_values) == sorted(expected_values)
def test_flaky_row_based_open_output_stream(ray_start_regular_shared, tmp_path):
class FlakyTextDatasink(RowBasedFileDatasink):
def __init__(self, path: str):
super().__init__(path)
self._num_attempts = 0
self._filesystem = LocalFileSystem()
def open_output_stream(self, path: str) -> "pyarrow.NativeFile":
stream = self._filesystem.open_output_stream(path)
flaky_stream = FlakyOutputStream(stream, self._num_attempts)
self._num_attempts += 1
return flaky_stream
def write_row_to_file(self, row: Dict[str, Any], file: "pyarrow.NativeFile"):
file.write(f"{row['id']}".encode())
ds = ray.data.range(100)
ds.write_datasink(FlakyTextDatasink(tmp_path))
expected_values = [str(i) for i in range(100)]
written_values = [row["text"] for row in ray.data.read_text(tmp_path).take_all()]
assert sorted(written_values) == sorted(expected_values)
def test_flaky_write_block_to_file(ray_start_regular_shared, tmp_path):
class FlakyCSVDatasink(BlockBasedFileDatasink):
def __init__(self, path: str):
super().__init__(path)
self._num_attempts = 0
def write_block_to_file(self, block: BlockAccessor, file: "pyarrow.NativeFile"):
if self._num_attempts < 2:
self._num_attempts += 1
raise RuntimeError("AWS Error INTERNAL_FAILURE")
block.to_pandas().to_csv(file)
ds = ray.data.range(100)
ds.write_datasink(FlakyCSVDatasink(tmp_path))
expected_values = list(range(100))
written_values = [row["id"] for row in ray.data.read_csv(tmp_path).take_all()]
assert sorted(written_values) == sorted(expected_values)
def test_flaky_write_row_to_file(ray_start_regular_shared, tmp_path):
class FlakyTextDatasink(RowBasedFileDatasink):
def __init__(self, path: str):
super().__init__(path)
self._num_attempts = 0
def write_row_to_file(self, row: Dict[str, Any], file: "pyarrow.NativeFile"):
if self._num_attempts < 2:
self._num_attempts += 1
raise RuntimeError("AWS Error INTERNAL_FAILURE")
file.write(f"{row['id']}".encode())
ds = ray.data.range(100)
ds.write_datasink(FlakyTextDatasink(tmp_path))
expected_values = [str(i) for i in range(100)]
written_values = [row["text"] for row in ray.data.read_text(tmp_path).take_all()]
assert sorted(written_values) == sorted(expected_values)
@pytest.mark.parametrize("num_rows", [0, 1])
def test_write_preserves_user_directory(num_rows, tmp_path, ray_start_regular_shared):
class MockFileDatasink(BlockBasedFileDatasink):
def write_block_to_file(self, block: BlockAccessor, file: "pyarrow.NativeFile"):
file.write(b"")
ds = ray.data.range(num_rows)
path = os.path.join(tmp_path, "test")
os.mkdir(path) # User-created directory
ds.write_datasink(MockFileDatasink(path=path))
assert os.path.isdir(path)
def test_write_creates_dir(tmp_path, ray_start_regular_shared):
class MockFileDatasink(BlockBasedFileDatasink):
def write_block_to_file(self, block: BlockAccessor, file: "pyarrow.NativeFile"):
file.write(b"")
ds = ray.data.range(1)
path = os.path.join(tmp_path, "test")
ds.write_datasink(MockFileDatasink(path=path, try_create_dir=True))
assert os.path.isdir(path)
@pytest.mark.parametrize("min_rows_per_file", [5, 10, 50])
def test_write_min_rows_per_file(tmp_path, ray_start_regular_shared, min_rows_per_file):
class MockFileDatasink(BlockBasedFileDatasink):
def write_block_to_file(self, block: BlockAccessor, file: "pyarrow.NativeFile"):
for _ in range(block.num_rows()):
file.write(b"row\n")
ds = ray.data.range(100, override_num_blocks=20)
ds.write_datasink(
MockFileDatasink(path=tmp_path, min_rows_per_file=min_rows_per_file)
)
num_rows_written_total = 0
for filename in os.listdir(tmp_path):
with open(os.path.join(tmp_path, filename), "r") as file:
num_rows_written = len(file.read().splitlines())
assert num_rows_written == min_rows_per_file
num_rows_written_total += num_rows_written
assert num_rows_written_total == 100
if __name__ == "__main__":
import sys
sys.exit(pytest.main(["-v", __file__]))