Skip to content

Commit 02c897c

Browse files
test: add some negative UTs to cover the problematic use cases
1 parent 47fd8f9 commit 02c897c

1 file changed

Lines changed: 92 additions & 12 deletions

File tree

default-engine/src/json.rs

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -419,13 +419,13 @@ mod tests {
419419
CopyOptions, GetOptions, GetResult, ListResult, MultipartUpload, ObjectMeta, ObjectStore,
420420
PutMultipartOptions, PutOptions, PutPayload, PutResult, Result,
421421
};
422-
use delta_kernel::schema::schema_ref;
422+
use delta_kernel::schema::{schema_ref, DataType as KernelDataType, StructField, StructType};
423423
use delta_kernel_default_engine_test_utils::{into_record_batch, string_array_to_engine_data};
424424
use futures::future;
425425
use itertools::Itertools;
426426
use serde_json::json;
427427
use test_utils::engine_contract::test_json_handler_file_path_contract;
428-
use test_utils::TestCancellationToken;
428+
use test_utils::{assert_result_error_with_message, TestCancellationToken};
429429
use tracing::info;
430430

431431
use super::*;
@@ -1178,10 +1178,7 @@ mod tests {
11781178
async fn test_read_json_files_parallel_with_cancelled_token() {
11791179
let store = Arc::new(InMemory::new());
11801180
store
1181-
.put(
1182-
&Path::from("test/0"),
1183-
Bytes::from(r#"{"val": 0}"#).into(),
1184-
)
1181+
.put(&Path::from("test/0"), Bytes::from(r#"{"val": 0}"#).into())
11851182
.await
11861183
.unwrap();
11871184
let url = Url::parse("memory:///test/0").unwrap();
@@ -1193,19 +1190,102 @@ mod tests {
11931190
let executor = Arc::new(TokioMultiThreadExecutor::new(
11941191
tokio::runtime::Handle::current(),
11951192
));
1196-
let handler = DefaultJsonHandler::new(store, executor)
1197-
.with_parallel_chunks(NonZero::new(4));
1193+
let handler =
1194+
DefaultJsonHandler::new(store, executor).with_parallel_chunks(NonZero::new(4));
11981195
let physical_schema = schema_ref! { nullable "val": INTEGER };
1199-
let token: CancellationTokenRef =
1200-
Arc::new(TestCancellationToken::cancelled());
1201-
let result = handler
1202-
.read_json_files_with_cancellation(&files, physical_schema, None, Some(token));
1196+
let token: CancellationTokenRef = Arc::new(TestCancellationToken::cancelled());
1197+
let result =
1198+
handler.read_json_files_with_cancellation(&files, physical_schema, None, Some(token));
12031199
assert!(
12041200
matches!(result, Err(Error::Cancelled)),
12051201
"pre-cancelled token must yield Cancelled, not data"
12061202
);
12071203
}
12081204

1205+
#[tokio::test(flavor = "multi_thread")]
1206+
async fn test_read_json_files_parallel_propagates_schema_conversion_error() {
1207+
let store = Arc::new(InMemory::new());
1208+
store
1209+
.put(&Path::from("test/0"), Bytes::from(r#"{"val": 0}"#).into())
1210+
.await
1211+
.unwrap();
1212+
let files = vec![FileMeta {
1213+
location: Url::parse("memory:///test/0").unwrap(),
1214+
last_modified: 0,
1215+
size: 12,
1216+
}];
1217+
1218+
// A shredded Variant is a valid kernel type but Arrow conversion only
1219+
// accepts the unshredded (metadata + value) shape. That makes
1220+
// json_arrow_schema fail inside each spawned chunk task.
1221+
let shredded_variant = KernelDataType::variant_type([
1222+
StructField::not_null("metadata", KernelDataType::BINARY),
1223+
StructField::not_null("value", KernelDataType::BINARY),
1224+
StructField::nullable("typed_value", KernelDataType::INTEGER),
1225+
])
1226+
.unwrap();
1227+
let physical_schema =
1228+
Arc::new(StructType::try_new([StructField::nullable("v", shredded_variant)]).unwrap());
1229+
1230+
let handler = DefaultJsonHandler::new(
1231+
store,
1232+
Arc::new(TokioMultiThreadExecutor::new(
1233+
tokio::runtime::Handle::current(),
1234+
)),
1235+
)
1236+
.with_parallel_chunks(NonZero::new(2));
1237+
let result: DeltaResult<Vec<_>> = handler
1238+
.read_json_files(&files, physical_schema, None)
1239+
.unwrap()
1240+
.try_collect();
1241+
assert_result_error_with_message(result, "Incorrect Variant Schema");
1242+
}
1243+
1244+
#[tokio::test(flavor = "multi_thread")]
1245+
async fn test_read_json_files_parallel_stops_when_consumer_drops() {
1246+
// More files per chunk than the per-chunk channel can hold, so producers
1247+
// block on send. Dropping after the first batch then hits is_err().
1248+
const N: usize = 64;
1249+
let memory_store = InMemory::new();
1250+
for i in 0..N {
1251+
memory_store
1252+
.put(
1253+
&Path::from(format!("test/{i}")),
1254+
Bytes::from(format!("{{\"val\": {i}}}")).into(),
1255+
)
1256+
.await
1257+
.unwrap();
1258+
}
1259+
let store = Arc::new(LatencyStore::new(memory_store, Duration::from_millis(5)));
1260+
let files: Vec<FileMeta> = (0..N)
1261+
.map(|i| FileMeta {
1262+
location: Url::parse(&format!("memory:///test/{i}")).unwrap(),
1263+
last_modified: 0,
1264+
size: 12,
1265+
})
1266+
.collect();
1267+
1268+
let handler = DefaultJsonHandler::new(
1269+
store,
1270+
Arc::new(TokioMultiThreadExecutor::new(
1271+
tokio::runtime::Handle::current(),
1272+
)),
1273+
)
1274+
.with_buffer_size(NonZero::new(1).unwrap())
1275+
.with_batch_size(NonZero::new(1).unwrap())
1276+
.with_parallel_chunks(NonZero::new(2));
1277+
let physical_schema = schema_ref! { nullable "val": INTEGER };
1278+
let mut iter = handler
1279+
.read_json_files(&files, physical_schema, None)
1280+
.unwrap();
1281+
let first = iter.next().expect("at least one batch");
1282+
assert!(first.is_ok(), "first batch must succeed before drop");
1283+
drop(iter);
1284+
// Let blocked chunk tasks observe the closed channel and take the
1285+
// consumer-dropped return before the test runtime shuts down.
1286+
tokio::time::sleep(Duration::from_millis(50)).await;
1287+
}
1288+
12091289
#[tokio::test(flavor = "multi_thread")]
12101290
async fn test_read_json_files_parallel_via_builder() {
12111291
let store = Arc::new(InMemory::new());

0 commit comments

Comments
 (0)