Skip to content

Commit 3accfdb

Browse files
committed
Add tests for serializing/deserializing the different error messages
1 parent a5ad1ea commit 3accfdb

File tree

7 files changed

+523
-0
lines changed

7 files changed

+523
-0
lines changed

src/errors/arrow_error.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,69 @@ impl ArrowErrorProto {
176176
(err, self.ctx.clone())
177177
}
178178
}
179+
180+
#[cfg(test)]
181+
mod tests {
182+
use super::*;
183+
use std::io::{Error as IoError, ErrorKind};
184+
185+
#[test]
186+
fn test_arrow_error_roundtrip() {
187+
let test_cases = vec![
188+
ArrowError::NotYetImplemented("test not implemented".to_string()),
189+
ArrowError::ExternalError(Box::new(std::io::Error::new(
190+
ErrorKind::Other,
191+
"external error",
192+
))),
193+
ArrowError::CastError("cast error".to_string()),
194+
ArrowError::MemoryError("memory error".to_string()),
195+
ArrowError::ParseError("parse error".to_string()),
196+
ArrowError::SchemaError("schema error".to_string()),
197+
ArrowError::ComputeError("compute error".to_string()),
198+
ArrowError::DivideByZero,
199+
ArrowError::ArithmeticOverflow("overflow".to_string()),
200+
ArrowError::CsvError("csv error".to_string()),
201+
ArrowError::JsonError("json error".to_string()),
202+
ArrowError::IoError(
203+
"io message".to_string(),
204+
IoError::new(ErrorKind::NotFound, "file not found"),
205+
),
206+
ArrowError::IpcError("ipc error".to_string()),
207+
ArrowError::InvalidArgumentError("invalid arg".to_string()),
208+
ArrowError::ParquetError("parquet error".to_string()),
209+
ArrowError::CDataInterface("cdata error".to_string()),
210+
ArrowError::DictionaryKeyOverflowError,
211+
ArrowError::RunEndIndexOverflowError,
212+
];
213+
214+
for original_error in test_cases {
215+
let proto = ArrowErrorProto::from_arrow_error(
216+
&original_error,
217+
Some(&"test context".to_string()),
218+
);
219+
let (recovered_error, recovered_ctx) = proto.to_arrow_error();
220+
221+
assert_eq!(original_error.to_string(), recovered_error.to_string());
222+
assert_eq!(recovered_ctx, Some("test context".to_string()));
223+
224+
let proto_no_ctx = ArrowErrorProto::from_arrow_error(&original_error, None);
225+
let (recovered_error_no_ctx, recovered_ctx_no_ctx) = proto_no_ctx.to_arrow_error();
226+
227+
assert_eq!(
228+
original_error.to_string(),
229+
recovered_error_no_ctx.to_string()
230+
);
231+
assert_eq!(recovered_ctx_no_ctx, None);
232+
}
233+
}
234+
235+
#[test]
236+
fn test_malformed_protobuf_message() {
237+
let malformed_proto = ArrowErrorProto {
238+
inner: None,
239+
ctx: None,
240+
};
241+
let (recovered_error, _) = malformed_proto.to_arrow_error();
242+
assert!(matches!(recovered_error, ArrowError::ExternalError(_)));
243+
}
244+
}

src/errors/datafusion_error.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,134 @@ impl std::fmt::Display for DistributedDataFusionGenericError {
265265
}
266266

