@@ -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,96 @@ 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+ if (write_text_file (src_path , src_text ) != 0 ) {
130+ fprintf (stderr , "Failed to write source %s\n" , src_path );
131+ return 1 ;
132+ }
133+
134+ // Build argv
135+ int rc ;
136+ #if defined(_WIN32 )
137+ char masp_path [1024 ];
138+ snprintf (masp_path , sizeof (masp_path ), "%s\\src\\masp.exe" , BUILD_DIR );
139+ const char * argvp [] = { masp_path , "-p" , "-s" , "-c" , ";" , "-o" , out_path , "--" , src_path , NULL };
140+ rc = _spawnv (_P_WAIT , masp_path , (const char * const * )argvp );
141+ if (rc == -1 ) {
142+ fprintf (stderr , "spawn failed for %s (errno=%d)\n" , masp_path , errno );
143+ return 1 ;
144+ }
145+ #else
146+ {
147+ char masp_path [1024 ];
148+ snprintf (masp_path , sizeof (masp_path ), "%s/src/masp" , BUILD_DIR );
149+ pid_t pid = fork ();
150+ if (pid == 0 ) {
151+ const char * argvp [] = { masp_path , "-p" , "-s" , "-c" , ";" , "-o" , out_path , "--" , src_path , NULL };
152+ execv (masp_path , (char * const * )argvp );
153+ _exit (127 );
154+ } else if (pid > 0 ) {
155+ int status = 0 ;
156+ if (waitpid (pid , & status , 0 ) < 0 ) { perror ("waitpid" ); return 1 ; }
157+ if (WIFEXITED (status )) rc = WEXITSTATUS (status );
158+ else { fprintf (stderr , "subprocess terminated abnormally\n" ); return 1 ; }
159+ } else { perror ("fork" ); return 1 ; }
160+ }
161+ #endif
162+ if (rc != 0 ) {
163+ fprintf (stderr , "masp returned %d for %s\n" , rc , name );
164+ return 1 ;
165+ }
166+ struct stat st ; if (stat (out_path , & st ) != 0 || st .st_size <= 0 ) {
167+ fprintf (stderr , "Output empty for %s: %s\n" , name , out_path );
168+ return 1 ;
169+ }
170+ if (must_contain && * must_contain ) {
171+ char * buf = NULL ; size_t blen = 0 ;
172+ if (read_file_to_buf (out_path , & buf , & blen ) != 0 ) { fprintf (stderr , "Read failed %s\n" , out_path ); return 1 ; }
173+ int ok = strstr (buf , must_contain ) != NULL ;
174+ if (!ok ) {
175+ fprintf (stderr , "Expected substring not found for %s: '%s'\n" , name , must_contain );
176+ free (buf );
177+ return 1 ;
178+ }
179+ free (buf );
180+ }
181+ return 0 ;
182+ }
183+
97184static int run_vu1Triangle (void ) {
98185 char out_path [1024 ];
99186 char src_path [1024 ];
@@ -103,7 +190,6 @@ static int run_vu1Triangle(void) {
103190 snprintf (src_path , sizeof (src_path ), "%s/test/vu1Triangle.vcl" , SRC_DIR );
104191 snprintf (expected_path , sizeof (expected_path ), "%s/test/vu1Triangle.vcl_masp" , SRC_DIR );
105192#ifdef _WIN32
106- // Normalize to backslashes for Windows APIs
107193 for (char * p = out_path ; * p ; ++ p ) if (* p == '/' ) * p = '\\' ;
108194 for (char * p = src_path ; * p ; ++ p ) if (* p == '/' ) * p = '\\' ;
109195 for (char * p = expected_path ; * p ; ++ p ) if (* p == '/' ) * p = '\\' ;
@@ -131,7 +217,6 @@ static int run_vu1Triangle(void) {
131217 } else
132218#elif defined(__unix__ )
133219 {
134- // Prefer a clean subprocess exec on UNIX to avoid sanitizer/locale clashes on CI
135220 char masp_path [1024 ];
136221 snprintf (masp_path , sizeof (masp_path ), "%s/src/masp" , BUILD_DIR );
137222 pid_t pid = fork ();
@@ -150,7 +235,6 @@ static int run_vu1Triangle(void) {
150235 } else if (WIFSIGNALED (status )) {
151236 int sig = WTERMSIG (status );
152237 fprintf (stderr , "masp subprocess terminated by signal %d\n" , sig );
153- // Fallback to in-process to surface better diagnostics
154238 const char * argv0 [] = { "masp" , "-p" , "-s" , "-c" , ";" , "-o" , out_path , "--" , src_path , NULL };
155239 int argc = 9 ;
156240 rc = masp_program_main (argc , (char * * )argv0 );
@@ -174,11 +258,7 @@ static int run_vu1Triangle(void) {
174258 fprintf (stderr , "masp_program_main returned %d\n" , rc );
175259 return 1 ;
176260 }
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- }
261+ struct stat st ; if (stat (out_path , & st ) != 0 ) { fprintf (stderr , "Output file not created: %s\n" , out_path ); return 1 ; }
182262 if (!files_equal (out_path , expected_path )) {
183263 struct stat st2 ; st2 .st_size = -1 ; (void )stat (out_path , & st2 );
184264 fprintf (stderr , "Output differs from expected: %s (size=%ld)\n" , out_path , (long )st2 .st_size );
@@ -188,9 +268,90 @@ static int run_vu1Triangle(void) {
188268 return 0 ;
189269}
190270
271+ static int run_basic_suite (void ) {
272+ int failed = 0 ;
273+ // Ensure output dir exists
274+ char out_dir [1024 ];
275+ #ifdef _WIN32
276+ snprintf (out_dir , sizeof (out_dir ), "%s\\test_outputs" , BUILD_DIR );
277+ #else
278+ snprintf (out_dir , sizeof (out_dir ), "%s/test_outputs" , BUILD_DIR );
279+ #endif
280+ if (MKDIR (out_dir ) != 0 && errno != EEXIST ) {
281+ fprintf (stderr , "Failed to create: %s (errno=%d)\n" , out_dir , errno );
282+ return 1 ;
283+ }
284+
285+ // 1) .db outputs .byte
286+ failed += run_case ("db_bytes" , ".db 1,2,3\n" , ".byte\t1,2,3" );
287+ // 2) .dw outputs .short
288+ failed += run_case ("dw_short" , ".dw 258\n" , ".short\t258" );
289+ // 3) .dl outputs .long
290+ failed += run_case ("dl_long" , ".dl 65539\n" , ".long\t65539" );
291+ // 4) .assign substitution in data line
292+ failed += run_case ("assign_subst" , "X .assign 3\n.db X\n" , ".byte\t3" );
293+ // 5) trivial line is copied with comment when -s is used (first char ';')
294+ failed += run_case ("copysource" , ".db 7\n" , ";" );
295+ // 6) Macro define and expand: emits byte 5
296+ failed += run_case (
297+ "macro_expand" ,
298+ ".macro M x\n .db \\x\n .endm\n M 5\n" ,
299+ ".byte\t5" );
300+ // 7) Include file: create an included file that emits .db 42
301+ {
302+ char inc_path [1024 ];
303+ #ifdef _WIN32
304+ snprintf (inc_path , sizeof (inc_path ), "%s\\test_outputs\\inc_simple.vcl" , BUILD_DIR );
305+ #else
306+ snprintf (inc_path , sizeof (inc_path ), "%s/test_outputs/inc_simple.vcl" , BUILD_DIR );
307+ #endif
308+ if (write_text_file (inc_path , ".db 42\n" ) != 0 ) {
309+ fprintf (stderr , "Failed to write include file: %s\n" , inc_path );
310+ return failed + 1 ;
311+ }
312+ char src_text [1400 ];
313+ snprintf (src_text , sizeof (src_text ), ".include \"%s\"\n" , inc_path );
314+ failed += run_case ("include_simple" , src_text , ".byte\t42" );
315+ }
316+ // 8) Multiple data on one line
317+ failed += run_case ("db_multi" , ".db 10,11,12,13\n" , ".byte\t10,11,12,13" );
318+ // 9) Base conversions: binary, hex, octal, decimal
319+ failed += run_case ("base_bin" , ".db b'1010'\n" , ".byte\t10" );
320+ failed += run_case ("base_hex" , ".db h'FF'\n" , ".byte\t255" );
321+ failed += run_case ("base_oct" , ".db q'10'\n" , ".byte\t8" );
322+ failed += run_case ("base_dec" , ".db d'12'\n" , ".byte\t12" );
323+ // 10) Align directive
324+ failed += run_case ("align4" , ".align 4\n" , ".align\t4" );
325+ // 11) Conditional true branch
326+ failed += run_case ("aif_true" , ".AIF 1\n.DB 9\n.AENDI\n" , ".byte\t9" );
327+ // 12) Conditional else branch
328+ failed += run_case ("aif_false_else" , ".AIF 0\n.DB 1\n.AELSE\n.DB 2\n.AENDI\n" , ".byte\t2" );
329+ // 13) PRINT outputs string literal
330+ failed += run_case ("print_str" , ".PRINT \"HELLO\"\n" , "HELLO" );
331+ // 14) HEADING emits a .title line with the string
332+ failed += run_case ("heading" , ".HEADING \"TITLE\"\n" , ".title\t\"TITLE\"" );
333+ // 15) String in data
334+ failed += run_case ("db_string" , ".db \"ABC\"\n" , "ABC" );
335+ // 16) Page eject
336+ failed += run_case ("page" , ".PAGE\n" , ".eject" );
337+ // 17) Export emits .global
338+ failed += run_case ("export_global" , ".EXPORT foo\n" , ".global\tfoo" );
339+ // 18) Repeat block
340+ failed += run_case ("arepeat" , ".AREPEAT 3\n.DB 1\n.AENDR\n" , ".byte\t1" );
341+ // 19) Reserve space
342+ failed += run_case ("res_space" , ".RES 4\n" , ".space" );
343+ failed += run_case ("sres_space" , ".SRES 2\n" , ".space" );
344+ // 20) Print listing toggles
345+ failed += run_case ("print_list_toggle" , ".PRINT LIST\n" , ".list" );
346+ failed += run_case ("print_nolist_toggle" , ".PRINT NOLIST\n" , ".nolist" );
347+
348+ return failed ;
349+ }
350+
191351int main (void ) {
192352 int failures = 0 ;
193353 failures += run_vu1Triangle ();
354+ failures += run_basic_suite ();
194355 if (failures ) {
195356 fprintf (stderr , "Unit tests failed: %d\n" , failures );
196357 return 1 ;
0 commit comments