#![cfg(any(
not(any(
feature = "parser_tests",
feature = "analyzer_tests",
feature = "codegen_tests",
feature = "interpreter_tests",
feature = "conformance_tests",
feature = "integration_tests",
)),
feature = "analyzer_tests",
))]
#[path = "common/test_utils.rs"]
mod test_utils;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::tempdir;
fn find_generated_rs_under(root: &Path, name: &str) -> Option<PathBuf> {
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let read = fs::read_dir(&dir).ok()?;
for entry in read.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if path.file_name().and_then(|n| n.to_str()) == Some(name) {
return Some(path);
}
}
}
None
}
fn compile_should_succeed(code: &str, test_name: &str) {
match test_utils::compile_single_result(code) {
Ok(_) => println!("✓ {} passed", test_name),
Err(e) => panic!("✗ {} failed: {}", test_name, e),
}
}
fn compile_and_check_rust_compiles(wj_file: &str, test_name: &str) {
let wj_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join(wj_file);
let work = tempdir().expect("tempdir");
let output = Command::new(test_utils::wj_binary())
.args([
"build",
wj_path.to_str().unwrap(),
"-o",
work.path().to_str().unwrap(),
"--no-cargo",
])
.output()
.expect("Failed to execute compiler");
if !output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
panic!(
"✗ {} failed to compile Windjammer: {}{}",
test_name, stdout, stderr
);
}
let rust_file = wj_file.replace(".wj", ".rs");
let rs_name = Path::new(&rust_file)
.file_name()
.and_then(|n| n.to_str())
.expect("rs file name");
let rust_path = find_generated_rs_under(work.path(), rs_name).unwrap_or_else(|| {
panic!(
"✗ {}: no {} under {:?}",
test_name,
rs_name,
fs::read_dir(work.path())
.map(|d| d
.filter_map(|e| e.ok())
.map(|e| e.path())
.collect::<Vec<_>>())
.unwrap_or_default()
)
});
let rust_output = Command::new("rustc")
.arg("--crate-type=lib")
.arg("--emit=metadata")
.arg("--edition=2021")
.arg("-O")
.arg("-o")
.arg(work.path().join("verify.rmeta"))
.arg(rust_path.as_os_str())
.output()
.expect("Failed to execute rustc");
if !rust_output.status.success() {
let stdout = String::from_utf8_lossy(&rust_output.stdout);
let stderr = String::from_utf8_lossy(&rust_output.stderr);
panic!(
"✗ {} failed to compile Rust: {}{}",
test_name, stdout, stderr
);
}
println!("✓ {} passed (Windjammer + Rust compilation)", test_name);
}
fn compile_and_check_generated_rust(wj_file: &str, expected_imports: &[&str], test_name: &str) {
let wj_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join(wj_file);
let work = tempdir().expect("tempdir");
let output = Command::new(test_utils::wj_binary())
.args([
"build",
wj_path.to_str().unwrap(),
"-o",
work.path().to_str().unwrap(),
"--no-cargo",
])
.output()
.expect("Failed to execute compiler");
if !output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
panic!("✗ {} failed to compile: {}{}", test_name, stdout, stderr);
}
let rust_file = wj_file.replace(".wj", ".rs");
let rs_name = Path::new(&rust_file)
.file_name()
.and_then(|n| n.to_str())
.expect("rs file name");
let rust_path = find_generated_rs_under(work.path(), rs_name).unwrap_or_else(|| {
panic!(
"✗ {}: no {} under {:?}",
test_name,
rs_name,
fs::read_dir(work.path())
.map(|d| d
.filter_map(|e| e.ok())
.map(|e| e.path())
.collect::<Vec<_>>())
.unwrap_or_default()
)
});
let generated_rust = fs::read_to_string(&rust_path)
.unwrap_or_else(|_| panic!("Failed to read generated Rust file: {:?}", rust_path));
for expected_import in expected_imports {
if !generated_rust.contains(expected_import) {
panic!(
"✗ {} failed: Expected import '{}' not found in generated Rust:\n{}",
test_name, expected_import, generated_rust
);
}
}
println!("✓ {} passed", test_name);
}
fn compile_should_fail(code: &str, expected_error: &str, test_name: &str) {
match test_utils::compile_single_result(code) {
Ok(_) => panic!("✗ {} should have failed but succeeded", test_name),
Err(e) => {
if e.contains(expected_error) {
println!("✓ {} passed (correctly rejected)", test_name);
} else {
panic!(
"✗ {} failed with wrong error.\nExpected: {}\nGot: {}",
test_name, expected_error, e
);
}
}
}
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_tuple_enum_definition_single_field() {
let code = r#"
enum Option<T> {
Some(T),
None,
}
"#;
compile_should_succeed(code, "tuple_enum_single_field");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_tuple_enum_definition_multiple_fields() {
let code = r#"
enum Color {
Rgb(i32, i32, i32),
Rgba(i32, i32, i32, i32),
}
"#;
compile_should_succeed(code, "tuple_enum_multiple_fields");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_tuple_enum_definition_mixed() {
let code = r#"
enum Shape {
Circle(f32),
Rectangle(f32, f32),
Point,
}
"#;
compile_should_succeed(code, "tuple_enum_mixed");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_tuple_enum_match_single_binding() {
let code = r#"
enum Option<T> {
Some(T),
None,
}
fn unwrap(opt: Option<i32>) -> i32 {
match opt {
Option::Some(x) => { return x }
Option::None => { return 0 }
}
}
"#;
compile_should_succeed(code, "tuple_enum_match_single");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_tuple_enum_match_multiple_bindings() {
let code = r#"
enum Color {
Rgb(i32, i32, i32),
}
fn sum_rgb(color: Color) -> i32 {
match color {
Color::Rgb(r, g, b) => { return r + g + b }
}
}
"#;
compile_should_succeed(code, "tuple_enum_match_multiple");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_tuple_enum_match_wildcards() {
let code = r#"
enum Color {
Rgb(i32, i32, i32),
}
fn get_red(color: Color) -> i32 {
match color {
Color::Rgb(r, _, _) => { return r }
}
}
"#;
compile_should_succeed(code, "tuple_enum_match_wildcards");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_let_tuple_destructuring() {
let code = r#"
fn test() -> i32 {
let (x, y) = (10, 20)
x + y
}
"#;
compile_should_succeed(code, "let_tuple_destructuring");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_let_nested_tuple_destructuring() {
let code = r#"
fn test() -> i32 {
let ((a, b), (c, d)) = ((1, 2), (3, 4))
a + b + c + d
}
"#;
compile_should_succeed(code, "let_nested_tuple");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_let_wildcard() {
let code = r#"
fn test() -> i32 {
let _ = 100
let (x, _) = (10, 20)
return x
}
"#;
compile_should_succeed(code, "let_wildcard");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_let_enum_variant_rejected() {
let code = r#"
enum Option<T> {
Some(T),
None,
}
fn test() -> i32 {
let opt = Option::Some(42)
let Option::Some(x) = opt
return x
}
"#;
compile_should_fail(code, "Refutable pattern", "let_enum_variant_rejected");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_let_literal_rejected() {
let code = r#"
fn test() -> i32 {
let x = 42
let 42 = x
return x
}
"#;
compile_should_fail(code, "Refutable pattern", "let_literal_rejected");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_hex_literals() {
let code = r#"
fn test() -> i64 {
let x = 0xFF
let y = 0xDEADBEEF
return x + y
}
"#;
compile_should_succeed(code, "hex_literals");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_binary_literals() {
let code = r#"
fn test() -> i64 {
let x = 0b1010
let y = 0b1111_0000
return x + y
}
"#;
compile_should_succeed(code, "binary_literals");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_octal_literals() {
let code = r#"
fn test() -> i64 {
let x = 0o755
let y = 0o644
return x + y
}
"#;
compile_should_succeed(code, "octal_literals");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_module_path_double_colon() {
let code = r#"
use std::fs::File
"#;
compile_should_succeed(code, "module_path_double_colon");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_module_path_slash_rejected() {
let code = r#"
use std/fs
"#;
compile_should_fail(
code,
"Use '::' for module paths",
"module_path_slash_rejected",
);
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_module_path_dot_rejected() {
let code = r#"
use std.fs
"#;
compile_should_fail(
code,
"Use '::' for module paths",
"module_path_dot_rejected",
);
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_qualified_path_in_type() {
let code = r#"
struct Event {
pub value: i32,
}
"#;
compile_should_succeed(code, "qualified_path_in_type");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_qualified_path_in_match() {
let code = r#"
enum Color {
Red,
Green,
}
fn test(c: Color) -> i32 {
match c {
Color::Red => { return 1 }
Color::Green => { return 2 }
}
}
"#;
compile_should_succeed(code, "qualified_path_in_match");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_struct_pattern_basic() {
let code = r#"
enum Shape {
Circle { radius: f32 },
Rectangle { width: f32, height: f32 },
}
fn calculate_area(shape: Shape) -> f32 {
match shape {
Shape::Circle { radius: r } => {
return 3.14159 * r * r
}
Shape::Rectangle { width: w, height: h } => {
return w * h
}
}
}
"#;
compile_should_succeed(code, "struct_pattern_basic");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_struct_pattern_with_wildcard() {
let code = r#"
enum Shape {
Rectangle { width: f32, height: f32 },
}
fn has_large_width(shape: Shape) -> bool {
match shape {
Shape::Rectangle { width: w, height: _ } => w > 10.0,
}
}
"#;
compile_should_succeed(code, "struct_pattern_with_wildcard");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_struct_pattern_multiple_variants() {
let code = r#"
enum Shape {
Circle { radius: f32 },
Rectangle { width: f32, height: f32 },
Triangle { base: f32, height: f32 },
}
fn get_first_dimension(shape: Shape) -> f32 {
match shape {
Shape::Circle { radius: r } => r,
Shape::Rectangle { width: w, height: _ } => w,
Shape::Triangle { base: b, height: _ } => b,
}
}
"#;
compile_should_succeed(code, "struct_pattern_multiple_variants");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_module_import_resolution() {
compile_and_check_generated_rust(
"module_import_resolution_user.wj",
&[
"use super::module_import_resolution::RigidBody2D",
"use super::module_import_resolution::Collider2D", ],
"module_import_resolution",
);
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_operator_precedence_negation() {
compile_and_check_generated_rust(
"operator_precedence.wj",
&[
"!(a || b)", "!(a && b)", ],
"operator_precedence",
);
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_array_indexing_with_int() {
compile_and_check_rust_compiles("array_indexing.wj", "array_indexing_with_int");
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn test_param_mutability_inference() {
compile_and_check_rust_compiles(
"param_mutability_inference.wj",
"param_mutability_inference",
);
}
#[test]
fn test_trait_impl_stdlib() {
compile_and_check_rust_compiles("trait_impl_stdlib.wj", "trait_impl_stdlib");
}
#[test]
fn test_copy_type_ownership() {
compile_and_check_rust_compiles("copy_type_ownership.wj", "copy_type_ownership");
}
#[test]
fn run_all_pattern_tests() {
println!("\n=== Running Pattern Matching Tests ===\n");
println!("Run with: cargo test --test pattern_matching_tests");
}