use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
fn temp_script(src: &str) -> std::path::PathBuf {
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
let name = format!("rustscript_trace_{}_{}.rs", std::process::id(), id);
let path = std::env::temp_dir().join(name);
std::fs::write(&path, src).unwrap();
path
}
fn run_fail(src: &str) -> String {
let path = temp_script(src);
let out = Command::new(env!("CARGO_BIN_EXE_rust"))
.arg(&path)
.env("RUSTSCRIPT_SKIP_CHECK", "1")
.output()
.expect("failed to launch rustscript");
std::fs::remove_file(&path).unwrap();
assert!(!out.status.success(), "script unexpectedly succeeded");
String::from_utf8_lossy(&out.stderr).into_owned()
}
#[test]
fn runtime_error_reports_line_and_backtrace() {
let err = run_fail(
r#"
fn helper(v: &Vec<i64>) -> i64 {
v[10]
}
fn main() {
let nums = vec![1, 2, 3];
println!("{}", helper(&nums));
}
"#,
);
assert!(
err.contains("index out of bounds: the len is 3 but the index is 10"),
"stderr was: {err}"
);
assert!(err.contains("panicked at"), "stderr was: {err}");
assert!(
err.contains("at helper (rustscript_trace"),
"stderr was: {err}"
);
assert!(err.contains(".rs:3)"), "stderr was: {err}");
assert!(
err.contains("at main (rustscript_trace"),
"stderr was: {err}"
);
assert!(err.contains(".rs:8)"), "stderr was: {err}");
}
#[test]
fn tokio_error_reports_line_and_backtrace() {
let err = run_fail(
r#"
async fn fetch(v: Vec<i64>) -> i64 {
v[99]
}
#[tokio::main]
async fn main() {
println!("{}", fetch(vec![1]).await);
}
"#,
);
assert!(
err.contains("index out of bounds: the len is 1 but the index is 99"),
"stderr was: {err}"
);
assert!(
err.contains("at fetch (rustscript_trace"),
"stderr was: {err}"
);
assert!(err.contains(".rs:3)"), "stderr was: {err}");
}
#[test]
fn unknown_method_error_names_the_struct() {
let err = run_fail(
r#"
struct Dog;
impl Dog {
fn speak(&self) -> String { "woof".to_string() }
}
fn main() {
let d = Dog;
println!("{}", d.speak());
d.fly();
}
"#,
);
assert!(
err.contains("`fly` on Dog is not implemented by the interpreter"),
"stderr was: {err}"
);
}
#[test]
fn stack_overflow_trace_is_capped() {
let err = run_fail(
r#"
fn down(n: i64) -> i64 {
down(n + 1)
}
fn main() {
println!("{}", down(0));
}
"#,
);
assert!(err.contains("stack overflow"), "stderr was: {err}");
assert!(err.contains("more frames"), "stderr was: {err}");
let lines = err.lines().count();
assert!(
lines < 25,
"trace should be capped, got {lines} lines:\n{err}"
);
}
#[test]
fn closure_error_traces_through_the_closure() {
let err = run_fail(
r#"
fn main() {
let vals = vec![1, 2, 3];
let bad = |i: i64| -> i64 { vals[i as usize + 10] };
println!("{}", bad(0));
}
"#,
);
assert!(err.contains("out of bounds"), "stderr was: {err}");
assert!(
err.contains("at <closure> (rustscript_trace"),
"stderr was: {err}"
);
assert!(err.contains(".rs:4)"), "stderr was: {err}");
}