1
//! Self-hosted test framework: `DEFTEST`, `ASSERT-EQUAL`, `RUN-TESTS`.
2
//!
3
//! `DEFTEST` registers a named body in the symbol table; `ASSERT-EQUAL`
4
//! signals a Compile error on mismatch (caught by `RUN-TESTS`); the
5
//! runner iterates the test registry and produces a multi-line String
6
//! summary with pass/fail counts and per-failure detail.
7

            
8
use crate::ast::{Expr, WasmType};
9
use crate::compiler::context::CompileContext;
10
use crate::compiler::emit::FunctionEmitter;
11
use crate::compiler::expr::{compile_expr, eval_value, format_expr};
12
use crate::error::{Error, Result};
13
use crate::runtime::SymbolTable;
14

            
15
use super::compile_static_result_for_stack;
16

            
17
/// `(deftest NAME body...)` registers a test in the symbol table.
18
/// `RUN-TESTS` later iterates the registry and evaluates each body
19
/// once, counting passes / failures. The test body is wrapped in
20
/// `BEGIN` so multi-form bodies behave correctly.
21
3133
pub(super) fn deftest(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
22
3133
    if args.len() < 2 {
23
68
        return Err(Error::Compile(
24
68
            "DEFTEST requires a name and at least one body form".to_string(),
25
68
        ));
26
3065
    }
27
3065
    let name = match &args[0] {
28
2996
        Expr::Symbol(s) => s.clone(),
29
69
        other => {
30
69
            return Err(Error::Compile(format!(
31
69
                "DEFTEST: expected symbol for test name, got {}",
32
69
                format_expr(other)
33
69
            )));
34
        }
35
    };
36
2996
    let body = if args.len() == 2 {
37
2927
        args[1].clone()
38
    } else {
39
69
        let mut forms = Vec::with_capacity(args.len());
40
69
        forms.push(Expr::Symbol("BEGIN".to_string()));
41
69
        forms.extend_from_slice(&args[1..]);
42
69
        Expr::List(forms)
43
    };
44
2996
    symbols.register_test(&name, body);
45
2996
    Ok(Expr::Quote(Box::new(Expr::Symbol(name))))
46
3133
}
47

            
48
816
pub(super) fn compile_deftest(
49
816
    ctx: &mut CompileContext,
50
816
    emit: &mut FunctionEmitter,
51
816
    symbols: &mut SymbolTable,
52
816
    args: &[Expr],
53
816
) -> Result<()> {
54
816
    let result = deftest(symbols, args)?;
55
680
    compile_expr(ctx, emit, symbols, &result)
56
816
}
57

            
58
/// `(assert-equal A B)` evaluates both sides; if the resulting
59
/// `Expr` values aren't `==` it signals a Compile error. Inside a
60
/// test, that error is caught by `RUN-TESTS` and counted as a
61
/// failure; outside a test it propagates as any other compile
62
/// error.
63
3200
pub(super) fn assert_equal(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
64
3200
    if args.len() != 2 {
65
68
        return Err(Error::Arity {
66
68
            name: "ASSERT-EQUAL".to_string(),
67
68
            expected: 2,
68
68
            actual: args.len(),
69
68
        });
70
3132
    }
71
3132
    let a = eval_value(symbols, &args[0])?;
72
3132
    let b = eval_value(symbols, &args[1])?;
73
3132
    if a == b {
74
2722
        Ok(Expr::Nil)
75
    } else {
76
410
        Err(Error::Compile(format!(
77
410
            "assertion failed: {} != {}",
78
410
            format_expr(&a),
79
410
            format_expr(&b)
80
410
        )))
81
    }
82
3200
}
83

            
84
272
pub(super) fn compile_assert_equal(
85
272
    ctx: &mut CompileContext,
86
272
    emit: &mut FunctionEmitter,
87
272
    symbols: &mut SymbolTable,
88
272
    args: &[Expr],
89
272
) -> Result<()> {
90
272
    let result = assert_equal(symbols, args)?;
91
136
    compile_expr(ctx, emit, symbols, &result)
92
272
}
93

            
94
/// `(run-tests)` evaluates every test registered via `DEFTEST` and
95
/// returns a multi-line String summary: `ran N tests: P passed, F
96
/// failed' followed by per-failure detail lines.
97
751
pub(super) fn run_tests(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
98
751
    if !args.is_empty() {
99
69
        return Err(Error::Arity {
100
69
            name: "RUN-TESTS".to_string(),
101
69
            expected: 0,
102
69
            actual: args.len(),
103
69
        });
104
682
    }
105
682
    let tests = symbols.tests();
106
682
    let mut pass = 0usize;
107
682
    let mut failures: Vec<String> = Vec::new();
108
2790
    for (name, body) in &tests {
109
2790
        match eval_value(symbols, body) {
110
2517
            Ok(_) => pass += 1,
111
273
            Err(err) => failures.push(format!("  {name}: {err}")),
112
        }
113
    }
114
682
    let total = tests.len();
115
682
    let fail = failures.len();
116
682
    let mut lines = vec![format!("ran {total} tests: {pass} passed, {fail} failed")];
117
682
    if !failures.is_empty() {
118
273
        lines.push("failures:".to_string());
119
273
        lines.extend(failures);
120
409
    }
121
682
    Ok(Expr::String(lines.join("\n")))
122
751
}
123

            
124
612
pub(super) fn compile_run_tests(
125
612
    ctx: &mut CompileContext,
126
612
    emit: &mut FunctionEmitter,
127
612
    symbols: &mut SymbolTable,
128
612
    args: &[Expr],
129
612
) -> Result<()> {
130
612
    let result = run_tests(symbols, args)?;
131
544
    compile_expr(ctx, emit, symbols, &result)
132
612
}
133

            
134
68
pub(super) fn compile_deftest_for_stack(
135
68
    ctx: &mut CompileContext,
136
68
    emit: &mut FunctionEmitter,
137
68
    symbols: &mut SymbolTable,
138
68
    args: &[Expr],
139
68
) -> Result<WasmType> {
140
68
    let result = deftest(symbols, args)?;
141
68
    compile_static_result_for_stack(ctx, emit, symbols, &result)
142
68
}
143

            
144
136
pub(super) fn compile_assert_equal_for_stack(
145
136
    ctx: &mut CompileContext,
146
136
    emit: &mut FunctionEmitter,
147
136
    symbols: &mut SymbolTable,
148
136
    args: &[Expr],
149
136
) -> Result<WasmType> {
150
136
    let result = assert_equal(symbols, args)?;
151
68
    compile_static_result_for_stack(ctx, emit, symbols, &result)
152
136
}
153

            
154
136
pub(super) fn compile_run_tests_for_stack(
155
136
    ctx: &mut CompileContext,
156
136
    emit: &mut FunctionEmitter,
157
136
    symbols: &mut SymbolTable,
158
136
    args: &[Expr],
159
136
) -> Result<WasmType> {
160
136
    let result = run_tests(symbols, args)?;
161
136
    compile_static_result_for_stack(ctx, emit, symbols, &result)
162
136
}