1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use semver::Version;
use std::env;
use std::fmt;
use std::path::PathBuf;
use std::process::Command;

#[derive(Debug)]
pub enum GcovError {
    ProcessFailure,
    Failure((String, String, String)),
}

impl fmt::Display for GcovError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            GcovError::ProcessFailure => write!(f, "Failed to execute gcov process"),
            GcovError::Failure((ref path, ref stdout, ref stderr)) => {
                writeln!(f, "gcov execution failed on {}", path)?;
                writeln!(f, "gcov stdout: {}", stdout)?;
                writeln!(f, "gcov stderr: {}", stderr)
            }
        }
    }
}

fn get_gcov() -> String {
    if let Ok(s) = env::var("GCOV") {
        s
    } else {
        "gcov".to_string()
    }
}

pub fn run_gcov(
    gcno_path: &PathBuf,
    branch_enabled: bool,
    working_dir: &PathBuf,
) -> Result<(), GcovError> {
    let mut command = Command::new(&get_gcov());
    let command = if branch_enabled {
        command.arg("-b").arg("-c")
    } else {
        &mut command
    };
    let status = command
        .arg(gcno_path)
        .arg("-i") // Generate intermediate gcov format, faster to parse.
        .current_dir(working_dir);

    let output = if let Ok(output) = status.output() {
        output
    } else {
        return Err(GcovError::ProcessFailure);
    };

    if !output.status.success() {
        return Err(GcovError::Failure((
            gcno_path.to_str().unwrap().to_string(),
            String::from_utf8_lossy(&output.stdout).to_string(),
            String::from_utf8_lossy(&output.stderr).to_string(),
        )));
    }

    Ok(())
}

fn is_recent_version(gcov_output: &str) -> bool {
    let min_ver = Version {
        major: 4,
        minor: 9,
        patch: 0,
        pre: vec![],
        build: vec![],
    };

    gcov_output.split(' ').all(|value| {
        if let Ok(ver) = Version::parse(value) {
            ver >= min_ver
        } else {
            true
        }
    })
}

pub fn check_gcov_version() -> bool {
    let output = Command::new("gcov")
        .arg("--version")
        .output()
        .expect("Failed to execute `gcov`. `gcov` is required (it is part of GCC).");

    assert!(output.status.success(), "`gcov` failed to execute.");

    is_recent_version(&String::from_utf8(output.stdout).unwrap())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_recent_version() {
        assert!(!is_recent_version(
            "gcov (Ubuntu 4.3.0-12ubuntu2) 4.3.0 20170406"
        ));
        assert!(is_recent_version(
            "gcov (Ubuntu 4.9.0-12ubuntu2) 4.9.0 20170406"
        ));
        assert!(is_recent_version(
            "gcov (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406"
        ));
    }

    #[cfg(unix)]
    #[test]
    fn test_check_gcov_version() {
        check_gcov_version();
    }

    #[cfg(windows)]
    #[test]
    #[should_panic]
    fn test_check_gcov_version() {
        check_gcov_version();
    }
}