├── specimen ├── build.rs ├── benches │ ├── cargo-kcov-test.rs │ ├── seventh.rs │ └── eighth.rs ├── tests │ ├── cargo-kcov-test.rs │ ├── fifth.rs │ ├── sixth.rs │ └── 한국어이름.rs ├── src │ ├── main.rs │ ├── bin │ │ ├── first.rs │ │ └── second.rs │ └── lib.rs ├── inner │ ├── src │ │ └── dummy_lib.rs │ └── Cargo.toml ├── Cargo.lock ├── examples │ ├── third.rs │ └── fourth.rs └── Cargo.toml ├── .gitignore ├── Cargo.toml ├── tests ├── check-colorless-output.rs └── check-specimen.rs ├── src ├── install_kcov.sh ├── stderr.rs ├── cargo.rs ├── errors.rs ├── main.rs └── target_finder.rs ├── .travis.yml ├── README.md └── Cargo.lock /specimen/build.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | -------------------------------------------------------------------------------- /specimen/benches/cargo-kcov-test.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /specimen/tests/cargo-kcov-test.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | *.swp 4 | *.swo 5 | kcov-v*/ -------------------------------------------------------------------------------- /specimen/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | #[test] 4 | fn will_it_run() { 5 | assert!(true); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /specimen/inner/src/dummy_lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /specimen/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "cargo-kcov-test" 3 | version = "0.0.1" 4 | 5 | [[package]] 6 | name = "inner" 7 | version = "0.1.0" 8 | 9 | -------------------------------------------------------------------------------- /specimen/inner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inner" 3 | version = "0.1.0" 4 | authors = ["Benjamin Gill "] 5 | 6 | [dependencies] 7 | 8 | [lib] 9 | name = "dummy_lib" 10 | -------------------------------------------------------------------------------- /specimen/tests/fifth.rs: -------------------------------------------------------------------------------- 1 | extern crate cargo_kcov_test; 2 | 3 | fn main() { 4 | assert!(cargo_kcov_test::test_this_in_fifth()); 5 | } 6 | 7 | #[test] 8 | fn fifth_test() { 9 | main(); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /specimen/tests/sixth.rs: -------------------------------------------------------------------------------- 1 | extern crate cargo_kcov_test; 2 | 3 | fn main() { 4 | assert!(cargo_kcov_test::test_this_in_sixth()); 5 | } 6 | 7 | #[test] 8 | fn sixth_test() { 9 | main(); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /specimen/tests/한국어이름.rs: -------------------------------------------------------------------------------- 1 | extern crate cargo_kcov_test; 2 | 3 | fn main() { 4 | assert!(cargo_kcov_test::test_this_in_korean()); 5 | } 6 | 7 | #[test] 8 | fn test_korean() { 9 | main(); 10 | } 11 | -------------------------------------------------------------------------------- /specimen/examples/third.rs: -------------------------------------------------------------------------------- 1 | extern crate cargo_kcov_test; 2 | 3 | fn main() { 4 | assert!(cargo_kcov_test::test_this_in_third()); 5 | } 6 | 7 | #[test] 8 | fn third_test() { 9 | main(); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /specimen/src/bin/first.rs: -------------------------------------------------------------------------------- 1 | extern crate cargo_kcov_test; 2 | 3 | fn main() { 4 | assert!(cargo_kcov_test::test_this_in_first()); 5 | } 6 | 7 | #[test] 8 | fn first_test() { 9 | main(); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /specimen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-kcov-test" 3 | version = "0.0.1" 4 | authors = ["kennytm "] 5 | build = "build.rs" 6 | publish = false 7 | 8 | [workspace] 9 | members = [".", "inner"] 10 | -------------------------------------------------------------------------------- /specimen/examples/fourth.rs: -------------------------------------------------------------------------------- 1 | extern crate cargo_kcov_test; 2 | 3 | fn main() { 4 | assert!(cargo_kcov_test::test_this_in_fourth()); 5 | } 6 | 7 | #[test] 8 | fn fourth_test() { 9 | main(); 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /specimen/src/bin/second.rs: -------------------------------------------------------------------------------- 1 | extern crate cargo_kcov_test; 2 | 3 | fn main() { 4 | assert!(cargo_kcov_test::test_this_in_second()); 5 | } 6 | 7 | #[test] 8 | fn second_test() { 9 | main(); 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /specimen/benches/seventh.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate cargo_kcov_test; 4 | extern crate test; 5 | 6 | use test::Bencher; 7 | 8 | fn main() { 9 | assert!(cargo_kcov_test::test_this_in_seventh()); 10 | } 11 | 12 | #[bench] 13 | fn seventh_test(_: &mut Bencher) { 14 | main(); 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /specimen/benches/eighth.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate cargo_kcov_test; 4 | extern crate test; 5 | 6 | use test::Bencher; 7 | 8 | fn main() { 9 | assert!(cargo_kcov_test::test_this_in_eighth()); 10 | } 11 | 12 | #[bench] 13 | fn eighth_test(_: &mut Bencher) { 14 | main(); 15 | } 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-kcov" 3 | version = "0.5.4" 4 | authors = ["kennytm "] 5 | description = "Cargo subcommand to run kcov to get coverage report on Linux" 6 | repository = "https://github.com/kennytm/cargo-kcov" 7 | keywords = ["cargo", "subcommand", "kcov", "coverage"] 8 | license = "MIT" 9 | 10 | [badges] 11 | travis-ci = { repository = "kennytm/cargo-kcov" } 12 | 13 | [dependencies] 14 | clap = "2" 15 | shlex = "0.1" 16 | term = "0.6" 17 | serde_json = "1" 18 | regex = "1" 19 | open = "1" 20 | 21 | [dev-dependencies] 22 | rquery = "0.4" 23 | tempdir = "0.3" 24 | 25 | -------------------------------------------------------------------------------- /specimen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn it_works() { 3 | for i in 0 .. 10 { 4 | if i % 2 == 0 { 5 | assert!(i % 4 != 1); 6 | } else if i < 11 { 7 | assert!(test_this_in_second()); 8 | } else { 9 | assert!(false); 10 | } 11 | } 12 | } 13 | 14 | #[ignore] 15 | #[test] 16 | fn should_skip_this_function() { 17 | assert!(false); 18 | } 19 | 20 | pub fn test_this_in_first() -> bool { 21 | true 22 | } 23 | 24 | pub fn test_this_in_second() -> bool { 25 | true 26 | } 27 | 28 | pub fn test_this_in_third() -> bool { 29 | true 30 | } 31 | 32 | pub fn test_this_in_fourth() -> bool { 33 | true 34 | } 35 | 36 | pub fn test_this_in_fifth() -> bool { 37 | true 38 | } 39 | 40 | pub fn test_this_in_sixth() -> bool { 41 | true 42 | } 43 | 44 | pub fn test_this_in_seventh() -> bool { 45 | true 46 | } 47 | 48 | pub fn test_this_in_eighth() -> bool { 49 | true 50 | } 51 | 52 | pub fn test_this_in_korean() -> bool { 53 | true 54 | } 55 | 56 | pub fn should_never_test_this() -> bool { 57 | true 58 | } 59 | 60 | /// Doc test 61 | /// 62 | /// ``` 63 | /// use cargo_kcov_test::foo; 64 | /// assert!(foo()); 65 | /// ``` 66 | pub fn foo() -> bool { 67 | true 68 | } 69 | 70 | -------------------------------------------------------------------------------- /tests/check-colorless-output.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2017 Kenny Chan 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | // and associated documentation files (the "Software"), to deal in the Software without 7 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies or 12 | // substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | #![cfg(not(target_os = "windows"))] 21 | 22 | use std::process::Command; 23 | 24 | // See issue #5 for detail. 25 | #[test] 26 | fn test_colorless_stderr() { 27 | let status = Command::new("cargo") 28 | .args(&["run", "--", "kcov", "--manifest-path", "/dev/null"]) 29 | .env("TERM", "none") 30 | .status() 31 | .expect("finished normally"); 32 | 33 | // the return code should be "2" to indicate the /dev/null is not a valid Cargo.toml. 34 | // if the code is "101", it means we have panicked. 35 | assert_eq!(status.code(), Some(2)); 36 | } 37 | -------------------------------------------------------------------------------- /src/install_kcov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | command_exists() { 5 | command -v $1 &> /dev/null 6 | } 7 | 8 | CARGO_HOME=${CARGO_HOME:-${HOME}/.cargo} 9 | KCOV_DEFAULT_VERSION="v37" 10 | GITHUB_KCOV="https://api.github.com/repos/SimonKagstrom/kcov/releases/latest" 11 | 12 | # Usage: download and install the latest kcov version by default. 13 | # Fall back to ${KCOV_DEFAULT_VERSION} from the kcov archive if the latest is unavailable. 14 | KCOV_VERSION=$(curl --silent --show-error --fail ${GITHUB_KCOV} | jq -Mr .tag_name || echo) 15 | KCOV_VERSION=${KCOV_VERSION:-$KCOV_DEFAULT_VERSION} 16 | 17 | KCOV_TGZ="https://github.com/SimonKagstrom/kcov/archive/${KCOV_VERSION}.tar.gz" 18 | 19 | rm -rf kcov-${KCOV_VERSION}/ 20 | mkdir kcov-${KCOV_VERSION} 21 | curl -L --retry 3 "${KCOV_TGZ}" | tar xzvf - -C kcov-${KCOV_VERSION} --strip-components 1 22 | 23 | num_proc=1 24 | # If PARALLEL_BUILD environment variable is set then parallel build is enabled 25 | if [ "${PARALLEL_BUILD:-}" != "" ]; then 26 | # If PARALLEL_BUILD content is a number then use it as number of parallel jobs 27 | if [ ! -z "${PARALLEL_BUILD##*[!0-9]*}" ]; then 28 | num_proc=${PARALLEL_BUILD} 29 | else 30 | # Try to determine the number of available CPUs 31 | if command_exists nproc; then 32 | num_proc=$(nproc) 33 | elif command_exists sysctl; then 34 | num_proc=$(sysctl -n hw.ncpu) 35 | fi 36 | fi 37 | fi 38 | 39 | cd kcov-${KCOV_VERSION} 40 | mkdir build 41 | cd build 42 | if [ "$(uname)" = Darwin ]; then 43 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -GXcode .. 44 | xcodebuild -configuration Release 45 | cp src/Release/kcov src/Release/libkcov_system_lib.so "${CARGO_HOME}/bin" 46 | else 47 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. 48 | make -j ${num_proc} 49 | cp src/kcov src/libkcov_sowrapper.so "${CARGO_HOME}/bin" 50 | fi 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: rust 3 | sudo: true 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - libcurl4-openssl-dev 9 | - libelf-dev 10 | - libdw-dev 11 | - binutils-dev 12 | - libiberty-dev 13 | - g++ 14 | 15 | # ignoring 32-bit targets until a proper 32-bit environment is provided. 16 | # see https://github.com/travis-ci/travis-ci/issues/986 17 | 18 | matrix: 19 | include: 20 | - os: linux 21 | rust: 1.40.0 22 | env: ARCH=x86_64 23 | 24 | - os: linux 25 | rust: stable 26 | env: ARCH=x86_64 27 | 28 | - os: linux 29 | rust: beta 30 | env: ARCH=x86_64 31 | 32 | - os: linux 33 | rust: nightly 34 | env: ARCH=x86_64 35 | 36 | - os: osx 37 | osx_image: xcode11.3 38 | rust: stable 39 | env: ARCH=x86_64 40 | 41 | install: 42 | - rustc -vV 43 | - cargo -vV 44 | 45 | # Install kcov. 46 | - mkdir -p ~/.cargo/bin 47 | - sh src/install_kcov.sh 48 | - export PATH=$HOME/.local/bin:$HOME/.cargo/bin:$HOME/Library/Python/2.7/bin:$PATH 49 | - export RUSTFLAGS="-C link-dead-code" 50 | - kcov --version 51 | 52 | # See https://github.com/rust-lang/rust/issues/38380 53 | - if [ $TRAVIS_OS_NAME = osx ]; then brew unlink python2 && brew unlink python3; fi 54 | 55 | script: 56 | - cargo build 57 | - cargo test 58 | 59 | # Note: we skip the `check-specimen` test in coverage because kcov cannot do recursive-ptrace. 60 | # Track this file: https://github.com/SimonKagstrom/kcov/blob/master/tests/recursive-ptrace/main.cc 61 | # 62 | # Note: disable covering macOS, otherwise we will timeout. 63 | # see https://travis-ci.org/kennytm/cargo-kcov/jobs/376598886 64 | - if [ $TRAVIS_OS_NAME = linux ]; then cargo run -- kcov --verbose --no-clean-rebuild --lib --coveralls; fi 65 | -------------------------------------------------------------------------------- /tests/check-specimen.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2017 Kenny Chan 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | // and associated documentation files (the "Software"), to deal in the Software without 7 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies or 12 | // substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | #![cfg(not(target_os = "windows"))] 21 | 22 | extern crate rquery; 23 | 24 | use std::process::Command; 25 | 26 | use rquery::Document; 27 | 28 | #[test] 29 | fn test_specimen() { 30 | Command::new("cargo") 31 | .args(&["clean", "--manifest-path", "specimen/Cargo.toml"]) 32 | .status() 33 | .expect("cargo clean"); 34 | 35 | Command::new("cargo") 36 | .args(&[ 37 | "run", 38 | "--", 39 | "kcov", 40 | "--manifest-path", 41 | "specimen/Cargo.toml", 42 | "--all", 43 | ]) 44 | .status() 45 | .expect("cargo run kcov"); 46 | 47 | let xml = Document::new_from_xml_file("specimen/target/cov/kcov-merged/cobertura.xml") 48 | .expect("cobertura.xml exists"); 49 | let elem = xml 50 | .select(r#"class[name="lib_rs"]"#) 51 | .expect("lib_rs element"); 52 | let coverage = elem 53 | .attr("line-rate") 54 | .unwrap() 55 | .parse::() 56 | .expect("line-rate"); 57 | assert!( 58 | 0.1 < coverage && coverage < 1.0, 59 | "Wrong coverage count {}", 60 | coverage 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/stderr.rs: -------------------------------------------------------------------------------- 1 | //! Wrapper of `term::stderr` which fallbacks to colorless output if disabled. 2 | 3 | use std::fmt::Arguments; 4 | use std::io; 5 | 6 | use term::color::Color; 7 | use term::{stderr, Attr, Error, Result, StderrTerminal, Terminal}; 8 | 9 | /// Creates a new stderr console, which is capable of coloring, and gracefully fallback to colorless 10 | /// output if stderr does not support it. 11 | pub fn new() -> Box { 12 | stderr().unwrap_or_else(|| Box::new(ColorlessWriter(io::stderr()))) 13 | } 14 | 15 | /// Wraps a writer which implements `term::Terminal` which ignores all styling commands. This 16 | /// structure is used when `term::stderr()` returns None when targeting non-TTY. 17 | struct ColorlessWriter(W); 18 | 19 | impl Terminal for ColorlessWriter { 20 | type Output = W; 21 | 22 | fn fg(&mut self, _: Color) -> Result<()> { 23 | Ok(()) 24 | } 25 | fn bg(&mut self, _: Color) -> Result<()> { 26 | Ok(()) 27 | } 28 | fn attr(&mut self, _: Attr) -> Result<()> { 29 | Ok(()) 30 | } 31 | fn supports_attr(&self, _: Attr) -> bool { 32 | true 33 | } 34 | fn reset(&mut self) -> Result<()> { 35 | Ok(()) 36 | } 37 | fn supports_reset(&self) -> bool { 38 | true 39 | } 40 | fn supports_color(&self) -> bool { 41 | true 42 | } 43 | fn cursor_up(&mut self) -> Result<()> { 44 | Err(Error::NotSupported) 45 | } 46 | fn delete_line(&mut self) -> Result<()> { 47 | Err(Error::NotSupported) 48 | } 49 | fn carriage_return(&mut self) -> Result<()> { 50 | Err(Error::NotSupported) 51 | } 52 | fn get_ref(&self) -> &Self::Output { 53 | &self.0 54 | } 55 | fn get_mut(&mut self) -> &mut Self::Output { 56 | &mut self.0 57 | } 58 | fn into_inner(self) -> Self::Output { 59 | self.0 60 | } 61 | } 62 | 63 | impl io::Write for ColorlessWriter { 64 | fn write(&mut self, buf: &[u8]) -> io::Result { 65 | self.0.write(buf) 66 | } 67 | fn flush(&mut self) -> io::Result<()> { 68 | self.0.flush() 69 | } 70 | fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { 71 | self.0.write_all(buf) 72 | } 73 | fn write_fmt(&mut self, fmt: Arguments) -> io::Result<()> { 74 | self.0.write_fmt(fmt) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/cargo.rs: -------------------------------------------------------------------------------- 1 | use std::convert::AsRef; 2 | use std::env::var_os; 3 | use std::ffi::OsStr; 4 | use std::fmt; 5 | use std::process::Command; 6 | 7 | use clap::ArgMatches; 8 | 9 | use errors::Error; 10 | 11 | pub struct Cmd { 12 | cmd: Command, 13 | subcommand: &'static str, 14 | } 15 | 16 | impl fmt::Display for Cmd { 17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 18 | use std::fmt::Debug; 19 | self.cmd.fmt(f) 20 | } 21 | } 22 | 23 | enum ArgType { 24 | Flag, 25 | Single, 26 | Multiple, 27 | } 28 | 29 | fn parse_arg_type(option: &str) -> Option { 30 | match option { 31 | "--manifest-path" | "--target" | "--jobs" | "--features" | "--coveralls" => { 32 | Some(ArgType::Single) 33 | } 34 | "--release" | "--lib" | "--no-default-features" | "--no-fail-fast" | "--all" => { 35 | Some(ArgType::Flag) 36 | } 37 | "--bin" | "--example" | "--test" | "--bench" => Some(ArgType::Multiple), 38 | _ => None, 39 | } 40 | } 41 | 42 | impl Cmd { 43 | pub fn new>(command: S, subcommand: &'static str) -> Self { 44 | let mut command = Command::new(command); 45 | if !subcommand.is_empty() { 46 | command.arg(subcommand); 47 | } 48 | Cmd { 49 | cmd: command, 50 | subcommand, 51 | } 52 | } 53 | 54 | pub fn args>(mut self, args: &[S]) -> Self { 55 | self.cmd.args(args); 56 | self 57 | } 58 | 59 | pub fn forward(mut self, matches: &ArgMatches, options: &[&'static str]) -> Self { 60 | for option in options { 61 | let opt_name = &option[2..]; 62 | match parse_arg_type(option).unwrap_or_else(|| panic!("Cannot forward {}", option)) { 63 | ArgType::Flag => { 64 | if matches.is_present(opt_name) { 65 | self.cmd.arg(option); 66 | } 67 | } 68 | ArgType::Single => { 69 | if let Some(opt) = matches.value_of_os(opt_name) { 70 | self.cmd.arg(option).arg(opt); 71 | } 72 | } 73 | ArgType::Multiple => { 74 | if let Some(opts) = matches.values_of_os(opt_name) { 75 | for opt in opts { 76 | self.cmd.arg(option).arg(opt); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | self 83 | } 84 | 85 | pub fn env(mut self, key: &str, sep: &str, val: &str) -> Self { 86 | match var_os(key) { 87 | None => { 88 | self.cmd.env(key, val); 89 | } 90 | Some(mut old_val) => { 91 | old_val.push(sep); 92 | old_val.push(val); 93 | self.cmd.env(key, old_val); 94 | } 95 | } 96 | self 97 | } 98 | 99 | pub fn output(mut self) -> Result<(String, String), Error> { 100 | let output = match self.cmd.output() { 101 | Ok(o) => o, 102 | Err(e) => return Err(Error::CannotRunCargo(e)), 103 | }; 104 | if !output.status.success() { 105 | return Err(Error::Cargo { 106 | subcommand: self.subcommand, 107 | status: output.status, 108 | stderr: output.stderr, 109 | }); 110 | } 111 | 112 | let stdout = String::from_utf8(output.stdout)?; 113 | let stderr = String::from_utf8(output.stderr)?; 114 | Ok((stdout, stderr)) 115 | } 116 | 117 | pub fn run_kcov(mut self) -> Result<(), Error> { 118 | match self.cmd.status() { 119 | Ok(ref s) if s.success() => Ok(()), 120 | s => Err(Error::KcovFailed(s)), 121 | } 122 | } 123 | } 124 | 125 | pub fn cargo(subcommand: &'static str) -> Cmd { 126 | Cmd::new("cargo", subcommand) 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cargo-kcov 2 | ========== 3 | 4 | [![Build status](https://travis-ci.org/kennytm/cargo-kcov.svg?branch=master)](https://travis-ci.org/kennytm/cargo-kcov) 5 | [![Coverage Status](https://coveralls.io/repos/github/kennytm/cargo-kcov/badge.svg?branch=master)](https://coveralls.io/github/kennytm/cargo-kcov?branch=master) 6 | [![crates.io](http://meritbadge.herokuapp.com/cargo-kcov)](https://crates.io/crates/cargo-kcov) 7 | ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) 8 | 9 | Collect test coverage on all the test cases for the current project using 10 | [`kcov`](https://simonkagstrom.github.io/kcov/) on Linux and macOS. 11 | 12 | Usage 13 | ----- 14 | 15 | In the project run 16 | 17 | ```sh 18 | $ cargo kcov 19 | ``` 20 | 21 | It will run all test cases and collect coverage statistics via kcov. The coverage report can be read 22 | from `target/cov/index.html`. 23 | 24 | Prerequisite 25 | ------------ 26 | 27 | You need to install `kcov` v26 or above to collect coverage report from Rust. Some distro is still 28 | shipping v25 or v11, so you will need to build from source. 29 | 30 | For macOS, you will need `kcov` v35 or above. Be aware that macOS performance is significantly 31 | slower when compared with Linux. If you are collecting coverage statistics on the CI and you don't 32 | have time to spend, consider ignoring macOS. 33 | 34 | Please follow the instruction in . **`cargo-kcov` requires v30 or 35 | above** since earlier versions of kcov do not report its version number. 36 | 37 | cargo-kcov requires Rust 1.20.0 or above (due to `bitflags` dependency). 38 | 39 | Install 40 | ------- 41 | 42 | `cargo-kcov` can be installed with `cargo install`. 43 | 44 | ```sh 45 | $ cargo install cargo-kcov 46 | ``` 47 | 48 | Options 49 | ------- 50 | 51 | ```text 52 | cargo-kcov 0.5.1 53 | Generate coverage report via kcov 54 | 55 | USAGE: 56 | cargo kcov [OPTIONS] [--] [KCOV-ARGS]... 57 | 58 | OPTIONS: 59 | --lib Test only this package's library 60 | --bin ... Test only the specified binary 61 | --example ... Test only the specified example 62 | --test ... Test only the specified integration test target 63 | --bench ... Test only the specified benchmark target 64 | -j, --jobs The number of jobs to run in parallel 65 | --release Build artifacts in release mode, with optimizations 66 | --features Space-separated list of features to also build 67 | --no-default-features Do not build the `default` feature 68 | --target Build for the target triple 69 | --manifest-path Path to the manifest to build tests for 70 | --no-fail-fast Run all tests regardless of failure 71 | --kcov Path to the kcov executable 72 | -o, --output Output directory, default to [target/cov] 73 | -v, --verbose Use verbose output 74 | --all In a workspace, test all members 75 | --open Open the coverage report on finish 76 | --coveralls Upload merged coverage data to coveralls.io from Travis CI 77 | --no-clean-rebuild Do not perform a clean rebuild before collecting coverage. This improves performance 78 | when the test case was already built for coverage, but may cause wrong coverage 79 | statistics if used incorrectly. If you use this option, make sure the `target/` 80 | folder is used exclusively by one rustc/cargo version only, and the test cases are 81 | built with `RUSTFLAGS="-C link-dead-code" cargo test`. 82 | --print-install-kcov-sh Prints the sh code that installs kcov to `~/.cargo/bin`. Note that this will *not* 83 | install dependencies required by kcov. 84 | -h, --help Prints help information 85 | -V, --version Prints version information 86 | 87 | ARGS: 88 | ... Further arguments passed to kcov. If empty, the default arguments `--verify --exclude- 89 | pattern=$CARGO_HOME` will be passed to kcov. 90 | ``` 91 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | //use std::error; 3 | use std::convert::From; 4 | use std::fmt::Display; 5 | use std::process::{exit, ExitStatus}; 6 | use std::str::Utf8Error; 7 | use std::string::FromUtf8Error; 8 | 9 | use serde_json; 10 | use term::color::{GREEN, RED, WHITE, YELLOW}; 11 | use term::Attr; 12 | 13 | use stderr; 14 | 15 | #[derive(Debug)] 16 | pub enum Error { 17 | UnsupportedOS, 18 | KcovTooOld, 19 | KcovNotInstalled(io::Error), 20 | CannotRunCargo(io::Error), 21 | Utf8(Utf8Error), 22 | Json(Option), 23 | CannotCreateCoverageDirectory(io::Error), 24 | Cargo { 25 | subcommand: &'static str, 26 | status: ExitStatus, 27 | stderr: Vec, 28 | }, 29 | KcovFailed(io::Result), 30 | NoCoverallsId, 31 | CannotFindTestTargets(Option), 32 | } 33 | 34 | impl Error { 35 | fn description(&self) -> &str { 36 | match *self { 37 | Error::UnsupportedOS => "kcov cannot collect coverage on Windows.", 38 | Error::KcovTooOld => "kcov is too old. v30 or above is required.", 39 | Error::KcovNotInstalled(_) => "kcov not installed.", 40 | Error::CannotRunCargo(_) => "cannot run cargo", 41 | Error::Utf8(_) => "output is not UTF-8 encoded", 42 | Error::Json(_) => "cannot parse JSON", 43 | Error::Cargo { .. } => "cargo subcommand failure", 44 | Error::CannotCreateCoverageDirectory(_) => "cannot create coverage output directory", 45 | Error::KcovFailed(_) => "failed to get coverage", 46 | Error::NoCoverallsId => "missing environment variable TRAVIS_JOB_ID for coveralls", 47 | Error::CannotFindTestTargets(_) => "cannot find test targets", 48 | } 49 | } 50 | 51 | fn cause(&self) -> Option<&dyn Display> { 52 | match *self { 53 | Error::KcovNotInstalled(ref e) 54 | | Error::CannotRunCargo(ref e) 55 | | Error::CannotCreateCoverageDirectory(ref e) 56 | | Error::KcovFailed(Err(ref e)) => Some(e), 57 | Error::Utf8(ref e) => Some(e), 58 | Error::Json(ref e) => e.as_ref().map(|a| a as &dyn Display), 59 | Error::KcovFailed(Ok(ref e)) => Some(e), 60 | Error::CannotFindTestTargets(ref e) => e.as_ref().map(|a| a as &dyn Display), 61 | _ => None, 62 | } 63 | } 64 | } 65 | 66 | impl From for Error { 67 | fn from(e: FromUtf8Error) -> Self { 68 | Error::Utf8(e.utf8_error()) 69 | } 70 | } 71 | 72 | impl From for Error { 73 | fn from(e: serde_json::Error) -> Self { 74 | Error::Json(Some(e)) 75 | } 76 | } 77 | 78 | impl Error { 79 | /// Prints the error message and quit. 80 | pub fn print_error_and_quit(&self) -> ! { 81 | let mut t = stderr::new(); 82 | 83 | t.fg(RED).unwrap(); 84 | t.attr(Attr::Bold).unwrap(); 85 | t.write_all(b"error: ").unwrap(); 86 | t.reset().unwrap(); 87 | writeln!(t, "{}", self.description()).unwrap(); 88 | 89 | if let Error::Cargo { 90 | subcommand, 91 | ref status, 92 | ref stderr, 93 | } = *self 94 | { 95 | t.fg(YELLOW).unwrap(); 96 | t.attr(Attr::Bold).unwrap(); 97 | t.write_all(b"note: ").unwrap(); 98 | t.reset().unwrap(); 99 | writeln!(t, "cargo {} exited with code {}", subcommand, status).unwrap(); 100 | t.write_all(stderr).unwrap(); 101 | } 102 | 103 | if let Some(cause) = self.cause() { 104 | t.fg(YELLOW).unwrap(); 105 | t.attr(Attr::Bold).unwrap(); 106 | t.write_all(b"caused by: ").unwrap(); 107 | t.reset().unwrap(); 108 | writeln!(t, "{}", cause).unwrap(); 109 | } 110 | 111 | match *self { 112 | Error::KcovTooOld | Error::KcovNotInstalled(_) => { 113 | t.fg(GREEN).unwrap(); 114 | t.attr(Attr::Bold).unwrap(); 115 | t.write_all(b"note: ").unwrap(); 116 | t.reset().unwrap(); 117 | t.write_all(b"you may follow ").unwrap(); 118 | t.attr(Attr::Underline(true)).unwrap(); 119 | t.write_all(b"https://users.rust-lang.org/t/650").unwrap(); 120 | t.reset().unwrap(); 121 | t.write_all(b" to install kcov:\n\n").unwrap(); 122 | 123 | #[cfg(target_os = "linux")] 124 | { 125 | t.fg(WHITE).unwrap(); 126 | t.write_all(b" $ ").unwrap(); 127 | t.reset().unwrap(); 128 | writeln!(t, "sudo apt-get install cmake g++ pkg-config jq\n").unwrap(); 129 | 130 | t.fg(WHITE).unwrap(); 131 | t.write_all(b" $ ").unwrap(); 132 | t.reset().unwrap(); 133 | writeln!(t, "sudo apt-get install libcurl4-openssl-dev libelf-dev libdw-dev binutils-dev libiberty-dev\n").unwrap(); 134 | } 135 | #[cfg(target_os = "macos")] 136 | { 137 | t.fg(WHITE).unwrap(); 138 | t.write_all(b" $ ").unwrap(); 139 | t.reset().unwrap(); 140 | writeln!(t, "brew install cmake jq\n").unwrap(); 141 | } 142 | 143 | t.fg(WHITE).unwrap(); 144 | t.write_all(b" $ ").unwrap(); 145 | t.reset().unwrap(); 146 | writeln!(t, "cargo kcov --print-install-kcov-sh | sh").unwrap(); 147 | } 148 | Error::CannotFindTestTargets(_) => { 149 | t.fg(GREEN).unwrap(); 150 | t.attr(Attr::Bold).unwrap(); 151 | t.write_all(b"note: ").unwrap(); 152 | t.reset().unwrap(); 153 | t.write_all(b"try a clean rebuild first:\n\n").unwrap(); 154 | t.fg(WHITE).unwrap(); 155 | t.write_all(b" $ ").unwrap(); 156 | t.reset().unwrap(); 157 | writeln!( 158 | t, 159 | "cargo clean && 160 | RUSTFLAGS=\"-C link-dead-code\" cargo test --no-run && 161 | cargo kcov --no-clean-rebuild 162 | 163 | " 164 | ) 165 | .unwrap(); 166 | } 167 | _ => {} 168 | } 169 | 170 | exit(2); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2017 Kenny Chan 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | // and associated documentation files (the "Software"), to deal in the Software without 7 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies or 12 | // substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | #[macro_use] 21 | extern crate clap; 22 | extern crate open; 23 | extern crate regex; 24 | extern crate serde_json; 25 | extern crate shlex; 26 | #[cfg(test)] 27 | extern crate tempdir; 28 | extern crate term; 29 | 30 | mod cargo; 31 | mod errors; 32 | mod stderr; 33 | mod target_finder; 34 | 35 | use std::borrow::Cow; 36 | use std::collections::HashSet; 37 | use std::env::var_os; 38 | use std::ffi::{OsStr, OsString}; 39 | use std::fs::{create_dir_all, remove_dir_all}; 40 | use std::path::{Path, PathBuf}; 41 | use std::process::Command; 42 | 43 | use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; 44 | 45 | use cargo::{cargo, Cmd}; 46 | use errors::Error; 47 | use target_finder::*; 48 | use term::color::{GREEN, YELLOW}; 49 | use term::Attr; 50 | 51 | fn main() { 52 | let matches = create_arg_parser().get_matches(); 53 | let matches = matches 54 | .subcommand_matches("kcov") 55 | .expect("Expecting subcommand `kcov`."); 56 | 57 | match run(matches) { 58 | Ok(_) => {} 59 | Err(e) => e.print_error_and_quit(), 60 | } 61 | } 62 | 63 | fn create_arg_parser() -> App<'static, 'static> { 64 | App::new("cargo-kcov") 65 | .about("Generate coverage report via kcov") 66 | .version(crate_version!()) 67 | .bin_name("cargo") 68 | .settings(&[AppSettings::SubcommandRequiredElseHelp, AppSettings::GlobalVersion]) 69 | .subcommand(SubCommand::with_name("kcov") 70 | .about("Generate coverage report via kcov") 71 | .settings(&[AppSettings::UnifiedHelpMessage, AppSettings::DeriveDisplayOrder]) 72 | .args(&[ 73 | Arg::with_name("lib").long("--lib").help("Test only this package's library"), 74 | filtering_arg("--bin", "Test only the specified binary"), 75 | filtering_arg("--example", "Test only the specified example"), 76 | filtering_arg("--test", "Test only the specified integration test target"), 77 | filtering_arg("--bench", "Test only the specified benchmark target"), 78 | ]) 79 | .args_from_usage(" 80 | -j, --jobs=[N] 'The number of jobs to run in parallel' 81 | --release 'Build artifacts in release mode, with optimizations' 82 | --features [FEATURES] 'Space-separated list of features to also build' 83 | --no-default-features 'Do not build the `default` feature' 84 | --target [TRIPLE] 'Build for the target triple' 85 | --manifest-path [PATH] 'Path to the manifest to build tests for' 86 | --no-fail-fast 'Run all tests regardless of failure' 87 | --kcov [PATH] 'Path to the kcov executable' 88 | -o, --output [PATH] 'Output directory, default to [target/cov]' 89 | -v, --verbose 'Use verbose output' 90 | --all 'In a workspace, test all members' 91 | --open 'Open the coverage report on finish' 92 | --coveralls 'Upload merged coverage data to coveralls.io from Travis CI' 93 | --no-clean-rebuild 'Do not perform a clean rebuild before collecting coverage. \ 94 | This improves performance when the test case was already \ 95 | built for coverage, but may cause wrong coverage statistics \ 96 | if used incorrectly. If you use this option, make sure the \ 97 | `target/` folder is used exclusively by one rustc/cargo \ 98 | version only, and the test cases are built with \ 99 | `RUSTFLAGS=\"-C link-dead-code\" cargo test`.' 100 | --print-install-kcov-sh 'Prints the sh code that installs kcov to `~/.cargo/bin`. \ 101 | Note that this will *not* install dependencies required by \ 102 | kcov.' 103 | [KCOV-ARGS]... 'Further arguments passed to kcov. If empty, the default \ 104 | arguments `--verify --exclude-pattern=$CARGO_HOME` will be \ 105 | passed to kcov.' 106 | ") 107 | ) 108 | } 109 | 110 | fn filtering_arg<'a, 'b>(name: &'a str, help: &'b str) -> Arg<'a, 'b> { 111 | Arg::with_name(&name[2..]) 112 | .long(name) 113 | .value_name("NAME") 114 | .number_of_values(1) 115 | .multiple(true) 116 | .help(help) 117 | } 118 | 119 | fn run(matches: &ArgMatches) -> Result<(), Error> { 120 | if cfg!(any(target_os = "windows")) { 121 | return Err(Error::UnsupportedOS); 122 | } 123 | 124 | if matches.is_present("print-install-kcov-sh") { 125 | println!("{}", include_str!("install_kcov.sh")); 126 | return Ok(()); 127 | } 128 | 129 | let is_verbose = matches.is_present("verbose"); 130 | let kcov_path = check_kcov(matches)?; 131 | 132 | let coveralls_option = get_coveralls_option(matches)?; 133 | let target_path = find_target_path(matches)?; 134 | 135 | let pkgid = if matches.is_present("all") { 136 | None 137 | } else { 138 | Some(get_pkgid(matches)?) 139 | }; 140 | 141 | let pkgid: Option<&str> = pkgid.as_ref().map(|id| &**id); 142 | 143 | let tests = if matches.is_present("no-clean-rebuild") { 144 | find_tests(matches, pkgid, target_path.clone())? 145 | } else { 146 | if is_verbose { 147 | write_msg("Clean", pkgid.unwrap_or("all")); 148 | } 149 | clean(matches, pkgid)?; 150 | 151 | if is_verbose { 152 | write_msg("Build", "test executables"); 153 | } 154 | build_test(matches)? 155 | }; 156 | 157 | if is_verbose { 158 | write_msg( 159 | "Coverage", 160 | &format!("found the following executables: {:?}", tests), 161 | ); 162 | } 163 | 164 | let cov_path = create_cov_path(matches, target_path)?; 165 | let kcov_args = match matches.values_of_os("KCOV-ARGS") { 166 | Some(a) => a.map(|s| s.to_owned()).collect(), 167 | None => { 168 | let mut exclude_pattern = OsString::from("--exclude-pattern="); 169 | exclude_pattern.push( 170 | var_os("CARGO_HOME") 171 | .as_ref() 172 | .map_or(OsStr::new("/.cargo"), |s| s), 173 | ); 174 | vec![ 175 | exclude_pattern, 176 | OsString::from(if cfg!(target_os = "macos") { 177 | // Exclude the standard library symbols, otherwise kcov will take forever to run. 178 | "--exclude-pattern=/Users/travis/build/rust-lang/rust/" 179 | } else { 180 | "--verify" 181 | }), 182 | ] 183 | } 184 | }; 185 | 186 | let mut merge_cov_paths = Vec::with_capacity(tests.len()); 187 | for test in tests { 188 | let mut pre_cov_path = cov_path.clone(); 189 | pre_cov_path.push(test.file_name().unwrap()); 190 | let cmd = Cmd::new(&kcov_path, "") 191 | .env("LD_LIBRARY_PATH", ":", "target/debug/deps") 192 | .args(&kcov_args) 193 | .args(&[&pre_cov_path, &test]); 194 | if is_verbose { 195 | write_msg("Running", &cmd.to_string()); 196 | } 197 | cmd.run_kcov()?; 198 | merge_cov_paths.push(pre_cov_path); 199 | } 200 | 201 | let mut merge_cmd = Cmd::new(&kcov_path, "--merge") 202 | .args(&kcov_args) 203 | .args(&[&cov_path]); 204 | if let Some(opt) = coveralls_option { 205 | merge_cmd = merge_cmd.args(&[opt]); 206 | } 207 | merge_cmd = merge_cmd.args(&merge_cov_paths); 208 | if is_verbose { 209 | write_msg("Running", &merge_cmd.to_string()); 210 | } 211 | merge_cmd.run_kcov()?; 212 | 213 | if matches.is_present("open") { 214 | open_coverage_report(&cov_path); 215 | } 216 | 217 | Ok(()) 218 | } 219 | 220 | fn write_msg(title: &str, msg: &str) { 221 | let mut t = stderr::new(); 222 | t.fg(GREEN).unwrap(); 223 | t.attr(Attr::Bold).unwrap(); 224 | write!(t, "{:>12}", title).unwrap(); 225 | t.reset().unwrap(); 226 | writeln!(t, " {}", msg).unwrap(); 227 | } 228 | 229 | fn check_kcov<'a>(matches: &'a ArgMatches<'a>) -> Result<&'a OsStr, Error> { 230 | let program = matches 231 | .value_of_os("kcov") 232 | .unwrap_or_else(|| OsStr::new("kcov")); 233 | let output = match Command::new(program).arg("--version").output() { 234 | Ok(o) => o, 235 | Err(e) => return Err(Error::KcovNotInstalled(e)), 236 | }; 237 | if output.stdout.starts_with(b"kcov ") { 238 | Ok(program) 239 | } else { 240 | Err(Error::KcovTooOld) 241 | } 242 | } 243 | 244 | fn get_pkgid(matches: &ArgMatches) -> Result { 245 | let (mut output, _) = cargo("pkgid") 246 | .forward(matches, &["--manifest-path"]) 247 | .output()?; 248 | let trimmed_len = output.trim_end().len(); 249 | output.truncate(trimmed_len); 250 | Ok(output) 251 | } 252 | 253 | fn get_coveralls_option(matches: &ArgMatches) -> Result, Error> { 254 | if !matches.is_present("coveralls") { 255 | Ok(None) 256 | } else { 257 | match var_os("TRAVIS_JOB_ID") { 258 | None => Err(Error::NoCoverallsId), 259 | Some(id) => { 260 | let mut res = OsString::from("--coveralls-id="); 261 | res.push(id); 262 | Ok(Some(res)) 263 | } 264 | } 265 | } 266 | } 267 | 268 | fn find_target_path(matches: &ArgMatches) -> Result { 269 | use serde_json::{from_str, Value}; 270 | 271 | let (json, _) = cargo("metadata") 272 | .forward(matches, &["--manifest-path"]) 273 | .args(&["--no-deps", "--format-version", "1"]) 274 | .output()?; 275 | 276 | let json = from_str::(&json)?; 277 | match json["target_directory"].as_str() { 278 | None => Err(Error::Json(None)), 279 | Some(p) => Ok(PathBuf::from(p)), 280 | } 281 | } 282 | 283 | fn create_cov_path(matches: &ArgMatches, mut target_path: PathBuf) -> Result { 284 | let cov_path = match matches.value_of_os("output") { 285 | Some(p) => PathBuf::from(p), 286 | None => { 287 | target_path.push("cov"); 288 | target_path 289 | } 290 | }; 291 | 292 | let _ = remove_dir_all(&cov_path); 293 | match create_dir_all(&cov_path) { 294 | Ok(_) => Ok(cov_path), 295 | Err(e) => Err(Error::CannotCreateCoverageDirectory(e)), 296 | } 297 | } 298 | 299 | fn clean(matches: &ArgMatches, pkg: Option<&str>) -> Result<(), Error> { 300 | let mut cmd = cargo("clean"); 301 | 302 | if let Some(pkg) = pkg { 303 | cmd = cmd.args(&["--package", pkg]); 304 | } 305 | 306 | cmd.forward(matches, &["--manifest-path", "--target", "--release"]) 307 | .output()?; 308 | 309 | Ok(()) 310 | } 311 | 312 | fn build_test(matches: &ArgMatches) -> Result, Error> { 313 | let (output, error) = cargo("test") 314 | .args(&["--no-run", "-v"]) 315 | .env("RUSTFLAGS", " ", "-C link-dead-code") 316 | .forward( 317 | matches, 318 | &[ 319 | "--lib", 320 | "--bin", 321 | "--example", 322 | "--test", 323 | "--bench", 324 | "--jobs", 325 | "--release", 326 | "--target", 327 | "--manifest-path", 328 | "--features", 329 | "--no-default-features", 330 | "--no-fail-fast", 331 | "--all", 332 | ], 333 | ) 334 | .output()?; 335 | 336 | let mut targets = Vec::new(); 337 | parse_rustc_command_lines_into(&mut targets, &error); 338 | parse_rustc_command_lines_into(&mut targets, &output); 339 | Ok(targets) 340 | } 341 | 342 | fn open_coverage_report(output_path: &Path) { 343 | let index_path = output_path.join("index.html"); 344 | write_msg("Opening", &index_path.to_string_lossy()); 345 | if let Err(e) = open::that(index_path) { 346 | let mut t = stderr::new(); 347 | t.fg(YELLOW).unwrap(); 348 | t.attr(Attr::Bold).unwrap(); 349 | write!(t, "warning").unwrap(); 350 | t.reset().unwrap(); 351 | writeln!(t, ": cannot open coverage report, {}", e).unwrap(); 352 | } 353 | } 354 | 355 | //------------------------------------------------------------------------------------------------- 356 | 357 | /// Find all test executables using `read_dir` without clean-rebuild. 358 | fn find_tests( 359 | matches: &ArgMatches, 360 | pkgid: Option<&str>, 361 | path: PathBuf, 362 | ) -> Result, Error> { 363 | let (path, file_name_filters) = get_args_for_find_test_targets(matches, pkgid, path); 364 | find_test_targets(&path, file_name_filters) 365 | } 366 | 367 | fn get_args_for_find_test_targets<'a>( 368 | matches: &'a ArgMatches, 369 | pkgid: Option<&'a str>, 370 | mut path: PathBuf, 371 | ) -> (PathBuf, HashSet>) { 372 | if let Some(target) = matches.value_of_os("target") { 373 | path.push(target); 374 | } 375 | path.push(if matches.is_present("release") { 376 | "release" 377 | } else { 378 | "debug" 379 | }); 380 | 381 | let mut file_name_filters = HashSet::new(); 382 | 383 | if let Some(pkgid) = pkgid { 384 | if matches.is_present("lib") { 385 | file_name_filters.insert(find_package_name_from_pkgid(pkgid)); 386 | } 387 | } 388 | 389 | extend_file_name_filters(&mut file_name_filters, matches, "bin"); 390 | extend_file_name_filters(&mut file_name_filters, matches, "example"); 391 | extend_file_name_filters(&mut file_name_filters, matches, "test"); 392 | extend_file_name_filters(&mut file_name_filters, matches, "bench"); 393 | 394 | (path, file_name_filters) 395 | } 396 | 397 | fn extend_file_name_filters<'a>( 398 | filters: &mut HashSet>, 399 | matches: &'a ArgMatches<'a>, 400 | key: &str, 401 | ) { 402 | if let Some(values) = matches.values_of(key) { 403 | filters.extend(values.map(normalize_package_name)); 404 | } 405 | } 406 | 407 | #[test] 408 | fn test_get_args_for_find_test_targets() { 409 | use std::path::Path; 410 | 411 | let path = Path::new("/path/to/some/great-project/target"); 412 | let pkgid = "file:///path/to/some/great-project#0.1.0"; 413 | let mut app = create_arg_parser(); 414 | 415 | let mut do_test = |args: &[&'static str], expected_path, expected_filters: &[&'static str]| { 416 | let matches = app.get_matches_from_safe_borrow(args).unwrap(); 417 | let matches = matches.subcommand_matches("kcov").unwrap(); 418 | let args = get_args_for_find_test_targets(&matches, Some(pkgid), path.to_path_buf()); 419 | assert_eq!(args.0, expected_path); 420 | assert_eq!( 421 | args.1, 422 | expected_filters.iter().map(|x| Cow::Borrowed(*x)).collect() 423 | ); 424 | }; 425 | 426 | do_test( 427 | &["cargo", "kcov", "--no-clean-rebuild"], 428 | Path::new("/path/to/some/great-project/target/debug"), 429 | &[], 430 | ); 431 | 432 | do_test( 433 | &["cargo", "kcov", "--no-clean-rebuild", "--release"], 434 | Path::new("/path/to/some/great-project/target/release"), 435 | &[], 436 | ); 437 | 438 | do_test( 439 | &[ 440 | "cargo", 441 | "kcov", 442 | "--no-clean-rebuild", 443 | "--target", 444 | "i586-unknown-linux-gnu", 445 | ], 446 | Path::new("/path/to/some/great-project/target/i586-unknown-linux-gnu/debug"), 447 | &[], 448 | ); 449 | 450 | do_test( 451 | &["cargo", "kcov", "--no-clean-rebuild", "--lib"], 452 | Path::new("/path/to/some/great-project/target/debug"), 453 | &["great_project"], 454 | ); 455 | 456 | do_test( 457 | &[ 458 | "cargo", 459 | "kcov", 460 | "--no-clean-rebuild", 461 | "--lib", 462 | "--bin", 463 | "a", 464 | "--bin", 465 | "b-c-d", 466 | "--test", 467 | "e", 468 | "--example", 469 | "ff", 470 | "--bench", 471 | "g", 472 | ], 473 | Path::new("/path/to/some/great-project/target/debug"), 474 | &["great_project", "a", "b_c_d", "e", "ff", "g"], 475 | ); 476 | } 477 | -------------------------------------------------------------------------------- /src/target_finder.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::convert::AsRef; 3 | use std::default::Default; 4 | use std::fs::Metadata; 5 | use std::iter::IntoIterator; 6 | use std::path::{Path, PathBuf}; 7 | 8 | use regex::{escape, RegexSet}; 9 | use shlex::Shlex; 10 | 11 | use errors::Error; 12 | 13 | /// Collects path of test executables by parsing the output of `cargo test --no-run --verbose`. 14 | pub fn parse_rustc_command_lines_into(targets: &mut Vec, output: &str) { 15 | targets.extend(output.lines().flat_map(parse_rustc_command_line)); 16 | } 17 | 18 | /// Used in `parse_rustc_command_line`. What token is expected after the current argument. 19 | #[derive(Debug)] 20 | enum NextState { 21 | /// A normal argument is expected. 22 | Normal, 23 | /// `--crate-name` was consumed, a crate name is expected next. 24 | CrateName, 25 | /// `-C` was consumed, a configuration (specifically, `extra-filename=X`) is expected next. 26 | C, 27 | /// `--out-dir` was consumed, an output directory is expected next. 28 | OutDir, 29 | } 30 | 31 | /// Used in `parse_rustc_command_line`. Stores information about the current parse state. 32 | #[derive(Default, Debug)] 33 | struct Info { 34 | crate_name: Option, 35 | extra_filename: Option, 36 | out_dir: Option, 37 | is_test_confirmed: bool, 38 | } 39 | 40 | /// Parses a single line of `cargo test --no-run --verbose` output. If the line indicates the 41 | /// compilation of a test executable, the path will be extracted. Otherwise, it returns `None`. 42 | fn parse_rustc_command_line(line: &str) -> Option { 43 | let trimmed_line = line.trim_start(); 44 | if !trimmed_line.starts_with("Running `rustc ") { 45 | return None; 46 | } 47 | 48 | let mut next_state = NextState::Normal; 49 | let mut info = Info::default(); 50 | 51 | for word in Shlex::new(trimmed_line) { 52 | match next_state { 53 | NextState::CrateName => { 54 | if word != "build_script_build" { 55 | info.crate_name = Some(word); 56 | next_state = NextState::Normal; 57 | } else { 58 | return None; 59 | } 60 | } 61 | NextState::C => { 62 | if word.starts_with("extra-filename=") { 63 | info.extra_filename = Some(word); 64 | } 65 | next_state = NextState::Normal; 66 | } 67 | NextState::OutDir => { 68 | info.out_dir = Some(word); 69 | next_state = NextState::Normal; 70 | } 71 | NextState::Normal => { 72 | next_state = match &*word { 73 | "--crate-name" => NextState::CrateName, 74 | "--test" => { 75 | info.is_test_confirmed = true; 76 | NextState::Normal 77 | } 78 | "-C" => NextState::C, 79 | "--out-dir" => NextState::OutDir, 80 | _ => NextState::Normal, 81 | }; 82 | } 83 | } 84 | } 85 | 86 | if !info.is_test_confirmed { 87 | return None; 88 | } 89 | 90 | let mut file_name = match info.crate_name { 91 | Some(c) => c, 92 | None => return None, 93 | }; 94 | 95 | if let Some(extra) = info.extra_filename { 96 | file_name.push_str(&extra[15..]); 97 | } 98 | 99 | let mut path = match info.out_dir { 100 | Some(o) => PathBuf::from(o), 101 | None => PathBuf::new(), 102 | }; 103 | path.push(file_name); 104 | 105 | Some(path) 106 | } 107 | 108 | #[test] 109 | fn test_parse_rustc_command_lines() { 110 | let msg = " 111 | Compiling cargo-kcov-test v0.0.1 (file:///path/to/cargo-kcov/specimen) 112 | Running `rustc build.rs --crate-name build_script_build --crate-type bin -g --out-dir /path/to/cargo-kcov/specimen/target/debug/build/cargo-kcov-test-e1855e3009763592 --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps` 113 | Running `/path/to/cargo-kcov/specimen/target/debug/build/cargo-kcov-test-e1855e3009763592/build-script-build` 114 | Running `rustc src/lib.rs --crate-name cargo_kcov_test --crate-type lib -g --test -C metadata=e4ea274689ebe015 -C extra-filename=-e4ea274689ebe015 --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps` 115 | Running `rustc src/lib.rs --crate-name cargo_kcov_test --crate-type lib -g --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps` 116 | Running `rustc src/bin/first.rs --crate-name first --crate-type bin -g --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 117 | Running `rustc tests/sixth.rs --crate-name sixth --crate-type bin -g --test -C metadata=cd20d019c38b7035 -C extra-filename=-cd20d019c38b7035 --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 118 | Running `rustc examples/third.rs --crate-name third --crate-type bin -g --out-dir /path/to/cargo-kcov/specimen/target/debug/examples --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 119 | Running `rustc tests/한국어이름.rs --crate-name 한국어이름 --crate-type bin -g --test -C metadata=a696584af54c95b4 -C extra-filename=-a696584af54c95b4 --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 120 | Running `rustc src/main.rs --crate-name cargo_kcov_test --crate-type bin -g --test -C metadata=207b5062b0bafac9 -C extra-filename=-207b5062b0bafac9 --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 121 | Running `rustc src/main.rs --crate-name cargo_kcov_test --crate-type bin -g --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 122 | Running `rustc src/bin/second.rs --crate-name second --crate-type bin -g --test -C metadata=f0ac3ec8d3d3bcd5 -C extra-filename=-f0ac3ec8d3d3bcd5 --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 123 | Running `rustc tests/cargo-kcov-test.rs --crate-name cargo_kcov_test --crate-type bin -g --test -C metadata=41b658cb1ecbc7a1 -C extra-filename=-41b658cb1ecbc7a1 --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 124 | Running `rustc tests/fifth.rs --crate-name fifth --crate-type bin -g --test -C metadata=eaaacda44386e87c -C extra-filename=-eaaacda44386e87c --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 125 | Running `rustc src/bin/second.rs --crate-name second --crate-type bin -g --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 126 | Running `rustc src/bin/first.rs --crate-name first --crate-type bin -g --test -C metadata=d5d6293fc6d22a93 -C extra-filename=-d5d6293fc6d22a93 --out-dir /path/to/cargo-kcov/specimen/target/debug --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 127 | Running `rustc examples/fourth.rs --crate-name fourth --crate-type bin -g --out-dir /path/to/cargo-kcov/specimen/target/debug/examples --emit=dep-info,link -L dependency=/path/to/cargo-kcov/specimen/target/debug -L dependency=/path/to/cargo-kcov/specimen/target/debug/deps --extern cargo_kcov_test=/path/to/cargo-kcov/specimen/target/debug/libcargo_kcov_test.rlib` 128 | "; 129 | 130 | let expected_paths = [ 131 | Path::new("/path/to/cargo-kcov/specimen/target/debug/cargo_kcov_test-e4ea274689ebe015"), 132 | Path::new("/path/to/cargo-kcov/specimen/target/debug/sixth-cd20d019c38b7035"), 133 | Path::new("/path/to/cargo-kcov/specimen/target/debug/한국어이름-a696584af54c95b4"), 134 | Path::new("/path/to/cargo-kcov/specimen/target/debug/cargo_kcov_test-207b5062b0bafac9"), 135 | Path::new("/path/to/cargo-kcov/specimen/target/debug/second-f0ac3ec8d3d3bcd5"), 136 | Path::new("/path/to/cargo-kcov/specimen/target/debug/cargo_kcov_test-41b658cb1ecbc7a1"), 137 | Path::new("/path/to/cargo-kcov/specimen/target/debug/fifth-eaaacda44386e87c"), 138 | Path::new("/path/to/cargo-kcov/specimen/target/debug/first-d5d6293fc6d22a93"), 139 | ]; 140 | 141 | let mut actual_paths = Vec::new(); 142 | parse_rustc_command_lines_into(&mut actual_paths, msg); 143 | 144 | assert_eq!(actual_paths, expected_paths); 145 | } 146 | 147 | //------------------------------------------------------------------------------------------------- 148 | 149 | /// Finds all test targets in the target folder (usually `target/debug/`). 150 | /// 151 | /// If the `filter` set is empty, all test executables will be gathered. 152 | pub fn find_test_targets(target_folder: &Path, filter: I) -> Result, Error> 153 | where 154 | I: IntoIterator, 155 | I::IntoIter: ExactSizeIterator, 156 | E: AsRef, 157 | { 158 | let filter = filter.into_iter(); 159 | let test_target_regex = if filter.len() == 0 { 160 | RegexSet::new(&["^[^-]+-[0-9a-f]{16}$"]) 161 | } else { 162 | RegexSet::new(filter.map(|f| format!("^{}-[0-9a-f]{{16}}$", escape(f.as_ref())))) 163 | } 164 | .unwrap(); 165 | 166 | let result = (|| { 167 | let mut result = Vec::new(); 168 | 169 | for entry in target_folder.read_dir()? { 170 | let entry = entry?; 171 | let metadata = entry.metadata()?; 172 | let path = entry.path(); 173 | if !(metadata.is_file() && can_execute(&path, &metadata)) { 174 | continue; 175 | } 176 | // we need this `should_push` variable due to borrowing. Hopefully MIR can fix this 177 | let mut should_push = false; 178 | if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) { 179 | should_push = test_target_regex.is_match(stem); 180 | } 181 | if should_push { 182 | result.push(path); 183 | } 184 | } 185 | 186 | Ok(result) 187 | })(); 188 | 189 | match result { 190 | Ok(r) => { 191 | if r.is_empty() { 192 | Err(Error::CannotFindTestTargets(None)) 193 | } else { 194 | Ok(r) 195 | } 196 | } 197 | Err(e) => Err(Error::CannotFindTestTargets(Some(e))), 198 | } 199 | } 200 | 201 | #[cfg(unix)] 202 | fn can_execute(_: &Path, metadata: &Metadata) -> bool { 203 | use std::os::unix::fs::PermissionsExt; 204 | // Perhaps use `libc::access` instead? 205 | (metadata.permissions().mode() & 0o111) != 0 206 | } 207 | 208 | #[cfg(windows)] 209 | fn can_execute(path: &Path, _: &Metadata) -> bool { 210 | path.extension() == Some(std::ffi::OsStr::new("exe")) 211 | } 212 | 213 | #[test] 214 | #[cfg(unix)] 215 | fn test_find_test_targets() { 216 | use std::fs::{create_dir, set_permissions, File, Permissions}; 217 | use std::os::unix::fs::PermissionsExt; 218 | use tempdir::TempDir; 219 | 220 | let root = TempDir::new("test_find_test_targets").unwrap(); 221 | let root_path = root.path(); 222 | 223 | //setup: 224 | { 225 | let files_to_add = &[ 226 | ".cargo-lock", 227 | ".fingerprint/", 228 | "build/", 229 | "cargo-kcov-test", 230 | "cargo_kcov_test-207b5062b0bafac9", 231 | "cargo_kcov_test-207b5062b0bafac9.dSYM/", 232 | "cargo_kcov_test-41b658cb1ecbc7a1", 233 | "cargo_kcov_test-41b658cb1ecbc7a1.dSYM/", 234 | "cargo_kcov_test-e4ea274689ebe015", 235 | "cargo_kcov_test-e4ea274689ebe015.dSYM/", 236 | "cargo_kcov_test.dSYM/", 237 | "deps/", 238 | "examples/", 239 | "fifth-eaaacda44386e87c", 240 | "fifth-eaaacda44386e87c.dSYM/", 241 | "first", 242 | "first-d5d6293fc6d22a93", 243 | "first-d5d6293fc6d22a93.dSYM/", 244 | "first.dSYM/", 245 | "libcargo_kcov_test.rlib", 246 | "native/", 247 | "second", 248 | "second-f0ac3ec8d3d3bcd5", 249 | "second-f0ac3ec8d3d3bcd5.dSYM/", 250 | "second.dSYM/", 251 | "sixth-cd20d019c38b7035", 252 | "sixth-cd20d019c38b7035.dSYM/", 253 | "한국어이름-a696584af54c95b4", 254 | "한국어이름-a696584af54c95b4.dSYM/", 255 | ]; 256 | 257 | for filename in files_to_add { 258 | let path = root_path.join(filename); 259 | if filename.ends_with("/") { 260 | create_dir(path).unwrap(); 261 | } else { 262 | File::create(&path).unwrap(); 263 | set_permissions(path, Permissions::from_mode(0o755)).unwrap(); 264 | } 265 | } 266 | } 267 | 268 | //test_unfiltered: 269 | { 270 | let mut actual_paths = find_test_targets(root_path, &[] as &[&'static str]).unwrap(); 271 | actual_paths.sort(); 272 | 273 | let expected_paths = [ 274 | root_path.join("cargo_kcov_test-207b5062b0bafac9"), 275 | root_path.join("cargo_kcov_test-41b658cb1ecbc7a1"), 276 | root_path.join("cargo_kcov_test-e4ea274689ebe015"), 277 | root_path.join("fifth-eaaacda44386e87c"), 278 | root_path.join("first-d5d6293fc6d22a93"), 279 | root_path.join("second-f0ac3ec8d3d3bcd5"), 280 | root_path.join("sixth-cd20d019c38b7035"), 281 | root_path.join("한국어이름-a696584af54c95b4"), 282 | ]; 283 | assert_eq!(actual_paths, expected_paths); 284 | } 285 | 286 | //test_filtered: 287 | { 288 | let mut actual_paths = find_test_targets(root_path, &["cargo_kcov_test", "sixth"]).unwrap(); 289 | actual_paths.sort(); 290 | 291 | let expected_paths = [ 292 | root_path.join("cargo_kcov_test-207b5062b0bafac9"), 293 | root_path.join("cargo_kcov_test-41b658cb1ecbc7a1"), 294 | root_path.join("cargo_kcov_test-e4ea274689ebe015"), 295 | root_path.join("sixth-cd20d019c38b7035"), 296 | ]; 297 | assert_eq!(actual_paths, expected_paths); 298 | } 299 | 300 | //test_found_nothing 301 | { 302 | let result = find_test_targets(root_path, &["asdaksdhaskdkasdk"]); 303 | match result { 304 | Err(Error::CannotFindTestTargets(None)) => {} 305 | _ => assert!(false), 306 | } 307 | } 308 | } 309 | 310 | //------------------------------------------------------------------------------------------------- 311 | 312 | pub fn find_package_name_from_pkgid(pkgid: &str) -> Cow { 313 | // whoever think of this pkgid syntax... wtf??? 314 | let path = match pkgid.rfind('/') { 315 | Some(i) => &pkgid[i + 1..], 316 | None => pkgid, 317 | }; 318 | let pkg_name = match (path.rfind(':'), path.find('#')) { 319 | (None, None) => path, 320 | (Some(i), None) => &path[..i], 321 | (None, Some(j)) => &path[..j], 322 | (Some(i), Some(j)) => &path[j + 1..i], 323 | }; 324 | normalize_package_name(pkg_name) 325 | } 326 | 327 | pub fn normalize_package_name(name: &str) -> Cow { 328 | if name.contains('-') { 329 | Cow::Owned(name.replace('-', "_")) 330 | } else { 331 | Cow::Borrowed(name) 332 | } 333 | } 334 | 335 | #[test] 336 | fn test_find_package_name_from_pkgid() { 337 | assert_eq!(find_package_name_from_pkgid("foo"), "foo"); 338 | assert_eq!(find_package_name_from_pkgid("foo:1.2.3"), "foo"); 339 | assert_eq!(find_package_name_from_pkgid("crates.io/foo"), "foo"); 340 | assert_eq!(find_package_name_from_pkgid("crates.io/foo#1.2.3"), "foo"); 341 | assert_eq!( 342 | find_package_name_from_pkgid("crates.io/bar#foo:1.2.3"), 343 | "foo" 344 | ); 345 | assert_eq!( 346 | find_package_name_from_pkgid("http://crates.io/foo#1.2.3"), 347 | "foo" 348 | ); 349 | assert_eq!( 350 | find_package_name_from_pkgid("file:///path/to/cargo-kcov#0.2.0"), 351 | "cargo_kcov" 352 | ); 353 | assert_eq!( 354 | find_package_name_from_pkgid("file:///path/to/cargo-kcov/specimen#cargo-kcov-test:0.0.1"), 355 | "cargo_kcov_test" 356 | ); 357 | } 358 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.6" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "ansi_term" 13 | version = "0.11.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "arrayref" 21 | version = "0.3.5" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "arrayvec" 26 | version = "0.5.1" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "atty" 31 | version = "0.2.13" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "backtrace" 40 | version = "0.3.40" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "backtrace-sys" 51 | version = "0.1.32" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "base64" 60 | version = "0.10.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "bitflags" 68 | version = "1.2.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | 71 | [[package]] 72 | name = "blake2b_simd" 73 | version = "0.5.9" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | dependencies = [ 76 | "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 77 | "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "byteorder" 83 | version = "1.3.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "cargo-kcov" 88 | version = "0.5.4" 89 | dependencies = [ 90 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "open 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "rquery 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 96 | "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 98 | ] 99 | 100 | [[package]] 101 | name = "cc" 102 | version = "1.0.48" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | 105 | [[package]] 106 | name = "cfg-if" 107 | version = "0.1.10" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | 110 | [[package]] 111 | name = "clap" 112 | version = "2.33.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "cloudabi" 126 | version = "0.0.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | dependencies = [ 129 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 130 | ] 131 | 132 | [[package]] 133 | name = "constant_time_eq" 134 | version = "0.1.4" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | 137 | [[package]] 138 | name = "crossbeam-utils" 139 | version = "0.6.6" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | dependencies = [ 142 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 144 | ] 145 | 146 | [[package]] 147 | name = "dirs" 148 | version = "2.0.2" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | dependencies = [ 151 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 153 | ] 154 | 155 | [[package]] 156 | name = "dirs-sys" 157 | version = "0.3.4" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | dependencies = [ 160 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 161 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 164 | ] 165 | 166 | [[package]] 167 | name = "failure" 168 | version = "0.1.6" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | dependencies = [ 171 | "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 173 | ] 174 | 175 | [[package]] 176 | name = "failure_derive" 177 | version = "0.1.6" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | dependencies = [ 180 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "fuchsia-cprng" 188 | version = "0.1.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | 191 | [[package]] 192 | name = "itoa" 193 | version = "0.4.4" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | 196 | [[package]] 197 | name = "lazy_static" 198 | version = "1.4.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | 201 | [[package]] 202 | name = "libc" 203 | version = "0.2.66" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | 206 | [[package]] 207 | name = "memchr" 208 | version = "2.2.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | 211 | [[package]] 212 | name = "open" 213 | version = "1.3.2" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | dependencies = [ 216 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 217 | ] 218 | 219 | [[package]] 220 | name = "proc-macro2" 221 | version = "1.0.6" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | dependencies = [ 224 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 225 | ] 226 | 227 | [[package]] 228 | name = "quote" 229 | version = "1.0.2" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | dependencies = [ 232 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 233 | ] 234 | 235 | [[package]] 236 | name = "rand" 237 | version = "0.4.6" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 242 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 243 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 244 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 245 | ] 246 | 247 | [[package]] 248 | name = "rand_core" 249 | version = "0.3.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | dependencies = [ 252 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 253 | ] 254 | 255 | [[package]] 256 | name = "rand_core" 257 | version = "0.4.2" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | 260 | [[package]] 261 | name = "rand_os" 262 | version = "0.1.3" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | dependencies = [ 265 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 266 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 271 | ] 272 | 273 | [[package]] 274 | name = "rdrand" 275 | version = "0.4.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | dependencies = [ 278 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 279 | ] 280 | 281 | [[package]] 282 | name = "redox_syscall" 283 | version = "0.1.56" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | 286 | [[package]] 287 | name = "redox_users" 288 | version = "0.3.1" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | dependencies = [ 291 | "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 292 | "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 295 | ] 296 | 297 | [[package]] 298 | name = "regex" 299 | version = "1.3.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | dependencies = [ 302 | "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", 303 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 304 | "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 305 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 306 | ] 307 | 308 | [[package]] 309 | name = "regex-syntax" 310 | version = "0.6.12" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | 313 | [[package]] 314 | name = "remove_dir_all" 315 | version = "0.5.2" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | dependencies = [ 318 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 319 | ] 320 | 321 | [[package]] 322 | name = "rquery" 323 | version = "0.4.1" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | dependencies = [ 326 | "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 327 | ] 328 | 329 | [[package]] 330 | name = "rust-argon2" 331 | version = "0.5.1" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | dependencies = [ 334 | "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 335 | "blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", 336 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 337 | ] 338 | 339 | [[package]] 340 | name = "rustc-demangle" 341 | version = "0.1.16" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | 344 | [[package]] 345 | name = "ryu" 346 | version = "1.0.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | 349 | [[package]] 350 | name = "serde" 351 | version = "1.0.104" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | 354 | [[package]] 355 | name = "serde_json" 356 | version = "1.0.44" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 362 | ] 363 | 364 | [[package]] 365 | name = "shlex" 366 | version = "0.1.1" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | 369 | [[package]] 370 | name = "strsim" 371 | version = "0.8.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | 374 | [[package]] 375 | name = "syn" 376 | version = "1.0.11" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | dependencies = [ 379 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 380 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 381 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 382 | ] 383 | 384 | [[package]] 385 | name = "synstructure" 386 | version = "0.12.3" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | dependencies = [ 389 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 391 | "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 392 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 393 | ] 394 | 395 | [[package]] 396 | name = "tempdir" 397 | version = "0.3.7" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | dependencies = [ 400 | "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 401 | "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 402 | ] 403 | 404 | [[package]] 405 | name = "term" 406 | version = "0.6.1" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | dependencies = [ 409 | "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 410 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 411 | ] 412 | 413 | [[package]] 414 | name = "textwrap" 415 | version = "0.11.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | dependencies = [ 418 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 419 | ] 420 | 421 | [[package]] 422 | name = "thread_local" 423 | version = "0.3.6" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | dependencies = [ 426 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 427 | ] 428 | 429 | [[package]] 430 | name = "unicode-width" 431 | version = "0.1.7" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | 434 | [[package]] 435 | name = "unicode-xid" 436 | version = "0.2.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | 439 | [[package]] 440 | name = "vec_map" 441 | version = "0.8.1" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | 444 | [[package]] 445 | name = "winapi" 446 | version = "0.3.8" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | dependencies = [ 449 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 450 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 451 | ] 452 | 453 | [[package]] 454 | name = "winapi-i686-pc-windows-gnu" 455 | version = "0.4.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | 458 | [[package]] 459 | name = "winapi-x86_64-pc-windows-gnu" 460 | version = "0.4.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | 463 | [[package]] 464 | name = "xml-rs" 465 | version = "0.7.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | dependencies = [ 468 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 469 | ] 470 | 471 | [metadata] 472 | "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" 473 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 474 | "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" 475 | "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 476 | "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 477 | "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" 478 | "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" 479 | "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 480 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 481 | "checksum blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0" 482 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 483 | "checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" 484 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 485 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 486 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 487 | "checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" 488 | "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 489 | "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 490 | "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" 491 | "checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" 492 | "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" 493 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 494 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 495 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 496 | "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 497 | "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 498 | "checksum open 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "94b424e1086328b0df10235c6ff47be63708071881bead9e76997d9291c0134b" 499 | "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" 500 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 501 | "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 502 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 503 | "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 504 | "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 505 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 506 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 507 | "checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" 508 | "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" 509 | "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" 510 | "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 511 | "checksum rquery 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "57858adeb2de02d51fbde7181f62a3982ff89b7476d1531747acf05bf523cd52" 512 | "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" 513 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 514 | "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 515 | "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" 516 | "checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" 517 | "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 518 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 519 | "checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" 520 | "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 521 | "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 522 | "checksum term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" 523 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 524 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 525 | "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 526 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 527 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 528 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 529 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 530 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 531 | "checksum xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2" 532 | --------------------------------------------------------------------------------