use decy_core::{transpile, transpile_with_includes};
use std::fs;
use tempfile::TempDir;
fn create_test_project(files: &[(&str, &str)]) -> TempDir {
let temp = TempDir::new().expect("Failed to create temp dir");
for (filename, content) in files {
let file_path = temp.path().join(filename);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).expect("Failed to create parent dirs");
}
fs::write(&file_path, content).expect("Failed to write test file");
}
temp
}
#[test]
fn test_transpile_local_include_simple() {
let project = create_test_project(&[
("utils.h", "int add(int a, int b);"),
(
"main.c",
r#"
#include "utils.h"
int main() {
return add(1, 2);
}
"#,
),
]);
let main_path = project.path().join("main.c");
let c_code = fs::read_to_string(&main_path).expect("Failed to read main.c");
let rust_code = transpile_with_includes(&c_code, Some(project.path()))
.expect("Transpilation should succeed");
assert!(
rust_code.contains("fn add"),
"Should contain add function signature"
);
assert!(
rust_code.contains("fn main"),
"Should contain main function"
);
assert!(
rust_code.contains("add(1, 2)"),
"Should contain function call"
);
}
#[test]
fn test_transpile_multiple_includes() {
let project = create_test_project(&[
("math.h", "int add(int a, int b);"),
("string.h", "int strlen(char* s);"),
(
"main.c",
r#"
#include "math.h"
#include "string.h"
int main() {
int x = add(1, 2);
int len = strlen("hello");
return x + len;
}
"#,
),
]);
let main_path = project.path().join("main.c");
let c_code = fs::read_to_string(&main_path).expect("Failed to read main.c");
let rust_code = transpile_with_includes(&c_code, Some(project.path()))
.expect("Transpilation should succeed");
assert!(rust_code.contains("fn add"), "Should contain add function");
assert!(
rust_code.contains("fn strlen"),
"Should contain strlen function"
);
assert!(
rust_code.contains("fn main"),
"Should contain main function"
);
}
#[test]
fn test_transpile_nested_includes() {
let project = create_test_project(&[
("b.h", "typedef int number_t;"),
(
"a.h",
r#"
#include "b.h"
int process(number_t x);
"#,
),
(
"main.c",
r#"
#include "a.h"
int main() {
return process(42);
}
"#,
),
]);
let main_path = project.path().join("main.c");
let c_code = fs::read_to_string(&main_path).expect("Failed to read main.c");
let rust_code = transpile_with_includes(&c_code, Some(project.path()))
.expect("Transpilation should succeed");
assert!(
rust_code.contains("number_t") || rust_code.contains("i32"),
"Should contain typedef or resolved type"
);
assert!(
rust_code.contains("fn process"),
"Should contain process function"
);
}
#[test]
fn test_transpile_relative_include_path() {
let project = create_test_project(&[
("include/utils.h", "int helper(int x);"),
(
"src/main.c",
r#"
#include "../include/utils.h"
int main() {
return helper(10);
}
"#,
),
]);
let main_path = project.path().join("src/main.c");
let c_code = fs::read_to_string(&main_path).expect("Failed to read main.c");
let src_dir = main_path.parent().unwrap();
let rust_code =
transpile_with_includes(&c_code, Some(src_dir)).expect("Transpilation should succeed");
assert!(
rust_code.contains("fn helper"),
"Should resolve relative include path"
);
}
#[test]
fn test_transpile_header_guards_prevent_duplicate_parsing() {
let project = create_test_project(&[
(
"common.h",
r#"
#ifndef COMMON_H
#define COMMON_H
typedef int value_t;
#endif
"#,
),
(
"a.h",
r#"
#include "common.h"
int func_a(value_t x);
"#,
),
(
"b.h",
r#"
#include "common.h"
int func_b(value_t x);
"#,
),
(
"main.c",
r#"
#include "a.h"
#include "b.h"
int main() {
return func_a(1) + func_b(2);
}
"#,
),
]);
let main_path = project.path().join("main.c");
let c_code = fs::read_to_string(&main_path).expect("Failed to read main.c");
let rust_code = transpile_with_includes(&c_code, Some(project.path()))
.expect("Transpilation should succeed");
let typedef_count = rust_code.matches("type value_t").count();
assert!(
typedef_count <= 1,
"Should not duplicate typedef due to header guards"
);
assert!(rust_code.contains("fn func_a"), "Should contain func_a");
assert!(rust_code.contains("fn func_b"), "Should contain func_b");
}
#[test]
fn test_transpile_missing_include_file_error() {
let c_code = r#"
#include "nonexistent.h"
int main() {
return 0;
}
"#;
let result = transpile_with_includes(c_code, None);
assert!(result.is_err(), "Should error on missing include file");
let error_msg = format!("{:?}", result.unwrap_err());
assert!(
error_msg.contains("nonexistent")
|| error_msg.contains("not found")
|| error_msg.contains("Failed to find"),
"Error should mention missing file"
);
}
#[test]
fn test_transpile_circular_dependency_detection() {
let project = create_test_project(&[
(
"a.h",
r#"
#ifndef A_H
#define A_H
#include "b.h"
int func_a();
#endif
"#,
),
(
"b.h",
r#"
#ifndef B_H
#define B_H
#include "a.h"
int func_b();
#endif
"#,
),
(
"main.c",
r#"
#include "a.h"
int main() {
return func_a();
}
"#,
),
]);
let main_path = project.path().join("main.c");
let c_code = fs::read_to_string(&main_path).expect("Failed to read main.c");
let result = transpile_with_includes(&c_code, Some(project.path()));
match result {
Err(e) => {
let error_msg = format!("{:?}", e);
assert!(
error_msg.contains("circular") || error_msg.contains("cycle"),
"If error, should mention circular dependency"
);
}
Ok(rust_code) => {
assert!(rust_code.contains("fn func_a"));
assert!(rust_code.contains("fn func_b"));
}
}
}
#[test]
fn test_transpile_cross_file_function_call() {
let project = create_test_project(&[
(
"utils.h",
r#"
int add(int a, int b) {
return a + b;
}
"#,
),
(
"main.c",
r#"
#include "utils.h"
int main() {
int result = add(10, 20);
return result;
}
"#,
),
]);
let main_path = project.path().join("main.c");
let c_code = fs::read_to_string(&main_path).expect("Failed to read main.c");
let rust_code = transpile_with_includes(&c_code, Some(project.path()))
.expect("Transpilation should succeed");
assert!(rust_code.contains("fn add"), "Should contain add function");
assert!(
rust_code.contains("a + b"),
"Should contain add implementation"
);
assert!(
rust_code.contains("fn main"),
"Should contain main function"
);
assert!(
rust_code.contains("add(10, 20)"),
"Should contain function call"
);
}
#[test]
fn test_transpile_system_include_placeholder() {
let c_code = r#"
#include <stdio.h>
int main() {
return 0;
}
"#;
let result = transpile(c_code);
assert!(
result.is_ok() || result.is_err(),
"Should handle system includes"
);
}