Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
454 lines
16 KiB
Python
454 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Cross-filesystem compatibility tests for PyArrow Parquet files.
|
|
|
|
This test verifies that Parquet files written using one filesystem implementation
|
|
(s3fs or PyArrow native S3) can be correctly read using the other implementation.
|
|
|
|
Test Matrix:
|
|
- Write with s3fs → Read with PyArrow native S3
|
|
- Write with PyArrow native S3 → Read with s3fs
|
|
|
|
Requirements:
|
|
- pyarrow>=22.0.0
|
|
- s3fs>=2024.12.0
|
|
- boto3>=1.40.0
|
|
|
|
Environment Variables:
|
|
S3_ENDPOINT_URL: S3 endpoint (default: http://localhost:8333)
|
|
S3_ACCESS_KEY: S3 access key (default: some_access_key1)
|
|
S3_SECRET_KEY: S3 secret key (default: some_secret_key1)
|
|
BUCKET_NAME: S3 bucket name (default: test-parquet-bucket)
|
|
TEST_QUICK: Run only small/quick tests (default: 0, set to 1 for quick mode)
|
|
|
|
Usage:
|
|
# Run with default environment variables
|
|
python3 test_cross_filesystem_compatibility.py
|
|
|
|
# Run with custom environment variables
|
|
S3_ENDPOINT_URL=http://localhost:8333 \
|
|
S3_ACCESS_KEY=mykey \
|
|
S3_SECRET_KEY=mysecret \
|
|
BUCKET_NAME=mybucket \
|
|
python3 test_cross_filesystem_compatibility.py
|
|
"""
|
|
|
|
import os
|
|
import secrets
|
|
import sys
|
|
import logging
|
|
from typing import Optional, Tuple
|
|
|
|
import pyarrow as pa
|
|
import pyarrow.dataset as pads
|
|
import pyarrow.fs as pafs
|
|
import pyarrow.parquet as pq
|
|
import s3fs
|
|
|
|
try:
|
|
import boto3
|
|
from botocore.exceptions import ClientError
|
|
HAS_BOTO3 = True
|
|
except ImportError:
|
|
HAS_BOTO3 = False
|
|
|
|
from parquet_test_utils import create_sample_table
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
|
|
# Configuration from environment variables with defaults
|
|
S3_ENDPOINT_URL = os.environ.get("S3_ENDPOINT_URL", "http://localhost:8333")
|
|
S3_ACCESS_KEY = os.environ.get("S3_ACCESS_KEY", "some_access_key1")
|
|
S3_SECRET_KEY = os.environ.get("S3_SECRET_KEY", "some_secret_key1")
|
|
BUCKET_NAME = os.getenv("BUCKET_NAME", "test-parquet-bucket")
|
|
TEST_QUICK = os.getenv("TEST_QUICK", "0") == "1"
|
|
|
|
# Create randomized test directory
|
|
TEST_RUN_ID = secrets.token_hex(8)
|
|
TEST_DIR = f"parquet-cross-fs-tests/{TEST_RUN_ID}"
|
|
|
|
# Test file sizes
|
|
TEST_SIZES = {
|
|
"small": 5,
|
|
"large": 200_000, # This will create multiple row groups
|
|
}
|
|
|
|
# Filter to only small tests if quick mode is enabled
|
|
if TEST_QUICK:
|
|
TEST_SIZES = {"small": TEST_SIZES["small"]}
|
|
logging.info("Quick test mode enabled - running only small tests")
|
|
|
|
|
|
def init_s3fs() -> Optional[s3fs.S3FileSystem]:
|
|
"""Initialize s3fs filesystem."""
|
|
try:
|
|
logging.info("Initializing s3fs...")
|
|
fs = s3fs.S3FileSystem(
|
|
client_kwargs={"endpoint_url": S3_ENDPOINT_URL},
|
|
key=S3_ACCESS_KEY,
|
|
secret=S3_SECRET_KEY,
|
|
use_listings_cache=False,
|
|
)
|
|
logging.info("✓ s3fs initialized successfully")
|
|
return fs
|
|
except Exception:
|
|
logging.exception("✗ Failed to initialize s3fs")
|
|
return None
|
|
|
|
|
|
def init_pyarrow_s3() -> Tuple[Optional[pafs.S3FileSystem], str, str]:
|
|
"""Initialize PyArrow's native S3 filesystem.
|
|
|
|
Returns:
|
|
tuple: (S3FileSystem instance, scheme, endpoint)
|
|
"""
|
|
try:
|
|
logging.info("Initializing PyArrow S3FileSystem...")
|
|
|
|
# Determine scheme from endpoint
|
|
if S3_ENDPOINT_URL.startswith("http://"):
|
|
scheme = "http"
|
|
endpoint = S3_ENDPOINT_URL[7:] # Remove http://
|
|
elif S3_ENDPOINT_URL.startswith("https://"):
|
|
scheme = "https"
|
|
endpoint = S3_ENDPOINT_URL[8:] # Remove https://
|
|
else:
|
|
# Default to http for localhost
|
|
scheme = "http"
|
|
endpoint = S3_ENDPOINT_URL
|
|
|
|
# Enable bucket creation and deletion for testing
|
|
s3 = pafs.S3FileSystem(
|
|
access_key=S3_ACCESS_KEY,
|
|
secret_key=S3_SECRET_KEY,
|
|
endpoint_override=endpoint,
|
|
scheme=scheme,
|
|
allow_bucket_creation=True,
|
|
allow_bucket_deletion=True,
|
|
)
|
|
|
|
logging.info("✓ PyArrow S3FileSystem initialized successfully")
|
|
return s3, scheme, endpoint
|
|
except Exception:
|
|
logging.exception("✗ Failed to initialize PyArrow S3FileSystem")
|
|
return None, "", ""
|
|
|
|
|
|
def ensure_bucket_exists(s3fs_fs: s3fs.S3FileSystem, pyarrow_s3: pafs.S3FileSystem) -> bool:
|
|
"""Ensure the test bucket exists using s3fs."""
|
|
try:
|
|
if not s3fs_fs.exists(BUCKET_NAME):
|
|
logging.info(f"Creating bucket: {BUCKET_NAME}")
|
|
try:
|
|
s3fs_fs.mkdir(BUCKET_NAME)
|
|
logging.info(f"✓ Bucket created: {BUCKET_NAME}")
|
|
except FileExistsError:
|
|
# Bucket was created between the check and mkdir call
|
|
logging.info(f"✓ Bucket exists: {BUCKET_NAME}")
|
|
else:
|
|
logging.info(f"✓ Bucket exists: {BUCKET_NAME}")
|
|
return True
|
|
except Exception:
|
|
logging.exception("✗ Failed to create/check bucket")
|
|
return False
|
|
|
|
|
|
def write_with_s3fs(table: pa.Table, path: str, s3fs_fs: s3fs.S3FileSystem) -> bool:
|
|
"""Write Parquet file using s3fs filesystem."""
|
|
try:
|
|
pads.write_dataset(table, path, format="parquet", filesystem=s3fs_fs)
|
|
return True
|
|
except Exception:
|
|
logging.exception("✗ Failed to write with s3fs")
|
|
return False
|
|
|
|
|
|
def write_with_pyarrow_s3(table: pa.Table, path: str, pyarrow_s3: pafs.S3FileSystem) -> bool:
|
|
"""Write Parquet file using PyArrow native S3 filesystem."""
|
|
try:
|
|
pads.write_dataset(table, path, format="parquet", filesystem=pyarrow_s3)
|
|
return True
|
|
except Exception:
|
|
logging.exception("✗ Failed to write with PyArrow S3")
|
|
return False
|
|
|
|
|
|
def read_with_s3fs(path: str, s3fs_fs: s3fs.S3FileSystem) -> Tuple[bool, Optional[pa.Table], str]:
|
|
"""Read Parquet file using s3fs filesystem with multiple methods."""
|
|
errors = []
|
|
|
|
# Try pq.read_table
|
|
try:
|
|
table = pq.read_table(path, filesystem=s3fs_fs)
|
|
except Exception as e: # noqa: BLE001 - Intentionally broad for compatibility testing
|
|
errors.append(f"pq.read_table: {type(e).__name__}: {e}")
|
|
else:
|
|
return True, table, "pq.read_table"
|
|
|
|
# Try pq.ParquetDataset
|
|
try:
|
|
dataset = pq.ParquetDataset(path, filesystem=s3fs_fs)
|
|
table = dataset.read()
|
|
except Exception as e: # noqa: BLE001 - Intentionally broad for compatibility testing
|
|
errors.append(f"pq.ParquetDataset: {type(e).__name__}: {e}")
|
|
else:
|
|
return True, table, "pq.ParquetDataset"
|
|
|
|
# Try pads.dataset
|
|
try:
|
|
dataset = pads.dataset(path, format="parquet", filesystem=s3fs_fs)
|
|
table = dataset.to_table()
|
|
except Exception as e: # noqa: BLE001 - Intentionally broad for compatibility testing
|
|
errors.append(f"pads.dataset: {type(e).__name__}: {e}")
|
|
else:
|
|
return True, table, "pads.dataset"
|
|
|
|
return False, None, " | ".join(errors)
|
|
|
|
|
|
def read_with_pyarrow_s3(path: str, pyarrow_s3: pafs.S3FileSystem) -> Tuple[bool, Optional[pa.Table], str]:
|
|
"""Read Parquet file using PyArrow native S3 filesystem with multiple methods."""
|
|
errors = []
|
|
|
|
# Try pq.read_table
|
|
try:
|
|
table = pq.read_table(path, filesystem=pyarrow_s3)
|
|
except Exception as e: # noqa: BLE001 - Intentionally broad for compatibility testing
|
|
errors.append(f"pq.read_table: {type(e).__name__}: {e}")
|
|
else:
|
|
return True, table, "pq.read_table"
|
|
|
|
# Try pq.ParquetDataset
|
|
try:
|
|
dataset = pq.ParquetDataset(path, filesystem=pyarrow_s3)
|
|
table = dataset.read()
|
|
except Exception as e: # noqa: BLE001 - Intentionally broad for compatibility testing
|
|
errors.append(f"pq.ParquetDataset: {type(e).__name__}: {e}")
|
|
else:
|
|
return True, table, "pq.ParquetDataset"
|
|
|
|
# Try pads.dataset
|
|
try:
|
|
dataset = pads.dataset(path, filesystem=pyarrow_s3)
|
|
table = dataset.to_table()
|
|
except Exception as e: # noqa: BLE001 - Intentionally broad for compatibility testing
|
|
errors.append(f"pads.dataset: {type(e).__name__}: {e}")
|
|
else:
|
|
return True, table, "pads.dataset"
|
|
|
|
return False, None, " | ".join(errors)
|
|
|
|
|
|
def verify_table_integrity(original: pa.Table, read: pa.Table) -> Tuple[bool, str]:
|
|
"""Verify that read table matches the original table."""
|
|
# Check row count
|
|
if read.num_rows != original.num_rows:
|
|
return False, f"Row count mismatch: expected {original.num_rows}, got {read.num_rows}"
|
|
|
|
# Check schema
|
|
if not read.schema.equals(original.schema):
|
|
return False, f"Schema mismatch: expected {original.schema}, got {read.schema}"
|
|
|
|
# Sort both tables by 'id' column before comparison to handle potential row order differences
|
|
original_sorted = original.sort_by([('id', 'ascending')])
|
|
read_sorted = read.sort_by([('id', 'ascending')])
|
|
|
|
# Check data equality
|
|
if not read_sorted.equals(original_sorted):
|
|
# Provide detailed error information
|
|
error_details = []
|
|
for col_name in original.column_names:
|
|
col_original = original_sorted.column(col_name)
|
|
col_read = read_sorted.column(col_name)
|
|
if not col_original.equals(col_read):
|
|
error_details.append(f"column '{col_name}' differs")
|
|
return False, f"Data mismatch: {', '.join(error_details)}"
|
|
|
|
return True, "Data verified successfully"
|
|
|
|
|
|
def test_write_s3fs_read_pyarrow(
|
|
test_name: str,
|
|
num_rows: int,
|
|
s3fs_fs: s3fs.S3FileSystem,
|
|
pyarrow_s3: pafs.S3FileSystem
|
|
) -> Tuple[bool, str]:
|
|
"""Test: Write with s3fs, read with PyArrow native S3."""
|
|
try:
|
|
table = create_sample_table(num_rows)
|
|
path = f"{BUCKET_NAME}/{TEST_DIR}/{test_name}/data.parquet"
|
|
|
|
# Write with s3fs
|
|
logging.info(f" Writing {num_rows:,} rows with s3fs to {path}...")
|
|
if not write_with_s3fs(table, path, s3fs_fs):
|
|
return False, "Write with s3fs failed"
|
|
logging.info(" ✓ Write completed")
|
|
|
|
# Read with PyArrow native S3
|
|
logging.info(" Reading with PyArrow native S3...")
|
|
success, read_table, method = read_with_pyarrow_s3(path, pyarrow_s3)
|
|
if not success:
|
|
return False, f"Read with PyArrow S3 failed: {method}"
|
|
logging.info(f" ✓ Read {read_table.num_rows:,} rows using {method}")
|
|
|
|
# Verify data integrity
|
|
verify_success, verify_msg = verify_table_integrity(table, read_table)
|
|
if not verify_success:
|
|
return False, f"Verification failed: {verify_msg}"
|
|
logging.info(f" ✓ {verify_msg}")
|
|
|
|
return True, f"s3fs→PyArrow: {method}"
|
|
|
|
except Exception as e: # noqa: BLE001 - Top-level exception handler for test orchestration
|
|
logging.exception(" ✗ Test failed")
|
|
return False, f"{type(e).__name__}: {e}"
|
|
|
|
|
|
def test_write_pyarrow_read_s3fs(
|
|
test_name: str,
|
|
num_rows: int,
|
|
s3fs_fs: s3fs.S3FileSystem,
|
|
pyarrow_s3: pafs.S3FileSystem
|
|
) -> Tuple[bool, str]:
|
|
"""Test: Write with PyArrow native S3, read with s3fs."""
|
|
try:
|
|
table = create_sample_table(num_rows)
|
|
path = f"{BUCKET_NAME}/{TEST_DIR}/{test_name}/data.parquet"
|
|
|
|
# Write with PyArrow native S3
|
|
logging.info(f" Writing {num_rows:,} rows with PyArrow native S3 to {path}...")
|
|
if not write_with_pyarrow_s3(table, path, pyarrow_s3):
|
|
return False, "Write with PyArrow S3 failed"
|
|
logging.info(" ✓ Write completed")
|
|
|
|
# Read with s3fs
|
|
logging.info(" Reading with s3fs...")
|
|
success, read_table, method = read_with_s3fs(path, s3fs_fs)
|
|
if not success:
|
|
return False, f"Read with s3fs failed: {method}"
|
|
logging.info(f" ✓ Read {read_table.num_rows:,} rows using {method}")
|
|
|
|
# Verify data integrity
|
|
verify_success, verify_msg = verify_table_integrity(table, read_table)
|
|
if not verify_success:
|
|
return False, f"Verification failed: {verify_msg}"
|
|
logging.info(f" ✓ {verify_msg}")
|
|
|
|
return True, f"PyArrow→s3fs: {method}"
|
|
|
|
except Exception as e: # noqa: BLE001 - Top-level exception handler for test orchestration
|
|
logging.exception(" ✗ Test failed")
|
|
return False, f"{type(e).__name__}: {e}"
|
|
|
|
|
|
def cleanup_test_files(s3fs_fs: s3fs.S3FileSystem) -> None:
|
|
"""Clean up test files from S3."""
|
|
try:
|
|
test_path = f"{BUCKET_NAME}/{TEST_DIR}"
|
|
if s3fs_fs.exists(test_path):
|
|
logging.info(f"Cleaning up test directory: {test_path}")
|
|
s3fs_fs.rm(test_path, recursive=True)
|
|
logging.info("✓ Test directory cleaned up")
|
|
except Exception:
|
|
logging.exception("Failed to cleanup test directory")
|
|
|
|
|
|
def main():
|
|
"""Run cross-filesystem compatibility tests."""
|
|
print("=" * 80)
|
|
print("Cross-Filesystem Compatibility Tests for PyArrow Parquet")
|
|
print("Testing: s3fs ↔ PyArrow Native S3 Filesystem")
|
|
if TEST_QUICK:
|
|
print("*** QUICK TEST MODE - Small files only ***")
|
|
print("=" * 80 + "\n")
|
|
|
|
print("Configuration:")
|
|
print(f" S3 Endpoint: {S3_ENDPOINT_URL}")
|
|
print(f" Access Key: {S3_ACCESS_KEY}")
|
|
print(f" Bucket: {BUCKET_NAME}")
|
|
print(f" Test Directory: {TEST_DIR}")
|
|
print(f" Quick Mode: {'Yes (small files only)' if TEST_QUICK else 'No (all file sizes)'}")
|
|
print(f" PyArrow Version: {pa.__version__}")
|
|
print()
|
|
|
|
# Initialize both filesystems
|
|
s3fs_fs = init_s3fs()
|
|
if s3fs_fs is None:
|
|
print("Cannot proceed without s3fs connection")
|
|
return 1
|
|
|
|
pyarrow_s3, _scheme, _endpoint = init_pyarrow_s3()
|
|
if pyarrow_s3 is None:
|
|
print("Cannot proceed without PyArrow S3 connection")
|
|
return 1
|
|
|
|
print()
|
|
|
|
# Ensure bucket exists
|
|
if not ensure_bucket_exists(s3fs_fs, pyarrow_s3):
|
|
print("Cannot proceed without bucket")
|
|
return 1
|
|
|
|
print()
|
|
|
|
results = []
|
|
|
|
# Test all file sizes
|
|
for size_name, num_rows in TEST_SIZES.items():
|
|
print(f"\n{'='*80}")
|
|
print(f"Testing with {size_name} files ({num_rows:,} rows)")
|
|
print(f"{'='*80}\n")
|
|
|
|
# Test 1: Write with s3fs, read with PyArrow native S3
|
|
test_name = f"{size_name}_s3fs_to_pyarrow"
|
|
print(f"Test: Write with s3fs → Read with PyArrow native S3")
|
|
success, message = test_write_s3fs_read_pyarrow(
|
|
test_name, num_rows, s3fs_fs, pyarrow_s3
|
|
)
|
|
results.append((test_name, success, message))
|
|
status = "✓ PASS" if success else "✗ FAIL"
|
|
print(f"{status}: {message}\n")
|
|
|
|
# Test 2: Write with PyArrow native S3, read with s3fs
|
|
test_name = f"{size_name}_pyarrow_to_s3fs"
|
|
print(f"Test: Write with PyArrow native S3 → Read with s3fs")
|
|
success, message = test_write_pyarrow_read_s3fs(
|
|
test_name, num_rows, s3fs_fs, pyarrow_s3
|
|
)
|
|
results.append((test_name, success, message))
|
|
status = "✓ PASS" if success else "✗ FAIL"
|
|
print(f"{status}: {message}\n")
|
|
|
|
# Summary
|
|
print("\n" + "=" * 80)
|
|
print("SUMMARY")
|
|
print("=" * 80)
|
|
passed = sum(1 for _, success, _ in results if success)
|
|
total = len(results)
|
|
print(f"\nTotal: {passed}/{total} passed\n")
|
|
|
|
for test_name, success, message in results:
|
|
status = "✓" if success else "✗"
|
|
print(f" {status} {test_name}: {message}")
|
|
|
|
print("\n" + "=" * 80)
|
|
if passed == total:
|
|
print("✓ ALL CROSS-FILESYSTEM TESTS PASSED!")
|
|
print()
|
|
print("Conclusion: Files written with s3fs and PyArrow native S3 are")
|
|
print("fully compatible and can be read by either filesystem implementation.")
|
|
else:
|
|
print(f"✗ {total - passed} test(s) failed")
|
|
|
|
print("=" * 80 + "\n")
|
|
|
|
# Cleanup
|
|
cleanup_test_files(s3fs_fs)
|
|
|
|
return 0 if passed == total else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|
|
|