@@ -436,3 +436,173 @@ def test_failure_message_lists_all_failed_metrics(self):
436436 {"loss" : [DeterministicTest ()], "num-zeros" : [DeterministicTest ()]},
437437 )
438438 assert "num-zeros" in str (exc_info .value )
439+
440+
441+ class TestGoldenErrorDiagnostics :
442+ def test_reports_absolute_and_relative_extrema_at_their_own_steps (self , caplog ):
443+ caplog .set_level ("INFO" , logger = common .__name__ )
444+ golden = make_metric ({10 : 4.0 , 30 : - 0.01 , 60 : 0.0 }, value_precision = ValuePrecision .FULL )
445+ # The observed samples are deliberately stored in a different order.
446+ actual = make_metric ({60 : 0.0 , 30 : - 0.02 , 10 : 5.0 })
447+
448+ with pytest .raises (AssertionError , match = "loss" ):
449+ run ({"loss" : golden }, {"loss" : actual }, {"loss" : [DeterministicTest ()]})
450+
451+ assert "2/3 samples outside tolerance" in caplog .text
452+ assert "max_absolute_error at 10: golden=4, actual=5, absolute_error=1" in caplog .text
453+ assert "relative_error=0.25 (25%)" in caplog .text
454+ assert "max_relative_error at 30:" in caplog .text
455+ assert "absolute_error=0.01, relative_error=1 (100%)" in caplog .text
456+ assert "allowed_absolute_error=0" in caplog .text
457+ assert "first_outside_tolerance" not in caplog .text
458+
459+ def test_reports_small_difference_that_passes_tolerance (self , caplog ):
460+ caplog .set_level ("INFO" , logger = common .__name__ )
461+ run (
462+ {"loss" : make_metric ({5 : 1.0 })},
463+ {"loss" : make_metric ({5 : 1.04 })},
464+ {"loss" : [ApproximateTest (rtol = 0.05 )]},
465+ )
466+
467+ assert "[APPROXIMATE]: PASSED; 0/1 samples outside tolerance" in caplog .text
468+ assert "absolute_error=0.04, relative_error=0.04 (4%)" in caplog .text
469+ assert "allowed_absolute_error=0.05" in caplog .text
470+
471+ @pytest .mark .parametrize ("atol,passes" , [(1.0 , True ), (0.25 , False )])
472+ def test_zero_golden_uses_absolute_tolerance (self , atol , passes , caplog ):
473+ caplog .set_level ("INFO" , logger = common .__name__ )
474+ args = (
475+ {"loss" : make_metric ({5 : 0.0 })},
476+ {"loss" : make_metric ({5 : 0.5 })},
477+ {"loss" : [ApproximateTest (atol = atol , rtol = 0.05 )]},
478+ )
479+ if passes :
480+ run (* args )
481+ else :
482+ with pytest .raises (AssertionError , match = "loss" ):
483+ run (* args )
484+
485+ assert "absolute_error=0.5, relative_error=n/a (zero golden)" in caplog .text
486+ assert "max_relative_error" not in caplog .text
487+
488+ def test_near_zero_golden_reports_large_relative_error_within_atol (self , caplog ):
489+ caplog .set_level ("INFO" , logger = common .__name__ )
490+ run (
491+ {"loss" : make_metric ({5 : 0.00001 })},
492+ {"loss" : make_metric ({5 : 0.00002 })},
493+ {"loss" : [ApproximateTest (atol = 0.001 , rtol = 0 )]},
494+ )
495+
496+ assert "[APPROXIMATE]: PASSED" in caplog .text
497+ assert "absolute_error=1e-05, relative_error=1 (100%)" in caplog .text
498+ assert "allowed_absolute_error=0.001" in caplog .text
499+
500+ @pytest .mark .parametrize ("actual_value" , ["nan" , float ("nan" ), float ("inf" )])
501+ def test_nonfinite_values_have_undefined_errors (self , actual_value , caplog ):
502+ caplog .set_level ("INFO" , logger = common .__name__ )
503+ with pytest .raises (AssertionError , match = "loss" ):
504+ run (
505+ {"loss" : make_metric ({5 : 1.0 })},
506+ {"loss" : make_metric ({5 : actual_value })},
507+ {"loss" : [DeterministicTest ()]},
508+ )
509+
510+ assert "first_outside_tolerance at 5:" in caplog .text
511+ assert "absolute_error=n/a (non-finite or missing)" in caplog .text
512+ assert "max_absolute_error" not in caplog .text
513+
514+ def test_missing_sample_is_identified_even_with_finite_extrema (self , caplog ):
515+ caplog .set_level ("INFO" , logger = common .__name__ )
516+ with pytest .raises (AssertionError , match = "loss" ):
517+ run (
518+ {"loss" : make_metric ({5 : 1.0 , 10 : 1.0 })},
519+ {"loss" : make_metric ({10 : 1.25 })},
520+ {"loss" : [DeterministicTest ()]},
521+ )
522+
523+ assert "max_absolute_error, max_relative_error at 10:" in caplog .text
524+ assert "first_outside_tolerance at 5:" in caplog .text
525+ assert "absolute_error=n/a (non-finite or missing)" in caplog .text
526+
527+ def test_single_sample_extrema_share_one_precision_appropriate_row (self , caplog ):
528+ caplog .set_level ("INFO" , logger = common .__name__ )
529+ with pytest .raises (AssertionError ):
530+ run (
531+ {"loss" : make_metric ({5 : 10.83456 })},
532+ {"loss" : make_metric ({5 : 10.83457 })},
533+ {"loss" : [ApproximateTest (atol = 0 , rtol = 0 )]},
534+ )
535+ assert caplog .text .count (" at 5: golden=" ) == 1
536+ assert "max_absolute_error, max_relative_error at 5:" in caplog .text
537+ assert "golden=10.83456, actual=10.83457," in caplog .text
538+ assert "Actual values: [10.83457]" in caplog .text
539+ assert "Golden values: [10.83456]" in caplog .text
540+
541+ def test_legacy_golden_placeholder_retains_undefined_diagnostics (self , caplog ):
542+ caplog .set_level ("INFO" , logger = common .__name__ )
543+ with pytest .raises (AssertionError ):
544+ run (
545+ {"loss" : make_metric ({5 : "nan" , 10 : 1.0 })},
546+ {"loss" : make_metric ({5 : 0.9 , 10 : 1.0 })},
547+ {"loss" : [DeterministicTest ()]},
548+ )
549+ assert "first_outside_tolerance at 5: golden=inf" in caplog .text
550+ assert "absolute_error=n/a (non-finite or missing)" in caplog .text
551+
552+ def test_full_precision_error_is_not_rounded_away (self , caplog ):
553+ caplog .set_level ("INFO" , logger = common .__name__ )
554+ with pytest .raises (AssertionError , match = "loss" ):
555+ run (
556+ {"loss" : make_metric ({5 : 0.5 }, value_precision = ValuePrecision .FULL )},
557+ {"loss" : make_metric ({5 : 0.5 + 2 ** - 24 })},
558+ {"loss" : [DeterministicTest ()]},
559+ )
560+
561+ assert "precision=full" in caplog .text
562+ assert "absolute_error=5.96046448e-08" in caplog .text
563+
564+ def test_reports_both_exact_failure_and_approximate_success (self , caplog ):
565+ caplog .set_level ("INFO" , logger = common .__name__ )
566+ with pytest .raises (AssertionError , match = "loss" ):
567+ run (
568+ {"loss" : make_metric ({5 : 1.0 }, value_precision = ValuePrecision .FULL )},
569+ {"loss" : make_metric ({5 : 1.01 })},
570+ {"loss" : [DeterministicTest (), ApproximateTest (rtol = 0.05 )]},
571+ )
572+
573+ assert "[DETERMINISTIC]: FAILED" in caplog .text
574+ assert "[APPROXIMATE]: PASSED" in caplog .text
575+
576+ def test_approximate_failure_budget_is_visible (self , caplog ):
577+ caplog .set_level ("INFO" , logger = common .__name__ )
578+ values = {step : 1.0 for step in range (1 , 101 )}
579+ run (
580+ {"loss" : make_metric (values )},
581+ {"loss" : make_metric ({** values , 100 : 999.0 })},
582+ {"loss" : [ApproximateTest (rtol = 0.05 )]},
583+ )
584+
585+ assert "[APPROXIMATE]: PASSED; 1/100 samples outside tolerance" in caplog .text
586+ assert "max_absolute_error, max_relative_error at 100:" in caplog .text
587+
588+ def test_timing_error_is_labelled_as_a_median (self , caplog ):
589+ caplog .set_level ("INFO" , logger = common .__name__ )
590+ with pytest .raises (AssertionError , match = "iteration-time" ):
591+ run (
592+ {"iteration-time" : make_metric ({5 : 0.25 , 10 : 0.25 , 15 : 0.25 })},
593+ {"iteration-time" : make_metric ({5 : 0.5 , 10 : 0.5 , 15 : 0.5 })},
594+ {"iteration-time" : [ApproximateTest (rtol = 0.05 )]},
595+ )
596+
597+ assert (
598+ "max_absolute_error, max_relative_error at median over steps 5, 10, 15:" in caplog .text
599+ )
600+ assert "absolute_error=0.25, relative_error=1 (100%)" in caplog .text
601+ assert "precision=full" in caplog .text
602+
603+ def test_matching_values_do_not_emit_error_diagnostics (self , caplog ):
604+ caplog .set_level ("INFO" , logger = common .__name__ )
605+ metric = make_metric ({1 : 0.0 , 5 : 1.0 })
606+ run ({"loss" : metric }, {"loss" : metric }, {"loss" : [DeterministicTest ()]})
607+
608+ assert "Golden comparison" not in caplog .text
0 commit comments