267267
impl Error for DistributedDataFusionGenericError {}
268+
269+
#[cfg(test)]
270+
mod tests {
271+
use super::*;
272+
use datafusion::arrow::error::ArrowError;
273+
use datafusion::common::{DataFusionError, SchemaError};
274+
use datafusion::logical_expr::sqlparser::parser::ParserError;
275+
use datafusion::parquet::errors::ParquetError;
276+
use object_store::Error as ObjectStoreError;
277+
use std::io::{Error as IoError, ErrorKind};
278+
use std::sync::Arc;
279+
280+
#[test]
281+
fn test_datafusion_error_roundtrip() {
282+
let test_cases = vec![
283+
DataFusionError::ArrowError(
284+
ArrowError::ComputeError("compute".to_string()),
285+
Some("arrow context".to_string()),
286+
),
287+
DataFusionError::ParquetError(ParquetError::General("parquet error".to_string())),
288+
DataFusionError::ObjectStore(ObjectStoreError::NotFound {
289+
path: "test/path".to_string(),
290+
source: Box::new(std::io::Error::new(ErrorKind::NotFound, "not found")),
291+
}),
292+
DataFusionError::IoError(IoError::new(
293+
ErrorKind::PermissionDenied,
294+
"permission denied",
295+
)),
296+
DataFusionError::SQL(
297+
ParserError::ParserError("sql parse error".to_string()),
298+
Some("sql backtrace".to_string()),
299+
),
300+
DataFusionError::NotImplemented("not implemented".to_string()),
301+
DataFusionError::Internal("internal error".to_string()),
302+
DataFusionError::Plan("plan error".to_string()),
303+
DataFusionError::Configuration("config error".to_string()),
304+
DataFusionError::SchemaError(
305+
SchemaError::AmbiguousReference {
306+
field: datafusion::common::Column::new_unqualified("test_field"),
307+
},
308+
Box::new(None),
309+
),
310+
DataFusionError::Execution("execution error".to_string()),
311+
DataFusionError::ResourcesExhausted("resources exhausted".to_string()),
312+
DataFusionError::External(Box::new(std::io::Error::new(ErrorKind::Other, "external"))),
313+
DataFusionError::Context(
314+
"context message".to_string(),
315+
Box::new(DataFusionError::Internal("nested".to_string())),
316+
),
317+
DataFusionError::Substrait("substrait error".to_string()),
318+
DataFusionError::Collection(vec![
319+
DataFusionError::Internal("error 1".to_string()),
320+
DataFusionError::Internal("error 2".to_string()),
321+
]),
322+
DataFusionError::Shared(Arc::new(DataFusionError::Internal(
323+
"shared error".to_string(),
324+
))),
325+
];
326+
327+
for original_error in test_cases {
328+
let proto = DataFusionErrorProto::from_datafusion_error(&original_error);
329+
let recovered_error = proto.to_datafusion_err();
330+
331+
assert_eq!(original_error.to_string(), recovered_error.to_string());
332+
}
333+
}
334+
335+
#[test]
336+
fn test_malformed_protobuf_message() {
337+
let malformed_proto = DataFusionErrorProto { inner: None };
338+
let recovered_error = malformed_proto.to_datafusion_err();
339+
assert!(matches!(recovered_error, DataFusionError::Internal(_)));
340+
}
341+
342+
#[test]
343+
fn test_nested_datafusion_errors() {
344+
let nested_error = DataFusionError::Context(
345+
"outer context".to_string(),
346+
Box::new(DataFusionError::Context(
347+
"inner context".to_string(),
348+
Box::new(DataFusionError::Internal("deepest error".to_string())),
349+
)),
350+
);
351+
352+
let proto = DataFusionErrorProto::from_datafusion_error(&nested_error);
353+
let recovered_error = proto.to_datafusion_err();
354+
355+
assert_eq!(nested_error.to_string(), recovered_error.to_string());
356+
}
357+
358+
#[test]
359+
fn test_collection_errors() {
360+
let collection_error = DataFusionError::Collection(vec![
361+
DataFusionError::Internal("error 1".to_string()),
362+
DataFusionError::Plan("error 2".to_string()),
363+
DataFusionError::Execution("error 3".to_string()),
364+
]);
365+
366+
let proto = DataFusionErrorProto::from_datafusion_error(&collection_error);
367+
let recovered_error = proto.to_datafusion_err();
368+
369+
assert_eq!(collection_error.to_string(), recovered_error.to_string());
370+
}
371+
372+
#[test]
373+
fn test_sql_error_with_backtrace() {
374+
let sql_error = DataFusionError::SQL(
375+
ParserError::ParserError("syntax error".to_string()),
376+
Some("test backtrace".to_string()),
377+
);
378+
379+
let proto = DataFusionErrorProto::from_datafusion_error(&sql_error);
380+
let recovered_error = proto.to_datafusion_err();
381+
382+
if let DataFusionError::SQL(_, backtrace) = recovered_error {
383+
assert_eq!(backtrace, Some("test backtrace".to_string()));
384+
} else {
385+
panic!("Expected SQL error");
386+
}
387+
}
388+
389+
#[test]
390+
fn test_distributed_generic_error() {
391+
let generic_error = DistributedDataFusionGenericError {
392+
message: "test message".to_string(),
393+
};
394+
395+
assert_eq!(generic_error.to_string(), "test message");
396+
assert!(Error::source(&generic_error).is_none());
397+
}
398+
}

