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

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

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

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

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

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

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