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
3823
pub(super) fn deftest(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
22
3823
    if args.len() < 2 {
23
83
        return Err(Error::Compile(
24
83
            "DEFTEST requires a name and at least one body form".to_string(),
25
83
        ));
26
3740
    }
27
3740
    let name = match &args[0] {
28
3656
        Expr::Symbol(s) => s.clone(),
29
84
        other => {
30
84
            return Err(Error::Compile(format!(
31
84
                "DEFTEST: expected symbol for test name, got {}",
32
84
                format_expr(other)
33
84
            )));
34
        }
35
    };
36
3656
    let body = if args.len() == 2 {
37
3572
        args[1].clone()
38
    } else {
39
84
        let mut forms = Vec::with_capacity(args.len());
40
84
        forms.push(Expr::Symbol("BEGIN".to_string()));
41
84
        forms.extend_from_slice(&args[1..]);
42
84
        Expr::List(forms)
43
    };
44
3656
    symbols.register_test(&name, body);
45
3656
    Ok(Expr::Quote(Box::new(Expr::Symbol(name))))
46
3823
}
47

            
48
996
pub(super) fn compile_deftest(
49
996
    ctx: &mut CompileContext,
50
996
    emit: &mut FunctionEmitter,
51
996
    symbols: &mut SymbolTable,
52
996
    args: &[Expr],
53
996
) -> Result<()> {
54
996
    let result = deftest(symbols, args)?;
55
830
    compile_expr(ctx, emit, symbols, &result)
56
996
}
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
3905
pub(super) fn assert_equal(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
64
3905
    if args.len() != 2 {
65
83
        return Err(Error::Arity {
66
83
            name: "ASSERT-EQUAL".to_string(),
67
83
            expected: 2,
68
83
            actual: args.len(),
69
83
        });
70
3822
    }
71
3822
    let a = eval_value(symbols, &args[0])?;
72
3822
    let b = eval_value(symbols, &args[1])?;
73
3822
    if a == b {
74
3322
        Ok(Expr::Nil)
75
    } else {
76
500
        Err(Error::Compile(format!(
77
500
            "assertion failed: {} != {}",
78
500
            format_expr(&a),
79
500
            format_expr(&b)
80
500
        )))
81
    }
82
3905
}
83

            
84
332
pub(super) fn compile_assert_equal(
85
332
    ctx: &mut CompileContext,
86
332
    emit: &mut FunctionEmitter,
87
332
    symbols: &mut SymbolTable,
88
332
    args: &[Expr],
89
332
) -> Result<()> {
90
332
    let result = assert_equal(symbols, args)?;
91
166
    compile_expr(ctx, emit, symbols, &result)
92
332
}
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
916
pub(super) fn run_tests(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
98
916
    if !args.is_empty() {
99
84
        return Err(Error::Arity {
100
84
            name: "RUN-TESTS".to_string(),
101
84
            expected: 0,
102
84
            actual: args.len(),
103
84
        });
104
832
    }
105
832
    let tests = symbols.tests();
106
832
    let mut pass = 0usize;
107
832
    let mut failures: Vec<String> = Vec::new();
108
3405
    for (name, body) in &tests {
109
3405
        match eval_value(symbols, body) {
110
3072
            Ok(_) => pass += 1,
111
333
            Err(err) => failures.push(format!("  {name}: {err}")),
112
        }
113
    }
114
832
    let total = tests.len();
115
832
    let fail = failures.len();
116
832
    let mut lines = vec![format!("ran {total} tests: {pass} passed, {fail} failed")];
117
832
    if !failures.is_empty() {
118
333
        lines.push("failures:".to_string());
119
333
        lines.extend(failures);
120
499
    }
121
832
    Ok(Expr::String(lines.join("\n")))
122
916
}
123

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

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

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

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