Skip to content

Commit 5db2ae2

Browse files
traeokzFernand0
andauthored
zedc(fix): code coverage path mismatch, look for ZOWE_OPT_PASSWORD (#3697)
* fix(zedc): Coverage not always identified in multi-line diffs Signed-off-by: Trae Yelovich <[email protected]> * demo: add uncovered lines and some tests Signed-off-by: Trae Yelovich <[email protected]> * fix(zedc/status): censor user & pass in env. vars Signed-off-by: Trae Yelovich <[email protected]> * chore: zedc changelog Signed-off-by: Trae Yelovich <[email protected]> * chore: bump zedc version Signed-off-by: Trae Yelovich <[email protected]> * Revert "demo: add uncovered lines and some tests" This reverts commit e78e4cd. Signed-off-by: Trae Yelovich <[email protected]> * chore: address changelog feedback Signed-off-by: Trae Yelovich <[email protected]> --------- Signed-off-by: Trae Yelovich <[email protected]> Co-authored-by: Fernando Rijo Cedeno <[email protected]>
1 parent b32941f commit 5db2ae2

File tree

5 files changed

+140
-117
lines changed

5 files changed

+140
-117
lines changed

zedc/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# TBD Release
1+
# 0.2.1
2+
3+
- **BugFix:** Fixed an issue where the `zedc test coverage` command incorrectly reported lines as uncovered due to OS-specific path inconsistencies between `git diff` and the coverage report.
4+
- **BugFix:** Fixed an issue where the `zedc status` command did not censor the `ZOWE_OPT_USER` environment variable.
5+
- **BugFix:** Fixed an issue where the `zedc status` command looked for the `ZOWE_OPT_PASS` environment variable instead of the variable `ZOWE_OPT_PASSWORD` when printing environment status.
6+
7+
# 0.2.0
28

39
- **Enhancement:** Added `zedc status` command to display working tree status, diagnostics and system info in a developer's environment.
410
- **Enhancement:** Added `zedc test coverage` command to show patch coverage and uncovered lines with hyperlinks (for [compatible terminals only](https://github.com/Alhadis/OSC8-Adoption))

zedc/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zedc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "zedc"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

zedc/src/status.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,18 @@ impl EnvironmentStatus {
145145
"VSCODE_EXTENSIONS",
146146
"ZOWE_CLI_HOME",
147147
"ZOWE_OPT_USER",
148-
"ZOWE_OPT_PASS",
148+
"ZOWE_OPT_PASSWORD",
149149
];
150150

151151
for var in relevant_vars {
152152
if let Ok(value) = std::env::var(var) {
153153
// Mask sensitive values
154-
let display_value = if var.contains("PASS") || var.contains("PAT") {
155-
"********".to_string()
156-
} else {
157-
value
158-
};
154+
let display_value =
155+
if var.contains("USER") || var.contains("PASS") || var.contains("PAT") {
156+
"********".to_string()
157+
} else {
158+
value
159+
};
159160
self.env_vars.push((var.to_string(), display_value));
160161
}
161162
}

zedc/src/test/coverage.rs

Lines changed: 124 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -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
884862
fn 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

Comments
 (0)