use super::{run_dead_code_analysis_with_filters, DeadCodeAnalysisFilters};
use std::time::{Duration, Instant};
fn filters(min_dead_lines: usize) -> DeadCodeAnalysisFilters {
DeadCodeAnalysisFilters {
include_unreachable: false,
include_tests: false,
min_dead_lines,
top_files: None,
include: Vec::new(),
exclude: Vec::new(),
max_depth: 10,
}
}
fn write_crate(root: &std::path::Path, name: &str, lib_rs: &str, build_rs: Option<&str>) {
std::fs::create_dir_all(root.join("src")).expect("src dir");
let build_line = if build_rs.is_some() {
"build=\"build.rs\"\n"
} else {
""
};
std::fs::write(
root.join("Cargo.toml"),
format!("[package]\nname=\"{name}\"\nversion=\"0.1.0\"\nedition=\"2021\"\n{build_line}"),
)
.expect("Cargo.toml");
std::fs::write(root.join("src/lib.rs"), lib_rs).expect("lib.rs");
if let Some(build) = build_rs {
std::fs::write(root.join("build.rs"), build).expect("build.rs");
}
}
#[tokio::test]
async fn a_suppressed_function_is_counted_and_typed_as_a_function() {
let tmp = tempfile::TempDir::new().expect("tempdir");
let root = tmp.path();
let allow = format!("#[allow({})]", "dead_code");
write_crate(
root,
"suppressed_fn_crate",
&format!(
"{allow}\nfn admitted_dead(x: i32) -> i32 {{ x + 1 }}\npub fn used() -> i32 {{ 1 }}\n"
),
None,
);
let outcome = run_dead_code_analysis_with_filters(root, filters(0), Duration::from_secs(120))
.await
.expect("analysis runs");
let summary = &outcome.report.summary;
let items: Vec<_> = outcome
.report
.files
.iter()
.flat_map(|f| f.items.iter())
.collect();
assert_eq!(
items.len(),
1,
"expected the one suppressed item: {:?}",
outcome.report.files
);
assert_eq!(
items[0].item_type,
crate::models::dead_code::DeadCodeType::Function,
"an item whose reason says `fn` was typed {:?}",
items[0].item_type
);
assert_eq!(
summary.dead_functions, 1,
"the summary counts 0 dead functions over a listed dead function: {summary:?}"
);
assert!(
summary.dead_functions + summary.dead_classes + summary.dead_modules > 0,
"{} dead lines in {} files, and every category counter is 0: {summary:?}",
summary.total_dead_lines,
summary.files_with_dead_code
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_cargo_check_that_outruns_the_budget_is_killed_and_reported() {
let tmp = tempfile::TempDir::new().expect("tempdir");
let root = tmp.path();
write_crate(
root,
"slowcheck_crate",
"fn dead_one() -> i32 { 1 }\npub fn used() -> i32 { 2 }\n",
Some("fn main() { std::thread::sleep(std::time::Duration::from_secs(20)); }\n"),
);
let started = Instant::now();
let result =
run_dead_code_analysis_with_filters(root, filters(0), Duration::from_secs(1)).await;
let elapsed = started.elapsed();
let error = result
.err()
.unwrap_or_else(|| panic!("--timeout 1 ran the 20s check to completion in {elapsed:?}"));
assert!(
error.to_string().contains("timed out after 1 seconds"),
"unexpected error: {error}"
);
assert!(
elapsed < Duration::from_secs(15),
"the budget was not enforced: {elapsed:?}"
);
}
#[test]
fn every_dead_code_kind_maps_to_a_type_that_names_it() {
use crate::models::dead_code::DeadCodeType;
use crate::services::cargo_dead_code_analyzer::{DeadCodeKind, DeadItem};
let item = |kind: DeadCodeKind, message: &str| DeadItem {
name: "x".to_string(),
kind,
line: 1,
column: 1,
message: message.to_string(),
};
let cases = [
(
item(DeadCodeKind::Module, "module `x` is never used"),
DeadCodeType::Module,
),
(
item(
DeadCodeKind::Other("union".to_string()),
"union `x` is never used",
),
DeadCodeType::Other,
),
(
item(DeadCodeKind::Constant, "constant `x` is never used"),
DeadCodeType::Variable,
),
(
item(DeadCodeKind::Function, "function `x` is never used"),
DeadCodeType::Function,
),
];
for (dead_item, expected) in cases {
let reason = dead_item.message.clone();
let reported = super::dead_items_to_report_items(std::slice::from_ref(&dead_item));
assert_eq!(
reported[0].item_type, expected,
"`{reason}` must not be reported as {:?}",
reported[0].item_type
);
}
}