summaryrefslogtreecommitdiff
path: root/src/TestFramework.kt
blob: b35020cc7bccc03821258f5921c75f5a79932aa5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package moe.nea.lisp

import java.time.Instant

object TestFramework {
    data class TestFailure(
        val callsite: LispAst,
        val message: String,
    )

    data class TestResult(
        val name: String,
        val failures: List<TestFailure>,
        val wasSkipped: Boolean,
    )

    data class TestSuite(
        val name: String,
        val startTime: Instant,
        var isTesting: Boolean,
        val allTests: MutableList<TestResult>,
        val testList: List<String>,
        val isWhitelist: Boolean,
    )

    data class ActiveTest(
        val testName: String,
        val currentFailures: MutableList<TestFailure>,
        var canMultifail: Boolean,
        val suite: TestSuite,
    )

    object TestSuiteMeta : StackFrame.MetaKey<TestSuite>
    object ActiveTestMeta : StackFrame.MetaKey<ActiveTest>

    val testBinding = LispData.externalRawCall("ntest.test") { context, callsite, stackFrame, args ->
        runTest(context, callsite, stackFrame, args)
        return@externalRawCall LispData.LispNil
    }

    val failTestBinding = LispData.externalCall("ntest.fail") { args, reportError ->
        val message = CoreBindings.stringify(args.singleOrNull() ?: return@externalCall reportError("Needs a message"))
        LispData.externalRawCall("ntest.fail.r") { context, callsite, stackFrame, args ->
            val activeTest = stackFrame.getMeta(ActiveTestMeta)
                ?: return@externalRawCall context.reportError("No active test", callsite)
            activeTest.currentFailures.add(TestFailure(callsite, message))
            return@externalRawCall LispData.LispNil
        }
    }

    val realizedTestModule = mapOf(
        "ntest.test" to testBinding,
        "ntest.fail" to failTestBinding,
    )

    fun runTest(
        context: LispExecutionContext,
        callsite: LispAst,
        stackFrame: StackFrame,
        args: List<LispAst.LispNode>
    ) {
        val meta = stackFrame.getMeta(TestSuiteMeta) ?: return
        if (!meta.isTesting) return
        if (args.size != 2) {
            context.reportError("Test case needs to be defined by a name and an executable", callsite)
            return
        }
        val (name, prog) = args
        val testName = when (val n = context.resolveValue(stackFrame, name)) {
            is LispData.Atom -> n.label
            is LispData.LispString -> n.string
            else -> {
                context.reportError("Test case needs an atom or string as name", name)
                return
            }
        }
        if (testName in meta.testList != meta.isWhitelist) {
            meta.allTests.add(TestResult(testName, listOf(), true))
            return
        }
        val child = stackFrame.fork()
        val test = ActiveTest(testName, mutableListOf(), false, meta)
        child.setMeta(ActiveTestMeta, test)
        context.resolveValue(child, prog)
        meta.allTests.add(TestResult(test.testName, test.currentFailures, false))
    }

    fun setup(stackFrame: StackFrame, name: String, testList: List<String>, isWhitelist: Boolean): TestSuite {
        val ts = TestSuite(name, Instant.now(), true, mutableListOf(), testList, isWhitelist)
        stackFrame.setMeta(TestSuiteMeta, ts)
        return ts
    }
}