@@ -792,18 +792,20 @@ fn check_file_coverage(
792792 verbose : bool ,
793793) {
794794 let changed_file_rel_path_from_repo_root = file_from_diff;
795- let paths_to_check_in_coverage_map =
796- generate_paths_to_check ( changed_file_rel_path_from_repo_root, repo_root_pathbuf) ;
797795
798796 if verbose {
799797 println ! (
800- "Debug - Paths to check in coverage map for {}: {:? }" ,
801- changed_file_rel_path_from_repo_root, paths_to_check_in_coverage_map
798+ "Debug - Attempting to find coverage for {}" ,
799+ changed_file_rel_path_from_repo_root
802800 ) ;
803801 }
804802
805- let ( file_coverage_data, matched_coverage_path) =
806- find_coverage_data ( package_coverage_data, & paths_to_check_in_coverage_map) ;
803+ let ( file_coverage_data, matched_coverage_path) = find_coverage_data (
804+ package_coverage_data,
805+ changed_file_rel_path_from_repo_root,
806+ repo_root_pathbuf,
807+ verbose,
808+ ) ;
807809
808810 match file_coverage_data {
809811 Some ( file_cov) => {
@@ -830,6 +832,14 @@ fn check_file_coverage(
830832 "Warning: No coverage data found for changed file {}. Lines from this file considered uncovered." ,
831833 changed_file_rel_path_from_repo_root
832834 ) ;
835+ if let Some ( map) = package_coverage_data. as_object ( ) {
836+ let keys: Vec < & String > = map. keys ( ) . collect ( ) ;
837+ println ! ( "Debug - Coverage map top-level keys: {:?}" , keys) ;
838+ if let Some ( coverage_map) = map. get ( "coverageMap" ) . and_then ( |m| m. as_object ( ) ) {
839+ let nested_keys: Vec < & String > = coverage_map. keys ( ) . collect ( ) ;
840+ println ! ( "Debug - 'coverageMap' nested keys: {:?}" , nested_keys) ;
841+ }
842+ }
833843 }
834844 // All lines in this diff for this file are considered uncovered
835845 for line_num in lines_in_diff {
@@ -848,67 +858,46 @@ fn check_file_coverage(
848858 }
849859}
850860
851- /// Generate all possible paths to check in coverage map
852- fn generate_paths_to_check ( path : & str , repo_root_pathbuf : & PathBuf ) -> Vec < String > {
853- let mut paths_to_check = Vec :: new ( ) ;
854- let abs_path_from_repo_root = repo_root_pathbuf. join ( path) ;
855-
856- if let Some ( abs_path_str) = abs_path_from_repo_root. to_str ( ) {
857- paths_to_check. push ( abs_path_str. to_string ( ) ) ;
858- paths_to_check. push ( abs_path_str. replace ( '\\' , "/" ) ) ;
859-
860- #[ cfg( windows) ]
861- if abs_path_str. len ( ) > 1 && abs_path_str. chars ( ) . nth ( 1 ) == Some ( ':' ) {
862- if let Some ( first_char) = abs_path_str. chars ( ) . next ( ) {
863- if first_char. is_ascii_uppercase ( ) {
864- let mut lower_drive_abs_path_chars = abs_path_str. chars ( ) ;
865- let drive_letter_lower = lower_drive_abs_path_chars
866- . next ( )
867- . unwrap ( )
868- . to_lowercase ( )
869- . to_string ( ) ;
870- let rest_of_path: String = lower_drive_abs_path_chars. collect ( ) ;
871- let path_with_lower_drive = format ! ( "{}{}" , drive_letter_lower, rest_of_path) ;
872-
873- paths_to_check. push ( path_with_lower_drive. clone ( ) ) ;
874- paths_to_check. push ( path_with_lower_drive. replace ( '\\' , "/" ) ) ;
875- }
876- }
877- }
878- }
879-
880- paths_to_check
881- }
882-
883861/// Find the coverage data for a file
884862fn find_coverage_data < ' a > (
885863 package_coverage_data : & ' a serde_json:: Value ,
886- paths_to_check : & [ String ] ,
864+ changed_file_rel_path : & str ,
865+ repo_root : & PathBuf ,
866+ verbose : bool ,
887867) -> ( Option < & ' a serde_json:: Value > , String ) {
888- let mut file_coverage_data = None ;
889- let mut matched_coverage_path = String :: new ( ) ;
890-
891- if let Some ( coverage_map_obj) = package_coverage_data. as_object ( ) {
892- // Istanbul often nests coverageMap
893- for path_to_check in paths_to_check {
894- if let Some ( data) = coverage_map_obj. get ( path_to_check) {
895- file_coverage_data = Some ( data) ;
896- matched_coverage_path = path_to_check. clone ( ) ;
897- break ;
898- }
899- }
900- } else {
901- // Fallback if the top-level is the coverage map (less common for Istanbul)
902- for path_to_check in paths_to_check {
903- if let Some ( data) = package_coverage_data. get ( path_to_check) {
904- file_coverage_data = Some ( data) ;
905- matched_coverage_path = path_to_check. clone ( ) ;
906- break ;
868+ let coverage_map = package_coverage_data
869+ . get ( "coverageMap" )
870+ . and_then ( |v| v. as_object ( ) )
871+ . or_else ( || package_coverage_data. as_object ( ) ) ;
872+
873+ // Construct a normalized, absolute path for the file from the git diff.
874+ let mut changed_file_abs_path = repo_root. clone ( ) ;
875+ for component in changed_file_rel_path. split ( '/' ) {
876+ changed_file_abs_path. push ( component) ;
877+ }
878+
879+ if let Some ( map) = coverage_map {
880+ for file_cov_data in map. values ( ) {
881+ if let Some ( path_from_report_str) = file_cov_data. get ( "path" ) . and_then ( |p| p. as_str ( ) ) {
882+ let report_path = PathBuf :: from ( path_from_report_str) ;
883+
884+ // Compare the path from the report with the one we constructed.
885+ // PathBuf's equality handles OS-specific details.
886+ if report_path == changed_file_abs_path {
887+ return ( Some ( file_cov_data) , path_from_report_str. to_string ( ) ) ;
888+ }
907889 }
908890 }
909891 }
910892
911- ( file_coverage_data, matched_coverage_path)
893+ if verbose {
894+ println ! (
895+ "Debug - Failed to find coverage. Constructed path for comparison: {:?}" ,
896+ changed_file_abs_path
897+ ) ;
898+ }
899+
900+ ( None , String :: new ( ) )
912901}
913902
914903/// Process statements in a file's coverage data
@@ -923,78 +912,105 @@ fn process_file_statements(
923912) {
924913 let statement_map = file_cov. get ( "statementMap" ) . and_then ( |sm| sm. as_object ( ) ) ;
925914 let s_map = file_cov. get ( "s" ) . and_then ( |s| s. as_object ( ) ) ;
915+ let branch_map = file_cov. get ( "branchMap" ) . and_then ( |bm| bm. as_object ( ) ) ;
916+ let b_map = file_cov. get ( "b" ) . and_then ( |b| b. as_object ( ) ) ;
917+
918+ let mut executable_lines = std:: collections:: HashSet :: new ( ) ;
919+ if let Some ( stmt_map) = statement_map {
920+ for stmt_data in stmt_map. values ( ) {
921+ if let ( Some ( start_line) , Some ( end_line) ) = (
922+ stmt_data
923+ . get ( "start" )
924+ . and_then ( |l| l. get ( "line" ) )
925+ . and_then ( |l| l. as_u64 ( ) ) ,
926+ stmt_data
927+ . get ( "end" )
928+ . and_then ( |l| l. get ( "line" ) )
929+ . and_then ( |l| l. as_u64 ( ) ) ,
930+ ) {
931+ for line in start_line..=end_line {
932+ executable_lines. insert ( line as usize ) ;
933+ }
934+ }
935+ }
936+ }
937+
938+ let mut uncovered_lines = std:: collections:: HashSet :: new ( ) ;
926939
927940 if let ( Some ( stmt_map) , Some ( s) ) = ( statement_map, s_map) {
928- for & line_num in lines_in_diff {
929- let mut line_covered = false ;
930-
931- for ( stmt_idx, stmt_data) in stmt_map {
932- // Check start line
933- if let Some ( loc) = stmt_data. get ( "start" ) . and_then ( |loc| loc. get ( "line" ) ) {
934- if loc. as_u64 ( ) == Some ( line_num as u64 ) {
935- // Check if this statement index is covered in 's'
936- if let Some ( count) = s. get ( stmt_idx) . and_then ( |c| c. as_u64 ( ) ) {
937- if count > 0 {
938- line_covered = true ;
939- if verbose {
940- println ! (
941- "Debug - Line {} in {} is COVERED (statement {})" ,
942- line_num, changed_file_rel_path, stmt_idx
943- ) ;
944- }
945- break ; // Line is covered by this statement
946- }
941+ for ( stmt_idx, stmt_data) in stmt_map {
942+ if let Some ( count) = s. get ( stmt_idx) . and_then ( |c| c. as_u64 ( ) ) {
943+ if count == 0 {
944+ if let ( Some ( start_line) , Some ( end_line) ) = (
945+ stmt_data
946+ . get ( "start" )
947+ . and_then ( |l| l. get ( "line" ) )
948+ . and_then ( |l| l. as_u64 ( ) ) ,
949+ stmt_data
950+ . get ( "end" )
951+ . and_then ( |l| l. get ( "line" ) )
952+ . and_then ( |l| l. as_u64 ( ) ) ,
953+ ) {
954+ for line in start_line..=end_line {
955+ uncovered_lines. insert ( line as usize ) ;
947956 }
948957 }
949958 }
959+ }
960+ }
961+ }
950962
951- // Also check "end" line for multi-line statements
952- if let Some ( loc) = stmt_data. get ( "end" ) . and_then ( |loc| loc. get ( "line" ) ) {
953- if loc. as_u64 ( ) == Some ( line_num as u64 ) {
954- if let Some ( count) = s. get ( stmt_idx) . and_then ( |c| c. as_u64 ( ) ) {
955- if count > 0 {
956- line_covered = true ;
957- if verbose {
958- println ! (
959- "Debug - Line {} in {} is COVERED (by end of statement {})" ,
960- line_num, changed_file_rel_path, stmt_idx
961- ) ;
962- }
963- break ;
964- }
965- }
963+ if let ( Some ( br_map) , Some ( b) ) = ( branch_map, b_map) {
964+ for ( branch_idx, branch_data) in br_map {
965+ // Check if branch is intentionally skipped
966+ if branch_data. get ( "skip" ) . and_then ( |s| s. as_bool ( ) ) == Some ( true ) {
967+ continue ;
968+ }
969+
970+ if let Some ( branch_counts) = b. get ( branch_idx) . and_then ( |bc| bc. as_array ( ) ) {
971+ // If any branch path has a count of 0, the branch is not fully covered.
972+ if branch_counts. iter ( ) . any ( |count| count. as_u64 ( ) == Some ( 0 ) ) {
973+ // The line where the branch is defined should be marked as uncovered.
974+ if let Some ( line) = branch_data. get ( "line" ) . and_then ( |l| l. as_u64 ( ) ) {
975+ uncovered_lines. insert ( line as usize ) ;
966976 }
967977 }
968978 }
979+ }
980+ }
969981
970- if line_covered {
971- * covered_lines_in_patch += 1 ;
972- } else {
982+ for & line_num in lines_in_diff {
983+ let is_executable = executable_lines. contains ( & line_num) ;
984+ let is_uncovered = uncovered_lines. contains ( & line_num) ;
985+
986+ if is_executable {
987+ if is_uncovered {
973988 if verbose {
974989 println ! (
975- "Debug - Line {} in {} is NOT COVERED" ,
990+ "Debug - Line {} in {} is NOT COVERED (uncovered executable code) " ,
976991 line_num, changed_file_rel_path
977992 ) ;
978993 }
979994 uncovered_lines_details
980995 . entry ( file_from_diff. to_string ( ) )
981996 . or_default ( )
982997 . push ( line_num) ;
998+ } else {
999+ if verbose {
1000+ println ! (
1001+ "Debug - Line {} in {} is COVERED" ,
1002+ line_num, changed_file_rel_path
1003+ ) ;
1004+ }
1005+ * covered_lines_in_patch += 1 ;
9831006 }
984- }
985- } else {
986- if verbose {
1007+ } else if verbose && is_uncovered {
1008+ // This is for verbose logging of non-executable but uncovered lines like 'else {'
9871009 println ! (
988- "Debug - Missing 'statementMap' or 's'. All lines for this file in diff considered uncovered."
1010+ "Debug - Line {} in {} is part of an uncovered block, but not executable. Ignoring." ,
1011+ line_num, changed_file_rel_path
9891012 ) ;
9901013 }
991- // All lines for this file in the diff are considered uncovered if maps are missing
992- for & line_num in lines_in_diff {
993- uncovered_lines_details
994- . entry ( file_from_diff. to_string ( ) )
995- . or_default ( )
996- . push ( line_num) ;
997- }
9981014 }
9991015}
10001016
0 commit comments