use std::path::Path;
use std::process::Command;
use fallow_types::discover::FileId;
use crate::parse_source_to_module;
const DEEP: usize = 20_000;
const DEEP_SLOW: usize = 2_000;
const PROBE_THREAD_STACK: usize = 1 << 20;
const STACK_PROBE_ENV: &str = "FALLOW_DEEP_SHAPES_PROBE_CHILD";
const STACK_PROBE_TEST: &str = "tests::deep_shapes::deep_shapes_parse_without_a_stack_overflow";
fn deep_shapes() -> Vec<(&'static str, &'static str, String)> {
let r = |unit: &str| unit.repeat(DEEP);
let slow = |unit: &str| unit.repeat(DEEP_SLOW);
vec![
(
"generic",
"a.ts",
format!("export type T = {}B{};\n", r("A<"), r(">")),
),
(
"jsx",
"a.jsx",
format!("export const x = {}{};\n", r("<a>"), r("</a>")),
),
(
"class",
"a.js",
format!(
"export const x = {}class {{}}{};\n",
r("class extends "),
r(" {}")
),
),
(
"assign",
"a.js",
format!("export let a = 0;\n{}1;\n", r("a=")),
),
(
"for",
"a.js",
format!("export const x = 1;\n{};\n", r("for(;;)")),
),
(
"else-if",
"a.js",
format!("export const x = 1;\n{};\n", r("if(x);else ")),
),
(
"ternary",
"a.js",
format!("export const x = {}c;\n", r("a?b:")),
),
(
"arrow",
"a.js",
format!("export const x = {}1;\n", slow("()=>")),
),
(
"member",
"a.js",
format!("export const x = a{};\n", slow(".b")),
),
(
"optional",
"a.js",
format!("export const x = a{};\n", slow("?.b")),
),
(
"call",
"a.js",
format!("export const x = a{};\n", slow("()")),
),
(
"binary",
"a.js",
format!("export const x = a{};\n", r("+a")),
),
(
"logical",
"a.js",
format!("export const x = {}a;\n", r("a&&")),
),
]
}
#[test]
fn deep_shapes_parse_without_a_stack_overflow() {
if std::env::var_os(STACK_PROBE_ENV).is_some() {
run_deep_shapes();
return;
}
let current_exe = std::env::current_exe().expect("current test binary should be known");
let output = Command::new(current_exe)
.arg("--exact")
.arg(STACK_PROBE_TEST)
.arg("--nocapture")
.env(STACK_PROBE_ENV, "1")
.env_remove("RUST_MIN_STACK")
.output()
.expect("deep shape probe child should start");
assert!(
output.status.success(),
"deep shape probe child failed with status {:?}\nstdout:\n{}\nstderr:\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn run_deep_shapes() {
std::thread::Builder::new()
.stack_size(PROBE_THREAD_STACK)
.spawn(|| {
for (shape, path, source) in deep_shapes() {
let info = parse_source_to_module(FileId(0), Path::new(path), &source, 0, true);
assert!(!info.parse_panicked, "{shape}: the file must parse");
assert_eq!(info.exports.len(), 1, "{shape}: the export must be found");
}
})
.expect("probe thread should start")
.join()
.expect("every deep shape should parse");
}