#[test]
fn test_FUNC_SHELL_002_analyze_detects_shell_find() {
use crate::make_parser::parse_makefile;
let makefile = "FILES := $(shell find src -name '*.c')";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
assert_eq!(issues.len(), 1);
assert_eq!(issues[0].rule, "NO_UNORDERED_FIND");
assert_eq!(issues[0].severity, IssueSeverity::High);
assert!(issues[0].message.contains("FILES"));
assert!(issues[0].suggestion.is_some());
}
#[test]
fn test_FUNC_SHELL_002_analyze_no_issues_clean_makefile() {
use crate::make_parser::parse_makefile;
let makefile = "FILES := src/a.c src/b.c";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
assert_eq!(issues.len(), 0);
}
#[test]
fn test_PHONY_002_analyze_detects_missing_phony() {
use crate::make_parser::parse_makefile;
let makefile = "test:\n\tcargo test";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
assert_eq!(issues.len(), 1);
assert_eq!(issues[0].rule, "AUTO_PHONY");
assert_eq!(issues[0].severity, IssueSeverity::High);
assert!(issues[0].message.contains("test"));
assert!(issues[0].suggestion.is_some());
assert!(issues[0]
.suggestion
.as_ref()
.unwrap()
.contains(".PHONY: test"));
}
#[test]
fn test_PHONY_002_analyze_no_issue_with_phony() {
use crate::make_parser::parse_makefile;
let makefile = ".PHONY: test\ntest:\n\tcargo test";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
let phony_issues: Vec<_> = issues.iter().filter(|i| i.rule == "AUTO_PHONY").collect();
assert_eq!(phony_issues.len(), 0);
}
#[test]
fn test_PHONY_002_analyze_multiple_missing_phony() {
use crate::make_parser::parse_makefile;
let makefile = r#"test:
cargo test
clean:
rm -f *.o
build:
cargo build"#;
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
let phony_issues: Vec<_> = issues.iter().filter(|i| i.rule == "AUTO_PHONY").collect();
assert_eq!(phony_issues.len(), 3);
}
#[test]
fn test_PHONY_002_analyze_file_target_no_issue() {
use crate::make_parser::parse_makefile;
let makefile = "main.o: main.c\n\tgcc -c main.c";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
let phony_issues: Vec<_> = issues.iter().filter(|i| i.rule == "AUTO_PHONY").collect();
assert_eq!(phony_issues.len(), 0);
}
#[test]
fn test_PHONY_002_analyze_mixed_targets() {
use crate::make_parser::parse_makefile;
let makefile = r#".PHONY: clean
clean:
rm -f *.o
main.o: main.c
gcc -c main.c
test:
cargo test"#;
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
let phony_issues: Vec<_> = issues.iter().filter(|i| i.rule == "AUTO_PHONY").collect();
assert_eq!(phony_issues.len(), 1);
assert!(phony_issues[0].message.contains("test"));
}
#[test]
fn test_FUNC_SHELL_003_detect_random_basic() {
assert!(detect_random("BUILD_ID := $RANDOM"));
}
#[test]
fn test_FUNC_SHELL_003_detect_double_dollar_random() {
assert!(detect_random("ID := $(shell echo $$RANDOM)"));
}
#[test]
fn test_FUNC_SHELL_003_no_false_positive() {
assert!(!detect_random("VERSION := 1.0.0"));
}
#[test]
fn test_FUNC_SHELL_003_detect_in_variable_context() {
let value = "SESSION_ID := $RANDOM";
assert!(detect_random(value));
}
#[test]
fn test_FUNC_SHELL_003_empty_string() {
assert!(!detect_random(""));
}
#[test]
fn test_FUNC_SHELL_003_random_text_not_variable() {
assert!(!detect_random("# Generate random numbers"));
}
#[test]
fn test_FUNC_SHELL_003_randomize_not_detected() {
assert!(!detect_random("randomize_data()"));
}
#[test]
fn test_FUNC_SHELL_003_multiple_randoms() {
assert!(detect_random("A=fixed B=$RANDOM"));
}
#[test]
fn test_FUNC_SHELL_003_case_sensitive() {
assert!(!detect_random("$random"));
}
#[test]
fn test_FUNC_SHELL_003_detect_both_variants() {
assert!(detect_random("$RANDOM"));
assert!(detect_random("$$RANDOM"));
}
#[test]
fn test_FUNC_SHELL_003_mut_contains_must_check_substring() {
assert!(detect_random("prefix $RANDOM suffix"));
}
#[test]
fn test_FUNC_SHELL_003_mut_exact_pattern() {
assert!(!detect_random("RANDOM_SEED := 42"));
}
#[test]
fn test_FUNC_SHELL_003_mut_non_empty_check() {
let result = detect_random("");
assert!(!result);
}
#[cfg(test)]
mod random_property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_FUNC_SHELL_003_any_string_no_panic(s in "\\PC*") {
let _ = detect_random(&s);
}
#[test]
fn prop_FUNC_SHELL_003_random_always_detected(
prefix in "[A-Z_]{3,10}"
) {
let input = format!("{} := $RANDOM", prefix);
prop_assert!(detect_random(&input));
}
#[test]
fn prop_FUNC_SHELL_003_double_dollar_random_always_detected(
prefix in "[A-Z_]{3,10}"
) {
let input = format!("{} := $$RANDOM", prefix);
prop_assert!(detect_random(&input));
}
#[test]
fn prop_FUNC_SHELL_003_no_dollar_never_detected(
s in "[^$]*"
) {
prop_assert!(!detect_random(&s));
}
#[test]
fn prop_FUNC_SHELL_003_deterministic(s in "\\PC*") {
let result1 = detect_random(&s);
let result2 = detect_random(&s);
prop_assert_eq!(result1, result2);
}
}
}
#[test]
fn test_FUNC_SHELL_003_analyze_detects_random() {
use crate::make_parser::parse_makefile;
let makefile = "BUILD_ID := $RANDOM";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
assert_eq!(issues.len(), 1);
assert_eq!(issues[0].rule, "NO_RANDOM");
assert_eq!(issues[0].severity, IssueSeverity::Critical);
assert!(issues[0].message.contains("BUILD_ID"));
assert!(issues[0].suggestion.is_some());
}
#[test]
fn test_FUNC_SHELL_003_analyze_detects_double_dollar_random() {
use crate::make_parser::parse_makefile;
let makefile = "SESSION := $(shell echo $$RANDOM)";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
assert_eq!(issues.len(), 1);
assert_eq!(issues[0].rule, "NO_RANDOM");
assert_eq!(issues[0].severity, IssueSeverity::Critical);
}
#[test]
fn test_FUNC_SHELL_003_analyze_no_issues_clean_makefile() {
use crate::make_parser::parse_makefile;
let makefile = "BUILD_ID := 42\nVERSION := 1.0.0";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
assert_eq!(issues.len(), 0);
}
#[test]
fn test_FUNC_SHELL_003_analyze_multiple_issues() {
use crate::make_parser::parse_makefile;
let makefile = r#"SESSION_ID := $RANDOM
BUILD_ID := $$RANDOM
VERSION := 1.0.0"#;
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
let random_issues: Vec<_> = issues.iter().filter(|i| i.rule == "NO_RANDOM").collect();
assert_eq!(random_issues.len(), 2);
assert!(random_issues[0].message.contains("SESSION_ID"));
assert!(random_issues[1].message.contains("BUILD_ID"));
}
#[test]
fn test_FUNC_SHELL_003_analyze_mixed_issues() {
use crate::make_parser::parse_makefile;
let makefile = r#"BUILD_ID := $RANDOM
SOURCES := $(wildcard *.c)"#;
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
assert_eq!(issues.len(), 2);
assert_eq!(issues[0].rule, "NO_RANDOM");
assert_eq!(issues[0].severity, IssueSeverity::Critical);
assert_eq!(issues[1].rule, "NO_WILDCARD");
assert_eq!(issues[1].severity, IssueSeverity::High);
}
#[test]
fn test_FUNC_SHELL_003_analyze_suggestion_format() {
use crate::make_parser::parse_makefile;
let makefile = "RANDOM_ID := $RANDOM";
let ast = parse_makefile(makefile).unwrap();
let issues = analyze_makefile(&ast);
assert_eq!(issues.len(), 1);
let suggestion = issues[0].suggestion.as_ref().unwrap();
assert!(suggestion.contains("RANDOM_ID"));
assert!(suggestion.contains(":="));
assert!(suggestion.contains("42"));
}