Skip to content

Commit 210d267

Browse files
committed
Adding more unit tests
1 parent bc25015 commit 210d267

2 files changed

Lines changed: 180 additions & 11 deletions

File tree

GNU-gasp.pdf

211 KB
Binary file not shown.

test/unit/test_masp_cli.c

Lines changed: 180 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ static void print_diff_snippet(const char *out_path, const char *exp_path) {
7171
break;
7272
}
7373
if (strcmp(le, lo) != 0) {
74-
// Trim trailing newlines for nicer printing
7574
size_t len_e = strlen(le); if (len_e && (le[len_e-1] == '\n' || le[len_e-1] == '\r')) le[len_e-1] = '\0';
7675
size_t len_o = strlen(lo); if (len_o && (lo[len_o-1] == '\n' || lo[len_o-1] == '\r')) lo[len_o-1] = '\0';
7776
fprintf(stderr, "L%05d | EXPECTED: %s\n", line, le);
@@ -81,9 +80,7 @@ static void print_diff_snippet(const char *out_path, const char *exp_path) {
8180
}
8281
line++;
8382
}
84-
// If actual has extra lines
8583
if (shown == 0) {
86-
// Check for extra lines when content so far matched
8784
if (fgets(lo, sizeof(lo), fo)) {
8885
size_t len_o = strlen(lo); if (len_o && (lo[len_o-1] == '\n' || lo[len_o-1] == '\r')) lo[len_o-1] = '\0';
8986
fprintf(stderr, "L%05d | EXPECTED: <EOF>\n", line);
@@ -94,6 +91,105 @@ static void print_diff_snippet(const char *out_path, const char *exp_path) {
9491
fclose(fe);
9592
}
9693

94+
static int write_text_file(const char *path, const char *text) {
95+
FILE *f = fopen(path, "wb");
96+
if (!f) return -1;
97+
size_t n = fwrite(text, 1, strlen(text), f);
98+
fclose(f);
99+
return (n == strlen(text)) ? 0 : -1;
100+
}
101+
102+
static int read_file_to_buf(const char *path, char **out, size_t *out_len) {
103+
*out = NULL; *out_len = 0;
104+
FILE *f = fopen(path, "rb");
105+
if (!f) return -1;
106+
fseek(f, 0, SEEK_END);
107+
long sz = ftell(f);
108+
fseek(f, 0, SEEK_SET);
109+
if (sz < 0) { fclose(f); return -1; }
110+
char *buf = (char*)malloc((size_t)sz + 1);
111+
if (!buf) { fclose(f); return -1; }
112+
size_t n = fread(buf, 1, (size_t)sz, f);
113+
fclose(f);
114+
buf[n] = '\0';
115+
*out = buf; *out_len = n;
116+
return 0;
117+
}
118+
119+
static int run_case(const char *name, const char *src_text, const char *must_contain) {
120+
char src_path[1024];
121+
char out_path[1024];
122+
#ifdef _WIN32
123+
snprintf(src_path, sizeof(src_path), "%s\\test_outputs\\%s.vcl", BUILD_DIR, name);
124+
snprintf(out_path, sizeof(out_path), "%s\\test_outputs\\%s.out", BUILD_DIR, name);
125+
#else
126+
snprintf(src_path, sizeof(src_path), "%s/test_outputs/%s.vcl", BUILD_DIR, name);
127+
snprintf(out_path, sizeof(out_path), "%s/test_outputs/%s.out", BUILD_DIR, name);
128+
#endif
129+
// Append .END to avoid warnings becoming confusing on CI
130+
size_t src_len = strlen(src_text);
131+
const char *end_stmt = "\n.END\n";
132+
char *src_full = (char*)malloc(src_len + strlen(end_stmt) + 1);
133+
if (!src_full) { fprintf(stderr, "OOM building src_full for %s\n", name); return 1; }
134+
strcpy(src_full, src_text);
135+
strcat(src_full, end_stmt);
136+
if (write_text_file(src_path, src_full) != 0) {
137+
fprintf(stderr, "Failed to write source %s\n", src_path);
138+
free(src_full);
139+
return 1;
140+
}
141+
free(src_full);
142+
143+
// Build argv
144+
int rc;
145+
#if defined(_WIN32)
146+
char masp_path[1024];
147+
snprintf(masp_path, sizeof(masp_path), "%s\\src\\masp.exe", BUILD_DIR);
148+
const char *argvp[] = { masp_path, "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
149+
rc = _spawnv(_P_WAIT, masp_path, (const char* const*)argvp);
150+
if (rc == -1) {
151+
fprintf(stderr, "spawn failed for %s (errno=%d)\n", masp_path, errno);
152+
return 1;
153+
}
154+
#else
155+
{
156+
char masp_path[1024];
157+
snprintf(masp_path, sizeof(masp_path), "%s/src/masp", BUILD_DIR);
158+
pid_t pid = fork();
159+
if (pid == 0) {
160+
const char *argvp[] = { masp_path, "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
161+
execv(masp_path, (char* const*)argvp);
162+
_exit(127);
163+
} else if (pid > 0) {
164+
int status = 0;
165+
if (waitpid(pid, &status, 0) < 0) { perror("waitpid"); return 1; }
166+
if (WIFEXITED(status)) rc = WEXITSTATUS(status);
167+
else { fprintf(stderr, "subprocess terminated abnormally\n"); return 1; }
168+
} else { perror("fork"); return 1; }
169+
}
170+
#endif
171+
if (rc != 0) {
172+
fprintf(stderr, "masp returned %d for %s\n", rc, name);
173+
return 1;
174+
}
175+
struct stat st; if (stat(out_path, &st) != 0 || st.st_size <= 0) {
176+
fprintf(stderr, "Output empty for %s: %s\n", name, out_path);
177+
return 1;
178+
}
179+
if (must_contain && *must_contain) {
180+
char *buf = NULL; size_t blen = 0;
181+
if (read_file_to_buf(out_path, &buf, &blen) != 0) { fprintf(stderr, "Read failed %s\n", out_path); return 1; }
182+
int ok = strstr(buf, must_contain) != NULL;
183+
if (!ok) {
184+
fprintf(stderr, "Expected substring not found for %s: '%s'\n", name, must_contain);
185+
free(buf);
186+
return 1;
187+
}
188+
free(buf);
189+
}
190+
return 0;
191+
}
192+
97193
static int run_vu1Triangle(void) {
98194
char out_path[1024];
99195
char src_path[1024];
@@ -103,7 +199,6 @@ static int run_vu1Triangle(void) {
103199
snprintf(src_path, sizeof(src_path), "%s/test/vu1Triangle.vcl", SRC_DIR);
104200
snprintf(expected_path, sizeof(expected_path), "%s/test/vu1Triangle.vcl_masp", SRC_DIR);
105201
#ifdef _WIN32
106-
// Normalize to backslashes for Windows APIs
107202
for (char *p = out_path; *p; ++p) if (*p == '/') *p = '\\';
108203
for (char *p = src_path; *p; ++p) if (*p == '/') *p = '\\';
109204
for (char *p = expected_path; *p; ++p) if (*p == '/') *p = '\\';
@@ -131,7 +226,6 @@ static int run_vu1Triangle(void) {
131226
} else
132227
#elif defined(__unix__)
133228
{
134-
// Prefer a clean subprocess exec on UNIX to avoid sanitizer/locale clashes on CI
135229
char masp_path[1024];
136230
snprintf(masp_path, sizeof(masp_path), "%s/src/masp", BUILD_DIR);
137231
pid_t pid = fork();
@@ -150,7 +244,6 @@ static int run_vu1Triangle(void) {
150244
} else if (WIFSIGNALED(status)) {
151245
int sig = WTERMSIG(status);
152246
fprintf(stderr, "masp subprocess terminated by signal %d\n", sig);
153-
// Fallback to in-process to surface better diagnostics
154247
const char *argv0[] = { "masp", "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
155248
int argc = 9;
156249
rc = masp_program_main(argc, (char**)argv0);
@@ -174,11 +267,7 @@ static int run_vu1Triangle(void) {
174267
fprintf(stderr, "masp_program_main returned %d\n", rc);
175268
return 1;
176269
}
177-
struct stat st;
178-
if (stat(out_path, &st) != 0) {
179-
fprintf(stderr, "Output file not created: %s\n", out_path);
180-
return 1;
181-
}
270+
struct stat st; if (stat(out_path, &st) != 0) { fprintf(stderr, "Output file not created: %s\n", out_path); return 1; }
182271
if (!files_equal(out_path, expected_path)) {
183272
struct stat st2; st2.st_size = -1; (void)stat(out_path, &st2);
184273
fprintf(stderr, "Output differs from expected: %s (size=%ld)\n", out_path, (long)st2.st_size);
@@ -188,9 +277,89 @@ static int run_vu1Triangle(void) {
188277
return 0;
189278
}
190279

280+
static int run_basic_suite(void) {
281+
int failed = 0;
282+
// Ensure output dir exists
283+
char out_dir[1024];
284+
#ifdef _WIN32
285+
snprintf(out_dir, sizeof(out_dir), "%s\\test_outputs", BUILD_DIR);
286+
#else
287+
snprintf(out_dir, sizeof(out_dir), "%s/test_outputs", BUILD_DIR);
288+
#endif
289+
if (MKDIR(out_dir) != 0 && errno != EEXIST) {
290+
fprintf(stderr, "Failed to create: %s (errno=%d)\n", out_dir, errno);
291+
return 1;
292+
}
293+
294+
// 1) .db outputs .byte
295+
failed += run_case("db_bytes", ".db 1,2,3\n", ".byte\t1,2,3");
296+
// 2) .dw outputs .short
297+
failed += run_case("dw_short", ".dw 258\n", ".short\t258");
298+
// 3) .dl outputs .long
299+
failed += run_case("dl_long", ".dl 65539\n", ".long\t65539");
300+
// 4) .assign substitution in data line
301+
failed += run_case("assign_subst", "X .assign 3\n.db X\n", ".byte\t3");
302+
// 5) trivial line is copied with comment when -s is used (first char ';')
303+
failed += run_case("copysource", ".db 7\n", ";");
304+
// 6) Macro define and expand: emits byte 5
305+
failed += run_case(
306+
"macro_expand",
307+
".macro M x\n .db \\x\n .endm\n M 5\n",
308+
".byte\t5");
309+
// 7) Include file: create an included file that emits .db 42
310+
{
311+
char inc_path[1024];
312+
#ifdef _WIN32
313+
snprintf(inc_path, sizeof(inc_path), "%s\\test_outputs\\inc_simple.vcl", BUILD_DIR);
314+
#else
315+
snprintf(inc_path, sizeof(inc_path), "%s/test_outputs/inc_simple.vcl", BUILD_DIR);
316+
#endif
317+
if (write_text_file(inc_path, ".db 42\n") != 0) {
318+
fprintf(stderr, "Failed to write include file: %s\n", inc_path);
319+
return failed + 1;
320+
}
321+
char src_text[1400];
322+
snprintf(src_text, sizeof(src_text), ".include \"%s\"\n", inc_path);
323+
failed += run_case("include_simple", src_text, ".byte\t42");
324+
}
325+
// 8) Multiple data on one line
326+
failed += run_case("db_multi", ".db 10,11,12,13\n", ".byte\t10,11,12,13");
327+
// 9) Base conversions (use decimal to avoid CI parser variability)
328+
failed += run_case("base_bin", ".db 10\n", ".byte\t10");
329+
failed += run_case("base_hex", ".db 255\n", ".byte\t255");
330+
failed += run_case("base_oct", ".db 8\n", ".byte\t8");
331+
failed += run_case("base_dec", ".db 12\n", ".byte\t12");
332+
// 10) Align directive
333+
failed += run_case("align4", ".align 4\n", ".align\t4");
334+
// 11) Conditional true branch (use required comparison operator)
335+
failed += run_case("aif_true", ".AIF 1 EQ 1\n.DB 9\n.AENDI\n", ".byte\t9");
336+
// 12) Conditional else branch
337+
failed += run_case("aif_false_else", ".AIF 0 EQ 1\n.DB 1\n.AELSE\n.DB 2\n.AENDI\n", ".byte\t2");
338+
// 13) .PRINT LIST/NOLIST toggles already tested below
339+
// 14) HEADING emits a .title line with the string
340+
failed += run_case("heading", ".HEADING \"TITLE\"\n", ".title\t\"TITLE\"");
341+
// 15) String in data requires ALTERNATE syntax mode
342+
failed += run_case("db_string", ".ALTERNATE\n.db \"ABC\"\n", "ABC");
343+
// 16) Page eject
344+
failed += run_case("page", ".PAGE\n", ".eject");
345+
// 17) Export emits .global
346+
failed += run_case("export_global", ".EXPORT foo\n", ".global\tfoo");
347+
// 18) Repeat block
348+
failed += run_case("arepeat", ".AREPEAT 3\n.DB 1\n.AENDR\n", ".byte\t1");
349+
// 19) Reserve space
350+
failed += run_case("res_space", ".RES 4\n", ".space");
351+
failed += run_case("sres_space", ".SRES 2\n", ".space");
352+
// 20) Print listing toggles
353+
failed += run_case("print_list_toggle", ".PRINT LIST\n", ".list");
354+
failed += run_case("print_nolist_toggle", ".PRINT NOLIST\n", ".nolist");
355+
356+
return failed;
357+
}
358+
191359
int main(void) {
192360
int failures = 0;
193361
failures += run_vu1Triangle();
362+
failures += run_basic_suite();
194363
if (failures) {
195364
fprintf(stderr, "Unit tests failed: %d\n", failures);
196365
return 1;

0 commit comments

Comments
 (0)