#![allow(dead_code)]
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::Mutex;
use std::time::{Duration, Instant};
use tempfile::TempDir;
use windjammer::compiler::build_project;
use windjammer::CompilationTarget;
const SUBPROCESS_TIMEOUT: Duration = Duration::from_secs(180);
fn run_with_timeout(mut cmd: Command, timeout: Duration) -> std::io::Result<Output> {
let mut child = cmd
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
let deadline = Instant::now() + timeout;
loop {
match child.try_wait()? {
Some(_status) => return child.wait_with_output(),
None => {
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
return Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
format!("subprocess timed out after {}s", timeout.as_secs()),
));
}
std::thread::sleep(Duration::from_millis(250));
}
}
}
}
static CARGO_LOCK: Mutex<()> = Mutex::new(());
static WJ_BUILD_LOCK: Mutex<()> = Mutex::new(());
pub fn run_wj_command<I, S>(args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
let _guard = WJ_BUILD_LOCK.lock().unwrap_or_else(|p| p.into_inner());
run_with_timeout(
{
let mut cmd = Command::new(wj_binary());
cmd.args(args);
cmd
},
SUBPROCESS_TIMEOUT,
)
.expect("run wj")
}
fn shared_cargo_target_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("wj_integration_verify")
}
pub fn cargo_check_generated(build_dir: &Path) {
let _guard = CARGO_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let shared_target = shared_cargo_target_dir();
let mut cmd = Command::new("cargo");
cmd.current_dir(build_dir)
.env("CARGO_TARGET_DIR", &shared_target)
.args(["check", "--quiet"]);
let output = run_with_timeout(cmd, SUBPROCESS_TIMEOUT)
.unwrap_or_else(|e| panic!("cargo check failed to run: {}", e));
assert!(
output.status.success(),
"cargo check failed in {}.\nstderr:\n{}",
build_dir.display(),
String::from_utf8_lossy(&output.stderr),
);
}
pub fn cargo_build_generated(build_dir: &Path) -> PathBuf {
let _guard = CARGO_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let shared_target = shared_cargo_target_dir();
let mut cmd = Command::new("cargo");
cmd.current_dir(build_dir)
.env("CARGO_TARGET_DIR", &shared_target)
.args(["build", "--quiet"]);
let output = run_with_timeout(cmd, SUBPROCESS_TIMEOUT)
.unwrap_or_else(|e| panic!("cargo build failed to run: {}", e));
assert!(
output.status.success(),
"cargo build failed in {}.\nstderr:\n{}",
build_dir.display(),
String::from_utf8_lossy(&output.stderr),
);
shared_target
}
pub fn compile_single(source: &str) -> String {
compile_single_result(source).unwrap_or_else(|e| panic!("Compilation failed:\n{}", e))
}
pub fn compile_single_result(source: &str) -> Result<String, String> {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join("test.wj");
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
build_project(&wj_file, &out_dir, CompilationTarget::Rust, false).map_err(|e| e.to_string())?;
fs::read_to_string(out_dir.join("test.rs"))
.map_err(|e| format!("Failed to read generated file: {}", e))
}
pub fn compile_with_external_sigs(
source: &str,
external_sigs: &windjammer::analyzer::SignatureRegistry,
) -> String {
use windjammer::analyzer::Analyzer;
use windjammer::codegen::rust::CodeGenerator;
use windjammer::lexer::Lexer;
use windjammer::parser::Parser;
let mut lexer = Lexer::new(source);
let tokens = lexer.tokenize_with_locations();
let parser = Box::leak(Box::new(Parser::new(tokens)));
let program = parser.parse().unwrap();
let mut analyzer = Analyzer::new();
let (analyzed_fns, registry, _) = analyzer
.analyze_program_with_global_signatures(&program, external_sigs)
.unwrap();
let mut codegen = CodeGenerator::new_for_module(registry, CompilationTarget::Rust);
codegen.generate_program(&program, &analyzed_fns)
}
pub fn compile_single_check(source: &str) -> (String, bool) {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join("test.wj");
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
let success = build_project(&wj_file, &out_dir, CompilationTarget::Rust, false).is_ok();
let generated = fs::read_to_string(out_dir.join("test.rs")).unwrap_or_default();
(generated, success)
}
pub fn compile_via_cli(source: &str) -> (bool, String, String) {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join("test.wj");
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
let output = Command::new(env!("CARGO_BIN_EXE_wj"))
.args([
"build",
wj_file.to_str().unwrap(),
"--output",
out_dir.to_str().unwrap(),
"--no-cargo",
])
.output()
.expect("Failed to run wj binary");
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
(output.status.success(), stdout, stderr)
}
pub fn compile_via_cli_exit(source: &str) -> (i32, String, String) {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join("test.wj");
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
let output = Command::new(env!("CARGO_BIN_EXE_wj"))
.args([
"build",
wj_file.to_str().unwrap(),
"--output",
out_dir.to_str().unwrap(),
"--no-cargo",
])
.output()
.expect("Failed to run wj binary");
let exit_code = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
(exit_code, stdout, stderr)
}
pub fn compile_via_cli_read(source: &str) -> (String, bool) {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join("test.wj");
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
let output = Command::new(env!("CARGO_BIN_EXE_wj"))
.args([
"build",
wj_file.to_str().unwrap(),
"--output",
out_dir.to_str().unwrap(),
"--no-cargo",
])
.output()
.expect("Failed to run wj binary");
let generated = fs::read_to_string(out_dir.join("test.rs")).unwrap_or_default();
(generated, output.status.success())
}
pub fn compile_via_cli_with_stderr(source: &str) -> (String, String) {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join("test.wj");
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
let output = Command::new(env!("CARGO_BIN_EXE_wj"))
.args([
"build",
wj_file.to_str().unwrap(),
"--output",
out_dir.to_str().unwrap(),
"--no-cargo",
])
.output()
.expect("Failed to run wj binary");
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let generated = fs::read_to_string(out_dir.join("test.rs")).unwrap_or_else(|_| {
panic!(
"Failed to read generated file. Compiler stderr:\n{}",
stderr
)
});
(generated, stderr)
}
pub fn compile_via_cli_full(source: &str) -> (String, String, String) {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join("test.wj");
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
let output = Command::new(env!("CARGO_BIN_EXE_wj"))
.args([
"build",
wj_file.to_str().unwrap(),
"--output",
out_dir.to_str().unwrap(),
"--no-cargo",
])
.output()
.expect("Failed to run wj binary");
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let generated = fs::read_to_string(out_dir.join("test.rs")).unwrap_or_default();
(generated, stdout, stderr)
}
pub fn compile_named(source: &str, filename: &str) -> String {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join(filename);
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
build_project(&wj_file, &out_dir, CompilationTarget::Rust, false)
.unwrap_or_else(|e| panic!("Compilation of {} failed:\n{}", filename, e));
let rs_name = filename.replace(".wj", ".rs");
fs::read_to_string(out_dir.join(&rs_name))
.unwrap_or_else(|e| panic!("Failed to read {}: {}", rs_name, e))
}
pub fn compile_named_check(source: &str, filename: &str) -> (String, bool) {
let tmp = TempDir::new().expect("tempdir");
let wj_file = tmp.path().join(filename);
fs::write(&wj_file, source).unwrap();
let out_dir = tmp.path().join("build");
let success = build_project(&wj_file, &out_dir, CompilationTarget::Rust, false).is_ok();
let rs_name = filename.replace(".wj", ".rs");
let generated = fs::read_to_string(out_dir.join(&rs_name)).unwrap_or_default();
(generated, success)
}
pub fn create_temp_project(files: &[(&str, &str)]) -> (TempDir, PathBuf) {
let tmp = TempDir::new().expect("tempdir");
let project = tmp.path().to_path_buf();
for (name, content) in files {
let path = project.join(name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&path, content).unwrap();
}
(tmp, project)
}
pub fn compile_project(files: &[(&str, &str)]) -> HashMap<String, String> {
compile_project_result(files).unwrap_or_else(|e| panic!("Project compilation failed:\n{}", e))
}
pub fn compile_project_result(files: &[(&str, &str)]) -> Result<HashMap<String, String>, String> {
let tmp = TempDir::new().expect("tempdir");
let src_dir = tmp.path().join("src");
let out_dir = tmp.path().join("build");
fs::create_dir_all(&src_dir).unwrap();
for (name, content) in files {
let path = src_dir.join(name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&path, content).unwrap();
}
build_project(&src_dir, &out_dir, CompilationTarget::Rust, false).map_err(|e| e.to_string())?;
let mut results = HashMap::new();
for (name, _) in files {
let rs_name = name.replace(".wj", ".rs");
if let Ok(content) = fs::read_to_string(out_dir.join(&rs_name)) {
results.insert(rs_name, content);
}
}
Ok(results)
}
pub fn compile_project_dir(files: &[(&str, &str)]) -> (HashMap<String, String>, bool) {
let tmp = TempDir::new().expect("tempdir");
let src_dir = tmp.path().join("src");
let out_dir = tmp.path().join("build");
fs::create_dir_all(&src_dir).unwrap();
for (name, content) in files {
let path = src_dir.join(name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&path, content).unwrap();
}
let output = Command::new(env!("CARGO_BIN_EXE_wj"))
.args([
"build",
"--output",
out_dir.to_str().unwrap(),
src_dir.to_str().unwrap(),
"--no-cargo",
])
.output()
.expect("Failed to run wj binary");
let mut results = HashMap::new();
if output.status.success() {
for (name, _) in files {
let rs_name = name.replace(".wj", ".rs");
if let Ok(content) = fs::read_to_string(out_dir.join(&rs_name)) {
results.insert(rs_name, content);
}
}
}
(results, output.status.success())
}
pub fn verify_rust_compiles(rust_code: &str) -> Result<(), String> {
let tmp = TempDir::new().expect("tempdir");
let rs_file = tmp.path().join("verify.rs");
fs::write(&rs_file, rust_code).unwrap();
let output = Command::new("rustc")
.arg("--edition=2021")
.arg("--crate-type=lib")
.arg("--emit=metadata")
.arg("-o")
.arg(tmp.path().join("verify.rmeta"))
.arg(&rs_file)
.output()
.map_err(|e| format!("failed to run rustc: {}", e))?;
if output.status.success() {
Ok(())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
pub fn verify_rust_compiles_with_deps(
rust_code: &str,
deps: &[(&str, &Path)],
) -> Result<(), String> {
let tmp = TempDir::new().expect("tempdir");
let rs_file = tmp.path().join("verify.rs");
fs::write(&rs_file, rust_code).unwrap();
let mut cmd = Command::new("rustc");
cmd.arg("--edition=2021")
.arg("--crate-type=lib")
.arg("--emit=metadata")
.arg("-o")
.arg(tmp.path().join("verify.rmeta"));
for (name, path) in deps {
cmd.arg("--extern")
.arg(format!("{}={}", name, path.display()));
}
cmd.arg(&rs_file);
let output = cmd
.output()
.map_err(|e| format!("failed to run rustc: {}", e))?;
if output.status.success() {
Ok(())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
pub fn compile_fixture(fixture_name: &str) -> Result<String, String> {
let fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join(format!("{}.wj", fixture_name));
let tmp = TempDir::new().expect("tempdir");
let out_dir = tmp.path().join("build");
build_project(&fixture_path, &out_dir, CompilationTarget::Rust, false)
.map_err(|e| e.to_string())?;
let rs_name = format!("{}.rs", fixture_name);
fs::read_to_string(out_dir.join(&rs_name))
.map_err(|e| format!("Failed to read generated {}: {}", rs_name, e))
}
pub fn path_to_toml_string(path: &Path) -> String {
let s = path.display().to_string();
let s = s.strip_prefix(r"\\?\").unwrap_or(&s);
s.replace('\\', "/")
}
pub fn wj_binary() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_wj"))
}