weavatrix-rust 2.0.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use crate::engine::RepositoryState;
use crate::operations::{optional_str, optional_u64};
use blazingly_json::{Value, json};
use std::fs;
use std::path::Path;
use weavatrix_graph::NodeKind;

pub(in crate::operations) fn coverage(
    state: &RepositoryState,
    args: &Value,
) -> Result<Value, String> {
    let candidates = [
        "lcov.info",
        "coverage/lcov.info",
        ".weavatrix/coverage/lcov.info",
        "tarpaulin-report.json",
        "target/tarpaulin/tarpaulin-report.json",
        "target/llvm-cov/coverage.json",
        "coverage/coverage-final.json",
    ];
    let path_filter = optional_str(args, "path")?;
    let top = usize::try_from(optional_u64(args, "top_n")?.unwrap_or(u64::MAX))
        .map_err(|_| "top_n is too large".to_owned())?;
    for candidate in candidates {
        let path = state.root().join(candidate);
        if !path.is_file() {
            continue;
        }
        let parsed = if path.extension().and_then(|value| value.to_str()) == Some("info") {
            parse_lcov(&path)
        } else {
            parse_json_coverage(&path)
        };
        let mut files = parsed
            .map_err(|reason| format!("cannot parse coverage report {candidate}: {reason}"))?;
        if let Some(filter) = path_filter {
            files.retain(|file| {
                file["path"]
                    .as_str()
                    .is_some_and(|path| path.contains(filter))
            });
        }
        files.truncate(top);
        return Ok(json!({
            "status": "COMPLETE",
            "measured_coverage": {
                "present": true,
                "report": candidate,
                "source": "measured report; Weavatrix did not execute tests"
            },
            "report": candidate,
            "files": files,
            "static_reachability": static_test_reachability(state)
        }));
    }
    Ok(json!({
        "status": "COMPLETE",
        "measured_coverage": {
            "present": false,
            "reason": "no supported measured coverage report exists in the analyzed repository",
            "searched": candidates
        },
        "files": [],
        "static_reachability": static_test_reachability(state),
        "warning": "static test reachability is not measured coverage"
    }))
}

fn parse_lcov(path: &Path) -> Result<Vec<Value>, String> {
    let text = fs::read_to_string(path).map_err(|error| error.to_string())?;
    let mut files = Vec::new();
    let mut current = None::<String>;
    let mut found = 0_u64;
    let mut hit = 0_u64;
    for line in text.lines() {
        if let Some(path) = line.strip_prefix("SF:") {
            current = Some(path.replace('\\', "/"));
            found = 0;
            hit = 0;
        } else if let Some(record) = line.strip_prefix("DA:") {
            found += 1;
            if record
                .split_once(',')
                .and_then(|(_, count)| count.parse::<u64>().ok())
                .unwrap_or(0)
                > 0
            {
                hit += 1;
            }
        } else if line == "end_of_record"
            && let Some(path) = current.take()
        {
            files.push(json!({
                "path": path,
                "lines_found": found,
                "lines_hit": hit
            }));
        }
    }
    if files.is_empty() {
        Err("LCOV contains no complete source records".to_owned())
    } else {
        Ok(files)
    }
}

fn parse_json_coverage(path: &Path) -> Result<Vec<Value>, String> {
    let value: Value =
        blazingly_json::from_slice(&fs::read(path).map_err(|error| error.to_string())?)
            .map_err(|error| error.to_string())?;
    if let Some(files) = tarpaulin_files(&value) {
        return Ok(files);
    }
    if let Some(files) = llvm_files(&value) {
        return Ok(files);
    }
    let object = value
        .as_object()
        .ok_or_else(|| "coverage JSON root is not an object".to_owned())?;
    let files = object
        .iter()
        .filter_map(|(path, record)| {
            let statements = record.get("s")?.as_object()?;
            let found = u64::try_from(statements.len()).ok()?;
            let hit = u64::try_from(
                statements
                    .values()
                    .filter(|value| value.as_u64().unwrap_or(0) > 0)
                    .count(),
            )
            .ok()?;
            Some(json!({
                "path": path.replace('\\', "/"),
                "lines_found": found,
                "lines_hit": hit
            }))
        })
        .collect::<Vec<_>>();
    if files.is_empty() {
        Err("known JSON coverage counters were not found".to_owned())
    } else {
        Ok(files)
    }
}

fn tarpaulin_files(value: &Value) -> Option<Vec<Value>> {
    let files = value.get("files")?.as_array()?;
    Some(
        files
            .iter()
            .filter_map(|file| {
                let path = file.get("path")?;
                let path = match path {
                    Value::Array(parts) => parts
                        .iter()
                        .filter_map(Value::as_str)
                        .collect::<Vec<_>>()
                        .join("/"),
                    Value::String(path) => path.replace('\\', "/"),
                    _ => return None,
                };
                Some(json!({
                    "path": path,
                    "lines_found": file.get("coverable")?.as_u64()?,
                    "lines_hit": file.get("covered")?.as_u64()?
                }))
            })
            .collect(),
    )
}

fn llvm_files(value: &Value) -> Option<Vec<Value>> {
    let mut files = Vec::new();
    for data in value.get("data")?.as_array()? {
        for file in data.get("files")?.as_array()? {
            let lines = file.pointer("/summary/lines")?;
            files.push(json!({
                "path": file.get("filename")?.as_str()?.replace('\\', "/"),
                "lines_found": lines.get("count")?.as_u64()?,
                "lines_hit": lines.get("covered")?.as_u64()?
            }));
        }
    }
    Some(files)
}

fn static_test_reachability(state: &RepositoryState) -> Value {
    let tests = state
        .graph()
        .nodes()
        .iter()
        .filter(|node| {
            node.kind == NodeKind::File
                && (node.label.contains("/test") || node.label.ends_with("_test.rs"))
        })
        .count();
    json!({
        "test_files": tests,
        "model": "file naming and static graph only"
    })
}