620 lines
12 KiB
C
620 lines
12 KiB
C
/*
|
|
* Public domain / CC0. Use freely for any purpose. RoyR 2026
|
|
* test_runner.c - Test harness for Common compiler
|
|
*
|
|
* Build: gcc -std=c99 -o test_runner test_runner.c
|
|
* Usage: ./test_runner
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
const char *source;
|
|
int expected_exit;
|
|
const char *expected_output;
|
|
} Test;
|
|
|
|
static int test_count = 0;
|
|
static int test_passed = 0;
|
|
static int test_failed = 0;
|
|
|
|
static int run_command(const char *cmd) {
|
|
int status = system(cmd);
|
|
if (WIFEXITED(status)) {
|
|
return WEXITSTATUS(status);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static char *read_file(const char *path) {
|
|
FILE *f = fopen(path, "r");
|
|
if (!f) return NULL;
|
|
fseek(f, 0, SEEK_END);
|
|
long sz = ftell(f);
|
|
rewind(f);
|
|
char *buf = malloc(sz + 1);
|
|
fread(buf, 1, sz, f);
|
|
buf[sz] = 0;
|
|
fclose(f);
|
|
return buf;
|
|
}
|
|
|
|
static void run_test(Test *t) {
|
|
char cmd[1024];
|
|
test_count++;
|
|
|
|
printf("Test %d: %s ... ", test_count, t->name);
|
|
fflush(stdout);
|
|
|
|
/* Write source file */
|
|
FILE *f = fopen("/tmp/test.cm", "w");
|
|
if (!f) { printf("FAIL (cannot write source)\n"); test_failed++; return; }
|
|
fprintf(f, "%s", t->source);
|
|
fclose(f);
|
|
|
|
/* Compile */
|
|
if (run_command("./common /tmp/test.cm /tmp/test.asm 2>/tmp/test.err") != 0) {
|
|
printf("FAIL (compiler error)\n");
|
|
test_failed++;
|
|
return;
|
|
}
|
|
|
|
/* Assemble */
|
|
if (run_command("nasm -f elf32 /tmp/test.asm -o /tmp/test.o 2>/tmp/test.err") != 0) {
|
|
printf("FAIL (assembler error)\n");
|
|
test_failed++;
|
|
return;
|
|
}
|
|
|
|
/* Link */
|
|
if (run_command("./commonl /tmp/test.o -o /tmp/test 2>/tmp/test.err") != 0) {
|
|
printf("FAIL (linker error)\n");
|
|
test_failed++;
|
|
return;
|
|
}
|
|
|
|
/* Run and capture output */
|
|
int exit_code = run_command("/tmp/test > /tmp/test.out 2>&1");
|
|
|
|
/* Check exit code */
|
|
if (exit_code != t->expected_exit) {
|
|
printf("FAIL (exit=%d, expected=%d)\n", exit_code, t->expected_exit);
|
|
test_failed++;
|
|
return;
|
|
}
|
|
|
|
/* Check output if specified */
|
|
if (t->expected_output) {
|
|
char *output = read_file("/tmp/test.out");
|
|
if (!output) {
|
|
printf("FAIL (cannot read output)\n");
|
|
test_failed++;
|
|
return;
|
|
}
|
|
if (strcmp(output, t->expected_output) != 0) {
|
|
printf("FAIL (output mismatch)\n");
|
|
printf(" Expected: %s\n", t->expected_output);
|
|
printf(" Got: %s\n", output);
|
|
free(output);
|
|
test_failed++;
|
|
return;
|
|
}
|
|
free(output);
|
|
}
|
|
|
|
printf("PASS\n");
|
|
test_passed++;
|
|
}
|
|
|
|
/* ============================================
|
|
TEST CASES
|
|
============================================ */
|
|
|
|
Test tests[] = {
|
|
/* Basic arithmetic */
|
|
{
|
|
"simple_return",
|
|
"int32 main(void) { return 42; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"addition",
|
|
"int32 main(void) { return 10 + 32; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"subtraction",
|
|
"int32 main(void) { return 50 - 8; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"multiplication",
|
|
"int32 main(void) { return 6 * 7; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"division",
|
|
"int32 main(void) { return 84 / 2; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"modulo",
|
|
"int32 main(void) { return 142 % 100; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Variables */
|
|
{
|
|
"local_variable",
|
|
"int32 main(void) { int32 x; x = 42; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"variable_with_init",
|
|
"int32 main(void) { int32 x = 42; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"multiple_variables",
|
|
"int32 main(void) { int32 x = 10; int32 y = 32; return x + y; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Global variables */
|
|
{
|
|
"global_variable",
|
|
"int32 g = 42;\n"
|
|
"int32 main(void) { return g; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"global_read_write",
|
|
"int32 g;\n"
|
|
"int32 main(void) { g = 42; return g; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Control flow */
|
|
{
|
|
"if_true",
|
|
"int32 main(void) { if (1) return 42; return 0; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"if_false",
|
|
"int32 main(void) { if (0) return 0; return 42; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"if_else",
|
|
"int32 main(void) { if (0) return 0; else return 42; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"while_loop",
|
|
"int32 main(void) {\n"
|
|
" int32 x = 0;\n"
|
|
" while (x < 42) x = x + 1;\n"
|
|
" return x;\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"for_loop",
|
|
"int32 main(void) {\n"
|
|
" int32 sum = 0;\n"
|
|
" for (int32 i = 0; i < 10; i = i + 1) sum = sum + i;\n"
|
|
" return sum;\n"
|
|
"}",
|
|
45,
|
|
NULL
|
|
},
|
|
|
|
/* Comparisons */
|
|
{
|
|
"eq_true",
|
|
"int32 main(void) { return 42 == 42; }",
|
|
1,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"eq_false",
|
|
"int32 main(void) { return 42 == 43; }",
|
|
0,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"neq_true",
|
|
"int32 main(void) { return 42 != 43; }",
|
|
1,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"lt_true",
|
|
"int32 main(void) { return 10 < 42; }",
|
|
1,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"gt_true",
|
|
"int32 main(void) { return 42 > 10; }",
|
|
1,
|
|
NULL
|
|
},
|
|
|
|
/* Logical operators */
|
|
{
|
|
"and_true",
|
|
"int32 main(void) { return 1 && 1; }",
|
|
1,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"and_false",
|
|
"int32 main(void) { return 1 && 0; }",
|
|
0,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"or_true",
|
|
"int32 main(void) { return 0 || 1; }",
|
|
1,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"or_false",
|
|
"int32 main(void) { return 0 || 0; }",
|
|
0,
|
|
NULL
|
|
},
|
|
|
|
/* Bitwise operators */
|
|
{
|
|
"bitwise_and",
|
|
"int32 main(void) { return 63 & 42; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"bitwise_or",
|
|
"int32 main(void) { return 32 | 10; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"bitwise_xor",
|
|
"int32 main(void) { return 50 ^ 24; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"left_shift",
|
|
"int32 main(void) { return 21 << 1; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"right_shift",
|
|
"int32 main(void) { return 84 >> 1; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Unary operators */
|
|
{
|
|
"negation",
|
|
"int32 main(void) { return -(-42); }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"logical_not",
|
|
"int32 main(void) { return !0; }",
|
|
1,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"bitwise_not",
|
|
"int32 main(void) { return ~(-43); }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Increment/decrement */
|
|
{
|
|
"post_increment",
|
|
"int32 main(void) { int32 x = 41; int32 y = x++; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"pre_increment",
|
|
"int32 main(void) { int32 x = 41; int32 y = ++x; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"post_decrement",
|
|
"int32 main(void) { int32 x = 43; int32 y = x--; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Compound assignment */
|
|
{
|
|
"add_assign",
|
|
"int32 main(void) { int32 x = 10; x += 32; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"sub_assign",
|
|
"int32 main(void) { int32 x = 50; x -= 8; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Ternary operator */
|
|
{
|
|
"ternary_true",
|
|
"int32 main(void) { return 1 ? 42 : 0; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"ternary_false",
|
|
"int32 main(void) { return 0 ? 0 : 42; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Functions */
|
|
{
|
|
"function_call",
|
|
"int32 add(int32 a, int32 b) { return a + b; }\n"
|
|
"int32 main(void) { return add(10, 32); }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"recursive_function",
|
|
"int32 fib(int32 n) {\n"
|
|
" if (n <= 1) return n;\n"
|
|
" return fib(n - 1) + fib(n - 2);\n"
|
|
"}\n"
|
|
"int32 main(void) { return fib(10); }",
|
|
55,
|
|
NULL
|
|
},
|
|
|
|
/* Arrays */
|
|
{
|
|
"local_array",
|
|
"int32 main(void) {\n"
|
|
" int32 arr[5];\n"
|
|
" arr[0] = 42;\n"
|
|
" return arr[0];\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"array_init",
|
|
"int32 main(void) {\n"
|
|
" int32 arr[3] = { 10, 32, 99 };\n"
|
|
" return arr[0] + arr[1];\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"global_array",
|
|
"int32 arr[3] = { 10, 32, 99 };\n"
|
|
"int32 main(void) { return arr[0] + arr[1]; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Pointers */
|
|
{
|
|
"address_and_deref",
|
|
"int32 main(void) {\n"
|
|
" int32 x = 42;\n"
|
|
" int32 *p = &x;\n"
|
|
" return *p;\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"pointer_assignment",
|
|
"int32 main(void) {\n"
|
|
" int32 x = 0;\n"
|
|
" int32 *p = &x;\n"
|
|
" *p = 42;\n"
|
|
" return x;\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Type casting */
|
|
{
|
|
"cast_to_uint8",
|
|
"int32 main(void) { return (uint8)42; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"cast_truncate",
|
|
"int32 main(void) { return (uint8)298; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Different integer sizes */
|
|
{
|
|
"uint8_type",
|
|
"int32 main(void) { uint8 x = 42; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"uint16_type",
|
|
"int32 main(void) { uint16 x = 42; return x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"int8_signed",
|
|
"int32 main(void) { int8 x = -42; return -x; }",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Switch statement */
|
|
{
|
|
"switch_basic",
|
|
"int32 main(void) {\n"
|
|
" int32 x = 2;\n"
|
|
" switch (x) {\n"
|
|
" case 1: return 10;\n"
|
|
" case 2: return 42;\n"
|
|
" case 3: return 20;\n"
|
|
" }\n"
|
|
" return 0;\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"switch_default",
|
|
"int32 main(void) {\n"
|
|
" int32 x = 99;\n"
|
|
" switch (x) {\n"
|
|
" case 1: return 10;\n"
|
|
" default: return 42;\n"
|
|
" }\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* Break/continue */
|
|
{
|
|
"break_loop",
|
|
"int32 main(void) {\n"
|
|
" int32 x = 0;\n"
|
|
" while (1) {\n"
|
|
" x = x + 1;\n"
|
|
" if (x == 42) break;\n"
|
|
" }\n"
|
|
" return x;\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
{
|
|
"continue_loop",
|
|
"int32 main(void) {\n"
|
|
" int32 x = 0;\n"
|
|
" int32 sum = 0;\n"
|
|
" while (x < 50) {\n"
|
|
" x = x + 1;\n"
|
|
" if (x > 42) continue;\n"
|
|
" sum = sum + 1;\n"
|
|
" }\n"
|
|
" return sum;\n"
|
|
"}",
|
|
42,
|
|
NULL
|
|
},
|
|
|
|
/* End marker */
|
|
{ NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
int main(void) {
|
|
printf("Common Compiler Test Suite\n");
|
|
printf("===========================\n\n");
|
|
|
|
/* Check if compiler exists */
|
|
if (access("./common", X_OK) != 0) {
|
|
fprintf(stderr, "Error: ./common not found or not executable\n");
|
|
fprintf(stderr, "Please build it first: gcc -o common common.c\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Run all tests */
|
|
for (int i = 0; tests[i].name != NULL; i++) {
|
|
run_test(&tests[i]);
|
|
}
|
|
|
|
/* Summary */
|
|
printf("\n===========================\n");
|
|
printf("Total: %d\n", test_count);
|
|
printf("Passed: %d\n", test_passed);
|
|
printf("Failed: %d\n", test_failed);
|
|
printf("===========================\n");
|
|
|
|
return test_failed > 0 ? 1 : 0;
|
|
}
|