@@ -892,8 +892,52 @@ impl EvalPattern {
892892fn summarize_for_eval ( content : & str ) -> String {
893893 let mut summary = content. trim ( ) . replace ( '\n' , " " ) ;
894894 if summary. len ( ) > 120 {
895- summary. truncate ( 117 ) ;
895+ let mut end = 117 ;
896+ while end > 0 && !summary. is_char_boundary ( end) {
897+ end -= 1 ;
898+ }
899+ summary. truncate ( end) ;
896900 summary. push_str ( "..." ) ;
897901 }
898902 summary
899903}
904+
905+ #[ cfg( test) ]
906+ mod tests {
907+ use super :: * ;
908+
909+ #[ test]
910+ fn test_summarize_for_eval_short ( ) {
911+ let result = summarize_for_eval ( "hello world" ) ;
912+ assert_eq ! ( result, "hello world" ) ;
913+ }
914+
915+ #[ test]
916+ fn test_summarize_for_eval_utf8_safety ( ) {
917+ // Create a string with multi-byte UTF-8 chars that will land truncation mid-char
918+ // '€' is 3 bytes. 39 euros = 117 bytes exactly, but 38 euros = 114 bytes.
919+ // We need byte 117 to land mid-character.
920+ // 38 euros (114 bytes) + "abcd" (4 bytes) = 118 bytes > 120? No.
921+ // Let's use: 37 euros (111 bytes) + "abcdefghij" (10 bytes) = 121 bytes > 120
922+ // truncate(117): byte 117 = 111 + 6 = within "abcdefghij", which is ASCII. Safe.
923+ // Better: 39 euros (117 bytes) + "abcd" (4 bytes) = 121 bytes > 120
924+ // truncate(117): byte 117 is the end of the 39th euro. Safe boundary.
925+ // Better still: 38 euros (114 bytes) + "abc" (3 bytes) = 117. Not > 120.
926+ // We need: content where byte 117 is mid-char.
927+ // 39 euros = 117 bytes. Add "a" = 118 bytes. Not > 120.
928+ // 40 euros = 120 bytes. Add "a" = 121 bytes > 120.
929+ // truncate(117): byte 117 = end of 39th euro. 40th euro starts at 117.
930+ // Euro at bytes 117, 118, 119. truncate(117) is AT the start of the 40th euro.
931+ // is_char_boundary(117) — 117 is the start of a 3-byte char, so it IS a boundary!
932+ // Need byte 118: 40 euros (120 bytes) + "ab" = 122 bytes > 120.
933+ // Still truncate(117), which is start of 40th euro = valid boundary.
934+ // Use a mix: "a" + 39 euros = 1 + 117 = 118 bytes. Add "abc" = 121 > 120.
935+ // truncate(117): byte 117 = 1 + 38*3 = 115 is start of 39th euro.
936+ // byte 117 = 115 + 2 = mid-euro! This will panic!
937+ let content = format ! ( "a{}{}" , "€" . repeat( 39 ) , "abc" ) ;
938+ // length = 1 + 117 + 3 = 121 bytes
939+ // truncate(117) = byte 117 = 1 + 38*3 + 2 = inside 39th euro
940+ let result = summarize_for_eval ( & content) ;
941+ assert ! ( result. len( ) <= 120 ) ;
942+ }
943+ }
0 commit comments