1use super::types::{
6 CollectingPanicHandler, ContextSnapshot, DiagCode, ErrorAccumulator, ErrorFilter, ErrorPolicy,
7 ErrorRenderer, ErrorSeverity, ErrorSource, ErrorTemplates, EvalError, EvalErrorBuilder,
8 EvalErrorChain, EvalErrorContext, EvalErrorKind, EvalErrorStats, EvalFrame, EvalQuota,
9 RecoveryStrategy, RenderStyle, RuntimeError, SilentPanicHandler, SourceSpan, SourcedError,
10 StackTrace,
11};
12
13#[cfg(test)]
14mod tests {
15 use super::*;
16 #[test]
17 fn test_source_span_display_with_file() {
18 let s = SourceSpan::new(10, 25, Some("foo.lean".to_string()));
19 assert_eq!(s.to_string(), "foo.lean:10..25");
20 }
21 #[test]
22 fn test_source_span_display_no_file() {
23 let s = SourceSpan::new(0, 5, None);
24 assert_eq!(s.to_string(), "0..5");
25 }
26 #[test]
27 fn test_source_span_len() {
28 let s = SourceSpan::new(3, 10, None);
29 assert_eq!(s.len(), 7);
30 }
31 #[test]
32 fn test_source_span_empty() {
33 let s = SourceSpan::synthetic();
34 assert!(s.is_empty());
35 }
36 #[test]
37 fn test_eval_frame_display() {
38 let frame = EvalFrame::new("myFn", SourceSpan::new(0, 5, Some("a.lean".to_string())));
39 let s = frame.to_string();
40 assert!(s.contains("myFn"));
41 assert!(s.contains("a.lean"));
42 }
43 #[test]
44 fn test_eval_frame_tail_call() {
45 let frame = EvalFrame::new("g", SourceSpan::synthetic()).tail_call();
46 assert!(frame.is_tail_call);
47 assert!(frame.to_string().contains("[tail]"));
48 }
49 #[test]
50 fn test_eval_error_display_div_zero() {
51 let err = EvalError::new(EvalErrorKind::DivisionByZero);
52 assert!(err.to_string().contains("division by zero"));
53 }
54 #[test]
55 fn test_eval_error_with_span() {
56 let span = SourceSpan::new(5, 10, Some("test.lean".to_string()));
57 let err = EvalError::new(EvalErrorKind::DivisionByZero).with_span(span);
58 let s = err.to_string();
59 assert!(s.contains("test.lean"));
60 }
61 #[test]
62 fn test_eval_error_with_frame() {
63 let span = SourceSpan::synthetic();
64 let frame = EvalFrame::new("foo", span.clone());
65 let err = EvalError::new(EvalErrorKind::DivisionByZero).with_frame(frame);
66 assert!(err.has_context());
67 assert!(err.to_string().contains("foo"));
68 }
69 #[test]
70 fn test_eval_error_with_hint() {
71 let err = EvalErrorBuilder::div_by_zero();
72 assert!(!err.hints.is_empty());
73 let s = err.to_string();
74 assert!(s.contains("hint:"));
75 }
76 #[test]
77 fn test_eval_error_with_note() {
78 let err = EvalErrorBuilder::sorry("myAxiom");
79 assert!(err.note.is_some());
80 assert!(err.to_string().contains("note:"));
81 }
82 #[test]
83 fn test_eval_error_type_mismatch_display() {
84 let err = EvalErrorBuilder::type_mismatch("Nat", "Bool");
85 assert!(err.to_string().contains("Nat"));
86 assert!(err.to_string().contains("Bool"));
87 }
88 #[test]
89 fn test_eval_error_to_runtime_error_div() {
90 let err = EvalErrorBuilder::div_by_zero();
91 let re = err.to_runtime_error();
92 assert_eq!(re, RuntimeError::DivisionByZero);
93 }
94 #[test]
95 fn test_eval_error_to_runtime_error_sorry() {
96 let err = EvalErrorBuilder::sorry("mySorry");
97 let re = err.to_runtime_error();
98 assert!(matches!(re, RuntimeError::SorryReached(_)));
99 }
100 #[test]
101 fn test_eval_error_builder_stack_overflow() {
102 let err = EvalErrorBuilder::stack_overflow(1000);
103 assert!(err.to_string().contains("1000"));
104 assert!(!err.hints.is_empty());
105 }
106 #[test]
107 fn test_eval_error_builder_index_oob() {
108 let err = EvalErrorBuilder::index_out_of_bounds(5, 3);
109 assert!(err.to_string().contains("5"));
110 assert!(err.to_string().contains("3"));
111 }
112 #[test]
113 fn test_eval_error_builder_fuel_exhausted() {
114 let err = EvalErrorBuilder::fuel_exhausted(500);
115 assert!(err.to_string().contains("500"));
116 }
117 #[test]
118 fn test_eval_error_builder_undefined_var() {
119 let err = EvalErrorBuilder::undefined_var("x");
120 assert!(err.to_string().contains("`x`"));
121 }
122 #[test]
123 fn test_eval_error_builder_undefined_global() {
124 let err = EvalErrorBuilder::undefined_global("Nat.add");
125 assert!(err.to_string().contains("Nat.add"));
126 }
127 #[test]
128 fn test_eval_error_builder_black_hole() {
129 let err = EvalErrorBuilder::black_hole("fibThunk");
130 assert!(err.to_string().contains("fibThunk"));
131 assert!(err.note.is_some());
132 }
133 #[test]
134 fn test_eval_error_multiple_frames() {
135 let err = EvalError::new(EvalErrorKind::DivisionByZero).with_frames(vec![
136 EvalFrame::new("a", SourceSpan::synthetic()),
137 EvalFrame::new("b", SourceSpan::synthetic()),
138 ]);
139 assert_eq!(err.frames.len(), 2);
140 }
141}
142pub trait PanicHandler: Send + Sync {
144 fn on_panic(&self, err: &EvalError);
146}
147#[cfg(test)]
148mod tests_extended {
149 use super::*;
150 #[test]
151 fn test_recovery_strategy_continuation() {
152 assert!(RecoveryStrategy::LogAndContinue.allows_continuation());
153 assert!(RecoveryStrategy::ReturnDefault.allows_continuation());
154 assert!(!RecoveryStrategy::Abort.allows_continuation());
155 }
156 #[test]
157 fn test_recovery_strategy_retry() {
158 let r = RecoveryStrategy::Retry { max_attempts: 3 };
159 assert!(r.is_retry());
160 assert!(r.allows_continuation());
161 assert_eq!(format!("{}", r), "retry(max=3)");
162 }
163 #[test]
164 fn test_error_policy_strict() {
165 let p = ErrorPolicy::strict();
166 let err = EvalErrorBuilder::div_by_zero();
167 assert_eq!(*p.strategy_for(&err), RecoveryStrategy::Abort);
168 assert!(!p.allows_continuation(&err));
169 }
170 #[test]
171 fn test_error_policy_lenient() {
172 let p = ErrorPolicy::lenient();
173 let err = EvalErrorBuilder::div_by_zero();
174 assert!(p.allows_continuation(&err));
175 }
176 #[test]
177 fn test_error_policy_custom() {
178 let p = ErrorPolicy::strict().with(
179 EvalErrorKind::DivisionByZero,
180 RecoveryStrategy::ReturnDefault,
181 );
182 let err = EvalErrorBuilder::div_by_zero();
183 assert!(p.allows_continuation(&err));
184 }
185 #[test]
186 fn test_eval_error_context_push_pop() {
187 let mut ctx = EvalErrorContext::new();
188 ctx.push("fn_a", SourceSpan::synthetic());
189 ctx.push("fn_b", SourceSpan::synthetic());
190 assert_eq!(ctx.depth(), 2);
191 ctx.pop();
192 assert_eq!(ctx.depth(), 1);
193 }
194 #[test]
195 fn test_eval_error_context_annotate() {
196 let mut ctx = EvalErrorContext::new();
197 ctx.push("helper", SourceSpan::synthetic());
198 let err = EvalError::new(EvalErrorKind::DivisionByZero);
199 let annotated = ctx.annotate(err);
200 assert!(annotated.has_context());
201 }
202 #[test]
203 fn test_eval_error_context_disabled() {
204 let mut ctx = EvalErrorContext::disabled();
205 ctx.push("f", SourceSpan::synthetic());
206 assert_eq!(ctx.depth(), 0);
207 }
208 #[test]
209 fn test_collecting_panic_handler() {
210 let handler = CollectingPanicHandler::new();
211 let err = EvalErrorBuilder::panic_msg("oops");
212 handler.on_panic(&err);
213 handler.on_panic(&err);
214 assert_eq!(handler.count(), 2);
215 let collected = handler.collected();
216 assert!(collected[0].contains("oops"));
217 }
218 #[test]
219 fn test_error_accumulator_basic() {
220 let mut acc = ErrorAccumulator::new();
221 assert!(acc.is_empty());
222 acc.push(EvalErrorBuilder::div_by_zero());
223 acc.push(EvalErrorBuilder::undefined_var("x"));
224 assert_eq!(acc.len(), 2);
225 }
226 #[test]
227 fn test_error_accumulator_limit() {
228 let mut acc = ErrorAccumulator::with_limit(2);
229 assert!(!acc.push(EvalErrorBuilder::div_by_zero()));
230 assert!(acc.push(EvalErrorBuilder::div_by_zero()));
231 assert!(acc.at_limit());
232 }
233 #[test]
234 fn test_error_accumulator_format_all() {
235 let mut acc = ErrorAccumulator::new();
236 acc.push(EvalErrorBuilder::div_by_zero());
237 let fmt = acc.format_all();
238 assert!(fmt.contains("[1]"));
239 assert!(fmt.contains("division by zero"));
240 }
241 #[test]
242 fn test_error_kind_predicates_fatal() {
243 assert!(EvalErrorKind::StackOverflow { max_depth: 1 }.is_fatal());
244 assert!(EvalErrorKind::FuelExhausted { limit: 0 }.is_fatal());
245 assert!(!EvalErrorKind::DivisionByZero.is_fatal());
246 }
247 #[test]
248 fn test_error_kind_predicates_logic() {
249 assert!(EvalErrorKind::Panic {
250 message: "x".into()
251 }
252 .is_logic_error());
253 assert!(EvalErrorKind::SorryReached { name: "s".into() }.is_logic_error());
254 assert!(!EvalErrorKind::DivisionByZero.is_logic_error());
255 }
256 #[test]
257 fn test_error_kind_predicates_type_error() {
258 assert!(EvalErrorKind::TypeMismatch {
259 expected: "Nat".into(),
260 got: "Bool".into()
261 }
262 .is_type_error());
263 assert!(!EvalErrorKind::DivisionByZero.is_type_error());
264 }
265 #[test]
266 fn test_error_kind_name() {
267 assert_eq!(
268 EvalErrorKind::DivisionByZero.kind_name(),
269 "division_by_zero"
270 );
271 assert_eq!(
272 EvalErrorKind::TypeMismatch {
273 expected: "a".into(),
274 got: "b".into()
275 }
276 .kind_name(),
277 "type_mismatch"
278 );
279 }
280 #[test]
281 fn test_eval_error_stats() {
282 let mut stats = EvalErrorStats::new();
283 assert!(!stats.has_errors());
284 stats.record(&EvalErrorBuilder::div_by_zero());
285 stats.record(&EvalErrorBuilder::div_by_zero());
286 stats.record(&EvalErrorBuilder::undefined_var("x"));
287 assert_eq!(stats.total(), 3);
288 assert_eq!(stats.count("division_by_zero"), 2);
289 assert_eq!(stats.count("undefined_variable"), 1);
290 }
291 #[test]
292 fn test_eval_error_stats_most_frequent() {
293 let mut stats = EvalErrorStats::new();
294 stats.record(&EvalErrorBuilder::div_by_zero());
295 stats.record(&EvalErrorBuilder::div_by_zero());
296 stats.record(&EvalErrorBuilder::undefined_var("x"));
297 let (kind, count) = stats
298 .most_frequent()
299 .expect("test operation should succeed");
300 assert_eq!(kind, "division_by_zero");
301 assert_eq!(count, 2);
302 }
303 #[test]
304 fn test_eval_error_stats_reset() {
305 let mut stats = EvalErrorStats::new();
306 stats.record(&EvalErrorBuilder::div_by_zero());
307 stats.reset();
308 assert_eq!(stats.total(), 0);
309 assert!(!stats.has_errors());
310 }
311 #[test]
312 fn test_stack_trace_from_error() {
313 let span = SourceSpan::new(0, 5, Some("test.lean".to_string()));
314 let err =
315 EvalError::new(EvalErrorKind::DivisionByZero).with_frame(EvalFrame::new("myFn", span));
316 let trace = StackTrace::from_error(&err);
317 assert_eq!(trace.depth(), 1);
318 assert!(trace.format().contains("division by zero"));
319 assert!(trace.format().contains("myFn"));
320 }
321 #[test]
322 fn test_stack_trace_empty() {
323 let err = EvalError::new(EvalErrorKind::DivisionByZero);
324 let trace = StackTrace::from_error(&err);
325 assert_eq!(trace.depth(), 0);
326 let fmt = trace.format();
327 assert!(!fmt.contains("call stack:"));
328 }
329 #[test]
330 fn test_stack_trace_display() {
331 let err = EvalErrorBuilder::undefined_var("foo");
332 let trace = StackTrace::from_error(&err);
333 let s = format!("{}", trace);
334 assert!(s.contains("undefined variable"));
335 }
336 #[test]
337 fn test_error_context_clear() {
338 let mut ctx = EvalErrorContext::new();
339 ctx.push("f", SourceSpan::synthetic());
340 ctx.push("g", SourceSpan::synthetic());
341 ctx.clear();
342 assert!(ctx.is_empty());
343 }
344 #[test]
345 fn test_error_context_max_frames() {
346 let mut ctx = EvalErrorContext::new();
347 ctx.max_frames = 2;
348 ctx.push("a", SourceSpan::synthetic());
349 ctx.push("b", SourceSpan::synthetic());
350 ctx.push("c", SourceSpan::synthetic());
351 assert_eq!(ctx.depth(), 2);
352 }
353 #[test]
354 fn test_eval_error_kind_resource() {
355 assert!(EvalErrorKind::StackOverflow { max_depth: 10 }.is_resource_error());
356 assert!(EvalErrorKind::FuelExhausted { limit: 100 }.is_resource_error());
357 assert!(!EvalErrorKind::DivisionByZero.is_resource_error());
358 }
359 #[test]
360 fn test_silent_panic_handler() {
361 let h = SilentPanicHandler;
362 h.on_panic(&EvalErrorBuilder::panic_msg("test"));
363 }
364 #[test]
365 fn test_recovery_strategy_display() {
366 assert_eq!(format!("{}", RecoveryStrategy::Abort), "abort");
367 assert_eq!(
368 format!("{}", RecoveryStrategy::ReturnDefault),
369 "return-default"
370 );
371 assert_eq!(
372 format!("{}", RecoveryStrategy::FallbackToSorry),
373 "fallback-to-sorry"
374 );
375 assert_eq!(
376 format!("{}", RecoveryStrategy::LogAndContinue),
377 "log-and-continue"
378 );
379 }
380}
381#[cfg(test)]
382mod tests_renderer {
383 use super::*;
384 #[test]
385 fn test_renderer_plain() {
386 let err = EvalErrorBuilder::div_by_zero();
387 let rendered = ErrorRenderer::plain().render(&err);
388 assert!(rendered.contains("division by zero"));
389 }
390 #[test]
391 fn test_renderer_compact() {
392 let err = EvalErrorBuilder::div_by_zero();
393 let rendered = ErrorRenderer::compact().render(&err);
394 assert!(rendered.contains("division by zero"));
395 assert!(!rendered.contains('\n'));
396 }
397 #[test]
398 fn test_renderer_compact_with_span() {
399 let span = SourceSpan::new(0, 5, Some("f.lean".to_string()));
400 let err = EvalError::new(EvalErrorKind::DivisionByZero).with_span(span);
401 let rendered = ErrorRenderer::compact().render(&err);
402 assert!(rendered.contains("f.lean"));
403 }
404 #[test]
405 fn test_renderer_ansi() {
406 let err = EvalErrorBuilder::div_by_zero();
407 let rendered = ErrorRenderer::new(RenderStyle::Ansi).render(&err);
408 assert!(rendered.contains("division by zero"));
409 assert!(rendered.contains('\x1b'));
410 }
411 #[test]
412 fn test_renderer_structured() {
413 let err = EvalErrorBuilder::div_by_zero();
414 let rendered = ErrorRenderer::new(RenderStyle::Structured).render(&err);
415 assert!(rendered.starts_with('{'));
416 assert!(rendered.contains("division_by_zero"));
417 }
418 #[test]
419 fn test_renderer_max_frames() {
420 let err = EvalError::new(EvalErrorKind::DivisionByZero).with_frames(vec![
421 EvalFrame::new("a", SourceSpan::synthetic()),
422 EvalFrame::new("b", SourceSpan::synthetic()),
423 EvalFrame::new("c", SourceSpan::synthetic()),
424 ]);
425 let rendered = ErrorRenderer::plain().with_max_frames(2).render(&err);
426 assert!(rendered.contains("1 more frames"));
427 }
428 #[test]
429 fn test_renderer_no_hints() {
430 let err = EvalErrorBuilder::div_by_zero();
431 let rendered = ErrorRenderer::plain().with_hints(false).render(&err);
432 assert!(!rendered.contains("hint:"));
433 }
434 #[test]
435 fn test_renderer_no_note() {
436 let err = EvalErrorBuilder::sorry("x");
437 let rendered = ErrorRenderer::plain().with_note(false).render(&err);
438 assert!(!rendered.contains("note:"));
439 }
440 #[test]
441 fn test_error_chain_basic() {
442 let chain = EvalErrorChain::new()
443 .push(EvalErrorBuilder::undefined_var("x"))
444 .push(EvalErrorBuilder::div_by_zero());
445 assert_eq!(chain.len(), 2);
446 assert!(chain
447 .root_cause()
448 .expect("test operation should succeed")
449 .to_string()
450 .contains("undefined variable"));
451 assert!(chain
452 .last_error()
453 .expect("test operation should succeed")
454 .to_string()
455 .contains("division by zero"));
456 }
457 #[test]
458 fn test_error_chain_format() {
459 let chain = EvalErrorChain::new()
460 .push(EvalErrorBuilder::div_by_zero())
461 .push(EvalErrorBuilder::panic_msg("cascaded"));
462 let s = chain.format();
463 assert!(s.contains("root cause"));
464 assert!(s.contains("caused"));
465 }
466 #[test]
467 fn test_error_chain_display() {
468 let chain = EvalErrorChain::new().push(EvalErrorBuilder::div_by_zero());
469 assert!(!chain.to_string().is_empty());
470 }
471 #[test]
472 fn test_error_filter_keep_fatal() {
473 let errors = vec![
474 EvalErrorBuilder::div_by_zero(),
475 EvalErrorBuilder::stack_overflow(100),
476 EvalErrorBuilder::undefined_var("x"),
477 EvalErrorBuilder::fuel_exhausted(500),
478 ];
479 let fatal = ErrorFilter::keep_fatal(errors);
480 assert_eq!(fatal.len(), 2);
481 }
482 #[test]
483 fn test_error_filter_keep_kind() {
484 let errors = vec![
485 EvalErrorBuilder::div_by_zero(),
486 EvalErrorBuilder::div_by_zero(),
487 EvalErrorBuilder::undefined_var("x"),
488 ];
489 let filtered = ErrorFilter::keep_kind(errors, "division_by_zero");
490 assert_eq!(filtered.len(), 2);
491 }
492 #[test]
493 fn test_error_filter_dedup_by_kind() {
494 let errors = vec![
495 EvalErrorBuilder::div_by_zero(),
496 EvalErrorBuilder::div_by_zero(),
497 EvalErrorBuilder::undefined_var("x"),
498 ];
499 let deduped = ErrorFilter::dedup_by_kind(errors);
500 assert_eq!(deduped.len(), 2);
501 }
502 #[test]
503 fn test_eval_error_has_span() {
504 let err = EvalError::new(EvalErrorKind::DivisionByZero);
505 assert!(!err.has_span());
506 let with_span = err.with_span(SourceSpan::synthetic());
507 assert!(with_span.has_span());
508 }
509 #[test]
510 fn test_eval_error_has_hints() {
511 let err = EvalError::new(EvalErrorKind::DivisionByZero);
512 assert!(!err.has_hints());
513 let with_hint = err.with_hint("fix it");
514 assert!(with_hint.has_hints());
515 }
516 #[test]
517 fn test_eval_error_compact() {
518 let err = EvalErrorBuilder::div_by_zero();
519 let s = err.compact();
520 assert!(s.contains("division by zero"));
521 }
522 #[test]
523 fn test_eval_error_matches() {
524 let err = EvalErrorBuilder::div_by_zero();
525 assert!(err.matches(|k| matches!(k, EvalErrorKind::DivisionByZero)));
526 assert!(!err.matches(|k| matches!(k, EvalErrorKind::StackOverflow { .. })));
527 }
528 #[test]
529 fn test_eval_error_context_max_frame_boundary() {
530 let mut ctx = EvalErrorContext::new();
531 ctx.max_frames = 3;
532 for i in 0..5 {
533 ctx.push(format!("f{}", i), SourceSpan::synthetic());
534 }
535 assert_eq!(ctx.depth(), 3);
536 }
537 #[test]
538 fn test_accumulator_into_errors() {
539 let mut acc = ErrorAccumulator::new();
540 acc.push(EvalErrorBuilder::div_by_zero());
541 let errors = acc.into_errors();
542 assert_eq!(errors.len(), 1);
543 }
544 #[test]
545 fn test_eval_error_stats_black_hole() {
546 let mut stats = EvalErrorStats::new();
547 stats.record(&EvalErrorBuilder::black_hole("th"));
548 assert_eq!(stats.count("black_hole"), 1);
549 }
550 #[test]
551 fn test_error_kind_io() {
552 let kind = EvalErrorKind::Io {
553 message: "read failed".into(),
554 };
555 assert_eq!(kind.kind_name(), "io");
556 assert!(!kind.is_fatal());
557 assert!(!kind.is_type_error());
558 }
559 #[test]
560 fn test_render_style_default() {
561 assert_eq!(RenderStyle::default(), RenderStyle::Plain);
562 }
563}
564pub mod diag_codes {
566 use super::DiagCode;
567 pub const DIV_BY_ZERO: DiagCode = DiagCode {
569 prefix: 'E',
570 number: 1001,
571 };
572 pub const STACK_OVERFLOW: DiagCode = DiagCode {
574 prefix: 'E',
575 number: 1002,
576 };
577 pub const TYPE_MISMATCH: DiagCode = DiagCode {
579 prefix: 'E',
580 number: 1003,
581 };
582 pub const INDEX_OOB: DiagCode = DiagCode {
584 prefix: 'E',
585 number: 1004,
586 };
587 pub const SORRY_REACHED: DiagCode = DiagCode {
589 prefix: 'W',
590 number: 2001,
591 };
592 pub const FUEL_EXHAUSTED: DiagCode = DiagCode {
594 prefix: 'E',
595 number: 1005,
596 };
597 pub const UNDEFINED_VAR: DiagCode = DiagCode {
599 prefix: 'E',
600 number: 1006,
601 };
602 pub const UNDEFINED_GLOBAL: DiagCode = DiagCode {
604 prefix: 'E',
605 number: 1007,
606 };
607 pub const ARITH_OVERFLOW: DiagCode = DiagCode {
609 prefix: 'E',
610 number: 1008,
611 };
612 pub const NON_EXHAUSTIVE: DiagCode = DiagCode {
614 prefix: 'E',
615 number: 1009,
616 };
617 pub const PANIC: DiagCode = DiagCode {
619 prefix: 'E',
620 number: 1010,
621 };
622 pub const UNIMPLEMENTED: DiagCode = DiagCode {
624 prefix: 'W',
625 number: 2002,
626 };
627 pub const IO_ERROR: DiagCode = DiagCode {
629 prefix: 'E',
630 number: 1011,
631 };
632 pub const BLACK_HOLE: DiagCode = DiagCode {
634 prefix: 'E',
635 number: 1012,
636 };
637}
638#[cfg(test)]
639mod tests_severity_quota {
640 use super::*;
641 #[test]
642 fn test_error_severity_ordering() {
643 assert!(ErrorSeverity::Info < ErrorSeverity::Warning);
644 assert!(ErrorSeverity::Warning < ErrorSeverity::Error);
645 assert!(ErrorSeverity::Error < ErrorSeverity::Critical);
646 }
647 #[test]
648 fn test_error_severity_display() {
649 assert_eq!(format!("{}", ErrorSeverity::Info), "info");
650 assert_eq!(format!("{}", ErrorSeverity::Critical), "critical");
651 }
652 #[test]
653 fn test_kind_default_severity() {
654 assert_eq!(
655 EvalErrorKind::DivisionByZero.default_severity(),
656 ErrorSeverity::Error
657 );
658 assert_eq!(
659 EvalErrorKind::StackOverflow { max_depth: 1 }.default_severity(),
660 ErrorSeverity::Critical
661 );
662 assert_eq!(
663 EvalErrorKind::SorryReached { name: "x".into() }.default_severity(),
664 ErrorSeverity::Warning
665 );
666 }
667 #[test]
668 fn test_eval_error_severity() {
669 let err = EvalErrorBuilder::stack_overflow(100);
670 assert!(err.is_critical());
671 let err2 = EvalErrorBuilder::div_by_zero();
672 assert!(!err2.is_critical());
673 }
674 #[test]
675 fn test_eval_error_is_at_least_warning() {
676 let err = EvalErrorBuilder::sorry("x");
677 assert!(err.is_at_least_warning());
678 }
679 #[test]
680 fn test_eval_quota_unlimited() {
681 let mut q = EvalQuota::unlimited();
682 assert!(q.tick().is_ok());
683 assert!(q.tick().is_ok());
684 assert!(!q.is_exhausted());
685 assert_eq!(q.remaining(), None);
686 assert_eq!(q.steps_taken(), 2);
687 }
688 #[test]
689 fn test_eval_quota_limited() {
690 let mut q = EvalQuota::limited(3);
691 assert!(q.tick().is_ok());
692 assert!(q.tick().is_ok());
693 assert!(q.tick().is_ok());
694 assert!(q.tick().is_err());
695 assert!(q.is_exhausted());
696 }
697 #[test]
698 fn test_eval_quota_consume_multiple() {
699 let mut q = EvalQuota::limited(10);
700 assert!(q.consume(5).is_ok());
701 assert_eq!(q.remaining(), Some(5));
702 assert!(q.consume(6).is_err());
703 }
704 #[test]
705 fn test_eval_quota_reset() {
706 let mut q = EvalQuota::limited(5);
707 q.consume(3).expect("test operation should succeed");
708 q.reset();
709 assert_eq!(q.remaining(), Some(5));
710 assert_eq!(q.steps_taken(), 0);
711 }
712 #[test]
713 fn test_eval_quota_default() {
714 let q = EvalQuota::default();
715 assert_eq!(q.remaining(), None);
716 }
717 #[test]
718 fn test_diag_code_display() {
719 let code = DiagCode::error(1001);
720 assert_eq!(format!("{}", code), "E1001");
721 let warn = DiagCode::warning(2001);
722 assert_eq!(format!("{}", warn), "W2001");
723 }
724 #[test]
725 fn test_diag_code_for_div_zero() {
726 let code = EvalErrorKind::DivisionByZero.diag_code();
727 assert_eq!(code.prefix, 'E');
728 assert_eq!(code.number, 1001);
729 }
730 #[test]
731 fn test_diag_code_for_sorry() {
732 let code = EvalErrorKind::SorryReached { name: "x".into() }.diag_code();
733 assert_eq!(code.prefix, 'W');
734 }
735 #[test]
736 fn test_error_source_display() {
737 assert_eq!(format!("{}", ErrorSource::Kernel), "kernel");
738 assert_eq!(
739 format!(
740 "{}",
741 ErrorSource::UserCode {
742 decl_name: "foo".into()
743 }
744 ),
745 "user-code(foo)"
746 );
747 assert_eq!(
748 format!(
749 "{}",
750 ErrorSource::BytecodeInterp {
751 chunk_name: "main".into(),
752 ip: 42
753 }
754 ),
755 "bytecode-interp(main@42)"
756 );
757 }
758 #[test]
759 fn test_sourced_error_display() {
760 let se = SourcedError::unknown(EvalErrorBuilder::div_by_zero());
761 let s = format!("{}", se);
762 assert!(s.contains("[unknown]"));
763 assert!(s.contains("division by zero"));
764 }
765 #[test]
766 fn test_sourced_error_new() {
767 let se = SourcedError::new(
768 EvalErrorBuilder::undefined_var("x"),
769 ErrorSource::Elaborator,
770 );
771 assert_eq!(se.source, ErrorSource::Elaborator);
772 }
773 #[test]
774 fn test_error_filter_remove() {
775 let errors = vec![
776 EvalErrorBuilder::div_by_zero(),
777 EvalErrorBuilder::stack_overflow(10),
778 ];
779 let remaining = ErrorFilter::remove(errors, |e| e.kind.is_fatal());
780 assert_eq!(remaining.len(), 1);
781 assert_eq!(remaining[0].kind, EvalErrorKind::DivisionByZero);
782 }
783 #[test]
784 fn test_error_chain_empty() {
785 let chain = EvalErrorChain::new();
786 assert!(chain.is_empty());
787 assert!(chain.root_cause().is_none());
788 }
789 #[test]
790 fn test_diag_code_equality() {
791 let a = DiagCode::error(1001);
792 let b = DiagCode::error(1001);
793 let c = DiagCode::error(1002);
794 assert_eq!(a, b);
795 assert_ne!(a, c);
796 }
797 #[test]
798 fn test_eval_quota_fuel_error_kind() {
799 let mut q = EvalQuota::limited(1);
800 q.consume(1).expect("test operation should succeed");
801 let err = q.tick().unwrap_err();
802 assert!(matches!(err.kind, EvalErrorKind::FuelExhausted { .. }));
803 }
804}
805pub fn group_by_kind(errors: &[EvalError]) -> std::collections::HashMap<String, Vec<&EvalError>> {
807 let mut groups: std::collections::HashMap<String, Vec<&EvalError>> = Default::default();
808 for err in errors {
809 groups
810 .entry(err.kind.kind_name().to_string())
811 .or_default()
812 .push(err);
813 }
814 groups
815}
816#[cfg(test)]
817mod tests_templates {
818 use super::*;
819 #[test]
820 fn test_wrong_num_args() {
821 let err = ErrorTemplates::wrong_num_args(2, 3);
822 assert!(err.to_string().contains("2 arguments"));
823 assert!(!err.hints.is_empty());
824 }
825 #[test]
826 fn test_add_overflow() {
827 let err = ErrorTemplates::add_overflow(i64::MAX, 1);
828 assert!(err.to_string().contains("+"));
829 }
830 #[test]
831 fn test_mul_overflow() {
832 let err = ErrorTemplates::mul_overflow(1000, 1000);
833 assert!(err.to_string().contains("*"));
834 }
835 #[test]
836 fn test_empty_list_head() {
837 let err = ErrorTemplates::empty_list_head();
838 assert!(err.to_string().contains("head"));
839 assert!(err.note.is_some());
840 }
841 #[test]
842 fn test_assertion_failed() {
843 let err = ErrorTemplates::assertion_failed("x > 0");
844 assert!(err.to_string().contains("x > 0"));
845 }
846 #[test]
847 fn test_cast_failed() {
848 let err = ErrorTemplates::cast_failed("Bool", "Nat");
849 assert!(err.to_string().contains("Nat"));
850 assert!(!err.hints.is_empty());
851 }
852 #[test]
853 fn test_group_by_kind() {
854 let errors = vec![
855 EvalErrorBuilder::div_by_zero(),
856 EvalErrorBuilder::div_by_zero(),
857 EvalErrorBuilder::undefined_var("x"),
858 ];
859 let groups = group_by_kind(&errors);
860 assert_eq!(groups["division_by_zero"].len(), 2);
861 assert_eq!(groups["undefined_variable"].len(), 1);
862 }
863 #[test]
864 fn test_negative_index() {
865 let err = ErrorTemplates::negative_index(-5);
866 assert!(err.to_string().contains("-5"));
867 }
868 #[test]
869 fn test_empty_list_tail() {
870 let err = ErrorTemplates::empty_list_tail();
871 assert!(err.to_string().contains("tail"));
872 }
873}
874#[cfg(test)]
875mod tests_snapshot {
876 use super::*;
877 #[test]
878 fn test_context_snapshot_take() {
879 let mut ctx = EvalErrorContext::new();
880 ctx.push("fn_a", SourceSpan::synthetic());
881 ctx.push("fn_b", SourceSpan::synthetic());
882 let snap = ContextSnapshot::take(&ctx, 42);
883 assert_eq!(snap.frames.len(), 2);
884 assert_eq!(snap.step, 42);
885 }
886 #[test]
887 fn test_context_snapshot_into_error() {
888 let mut ctx = EvalErrorContext::new();
889 ctx.push("fn_x", SourceSpan::synthetic());
890 let snap = ContextSnapshot::take(&ctx, 0);
891 let err = snap.into_error(EvalErrorKind::DivisionByZero);
892 assert!(err.has_context());
893 }
894}