93fe7237e3
Neue Top-Level-API check_module(&Module) -> Vec<Diagnostic> in ailang-check, plus stabile Codes (unbound-var, type-mismatch, arity-mismatch, non-exhaustive-match, unknown-ctor-in-pattern, duplicate-def, …). CLI bekommt --json-Flag für maschinenlesbares Output, Exit 1 bei Errors. Text-Modus unverändert. E2E-Test check_json_unbound_var sichert das Format ab.
97 lines
3.4 KiB
Rust
97 lines
3.4 KiB
Rust
//! End-to-End-Test: lade Beispielmodul, kompiliere zu Binary, führe es aus.
|
|
//!
|
|
//! Dieses Test schützt die wichtigste Eigenschaft der gesamten Pipeline:
|
|
//! AST → typecheck → LLVM IR → clang → Binary → korrekter stdout.
|
|
|
|
use std::path::Path;
|
|
use std::process::Command;
|
|
|
|
fn ail_bin() -> &'static str {
|
|
env!("CARGO_BIN_EXE_ail")
|
|
}
|
|
|
|
fn build_and_run(example: &str) -> String {
|
|
// Workspace-Root liegt zwei Ebenen über dem Crate-Manifest.
|
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
|
let src = workspace.join("examples").join(example);
|
|
let tmp = std::env::temp_dir().join(format!(
|
|
"ailang_e2e_{}_{}",
|
|
example.replace('.', "_"),
|
|
std::process::id()
|
|
));
|
|
std::fs::create_dir_all(&tmp).unwrap();
|
|
let out = tmp.join("bin");
|
|
let status = Command::new(ail_bin())
|
|
.args(["build", src.to_str().unwrap(), "-o"])
|
|
.arg(&out)
|
|
.status()
|
|
.expect("ail build failed to run");
|
|
assert!(status.success(), "ail build failed for {example}");
|
|
let output = Command::new(&out).output().expect("execute binary");
|
|
assert!(
|
|
output.status.success(),
|
|
"binary {} exited non-zero",
|
|
out.display()
|
|
);
|
|
String::from_utf8(output.stdout).expect("stdout utf8")
|
|
}
|
|
|
|
#[test]
|
|
fn sum_1_to_10_is_55() {
|
|
let stdout = build_and_run("sum.ail.json");
|
|
assert_eq!(stdout.trim(), "55");
|
|
}
|
|
|
|
/// Schützt das Block-Tracking im Codegen: max3 hat verschachtelte `if`s,
|
|
/// und falsches phi-Block-Tracking würde hier zu falschem Ergebnis führen.
|
|
#[test]
|
|
fn max3_picks_largest() {
|
|
let stdout = build_and_run("max3.ail.json");
|
|
assert_eq!(stdout.trim(), "17");
|
|
}
|
|
|
|
#[test]
|
|
fn hello_world_str_lit() {
|
|
let stdout = build_and_run("hello.ail.json");
|
|
assert_eq!(stdout.trim(), "Hallo, AILang.");
|
|
}
|
|
|
|
/// Schützt ADT-Codegen + Match: rekursive Liste, sum_list via match auf Cons/Nil.
|
|
#[test]
|
|
fn list_sum_via_match() {
|
|
let stdout = build_and_run("list.ail.json");
|
|
assert_eq!(stdout.trim(), "42");
|
|
}
|
|
|
|
/// Schützt das `--json`-Diagnostic-Format für Tooling-Konsumenten.
|
|
/// `broken_unbound.ail.json` referenziert eine nicht-existente Variable;
|
|
/// erwartet wird Exit-Code 1 und mindestens ein Diagnostic mit
|
|
/// `severity == "error"` und `code == "unbound-var"`.
|
|
#[test]
|
|
fn check_json_unbound_var() {
|
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
|
let src = workspace.join("examples").join("broken_unbound.ail.json");
|
|
|
|
let output = Command::new(ail_bin())
|
|
.args(["check", src.to_str().unwrap(), "--json"])
|
|
.output()
|
|
.expect("ail check --json failed to run");
|
|
|
|
let code = output.status.code().expect("process terminated by signal");
|
|
assert_eq!(code, 1, "expected exit code 1, stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
|
|
let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
|
|
let diags: serde_json::Value =
|
|
serde_json::from_str(stdout.trim()).expect("stdout must be valid JSON");
|
|
let arr = diags.as_array().expect("diagnostics must be a JSON array");
|
|
assert!(
|
|
arr.iter().any(|d| {
|
|
d.get("severity").and_then(|v| v.as_str()) == Some("error")
|
|
&& d.get("code").and_then(|v| v.as_str()) == Some("unbound-var")
|
|
}),
|
|
"expected at least one error diagnostic with code unbound-var; got: {stdout}"
|
|
);
|
|
}
|