#![allow(missing_docs)]
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
fn ruchy_cmd() -> Command {
assert_cmd::cargo::cargo_bin_cmd!("ruchy")
}
fn fixture_path(name: &str) -> String {
format!("tests/fixtures/fmt/{name}")
}
#[test]
fn test_fmt_no_operator_mangling() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("operators.ruchy");
fs::write(
&test_file,
"let a = 10 + 5\nlet b = a * 2\nlet c = b / 3\nlet d = c - 1",
)
.unwrap();
ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::contains("*").and(predicate::str::contains("Multiply").not()))
.stdout(predicate::str::contains("+").and(predicate::str::contains("Add").not()))
.stdout(predicate::str::contains("/").and(predicate::str::contains("Divide").not()))
.stdout(predicate::str::contains("-").and(predicate::str::contains("Subtract").not()));
}
#[test]
fn test_fmt_no_let_statement_rewriting() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("simple.ruchy");
fs::write(&test_file, "let x = 42\nlet y = x * 2\nprintln(y)").unwrap();
ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::contains("let x = 42 in ()").not())
.stdout(predicate::str::contains("let y = x * 2 in ()").not());
}
#[test]
fn test_fmt_round_trip_idempotency() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("roundtrip.ruchy");
fs::write(&test_file, "let x = 42\nlet y = x * 2 + 10\nprintln(y)").unwrap();
let output1 = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted1 = String::from_utf8(output1.stdout).unwrap();
let test_file2 = temp_dir.path().join("roundtrip2.ruchy");
fs::write(&test_file2, &formatted1).unwrap();
let output2 = ruchy_cmd()
.arg("fmt")
.arg(&test_file2)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted2 = String::from_utf8(output2.stdout).unwrap();
assert_eq!(formatted1, formatted2, "Formatting is not idempotent!");
}
#[test]
fn test_fmt_basic_file() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("simple.ruchy"))
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::contains("let"));
}
#[test]
fn test_fmt_stdout_option() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("simple.ruchy"))
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn test_fmt_check_option() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("simple.ruchy"))
.arg("--check")
.assert()
.code(predicate::eq(0).or(predicate::eq(1))); }
#[test]
fn test_fmt_missing_file() {
ruchy_cmd()
.arg("fmt")
.arg("tests/fixtures/fmt/nonexistent.ruchy")
.assert()
.failure()
.stderr(predicate::str::contains("not found").or(predicate::str::contains("No such file")));
}
#[test]
fn test_fmt_operators() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("operators.ruchy"))
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::contains("+"))
.stdout(predicate::str::contains("*"))
.stdout(predicate::str::contains("/"))
.stdout(predicate::str::contains("-"));
}
#[test]
fn test_fmt_control_flow() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("control_flow.ruchy"))
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::contains("if"))
.stdout(predicate::str::contains("else"));
}
#[test]
fn test_fmt_functions() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("functions.ruchy"))
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::contains("fun").or(predicate::str::contains("fn")))
.stdout(predicate::str::contains("add"));
}
#[test]
fn test_fmt_line_width_option() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("simple.ruchy"))
.arg("--line-width")
.arg("80")
.arg("--stdout")
.assert()
.success();
}
#[test]
fn test_fmt_indent_option() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("simple.ruchy"))
.arg("--indent")
.arg("2")
.arg("--stdout")
.assert()
.success();
}
#[test]
fn test_fmt_use_tabs_option() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("simple.ruchy"))
.arg("--use-tabs")
.arg("--stdout")
.assert()
.success();
}
#[test]
fn test_fmt_diff_option() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("simple.ruchy"))
.arg("--diff")
.assert()
.success();
}
#[test]
fn test_fmt_config_option() {
ruchy_cmd()
.arg("fmt")
.arg(fixture_path("simple.ruchy"))
.arg("--config")
.arg("fmt.toml")
.arg("--stdout")
.assert()
.code(predicate::ne(2)); }
#[test]
fn test_fmt_empty_file() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("empty.ruchy");
fs::write(&test_file, "").unwrap();
ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.assert()
.failure()
.stderr(predicate::str::contains("Empty program"));
}
#[test]
fn test_fmt_whitespace_only() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("whitespace.ruchy");
fs::write(&test_file, " \n\n \t\n").unwrap();
ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.assert()
.failure()
.stderr(predicate::str::contains("Empty program"));
}
#[test]
fn test_fmt_syntax_error() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("syntax_error.ruchy");
fs::write(&test_file, "let x = ").unwrap();
ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.assert()
.failure()
.stderr(
predicate::str::contains("Error")
.or(predicate::str::contains("Expected"))
.or(predicate::str::contains("Unexpected")),
);
}
#[test]
fn test_fmt_complex_expression() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("complex.ruchy");
fs::write(&test_file, "let x = (10 + 5) * (20 - 3) / 2").unwrap();
ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::contains("+"))
.stdout(predicate::str::contains("*"))
.stdout(predicate::str::contains("-"))
.stdout(predicate::str::contains("/"));
}
#[test]
fn test_fmt_nested_blocks() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("nested.ruchy");
fs::write(
&test_file,
"if x > 0 { if y > 0 { println(\"both positive\") } }",
)
.unwrap();
ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.assert()
.success()
.stdout(predicate::str::contains("if"));
}
#[test]
fn test_fmt_modifies_file_in_place() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("modify.ruchy");
let original = "let x=42\nlet y=x*2";
fs::write(&test_file, original).unwrap();
ruchy_cmd().arg("fmt").arg(&test_file).assert().success();
let modified = fs::read_to_string(&test_file).unwrap();
assert!(modified.contains("let"));
}
#[test]
fn test_fmt_check_no_modification() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("nomodify.ruchy");
let original = "let x=42\nlet y=x*2";
fs::write(&test_file, original).unwrap();
ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--check")
.assert()
.code(predicate::eq(0).or(predicate::eq(1)));
let unchanged = fs::read_to_string(&test_file).unwrap();
assert_eq!(unchanged, original, "File was modified with --check flag!");
}
#[test]
fn test_fmt_no_debug_fallback_array_indexing() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("indexing.ruchy");
fs::write(
&test_file,
"let arr = [1, 2, 3]\nlet x = arr[0]\nlet y = arr[1]",
)
.unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(
formatted.contains("[0]") || formatted.contains("arr["),
"Missing array indexing syntax"
);
assert!(
!formatted.contains("IndexAccess"),
"Debug fallback detected for IndexAccess"
);
assert!(
!formatted.contains("Expr { kind:"),
"AST Debug output detected"
);
}
#[test]
fn test_fmt_no_debug_fallback_assignment() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("assign.ruchy");
fs::write(&test_file, "let x = 0\nx = 42\nx = x + 1").unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains(" = "), "Missing assignment operator");
assert!(
!formatted.contains("Assign {"),
"Debug fallback detected for Assign"
);
assert!(
!formatted.contains("target: Expr"),
"AST Debug output detected"
);
}
#[test]
fn test_fmt_no_debug_fallback_return() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("return.ruchy");
fs::write(
&test_file,
"{\n\
fun test(x: Any) {\n\
if x > 10 {\n\
return x\n\
}\n\
0\n\
}\n\
}",
)
.unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains("return"), "Missing return keyword");
assert!(
!formatted.contains("Return {"),
"Debug fallback detected for Return"
);
assert!(
!formatted.contains("value: Some("),
"AST Debug output detected"
);
}
#[test]
fn test_fmt_no_debug_fallback_field_access() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("field.ruchy");
fs::write(&test_file, "let x = obj.field\nlet y = obj.method()").unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains('.'), "Missing field access operator");
assert!(
!formatted.contains("FieldAccess"),
"Debug fallback detected for FieldAccess"
);
}
#[test]
fn test_fmt_no_debug_fallback_while_loop() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("while.ruchy");
fs::write(&test_file, "let x = 0\nwhile x < 10 {\nx = x + 1\n}").unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains("while"), "Missing while keyword");
assert!(
!formatted.contains("While {"),
"Debug fallback detected for While"
);
}
#[test]
fn test_fmt_no_debug_fallback_break_continue() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("loop_control.ruchy");
fs::write(
&test_file,
"while true {\nif x > 5 { break }\nif x == 3 { continue }\n}",
)
.unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains("break"), "Missing break keyword");
assert!(formatted.contains("continue"), "Missing continue keyword");
assert!(
!formatted.contains("Break {"),
"Debug fallback detected for Break"
);
assert!(
!formatted.contains("Continue {"),
"Debug fallback detected for Continue"
);
}
#[test]
fn test_fmt_no_debug_fallback_range() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("range.ruchy");
fs::write(&test_file, "for i in 0..10 {\nprintln(i)\n}").unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains(".."), "Missing range operator");
assert!(
!formatted.contains("Range {"),
"Debug fallback detected for Range"
);
}
#[test]
fn test_fmt_no_debug_fallback_unary_ops() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("unary.ruchy");
fs::write(&test_file, "let x = -42\nlet flag = !true").unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(
formatted.contains('-') || formatted.contains('!'),
"Missing unary operators"
);
assert!(
!formatted.contains("Unary {"),
"Debug fallback detected for Unary"
);
assert!(
!formatted.contains("Negate"),
"Debug fallback detected for Negate operator"
);
}
#[test]
fn test_fmt_no_debug_fallback_list_literal() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("list.ruchy");
fs::write(&test_file, "let arr = [1, 2, 3, 4, 5]").unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains('['), "Missing list opening bracket");
assert!(formatted.contains(']'), "Missing list closing bracket");
assert!(
!formatted.contains("List {"),
"Debug fallback detected for List"
);
assert!(
!formatted.contains("elements:"),
"AST Debug output detected"
);
}
#[test]
fn test_fmt_no_debug_fallback_tuple_literal() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("tuple.ruchy");
fs::write(&test_file, "let pair = (1, 2)\nlet triple = (1, 2, 3)").unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(
formatted.contains('(') && formatted.contains(')'),
"Missing tuple parentheses"
);
assert!(
!formatted.contains("Tuple {"),
"Debug fallback detected for Tuple"
);
}
#[test]
fn test_fmt_no_debug_fallback_match_expr() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("match.ruchy");
fs::write(
&test_file,
"match x {\n1 => \"one\"\n2 => \"two\"\n_ => \"other\"\n}",
)
.unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains("match"), "Missing match keyword");
assert!(
!formatted.contains("Match {"),
"Debug fallback detected for Match"
);
assert!(!formatted.contains("arms:"), "AST Debug output detected");
}
#[test]
fn test_fmt_no_debug_fallback_compound_assign() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("compound.ruchy");
fs::write(&test_file, "let x = 0\nx += 1\nx *= 2\nx -= 5").unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(
formatted.contains("+=") || formatted.contains("*=") || formatted.contains("-="),
"Missing compound assignment operators"
);
assert!(
!formatted.contains("CompoundAssign"),
"Debug fallback detected for CompoundAssign"
);
}
#[test]
fn test_fmt_real_world_head_example() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("head.ruchy");
fs::write(
&test_file,
"fun head_lines(file_path, n) {\n\
let content = fs_read(file_path)\n\
let result = \"\"\n\
let line_count = 0\n\
for i in range(0, content.len()) {\n\
let ch = content[i]\n\
if line_count < n {\n\
result = result + ch\n\
}\n\
if ch == \"\\n\" {\n\
line_count = line_count + 1\n\
if line_count >= n {\n\
return result\n\
}\n\
}\n\
}\n\
result\n\
}",
)
.unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(
!formatted.contains("IndexAccess {"),
"CRITICAL: Array indexing corrupted with Debug output"
);
assert!(
!formatted.contains("Assign {"),
"CRITICAL: Assignment corrupted with Debug output"
);
assert!(
!formatted.contains("Return {"),
"CRITICAL: Return statement corrupted with Debug output"
);
assert!(
!formatted.contains("Expr { kind:"),
"CRITICAL: AST Debug output detected"
);
assert!(
!formatted.contains("span: Span"),
"CRITICAL: Span Debug output detected"
);
assert!(
formatted.contains("[i]") || formatted.contains("content["),
"Array indexing syntax missing"
);
assert!(formatted.contains("result = "), "Assignment syntax missing");
assert!(formatted.contains("return"), "Return keyword missing");
let check_result = ruchy_cmd()
.arg("check")
.arg(&test_file)
.output()
.expect("Failed to run check");
assert!(
check_result.status.success(),
"Formatted code failed syntax validation"
);
}
#[test]
fn test_fmt_all_binary_operators() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("all_ops.ruchy");
fs::write(
&test_file,
"let a = 1 + 2\n\
let b = 3 - 4\n\
let c = 5 * 6\n\
let d = 7 / 8\n\
let e = 9 % 10\n\
let f = 11 == 12\n\
let g = 13 != 14\n\
let h = 15 < 16\n\
let i = 17 > 18\n\
let j = 19 <= 20\n\
let k = 21 >= 22",
)
.unwrap();
let output = ruchy_cmd()
.arg("fmt")
.arg(&test_file)
.arg("--stdout")
.output()
.expect("Failed to run fmt");
let formatted = String::from_utf8(output.stdout).unwrap();
assert!(formatted.contains('+'), "Missing + operator");
assert!(formatted.contains('-'), "Missing - operator");
assert!(formatted.contains('*'), "Missing * operator");
assert!(formatted.contains('/'), "Missing / operator");
assert!(!formatted.contains("Add"), "Operator mangling: Add found");
assert!(
!formatted.contains("Subtract"),
"Operator mangling: Subtract found"
);
assert!(
!formatted.contains("Multiply"),
"Operator mangling: Multiply found"
);
assert!(
!formatted.contains("Divide"),
"Operator mangling: Divide found"
);
}
#[test]
fn test_fmt_preserves_semantics() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("semantics.ruchy");
let code = "let x = 10\nlet y = x * 2 + 5\nprintln(y)";
fs::write(&test_file, code).unwrap();
let original_output = ruchy_cmd()
.arg("run")
.arg(&test_file)
.output()
.expect("Failed to run original");
ruchy_cmd().arg("fmt").arg(&test_file).assert().success();
let formatted_output = ruchy_cmd()
.arg("run")
.arg(&test_file)
.output()
.expect("Failed to run formatted");
assert_eq!(
original_output.stdout, formatted_output.stdout,
"Formatted code has different semantics!"
);
}