summaryrefslogtreecommitdiff
path: root/src/TestFramework.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/TestFramework.kt')
-rw-r--r--src/TestFramework.kt91
1 files changed, 91 insertions, 0 deletions
diff --git a/src/TestFramework.kt b/src/TestFramework.kt
new file mode 100644
index 0000000..8406a79
--- /dev/null
+++ b/src/TestFramework.kt
@@ -0,0 +1,91 @@
+package moe.nea.lisp
+
+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,
+ 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 { context, callsite, stackFrame, args ->
+ runTest(context, callsite, stackFrame, args)
+ return@externalRawCall LispData.LispNil
+ }
+
+ val failTestBinding = LispData.externalCall { args, reportError ->
+ val message = CoreBindings.stringify(args.singleOrNull() ?: return@externalCall reportError("Needs a message"))
+ LispData.externalRawCall { 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, true, mutableListOf(), testList, isWhitelist)
+ stackFrame.setMeta(TestSuiteMeta, ts)
+ ts.isTesting = false
+ return ts
+ }
+}