src/errors/io_error.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,69 @@ impl IoErrorProto {
107107
)
108108
}
109109
}
110+
111+
#[cfg(test)]
112+
mod tests {
113+
use super::*;
114+
use prost::Message;
115+
use std::io::{Error as IoError, ErrorKind};
116+
117+
#[test]
118+
fn test_io_error_roundtrip() {
119+
let test_cases = vec![
120+
(ErrorKind::NotFound, "file not found"),
121+
(ErrorKind::PermissionDenied, "permission denied"),
122+
(ErrorKind::ConnectionRefused, "connection refused"),
123+
(ErrorKind::ConnectionReset, "connection reset"),
124+
(ErrorKind::ConnectionAborted, "connection aborted"),
125+
(ErrorKind::NotConnected, "not connected"),
126+
(ErrorKind::AddrInUse, "address in use"),
127+
(ErrorKind::AddrNotAvailable, "address not available"),
128+
(ErrorKind::BrokenPipe, "broken pipe"),
129+
(ErrorKind::AlreadyExists, "already exists"),
130+
(ErrorKind::WouldBlock, "would block"),
131+
(ErrorKind::InvalidInput, "invalid input"),
132+
(ErrorKind::InvalidData, "invalid data"),
133+
(ErrorKind::TimedOut, "timed out"),
134+
(ErrorKind::WriteZero, "write zero"),
135+
(ErrorKind::Interrupted, "interrupted"),
136+
(ErrorKind::UnexpectedEof, "unexpected eof"),
137+
(ErrorKind::Other, "other error"),
138+
];
139+
140+
for (kind, msg) in test_cases {
141+
let original_error = IoError::new(kind, msg);
142+
let proto = IoErrorProto::from_io_error("test message", &original_error);
143+
let (recovered_error, recovered_message) = proto.to_io_error();
144+
145+
assert_eq!(original_error.kind(), recovered_error.kind());
146+
assert_eq!(original_error.to_string(), recovered_error.to_string());
147+
assert_eq!(recovered_message, "test message");
148+
}
149+
}
150+
151+
#[test]
152+
fn test_protobuf_serialization() {
153+
let original_error = IoError::new(ErrorKind::NotFound, "file not found");
154+
let proto = IoErrorProto::from_io_error("test message", &original_error);
155+
let proto = IoErrorProto::decode(proto.encode_to_vec().as_ref()).unwrap();
156+
let (recovered_error, recovered_message) = proto.to_io_error();
157+
158+
assert_eq!(original_error.kind(), recovered_error.kind());
159+
assert_eq!(original_error.to_string(), recovered_error.to_string());
160+
assert_eq!(recovered_message, "test message");
161+
}
162+
163+
#[test]
164+
fn test_unknown_error_kind() {
165+
let proto = IoErrorProto {
166+
msg: "test message".to_string(),
167+
code: -1,
168+
err: "unknown error".to_string(),
169+
};
170+
let (recovered_error, recovered_message) = proto.to_io_error();
171+
172+
assert_eq!(recovered_error.kind(), ErrorKind::Other);
173+
assert_eq!(recovered_message, "test message");
174+
}
175+
}

