#![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)]
use bashrs::make_parser::{parse_makefile, MakeItem};
#[test]
fn integration_simple_rust_project_makefile() {
let makefile = r#"
# Rust project Makefile
CARGO = cargo
build:
$(CARGO) build --release
test:
@$(CARGO) test
clean:
$(CARGO) clean
.PHONY: build test clean
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok(), "Failed to parse simple Rust Makefile");
let ast = result.unwrap();
let targets: Vec<_> = ast
.items
.iter()
.filter(|item| matches!(item, MakeItem::Target { .. }))
.collect();
assert_eq!(
targets.len(),
4,
"Expected 4 targets (build, test, clean, .PHONY)"
);
}
#[test]
fn integration_makefile_with_line_continuations() {
let makefile = r#"
FILES = src/main.rs \
src/lib.rs \
src/parser.rs
build: $(FILES)
cargo build
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok());
let ast = result.unwrap();
let var = ast
.items
.iter()
.find(|item| matches!(item, MakeItem::Variable { name, .. } if name == "FILES"))
.expect("FILES variable not found");
if let MakeItem::Variable { value, .. } = var {
assert!(value.contains("src/main.rs"));
assert!(value.contains("src/lib.rs"));
assert!(value.contains("src/parser.rs"));
}
}
#[test]
fn integration_makefile_with_all_variable_flavors() {
let makefile = r#"
# All variable flavors
RECURSIVE = $(shell date)
SIMPLE := immediate
CONDITIONAL ?= default
APPEND += more
SHELL != echo "shell"
test:
@echo "Testing variables"
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok());
let ast = result.unwrap();
let var_count = ast
.items
.iter()
.filter(|item| matches!(item, MakeItem::Variable { .. }))
.count();
assert_eq!(var_count, 5, "Expected 5 variables (one per flavor)");
}
#[test]
fn integration_makefile_with_silent_recipes() {
let makefile = r#"
build:
cargo build --release
@echo "Build complete"
@echo "Running tests..."
cargo test
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok());
let ast = result.unwrap();
if let MakeItem::Target { recipe, .. } = &ast.items[0] {
assert_eq!(recipe.len(), 4);
assert!(
!recipe[0].starts_with('@'),
"First recipe should not be silent"
);
assert!(recipe[1].starts_with('@'), "Second recipe should be silent");
assert!(recipe[2].starts_with('@'), "Third recipe should be silent");
assert!(
!recipe[3].starts_with('@'),
"Fourth recipe should not be silent"
);
} else {
panic!("Expected Target");
}
}
#[test]
fn integration_complex_prerequisite_chains() {
let makefile = r#"
all: build test
build: compile link
compile: main.o lib.o
link:
ld -o program *.o
main.o: main.c
cc -c main.c
lib.o: lib.c
cc -c lib.c
test: build
./program --test
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok());
let ast = result.unwrap();
let all_target = ast
.items
.iter()
.find(|item| matches!(item, MakeItem::Target { name, .. } if name == "all"))
.expect("'all' target not found");
if let MakeItem::Target { prerequisites, .. } = all_target {
assert_eq!(prerequisites.len(), 2);
assert_eq!(prerequisites[0], "build");
assert_eq!(prerequisites[1], "test");
}
}
#[test]
fn integration_gnu_make_manual_example_1() {
let makefile = r#"
edit : main.o kbd.o command.o display.o
cc -o edit main.o kbd.o command.o display.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
clean :
rm edit main.o kbd.o command.o display.o
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok());
let ast = result.unwrap();
let target_count = ast
.items
.iter()
.filter(|item| matches!(item, MakeItem::Target { .. }))
.count();
assert_eq!(target_count, 4);
}
#[test]
fn integration_makefile_with_comments_everywhere() {
let makefile = r#"
# Header comment
# Another header comment
# Variable comment
VAR = value # inline comment not supported by parser yet
# Target comment
target: # prerequisite comment
# Recipe comment (tab-indented, should be ignored)
echo "command"
# Another recipe comment
# Footer comment
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok());
}
#[test]
fn integration_makefile_with_empty_lines() {
let makefile = r#"
VAR = value
target:
echo "command"
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok());
let ast = result.unwrap();
assert_eq!(ast.items.len(), 2); }
#[test]
fn integration_makefile_with_special_targets() {
let makefile = r#"
.PHONY: all clean test
.SUFFIXES:
.DEFAULT_GOAL := all
all: build
build:
cargo build
"#;
let result = parse_makefile(makefile);
assert!(result.is_ok());
let ast = result.unwrap();
let phony_found = ast
.items
.iter()
.any(|item| matches!(item, MakeItem::Target { name, .. } if name == ".PHONY"));
assert!(phony_found, ".PHONY target should be parsed");
}
#[test]
fn integration_large_makefile_performance() {
let mut makefile = String::from("# Large Makefile\n\n");
for i in 0..1000 {
makefile.push_str(&format!("VAR_{} = value_{}\n", i, i));
}
for i in 0..1000 {
makefile.push_str(&format!("target_{}:\n\techo {}\n\n", i, i));
}
let start = std::time::Instant::now();
let result = parse_makefile(&makefile);
let duration = start.elapsed();
assert!(result.is_ok(), "Failed to parse large Makefile");
assert!(
duration.as_millis() < 500, "Parsing took too long: {:?}",
duration
);
let ast = result.unwrap();
assert!(ast.items.len() >= 2000); }