#[cfg(feature = "slang")]
mod tests {
use std::collections::HashSet;
use assert_cmd::cargo;
fn run_script(args: &[&str]) -> String {
let mut full_args = vec!["-d", "tests/pickle", "script"];
full_args.extend(args);
let out = cargo::cargo_bin_cmd!()
.args(&full_args)
.output()
.expect("Failed to execute bender binary");
assert!(
out.status.success(),
"script command failed.\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout).expect("stdout must be utf-8")
}
fn run_script_failing(args: &[&str]) -> String {
let mut full_args = vec!["-d", "tests/pickle", "script"];
full_args.extend(args);
let out = cargo::cargo_bin_cmd!()
.args(&full_args)
.output()
.expect("Failed to execute bender binary");
assert!(
!out.status.success(),
"script command unexpectedly succeeded.\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stderr).expect("stderr must be utf-8")
}
fn basename(path: &str) -> &str {
match path.rfind(|c: char| c == '/' || c == '\\') {
Some(i) => &path[i + 1..],
None => path,
}
}
fn source_basenames(output: &str) -> HashSet<&str> {
output
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with("+incdir+") && !l.starts_with("+define+"))
.map(basename)
.collect()
}
fn incdir_basenames(output: &str) -> HashSet<&str> {
output
.lines()
.map(str::trim)
.filter_map(|l| l.strip_prefix("+incdir+"))
.map(basename)
.collect()
}
#[test]
fn script_top_filters_unreachable_files() {
let full_out = run_script(&["--target", "top", "flist-plus"]);
let full = source_basenames(&full_out);
assert!(full.contains("unused_top.sv"));
assert!(full.contains("unused_leaf.sv"));
let trimmed_out = run_script(&["--target", "top", "--top", "top", "flist-plus"]);
let trimmed = source_basenames(&trimmed_out);
assert!(trimmed.contains("top.sv"));
assert!(trimmed.contains("core.sv"));
assert!(trimmed.contains("leaf.sv"));
assert!(!trimmed.contains("unused_top.sv"));
assert!(!trimmed.contains("unused_leaf.sv"));
}
#[test]
fn script_top_multiple_tops() {
let out = run_script(&[
"--target",
"top",
"--top",
"top",
"--top",
"unused_top",
"flist-plus",
]);
let trimmed = source_basenames(&out);
assert!(trimmed.contains("top.sv"));
assert!(trimmed.contains("unused_top.sv"));
}
#[test]
fn script_top_empty_keeps_all_files() {
let out = run_script(&["--target", "top", "flist-plus"]);
let full = source_basenames(&out);
assert!(full.contains("top.sv"));
assert!(full.contains("core.sv"));
assert!(full.contains("leaf.sv"));
assert!(full.contains("unused_top.sv"));
assert!(full.contains("unused_leaf.sv"));
}
#[test]
fn script_trim_incdirs_auto() {
let full_out = run_script(&["--target", "top", "flist-plus"]);
let full_incs = incdir_basenames(&full_out);
assert!(full_incs.contains("include"));
assert!(full_incs.contains("include_unused"));
let trimmed_out = run_script(&["--target", "top", "--top", "top", "flist-plus"]);
let trimmed_incs = incdir_basenames(&trimmed_out);
assert!(
trimmed_incs.contains("include"),
"include/ should survive: {trimmed_incs:?}"
);
assert!(
!trimmed_incs.contains("include_unused"),
"include_unused/ should be dropped: {trimmed_incs:?}"
);
}
#[test]
fn script_trim_incdirs_always_without_top() {
let out = run_script(&["--target", "top", "--trim-incdirs", "always", "flist-plus"]);
let incs = incdir_basenames(&out);
assert!(incs.contains("include"));
assert!(
!incs.contains("include_unused"),
"include_unused/ should be dropped: {incs:?}"
);
let files = source_basenames(&out);
assert!(files.contains("top.sv"));
assert!(files.contains("unused_top.sv"));
assert!(files.contains("unused_leaf.sv"));
}
#[test]
fn script_trim_incdirs_never_with_top() {
let out = run_script(&[
"--target",
"top",
"--top",
"top",
"--trim-incdirs",
"never",
"flist-plus",
]);
let incs = incdir_basenames(&out);
assert!(incs.contains("include"));
assert!(
incs.contains("include_unused"),
"include_unused/ should be retained with --trim-incdirs never: {incs:?}"
);
let files = source_basenames(&out);
assert!(files.contains("top.sv"));
assert!(!files.contains("unused_top.sv"));
}
#[test]
fn script_top_keeps_encrypted_file() {
let out = run_script(&[
"--target",
"encrypted",
"--top",
"encrypted_top",
"flist-plus",
]);
let files = source_basenames(&out);
assert!(
files.contains("encrypted_top.sv"),
"top file missing: {files:?}"
);
assert!(
files.contains("encrypted_user.sv"),
"user of encrypted IP missing: {files:?}"
);
assert!(
files.contains("encrypted_ip.sv"),
"encrypted IP must be force-kept despite parse errors: {files:?}"
);
}
#[test]
fn script_encrypted_drop_excludes_encrypted_file() {
let out = run_script(&[
"--target",
"encrypted",
"--top",
"encrypted_top",
"--encrypted",
"drop",
"flist-plus",
]);
let files = source_basenames(&out);
assert!(files.contains("encrypted_top.sv"));
assert!(files.contains("encrypted_user.sv"));
assert!(
!files.contains("encrypted_ip.sv"),
"encrypted IP should be dropped by --encrypted drop: {files:?}"
);
}
#[test]
fn script_encrypted_error_aborts() {
let stderr = run_script_failing(&[
"--target",
"encrypted",
"--encrypted",
"error",
"flist-plus",
]);
assert!(
stderr.contains("encrypted file(s)") && stderr.contains("--encrypted error"),
"expected encrypted-lint abort message, got stderr:\n{stderr}"
);
}
#[test]
fn script_source_annotations_marks_unparseable() {
let out = run_script(&[
"--target",
"encrypted",
"--top",
"encrypted_top",
"--source-annotations",
"flist-plus",
]);
assert!(
out.contains("// UNPARSEABLE: slang reported parse errors"),
"expected UNPARSEABLE annotation in output:\n{out}"
);
}
#[test]
fn script_broken_file_fails_by_default() {
let stderr = run_script_failing(&["--target", "broken", "--top", "broken", "flist-plus"]);
assert!(
stderr.contains("looks like real syntax bugs"),
"expected real-bug error message, got stderr:\n{stderr}"
);
assert!(
stderr.contains("--broken keep") || stderr.contains("--broken drop"),
"error should point at the --broken keep/drop escape hatches:\n{stderr}"
);
}
#[test]
fn script_broken_keep_includes_broken_file() {
let out = run_script(&[
"--target",
"broken",
"--top",
"broken",
"--broken",
"keep",
"flist-plus",
]);
let files = source_basenames(&out);
assert!(
files.contains("broken.sv"),
"broken.sv should survive with --broken keep: {files:?}"
);
}
#[test]
fn script_broken_drop_excludes_broken_file() {
let out = run_script(&[
"--target",
"broken",
"--top",
"broken",
"--broken",
"drop",
"flist-plus",
]);
let files = source_basenames(&out);
assert!(
!files.contains("broken.sv"),
"broken.sv should be dropped with --broken drop: {files:?}"
);
}
#[test]
fn script_broken_drop_without_top_excludes_broken_file() {
let out = run_script(&["--target", "broken", "--broken", "drop", "flist-plus"]);
let files = source_basenames(&out);
assert!(
!files.contains("broken.sv"),
"broken.sv should be dropped without --top when --broken drop is set: {files:?}"
);
}
#[test]
fn script_broken_keep_without_top_no_slang() {
let out = run_script(&["--target", "broken", "--broken", "keep", "flist-plus"]);
let files = source_basenames(&out);
assert!(
files.contains("broken.sv"),
"broken.sv should pass through with --broken keep: {files:?}"
);
}
#[test]
fn script_top_duplicate_module_name_last_wins() {
let full_out = run_script(&["--target", "dup", "flist-plus"]);
let full = source_basenames(&full_out);
assert!(full.contains("dup_a.sv"));
assert!(full.contains("dup_b.sv"));
assert!(full.contains("dup_top.sv"));
let trimmed_out = run_script(&["--target", "dup", "--top", "dup_top", "flist-plus"]);
let trimmed = source_basenames(&trimmed_out);
assert!(trimmed.contains("dup_top.sv"));
assert!(
trimmed.contains("dup_b.sv"),
"dup_b.sv (last-wins) missing: {trimmed:?}"
);
assert!(
!trimmed.contains("dup_a.sv"),
"dup_a.sv (overwritten) should be absent: {trimmed:?}"
);
}
}