summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2026-07-31 00:06:20 +0200
committerPaul Oliver <contact@pauloliver.dev>2026-07-31 00:11:33 +0200
commit8a4563d6d0c9e81fdaa42f6f760cae2b49cad431 (patch)
tree868a54949ed79b3c1e585365f4f3385bb6bee12f
parentf2c34b1d2c18270a0327fb4896fa307fae098770 (diff)
Adds unit test framework
-rw-r--r--core/salis.c18
-rwxr-xr-xsalis.py20
-rw-r--r--test/test_build.py56
-rw-r--r--test/test_general.py54
-rw-r--r--test/test_run.py55
-rw-r--r--test/ui/push/links.json1
-rw-r--r--test/ui/push/ui.c25
7 files changed, 220 insertions, 9 deletions
diff --git a/core/salis.c b/core/salis.c
index 93dc842..6cc0725 100644
--- a/core/salis.c
+++ b/core/salis.c
@@ -360,6 +360,13 @@ static void core_save(const struct Core *core, FILE *f) {
arch_core_save(core, f);
}
+void core_event_array_init(struct EventArray *eva) {
+ assert(eva);
+ eva->params.size = sizeof(uint64_t) * MVEC_SIZE;
+ eva->params.in = (Bytef *)eva->data;
+ eva->params.out = (Bytef *)eva->comp;
+}
+
#if defined(COMMAND_NEW)
static void core_assemble_ancestor(struct Core *core) {
assert(core);
@@ -381,13 +388,6 @@ static void core_assemble_ancestor(struct Core *core) {
}
}
-void core_event_array_init(struct EventArray *eva) {
- assert(eva);
- eva->params.size = sizeof(uint64_t) * MVEC_SIZE;
- eva->params.in = (Bytef *)eva->data;
- eva->params.out = (Bytef *)eva->comp;
-}
-
static void core_init(struct Core *core, uint64_t *seed) {
assert(core);
assert(seed);
@@ -460,6 +460,10 @@ static void core_load(struct Core *core, FILE *f) {
fread(core->eeva.data, sizeof(uint64_t), MVEC_SIZE, f);
fread(core->beva.data, sizeof(uint64_t), MVEC_SIZE, f);
+ core_event_array_init(&core->aeva);
+ core_event_array_init(&core->eeva);
+ core_event_array_init(&core->beva);
+
arch_core_load(core, f);
}
#endif
diff --git a/salis.py b/salis.py
index 683da7d..0497ee4 100755
--- a/salis.py
+++ b/salis.py
@@ -7,6 +7,7 @@
# index:
# [section] imports
# [section] argument parser
+# [section] unit tests
# [section] build class
# [section] logger
# [section] options dict formatter
@@ -24,9 +25,9 @@ import json
import os
import random
import shutil
-import signal
import subprocess
import sys
+import unittest
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, ArgumentTypeError, RawTextHelpFormatter
from tempfile import TemporaryDirectory
@@ -44,6 +45,7 @@ formatter_class = lambda prog: ArgumentDefaultsHelpFormatter(max_help_position=4
new = sub_parsers.add_parser("new", formatter_class=formatter_class, help="create new simulation")
load = sub_parsers.add_parser("load", formatter_class=formatter_class, help="load saved simulation")
+test = sub_parsers.add_parser("test", formatter_class=formatter_class, help="run unit tests")
def seed(i):
ival = int(i, 0)
@@ -83,6 +85,7 @@ options = (
("m", "mvec-pow", (new,), fmt_id, {"metavar": "POW", "help": "memory core size exponent; size = 2^{POW}", "default": 20, "required": False, "type": pos}),
("n", "name", (new, load), fmt_id, {"metavar": "NAME", "help": "name of new or loaded simulation", "default": "def.sim", "required": False, "type": str}),
("o", "optimized", (new, load), fmt_id, {"action": "store_true", "help": "build with optimizations", "required": False}),
+ ("p", "pattern", (test,), fmt_id, {"metavar": "PATTERN", "help": "pattern for unit test discovery", "default": "*", "required": False, "type": str}),
("p", "pre-cmd", (new, load), fmt_id, {"metavar": "CMD", "help": "shell command with which to wrap call to executable; e.g. gdb, time, valgrind, etc.", "required": False, "type": str}),
("s", "seed", (new,), fmt_hex, {"metavar": "SEED", "help": "seed value for new simulation; a value of 0 disables cosmic rays; a value of -1 creates a random seed", "default": 0, "required": False, "type": seed}),
("T", "keep-temp-dir", (new, load), fmt_id, {"action": "store_true", "help": "keep temporary directory on exit", "required": False}),
@@ -97,6 +100,16 @@ options = (
args = parser.parse_args()
# ------------------------------------------------------------------------------
+# [section] unit tests
+# ------------------------------------------------------------------------------
+if args.command == "test":
+ loader = unittest.TestLoader()
+ loader.testNamePatterns = [args.pattern]
+ runner = unittest.TextTestRunner(verbosity=2)
+ result = runner.run(loader.discover("test"))
+ exit(0 if not result.errors and not result.failures else 1)
+
+# ------------------------------------------------------------------------------
# [section] build class
# ------------------------------------------------------------------------------
class Build:
@@ -143,7 +156,7 @@ class Build:
# When a signal is received (e.g. SIGINT), is propagates through the entire process group.
# Handle signal on the interpreter and allow the simulator to shut down gracefully.
try:
- signal.pause()
+ proc.wait()
except KeyboardInterrupt:
proc.wait()
@@ -316,6 +329,9 @@ ui_path = f"{args.ui_path}/{args.ui}"
ui_flags = [f"-Iarch/{args.vm_arch}", "-Icore", f"arch/{args.vm_arch}/arch.c", arch_inst_path, "core/compress.c", "core/logger.c", "core/salis.c", "core/sql.c"]
ui_defines = defines + logger_defines
+if not os.path.isdir(ui_path):
+ raise RuntimeError(f"UI not found at '{ui_path}'")
+
with open(f"{ui_path}/links.json", "r") as f:
ui_links = json.load(f)
diff --git a/test/test_build.py b/test/test_build.py
new file mode 100644
index 0000000..7c0126a
--- /dev/null
+++ b/test/test_build.py
@@ -0,0 +1,56 @@
+# file : test/test_build.py
+# project : Salis-VM
+# author : Paul Oliver <contact@pauloliver.dev>
+
+# index:
+# [section] imports
+# [section] constants
+# [section] command lists
+# [section] tests null
+
+# ------------------------------------------------------------------------------
+# [section] imports
+# ------------------------------------------------------------------------------
+from test_general import SalisTest
+
+class TestBuild(SalisTest):
+ # --------------------------------------------------------------------------
+ # [section] constants
+ # --------------------------------------------------------------------------
+ SIM_NAME = "test-build.sim"
+
+ # --------------------------------------------------------------------------
+ # [section] command lists
+ # --------------------------------------------------------------------------
+ def commands_new(self, anc, arch):
+ return [
+ f"./salis.py new -a{anc} -b -f -g{compiler} -H{self.tempdir.name} -n{self.SIM_NAME} {optimized} -s{seed} -u{ui} -v{arch}"
+ for compiler in ["clang", "gcc", "tcc"]
+ for optimized in ["", "-o"]
+ for seed in [-1, 0, 1234]
+ for ui in ["daemon"]
+ ]
+
+ def commands_load(self):
+ return [
+ f"./salis.py load -b -g{compiler} -H{self.tempdir.name} -n{self.SIM_NAME} {optimized} -u{ui}"
+ for compiler in ["clang", "gcc", "tcc"]
+ for optimized in ["", "-o"]
+ for ui in ["daemon"]
+ ]
+
+ # --------------------------------------------------------------------------
+ # [section] tests null
+ # --------------------------------------------------------------------------
+ def test_null_new(self):
+ for command in self.commands_new("0123", "null"):
+ self.run_subprocess(command)
+ self.assert_files_exist([f"{self.tempdir.name}/{self.SIM_NAME}/opts.json"])
+
+ def test_null_load(self):
+ self.run_subprocess(self.commands_new("0123", "null")[0])
+ self.assert_files_exist([f"{self.tempdir.name}/{self.SIM_NAME}/opts.json"])
+
+ for command in self.commands_load():
+ self.run_subprocess(command)
+ self.assert_files_exist([f"{self.tempdir.name}/{self.SIM_NAME}/opts.json"])
diff --git a/test/test_general.py b/test/test_general.py
new file mode 100644
index 0000000..b4e0e85
--- /dev/null
+++ b/test/test_general.py
@@ -0,0 +1,54 @@
+# file : test/test_general.py
+# project : Salis-VM
+# author : Paul Oliver <contact@pauloliver.dev>
+
+# index:
+# [section] imports
+# [section] setup/teardown
+# [section] file lists
+# [section] assertions
+# [section] subprocess
+
+# ------------------------------------------------------------------------------
+# [section] imports
+# ------------------------------------------------------------------------------
+import os
+import subprocess
+import unittest
+
+from tempfile import TemporaryDirectory
+
+class SalisTest(unittest.TestCase):
+ # --------------------------------------------------------------------------
+ # [section] setup/teardown
+ # --------------------------------------------------------------------------
+ def setUp(self):
+ self.tempdir = TemporaryDirectory(prefix="salis_test_")
+
+ def tearDown(self):
+ self.tempdir.cleanup()
+
+ # --------------------------------------------------------------------------
+ # [section] file lists
+ # --------------------------------------------------------------------------
+ def files_on_new(self, name):
+ return [
+ f"{self.tempdir.name}/{name}/evas/evas-{0:016x}",
+ f"{self.tempdir.name}/{name}/opts.json",
+ f"{self.tempdir.name}/{name}/{name}",
+ f"{self.tempdir.name}/{name}/{name}-{0:016x}",
+ f"{self.tempdir.name}/{name}/{name}.sqlite3",
+ ]
+
+ # --------------------------------------------------------------------------
+ # [section] assertions
+ # --------------------------------------------------------------------------
+ def assert_files_exist(self, files):
+ for file in files:
+ assert os.path.isfile(file), f"{file} does not exist"
+
+ # --------------------------------------------------------------------------
+ # [section] subprocess
+ # --------------------------------------------------------------------------
+ def run_subprocess(self, command):
+ subprocess.run(command.split(), check=True, stdout=subprocess.DEVNULL)
diff --git a/test/test_run.py b/test/test_run.py
new file mode 100644
index 0000000..cab195c
--- /dev/null
+++ b/test/test_run.py
@@ -0,0 +1,55 @@
+# file : test/test_run.py
+# project : Salis-VM
+# author : Paul Oliver <contact@pauloliver.dev>
+
+# index:
+# [section] imports
+# [section] constants
+# [section] command lists
+# [section] file lists
+# [section] tests null
+
+# ------------------------------------------------------------------------------
+# [section] imports
+# ------------------------------------------------------------------------------
+from test_general import SalisTest
+
+class TestRun(SalisTest):
+ # --------------------------------------------------------------------------
+ # [section] constants
+ # --------------------------------------------------------------------------
+ SIM_NAME = "test-run.sim"
+ PUSH_POW = 8
+
+ # --------------------------------------------------------------------------
+ # [section] command lists
+ # --------------------------------------------------------------------------
+ def commands(self, anc, arch):
+ return [
+ (
+ f"./salis.py new -a{anc} -d{self.PUSH_POW} -f -g{compiler} -H{self.tempdir.name} -n{self.SIM_NAME} {optimized} -s{seed} -upush -Utest/ui/ -v{arch} -y{self.PUSH_POW}",
+ f"./salis.py load -g{compiler} -H{self.tempdir.name} -n{self.SIM_NAME} {optimized} -upush -Utest/ui/",
+ )
+ for compiler in ["clang", "gcc", "tcc"]
+ for optimized in ["", "-o"]
+ for seed in [-1, 0, 1234]
+ ]
+
+ # --------------------------------------------------------------------------
+ # [section] file lists
+ # --------------------------------------------------------------------------
+ def files_on_first_push(self):
+ return [*self.files_on_new(self.SIM_NAME), f"{self.tempdir.name}/{self.SIM_NAME}/evas/evas-{2 ** self.PUSH_POW:016x}"]
+
+ def files_on_second_push(self):
+ return [*self.files_on_first_push(), f"{self.tempdir.name}/{self.SIM_NAME}/evas/evas-{2 * (2 ** self.PUSH_POW):016x}"]
+
+ # --------------------------------------------------------------------------
+ # [section] tests null
+ # --------------------------------------------------------------------------
+ def test_null(self):
+ for command in self.commands("0123", "null"):
+ self.run_subprocess(command[0])
+ self.assert_files_exist(self.files_on_first_push())
+ self.run_subprocess(command[1])
+ self.assert_files_exist(self.files_on_second_push())
diff --git a/test/ui/push/links.json b/test/ui/push/links.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/test/ui/push/links.json
@@ -0,0 +1 @@
+[]
diff --git a/test/ui/push/ui.c b/test/ui/push/ui.c
new file mode 100644
index 0000000..53baac9
--- /dev/null
+++ b/test/ui/push/ui.c
@@ -0,0 +1,25 @@
+// file : test/ui/push/ui.c
+// project : Salis-VM
+// author : Paul Oliver <contact@pauloliver.dev>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <threads.h>
+#include <zlib.h>
+
+#include "arch_spec.h"
+#include "compress.h"
+#include "logger.h"
+#include "salis.h"
+
+int main(void) {
+#if defined(COMMAND_NEW)
+ salis_init();
+#elif defined(COMMAND_LOAD)
+ salis_load();
+#endif
+
+ salis_step(DATA_PUSH_INTERVAL);
+ salis_free();
+ return 0;
+}