src/errors/objectstore_error.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,88 @@ fn parse_store(store: &str) -> &'static str {
250250
_ => "Unknown",
251251
}
252252
}
253+
254+
#[cfg(test)]
255+
mod tests {
256+
use super::*;
257+
use object_store::Error as ObjectStoreError;
258+
use std::io::ErrorKind;
259+
260+
#[test]
261+
fn test_object_store_error_roundtrip() {
262+
let test_cases = vec![
263+
// Use known store names that will be preserved
264+
ObjectStoreError::Generic {
265+
store: "S3",
266+
source: Box::new(std::io::Error::new(ErrorKind::Other, "generic error")),
267+
},
268+
ObjectStoreError::NotFound {
269+
path: "test/path".to_string(),
270+
source: Box::new(std::io::Error::new(ErrorKind::NotFound, "not found")),
271+
},
272+
ObjectStoreError::AlreadyExists {
273+
path: "existing/path".to_string(),
274+
source: Box::new(std::io::Error::new(ErrorKind::AlreadyExists, "exists")),
275+
},
276+
ObjectStoreError::Precondition {
277+
path: "precondition/path".to_string(),
278+
source: Box::new(std::io::Error::new(ErrorKind::Other, "precondition failed")),
279+
},
280+
ObjectStoreError::NotSupported {
281+
source: Box::new(std::io::Error::new(ErrorKind::Unsupported, "not supported")),
282+
},
283+
ObjectStoreError::NotModified {
284+
path: "not/modified".to_string(),
285+
source: Box::new(std::io::Error::new(ErrorKind::Other, "not modified")),
286+
},
287+
ObjectStoreError::NotImplemented,
288+
ObjectStoreError::PermissionDenied {
289+
path: "denied/path".to_string(),
290+
source: Box::new(std::io::Error::new(
291+
ErrorKind::PermissionDenied,
292+
"permission denied",
293+
)),
294+
},
295+
ObjectStoreError::Unauthenticated {
296+
path: "auth/path".to_string(),
297+
source: Box::new(std::io::Error::new(ErrorKind::Other, "unauthenticated")),
298+
},
299+
ObjectStoreError::UnknownConfigurationKey {
300+
key: "unknown_key".to_string(),
301+
store: "S3",
302+
},
303+
];
304+
305+
for original_error in test_cases {
306+
let proto = ObjectStoreErrorProto::from_object_store_error(&original_error);
307+
let recovered_error = proto.to_object_store_error();
308+
309+
assert_eq!(original_error.to_string(), recovered_error.to_string());
310+
}
311+
}
312+
313+
#[test]
314+
fn test_unknown_store_handling() {
315+
// Test that unknown store names get mapped to "Unknown"
316+
let original_error = ObjectStoreError::Generic {
317+
store: "unknown_store",
318+
source: Box::new(std::io::Error::new(ErrorKind::Other, "generic error")),
319+
};
320+
let proto = ObjectStoreErrorProto::from_object_store_error(&original_error);
321+
let recovered_error = proto.to_object_store_error();
322+
323+
// The store name will be changed from "unknown_store" to "Unknown"
324+
assert_eq!(
325+
recovered_error.to_string(),
326+
"Generic Unknown error: generic error"
327+
);
328+
assert_ne!(original_error.to_string(), recovered_error.to_string());
329+
}
330+
331+
#[test]
332+
fn test_malformed_protobuf_message() {
333+
let malformed_proto = ObjectStoreErrorProto { inner: None };
334+
let recovered_error = malformed_proto.to_object_store_error();
335+
assert!(matches!(recovered_error, ObjectStoreError::Generic { .. }));
336+
}
337+
}

0 commit comments

Comments
 (0)