1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <assert.h>
5#include "vm.h"
6#include "asm.h"
7
8static void test_example_file(const char *filepath, const char *expected_output) {
9 printf("Testing example: %s ... ", filepath);
10 uint8_t *code = NULL;
11 size_t code_size = 0;
12 AsmError asm_err;
13
14 AsmResult asm_res = assemble_file(filepath, &code, &code_size, &asm_err);
15 if (asm_res != ASM_OK) {
16 fprintf(stderr, "Assembly failed: %s (line %d: %s)\n",
17 asm_result_to_string(asm_res), asm_err.error_line, asm_err.error_msg);
18 assert(asm_res == ASM_OK);
19 }
20
21 char *buf = NULL;
22 size_t buf_size = 0;
23 FILE *out = open_memstream(&buf, &buf_size);
24
25 VM vm;
26 vm_init(&vm, code, code_size, out);
27 VMResult vm_res = vm_run(&vm);
28
29 fclose(out);
30
31 if (vm_res != VM_OK) {
32 fprintf(stderr, "VM execution failed: %s\n", vm_result_to_string(vm_res));
33 assert(vm_res == VM_OK);
34 }
35
36 assert(buf != NULL);
37 assert(strcmp(buf, expected_output) == 0);
38
39 free(buf);
40 free(code);
41 printf("PASSED\n");
42}
43
44static void test_vm_instructions(void) {
45 printf("Testing VM basic instructions ... ");
46 const char *src =
47 "PUSH 10\n"
48 "PUSH 20\n"
49 "DUP\n"
50 "PRINT\n"
51 "SWAP\n"
52 "PRINT\n"
53 "PRINT\n"
54 "PUSH 5\n"
55 "NEG\n"
56 "PRINT\n"
57 "PUSH 10\n"
58 "PUSH 10\n"
59 "EQ\n"
60 "PRINT\n"
61 "PUSH 5\n"
62 "PUSH 10\n"
63 "LT\n"
64 "PRINT\n"
65 "PUSH 5\n"
66 "PUSH 10\n"
67 "GT\n"
68 "PRINT\n"
69 "HALT\n";
70
71 uint8_t *code = NULL;
72 size_t code_size = 0;
73 AsmError asm_err;
74 AsmResult asm_res = assemble_string(src, &code, &code_size, &asm_err);
75 assert(asm_res == ASM_OK);
76
77 char *buf = NULL;
78 size_t buf_size = 0;
79 FILE *out = open_memstream(&buf, &buf_size);
80
81 VM vm;
82 vm_init(&vm, code, code_size, out);
83 VMResult vm_res = vm_run(&vm);
84 fclose(out);
85
86 assert(vm_res == VM_OK);
87 const char *expected = "20\n10\n20\n-5\n1\n1\n0\n";
88 assert(strcmp(buf, expected) == 0);
89
90 free(buf);
91 free(code);
92 printf("PASSED\n");
93}
94
95static void test_negative_stack_overflow(void) {
96 printf("Testing negative path: Stack overflow ... ");
97
98 size_t code_cap = 257 * 9 + 1;
99 uint8_t *code = malloc(code_cap);
100 size_t offset = 0;
101 int64_t val = 42;
102 for (int i = 0; i < 257; i++) {
103 code[offset++] = OP_PUSH;
104 memcpy(&code[offset], &val, 8);
105 offset += 8;
106 }
107 code[offset++] = OP_HALT;
108
109 VM vm;
110 vm_init(&vm, code, offset, NULL);
111 VMResult res = vm_run(&vm);
112 assert(res == VM_ERR_STACK_OVERFLOW);
113
114 free(code);
115 printf("PASSED (%s)\n", vm_result_to_string(res));
116}
117
118static void test_negative_stack_underflow(void) {
119 printf("Testing negative path: Stack underflow ... ");
120
121 uint8_t code[] = { OP_POP, OP_HALT };
122 VM vm;
123 vm_init(&vm, code, sizeof(code), NULL);
124 VMResult res = vm_run(&vm);
125 assert(res == VM_ERR_STACK_UNDERFLOW);
126
127
128 uint8_t code2[] = { OP_PUSH, 1,0,0,0,0,0,0,0, OP_ADD, OP_HALT };
129 vm_init(&vm, code2, sizeof(code2), NULL);
130 res = vm_run(&vm);
131 assert(res == VM_ERR_STACK_UNDERFLOW);
132
133 printf("PASSED (%s)\n", vm_result_to_string(res));
134}
135
136static void test_negative_call_stack_overflow(void) {
137 printf("Testing negative path: Call stack overflow ... ");
138
139 uint8_t code[] = { OP_CALL, 0, 0, 0, 0, OP_HALT };
140 VM vm;
141 vm_init(&vm, code, sizeof(code), NULL);
142 VMResult res = vm_run(&vm);
143 assert(res == VM_ERR_CALL_STACK_OVERFLOW);
144
145 printf("PASSED (%s)\n", vm_result_to_string(res));
146}
147
148static void test_negative_call_stack_underflow(void) {
149 printf("Testing negative path: Call stack underflow ... ");
150
151 uint8_t code[] = { OP_RET, OP_HALT };
152 VM vm;
153 vm_init(&vm, code, sizeof(code), NULL);
154 VMResult res = vm_run(&vm);
155 assert(res == VM_ERR_CALL_STACK_UNDERFLOW);
156
157 printf("PASSED (%s)\n", vm_result_to_string(res));
158}
159
160static void test_negative_division_by_zero(void) {
161 printf("Testing negative path: Division/Modulo by zero ... ");
162
163 const char *src_div = "PUSH 10\nPUSH 0\nDIV\nHALT\n";
164 uint8_t *code = NULL;
165 size_t code_size = 0;
166 AsmError asm_err;
167 AsmResult asm_res = assemble_string(src_div, &code, &code_size, &asm_err);
168 assert(asm_res == ASM_OK);
169
170 VM vm;
171 vm_init(&vm, code, code_size, NULL);
172 VMResult res = vm_run(&vm);
173 assert(res == VM_ERR_DIVISION_BY_ZERO);
174 free(code);
175
176
177 const char *src_mod = "PUSH 10\nPUSH 0\nMOD\nHALT\n";
178 asm_res = assemble_string(src_mod, &code, &code_size, &asm_err);
179 assert(asm_res == ASM_OK);
180
181 vm_init(&vm, code, code_size, NULL);
182 res = vm_run(&vm);
183 assert(res == VM_ERR_DIVISION_BY_ZERO);
184 free(code);
185
186 printf("PASSED (%s)\n", vm_result_to_string(res));
187}
188
189static void test_negative_invalid_opcode(void) {
190 printf("Testing negative path: Invalid opcode ... ");
191 uint8_t code[] = { 0xFF, OP_HALT };
192 VM vm;
193 vm_init(&vm, code, sizeof(code), NULL);
194 VMResult res = vm_run(&vm);
195 assert(res == VM_ERR_INVALID_OPCODE);
196
197 printf("PASSED (%s)\n", vm_result_to_string(res));
198}
199
200static void test_negative_invalid_jump(void) {
201 printf("Testing negative path: Invalid jump target ... ");
202
203 uint32_t bad_target = 1000;
204 uint8_t code[5];
205 code[0] = OP_JMP;
206 memcpy(&code[1], &bad_target, 4);
207
208 VM vm;
209 vm_init(&vm, code, sizeof(code), NULL);
210 VMResult res = vm_run(&vm);
211 assert(res == VM_ERR_INVALID_JUMP);
212
213 printf("PASSED (%s)\n", vm_result_to_string(res));
214}
215
216static void test_negative_invalid_local_index(void) {
217 printf("Testing negative path: Invalid local index ... ");
218
219 uint8_t code[] = { OP_LOAD, 16, OP_HALT };
220 VM vm;
221 vm_init(&vm, code, sizeof(code), NULL);
222 VMResult res = vm_run(&vm);
223 assert(res == VM_ERR_INVALID_LOCAL_INDEX);
224
225
226 uint8_t code2[] = { OP_PUSH, 1,0,0,0,0,0,0,0, OP_STORE, 16, OP_HALT };
227 vm_init(&vm, code2, sizeof(code2), NULL);
228 res = vm_run(&vm);
229 assert(res == VM_ERR_INVALID_LOCAL_INDEX);
230
231 printf("PASSED (%s)\n", vm_result_to_string(res));
232}
233
234static void test_assembler_negative_paths(void) {
235 printf("Testing Assembler negative error paths ... ");
236 uint8_t *code = NULL;
237 size_t code_size = 0;
238 AsmError err;
239
240
241 AsmResult res = assemble_string("FOOBAR 10\n", &code, &code_size, &err);
242 assert(res == ASM_ERR_UNKNOWN_INSTRUCTION);
243
244
245 res = assemble_string("PUSH\n", &code, &code_size, &err);
246 assert(res == ASM_ERR_INVALID_OPERAND);
247
248
249 res = assemble_string("JMP missing_label\n", &code, &code_size, &err);
250 assert(res == ASM_ERR_UNDEFINED_LABEL);
251
252
253 res = assemble_string("label:\nlabel:\nHALT\n", &code, &code_size, &err);
254 assert(res == ASM_ERR_DUPLICATE_LABEL);
255
256
257 res = assemble_string("STORE 20\n", &code, &code_size, &err);
258 assert(res == ASM_ERR_INVALID_OPERAND);
259
260 printf("PASSED\n");
261}
262
263int main(void) {
264 printf("=== RUNNING VM & ASSEMBLER TEST SUITE ===\n\n");
265
266
267 test_example_file("examples/fib.vasm", "6765\n");
268 test_example_file("examples/fact.vasm", "3628800\n");
269 test_example_file("examples/primes.vasm", "2\n3\n5\n7\n11\n13\n17\n19\n23\n29\n31\n37\n41\n43\n47\n");
270
271
272 test_vm_instructions();
273
274
275 test_negative_stack_overflow();
276 test_negative_stack_underflow();
277 test_negative_call_stack_overflow();
278 test_negative_call_stack_underflow();
279 test_negative_division_by_zero();
280 test_negative_invalid_opcode();
281 test_negative_invalid_jump();
282 test_negative_invalid_local_index();
283
284
285 test_assembler_negative_paths();
286
287 printf("\n=== ALL TESTS PASSED CLEANLY ===\n");
288 return 0;
289}
290
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.