#![allow(dead_code)]
use std::{
path::{Path, PathBuf},
process::{Command, Output},
sync::{Mutex, PoisonError},
};
use panicgraph::{
Artifact, Body, BuildConfig, CallSite, Category, EdgeKind, FuncKey, Graph,
Guard, PanicSite, StdMode, Termination, UnwindOrigin,
};
pub struct BodyBuilder {
body: Body,
}
impl BodyBuilder {
pub fn new(name: &str) -> Self {
Self {
body: Body {
key: FuncKey(name.to_owned()),
display: name.to_owned(),
krate: "test".to_owned(),
loc: None,
sites: Vec::new(),
calls: Vec::new(),
opaque: false,
foreign: false,
local: true,
from_tests: false,
},
}
}
pub fn panics(mut self, category: Category) -> Self {
self.body.sites.push(site(
category,
Termination::Unwind,
Guard::always(),
));
self
}
pub fn maybe_panics(self, category: Option<Category>) -> Self {
match category {
Some(category) => self.panics(category),
None => self,
}
}
pub fn calls(mut self, callee: &str) -> Self {
self.body.calls.push(call(callee, Guard::always()));
self
}
pub fn calls_candidate(mut self, callee: &str) -> Self {
let mut edge = call(callee, Guard::always());
edge.candidate = true;
edge.kind = EdgeKind::Vtable;
self.body.calls.push(edge);
self
}
pub fn calls_unresolved(mut self, kind: EdgeKind) -> Self {
let mut edge = call("<unresolved>", Guard::always());
edge.callee = None;
edge.kind = kind;
self.body.calls.push(edge);
self
}
pub fn calls_behind_barrier(mut self, callee: &str) -> Self {
let mut edge = call(callee, Guard::always());
edge.barrier = true;
self.body.calls.push(edge);
self
}
pub fn panics_without_leaving(mut self, category: Category) -> Self {
let mut raised = site(category, Termination::Unwind, Guard::always());
raised.terminates = true;
self.body.sites.push(raised);
self
}
pub fn calls_without_unwinding(mut self, callee: &str) -> Self {
let mut edge = call(callee, Guard::always());
edge.terminates = true;
self.body.calls.push(edge);
self
}
pub fn aborts(mut self, category: Category) -> Self {
self.body.sites.push(site(
category,
Termination::Abort,
Guard::always(),
));
self
}
pub fn calls_on_unwind_of(mut self, callee: &str, call_index: u32) -> Self {
self.body.calls.push(call(
callee,
Guard {
normal: false,
origins: vec![UnwindOrigin::Call(call_index)],
},
));
self
}
pub fn build(self) -> Body {
self.body
}
}
fn site(
category: Category,
termination: Termination,
guard: Guard,
) -> PanicSite {
PanicSite {
category,
termination,
reason: format!("{category} panic"),
sink: None,
loc: None,
guard,
certain: false,
terminates: false,
}
}
fn call(callee: &str, guard: Guard) -> CallSite {
CallSite {
callee: Some(FuncKey(callee.to_owned())),
callee_display: callee.to_owned(),
kind: EdgeKind::Static,
loc: None,
guard,
barrier: false,
terminates: false,
candidate: false,
sig: None,
self_ty: None,
}
}
pub fn artifact(bodies: Vec<Body>) -> Artifact {
Artifact {
reified: Vec::new(),
coerced: Vec::new(),
krate: "test".to_owned(),
source: None,
test: false,
config: BuildConfig {
rustc: "test".to_owned(),
profile: "release".to_owned(),
debug_assertions: false,
overflow_checks: false,
std_mode: StdMode::Shipped,
mir_opt_level: None,
},
bodies,
}
}
pub fn graph(bodies: Vec<Body>) -> Graph {
Graph::from_artifacts(vec![artifact(bodies)])
}
pub fn analyse_fixture(
profile: &str,
extra: &[&str],
) -> Vec<(String, Vec<String>)> {
findings(&analyse_fixture_json(profile, extra))
}
pub fn findings(report: &serde_json::Value) -> Vec<(String, Vec<String>)> {
let findings = report["findings"]
.as_array()
.expect("the report should list findings");
findings
.iter()
.map(|finding| {
let name = finding["function"].as_str().unwrap_or_default();
let categories = finding["categories"]
.as_array()
.map(|list| {
list.iter()
.filter_map(|c| c.as_str().map(str::to_owned))
.collect()
})
.unwrap_or_default();
(name.to_owned(), categories)
})
.collect()
}
pub fn run_on_fixture(args: &[&str]) -> Output {
run_on(&fixture_dir(), args)
}
pub fn run_on(dir: &Path, args: &[&str]) -> Output {
static TURN: Mutex<()> = Mutex::new(());
let _turn = TURN.lock().unwrap_or_else(PoisonError::into_inner);
let exe = PathBuf::from(env!("CARGO_BIN_EXE_panicgraph"));
Command::new(&exe)
.arg("--manifest-dir")
.arg(dir)
.args(args)
.output()
.expect("the front end should run")
}
pub fn analyse_fixture_json(
profile: &str,
extra: &[&str],
) -> serde_json::Value {
analyse_json(&fixture_dir(), profile, extra)
}
pub fn analyse_json(
dir: &Path,
profile: &str,
extra: &[&str],
) -> serde_json::Value {
let mut args = vec!["--profile", profile, "--suppress", "", "--json"];
args.extend_from_slice(extra);
let output = run_on(dir, &args);
serde_json::from_slice(&output.stdout).unwrap_or_else(|err| {
panic!(
"the report should be json: {err}\n{}",
String::from_utf8_lossy(&output.stderr)
)
})
}
pub fn fixture_dir() -> PathBuf {
fixture("known")
}
pub fn fixture(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join(name)
}