aboutsummaryrefslogtreecommitdiff
path: root/vm/test
diff options
context:
space:
mode:
Diffstat (limited to 'vm/test')
-rw-r--r--vm/test/test.h185
-rw-r--r--vm/test/vm_test.c182
2 files changed, 367 insertions, 0 deletions
diff --git a/vm/test/test.h b/vm/test/test.h
new file mode 100644
index 0000000..fd8dc22
--- /dev/null
+++ b/vm/test/test.h
@@ -0,0 +1,185 @@
1// SPDX-License-Identifier: MIT
2#pragma once
3
4#ifdef UNIT_TEST
5
6#include <stdbool.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
12 defined(__NetBSD__) || defined(__OpenBSD__)
13#define USE_SYSCTL_FOR_ARGS 1
14// clang-format off
15#include <sys/types.h>
16#include <sys/sysctl.h>
17// clang-format on
18#include <unistd.h> // getpid
19#endif
20
21struct test_file_metadata;
22
23struct test_failure {
24 bool present;
25 const char *message;
26 const char *file;
27 int line;
28};
29
30struct test_case_metadata {
31 void (*fn)(struct test_case_metadata *, struct test_file_metadata *);
32 struct test_failure failure;
33 const char *name;
34 struct test_case_metadata *next;
35};
36
37struct test_file_metadata {
38 bool registered;
39 const char *name;
40 struct test_file_metadata *next;
41 struct test_case_metadata *tests;
42};
43
44struct test_file_metadata __attribute__((weak)) * test_file_head;
45
46#define SET_FAILURE(_message) \
47 metadata->failure = (struct test_failure) { \
48 .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \
49 }
50
51#define TEST_EQUAL(a, b) \
52 do { \
53 if ((a) != (b)) { \
54 SET_FAILURE(#a " != " #b); \
55 return; \
56 } \
57 } while (0)
58
59#define TEST_TRUE(a) \
60 do { \
61 if (!(a)) { \
62 SET_FAILURE(#a " is not true"); \
63 return; \
64 } \
65 } while (0)
66
67#define TEST_STREQUAL(a, b) \
68 do { \
69 if (strcmp(a, b) != 0) { \
70 SET_FAILURE(#a " != " #b); \
71 return; \
72 } \
73 } while (0)
74
75#define TEST_CASE(_name) \
76 static void __test_h_##_name(struct test_case_metadata *, \
77 struct test_file_metadata *); \
78 static struct test_file_metadata __test_h_file; \
79 static struct test_case_metadata __test_h_meta_##_name = { \
80 .name = #_name, \
81 .fn = __test_h_##_name, \
82 }; \
83 static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \
84 __test_h_meta_##_name.next = __test_h_file.tests; \
85 __test_h_file.tests = &__test_h_meta_##_name; \
86 if (!__test_h_file.registered) { \
87 __test_h_file.name = __FILE__; \
88 __test_h_file.next = test_file_head; \
89 test_file_head = &__test_h_file; \
90 __test_h_file.registered = true; \
91 } \
92 } \
93 static void __test_h_##_name( \
94 struct test_case_metadata *metadata __attribute__((unused)), \
95 struct test_file_metadata *file_metadata __attribute__((unused)))
96
97extern void __attribute__((weak)) (*test_h_unittest_setup)(void);
98/// Run defined tests, return true if all tests succeeds
99/// @param[out] tests_run if not NULL, set to whether tests were run
100static inline void __attribute__((constructor(102))) run_tests(void) {
101 bool should_run = false;
102#ifdef USE_SYSCTL_FOR_ARGS
103 int mib[] = {
104 CTL_KERN,
105#if defined(__NetBSD__) || defined(__OpenBSD__)
106 KERN_PROC_ARGS,
107 getpid(),
108 KERN_PROC_ARGV,
109#else
110 KERN_PROC,
111 KERN_PROC_ARGS,
112 getpid(),
113#endif
114 };
115 char *arg = NULL;
116 size_t arglen;
117 sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0);
118 arg = malloc(arglen);
119 sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0);
120#else
121 FILE *cmdlinef = fopen("/proc/self/cmdline", "r");
122 char *arg = NULL;
123 int arglen;
124 fscanf(cmdlinef, "%ms%n", &arg, &arglen);
125 fclose(cmdlinef);
126#endif
127 for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) {
128 if (strcmp(pos, "--unittest") == 0) {
129 should_run = true;
130 break;
131 }
132 }
133 free(arg);
134
135 if (!should_run) {
136 return;
137 }
138
139 if (&test_h_unittest_setup) {
140 test_h_unittest_setup();
141 }
142
143 struct test_file_metadata *i = test_file_head;
144 int failed = 0, success = 0;
145 while (i) {
146 fprintf(stderr, "Running tests from %s:\n", i->name);
147 struct test_case_metadata *j = i->tests;
148 while (j) {
149 fprintf(stderr, "\t%s ... ", j->name);
150 j->failure.present = false;
151 j->fn(j, i);
152 if (j->failure.present) {
153 fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message,
154 j->failure.file, j->failure.line);
155 failed++;
156 } else {
157 fprintf(stderr, "passed\n");
158 success++;
159 }
160 j = j->next;
161 }
162 fprintf(stderr, "\n");
163 i = i->next;
164 }
165 int total = failed + success;
166 fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total,
167 failed, total);
168 exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
169}
170
171#else
172
173#include <stdbool.h>
174
175#define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void)
176
177#define TEST_EQUAL(a, b) \
178 (void)(a); \
179 (void)(b)
180#define TEST_TRUE(a) (void)(a)
181#define TEST_STREQUAL(a, b) \
182 (void)(a); \
183 (void)(b)
184
185#endif
diff --git a/vm/test/vm_test.c b/vm/test/vm_test.c
new file mode 100644
index 0000000..2d1a91f
--- /dev/null
+++ b/vm/test/vm_test.c
@@ -0,0 +1,182 @@
1#include "vm.h"
2
3#include "test.h"
4
5#include <stdio.h>
6
7/// Create and destroy a vm.
8TEST_CASE(vm_create_destroy) {
9 Vm* vm = vm_new();
10 TEST_TRUE(vm != 0);
11 vm_del(&vm);
12}
13
14// Exit with an implicit 0 exit code.
15TEST_CASE(vm_exit_implicit) {
16 // clang-format off
17 const Inst instructions[] = {
18 vmExit(),
19 };
20 // clang-format on
21
22 Vm* vm = vm_new();
23 TEST_TRUE(vm != 0);
24 const int exit_code =
25 vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst));
26 TEST_TRUE(exit_code == 0);
27 vm_del(&vm);
28}
29
30// Exit with an explicit exit code.
31TEST_CASE(vm_exit_explicit) {
32 const int32_t expected = 17;
33
34 // clang-format off
35 const Inst instructions[] = {
36 vmPushI32(expected),
37 vmExit(),
38 };
39 // clang-format on
40
41 Vm* vm = vm_new();
42 TEST_TRUE(vm != 0);
43 const int exit_code =
44 vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst));
45 TEST_TRUE(exit_code == expected);
46 vm_del(&vm);
47}
48
49/// Add two i32 numbers.
50TEST_CASE(vm_add_i32) {
51 const int n1 = 2;
52 const int n2 = 3;
53
54 // clang-format off
55 const Inst instructions[] = {
56 vmPushI32(n1),
57 vmPushI32(n2),
58 vmAdd(I32),
59 vmExit(),
60 };
61 // clang-format on
62
63 Vm* vm = vm_new();
64 TEST_TRUE(vm != 0);
65 const int exit_code =
66 vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst));
67 TEST_EQUAL(exit_code, n1 + n2);
68 vm_del(&vm);
69}
70
71/// Sum an array of numbers with 4 add instructions.
72TEST_CASE(vm_sum_array_i32_explicit) {
73 const int vals[5] = {1, 2, 3, 4, 5};
74
75 // clang-format off
76 const Inst instructions[] = {
77 vmPushI32(vals[0]),
78 vmPushI32(vals[1]),
79 vmPushI32(vals[2]),
80 vmPushI32(vals[3]),
81 vmPushI32(vals[4]),
82 vmAdd(I32),
83 vmAdd(I32),
84 vmAdd(I32),
85 vmAdd(I32),
86 vmExit(),
87 };
88 // clang-format on
89
90 int sum = 0;
91 for (size_t i = 0; i < sizeof(vals) / sizeof(vals[0]); ++i) {
92 sum += vals[i];
93 }
94
95 Vm* vm = vm_new();
96 TEST_TRUE(vm != 0);
97 const int exit_code =
98 vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst));
99 TEST_EQUAL(exit_code, sum);
100 vm_del(&vm);
101}
102
103/// Sum an array of numbers with a loop.
104TEST_CASE(vm_sum_array_i32_loop) {
105 const int vals[5] = {1, 2, 3, 4, 5};
106
107 const Label loop_label = 0;
108 const Label counter_index = 0;
109
110 // clang-format off
111 const Inst instructions[] = {
112 vmPushI32(vals[0]),
113 vmPushI32(vals[1]),
114 vmPushI32(vals[2]),
115 vmPushI32(vals[3]),
116 vmPushI32(vals[4]),
117 vmLocal(I32, counter_index),
118 vmPushI32(sizeof(vals) / sizeof(vals[0]) - 1),
119 vmLocalWr(I32, counter_index),
120 vmLoop(loop_label),
121 vmAdd(I32),
122 vmLocalRd(I32, counter_index),
123 vmDec(I32),
124 // TODO: Could be useful to have a function that writes the local but
125 // leaves its value on the stack.
126 vmLocalWr(I32, counter_index),
127 vmLocalRd(I32, counter_index),
128 // TODO: Perhaps we should expect the comparison value to also be pushed
129 // to the stack.
130 vmCmpI32(0),
131 vmBr_if(false, loop_label),
132 vmEnd(),
133 vmExit(),
134 };
135 // clang-format on
136
137 int sum = 0;
138 for (size_t i = 0; i < sizeof(vals) / sizeof(vals[0]); ++i) {
139 sum += vals[i];
140 }
141
142 Vm* vm = vm_new();
143 TEST_TRUE(vm != 0);
144 const int exit_code =
145 vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst));
146 TEST_EQUAL(exit_code, sum);
147 vm_del(&vm);
148}
149
150// Call a function to add two numbers.
151TEST_CASE(vm_function_call) {
152 const Label func = 0;
153 const Label a = 0;
154 const Label b = 1;
155 const int32_t a_val = 3;
156 const int32_t b_val = 5;
157 const int32_t expected = a + b;
158
159 // clang-format off
160 const Inst instructions[] = {
161 /* Function definition */
162 vmFunc(func),
163 vmArg(I32, b),
164 vmArg(I32, a),
165 vmAdd(I32),
166 vmEnd(),
167 /* Main program */
168 vmPushI32(a_val),
169 vmPushI32(b_val),
170 vmCall(func),
171 vmExit(),
172 };
173 // clang-format on
174
175 Vm* vm = vm_new();
176 TEST_TRUE(vm != 0);
177 // const int exit_code =
178 // vm_run(vm, instructions, sizeof(instructions) / sizeof(Inst));
179 vm_del(&vm);
180}
181
182int main() { return 0; }