#![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)]
use bashrs::linter::rules::lint_makefile;
#[test]
fn test_sc2133_makefile_arithmetic_with_dollar_dollar() {
let makefile = r#"
target:
@CORES=$$(nproc) && THREADS=$$((CORES > 2 ? CORES - 2 : 1))
"#;
let result = lint_makefile(makefile);
let sc2133_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2133")
.collect();
assert_eq!(
sc2133_errors.len(),
0,
"Expected 0 SC2133 errors, got {}: {:?}",
sc2133_errors.len(),
sc2133_errors
);
}
#[test]
fn test_sc2133_makefile_swap_arithmetic() {
let makefile = r#"
check-resources:
@SWAP_USED=$$(free | grep Swap | awk '{print $$3}')
@SWAP_TOTAL=$$(free | grep Swap | awk '{print $$2}')
@if [ $$((SWAP_USED * 100 / SWAP_TOTAL)) -gt 80 ]; then echo "High swap"; fi
"#;
let result = lint_makefile(makefile);
let sc2133_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2133")
.collect();
assert_eq!(
sc2133_errors.len(),
0,
"Expected 0 SC2133 errors for swap arithmetic, got {}: {:?}",
sc2133_errors.len(),
sc2133_errors
);
}
#[test]
fn test_sc2168_makefile_local_in_recipe() {
let makefile = r#"
test:
@sh -c 'local foo=bar; echo $$foo'
"#;
let result = lint_makefile(makefile);
let sc2168_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2168")
.collect();
println!("SC2168 errors: {}", sc2168_errors.len());
}
#[test]
fn test_sc2299_makefile_parameter_expansion() {
let makefile = r#"
extract:
@VALUE=$$(echo "test")
@RESULT=$${VALUE:0:4}
"#;
let result = lint_makefile(makefile);
let sc2299_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2299")
.collect();
println!("SC2299 errors: {}", sc2299_errors.len());
}
#[test]
fn test_det002_makefile_timestamp_not_error() {
let makefile = r#"
build:
@echo "$$(date +%s)" > timestamp.txt
"#;
let result = lint_makefile(makefile);
let det002_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "DET002")
.collect();
assert_eq!(
det002_errors.len(),
0,
"Expected 0 DET002 errors (timestamps OK in Makefiles), got {}: {:?}",
det002_errors.len(),
det002_errors
);
}
#[test]
fn test_preprocessing_preserves_make_variables() {
let makefile = r#"
PROJECT := myproject
build:
@echo "Building $(PROJECT)"
@CORES=$$(nproc)
@echo "Using $$CORES cores"
"#;
let result = lint_makefile(makefile);
let all_errors: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.severity == bashrs::linter::Severity::Error)
.collect();
println!("Total errors: {}", all_errors.len());
for error in &all_errors {
println!(" - {}: {}", error.code, error.message);
}
}
#[test]
fn test_all_false_positives_fixed() {
let makefile = r#"
PROJECT := paiml-mcp-agent-toolkit
# Target with arithmetic using $$
performance-test:
@CORES=$$(nproc) && THREADS=$$((CORES > 2 ? CORES - 2 : 1))
@echo "Using $$THREADS threads"
# Target with swap arithmetic
check-resources:
@SWAP_USED=$$(free | grep Swap | awk '{print $$3}')
@SWAP_TOTAL=$$(free | grep Swap | awk '{print $$2}')
@if [ $$((SWAP_USED * 100 / SWAP_TOTAL)) -gt 80 ]; then echo "High swap"; fi
# Target with local in sh -c
test-local:
@sh -c 'local foo=bar; echo $$foo'
# Target with timestamp (intentional for builds)
build-timestamp:
@echo "$$(date +%s)" > build-timestamp.txt
"#;
let result = lint_makefile(makefile);
let sc2133_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2133")
.count();
let sc2168_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2168")
.count();
let sc2299_count = result
.diagnostics
.iter()
.filter(|d| d.code == "SC2299")
.count();
let det002_count = result
.diagnostics
.iter()
.filter(|d| d.code == "DET002")
.count();
println!("Error counts:");
println!(" SC2133: {} (expected: 0)", sc2133_count);
println!(
" SC2168: {} (expected: 0-1, sh -c edge case)",
sc2168_count
);
println!(" SC2299: {} (expected: 0)", sc2299_count);
println!(" DET002: {} (expected: 0)", det002_count);
assert_eq!(sc2133_count, 0, "SC2133 false positives not fixed");
assert_eq!(det002_count, 0, "DET002 should not run on Makefiles");
assert_eq!(sc2299_count, 0, "SC2299 false positives not fixed");
assert!(
sc2168_count <= 1,
"SC2168 should have at most 1 edge case, got {}",
sc2168_count
);
let total_errors = result
.diagnostics
.iter()
.filter(|d| d.severity == bashrs::linter::Severity::Error)
.count();
println!("Total errors: {} (was 9 before fix)", total_errors);
assert!(
total_errors <= 2,
"Expected at most 2 errors (from 9), got {}",
total_errors
);
}