Skip to content

Commit bc25015

Browse files
committed
Improve testing and output
1 parent a70a3db commit bc25015

6 files changed

Lines changed: 248 additions & 50 deletions

File tree

CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,17 @@ enable_testing()
1111

1212
add_subdirectory(src)
1313

14+
if(BUILD_TESTING)
15+
add_subdirectory(test)
16+
# Ensure invoking the test target compiles the app first
17+
if(TARGET test)
18+
add_dependencies(test masp)
19+
endif()
20+
# Provide a convenient 'check' target that builds and runs tests
21+
add_custom_target(check
22+
COMMAND ${CMAKE_CTEST_COMMAND} -C $<CONFIG> --output-on-failure
23+
DEPENDS masp
24+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
25+
)
26+
endif()
1427

cmake/RunMaspAndCompare.cmake

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/CMakeLists.txt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,4 @@ if(MINGW)
6262
target_link_libraries(masp PRIVATE gnurx)
6363
endif()
6464

65-
# Tests
66-
if(BUILD_TESTING)
67-
add_test(NAME masp_vu1Triangle
68-
COMMAND ${CMAKE_COMMAND}
69-
-Dmasp_exe=$<TARGET_FILE:masp>
70-
-Dsrc=${CMAKE_SOURCE_DIR}/test/vu1Triangle.vcl
71-
-Dexpected=${CMAKE_SOURCE_DIR}/test/vu1Triangle.vcl_masp
72-
-Dout=${CMAKE_BINARY_DIR}/test_outputs/vu1Triangle.vcl_masp
73-
-P ${CMAKE_SOURCE_DIR}/cmake/RunMaspAndCompare.cmake)
74-
endif()
65+
# Tests are defined in test/CMakeLists.txt

test/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# C unit tests
2+
add_subdirectory(unit)
3+
4+

test/unit/CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Minimal C unit test that calls masp's main in-process to avoid CLI parsing issues
2+
3+
add_executable(test_masp_cli
4+
${CMAKE_SOURCE_DIR}/test/unit/test_masp_cli.c
5+
${CMAKE_SOURCE_DIR}/src/hash.c
6+
${CMAKE_SOURCE_DIR}/src/macro.c
7+
${CMAKE_SOURCE_DIR}/src/sb.c
8+
${CMAKE_SOURCE_DIR}/src/compat.c
9+
)
10+
11+
target_include_directories(test_masp_cli PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src)
12+
13+
target_compile_definitions(test_masp_cli PRIVATE
14+
SRC_DIR="${CMAKE_SOURCE_DIR}"
15+
BUILD_DIR="${CMAKE_BINARY_DIR}"
16+
PACKAGE_VERSION="test"
17+
REPORT_BUGS_TO="ci"
18+
LOCALEDIR=""
19+
)
20+
21+
add_test(NAME masp_cli_unit COMMAND test_masp_cli)
22+
23+
# Windows (MinGW) needs POSIX regex library (libgnurx)
24+
if(MINGW)
25+
target_link_libraries(test_masp_cli PRIVATE gnurx)
26+
endif()
27+
28+

test/unit/test_masp_cli.c

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <sys/stat.h>
5+
#include <errno.h>
6+
#ifdef _WIN32
7+
#include <direct.h>
8+
#include <process.h>
9+
#define MKDIR(p) _mkdir(p)
10+
#else
11+
#include <sys/types.h>
12+
#include <unistd.h>
13+
#include <sys/wait.h>
14+
#define MKDIR(p) mkdir((p), 0777)
15+
#endif
16+
17+
#ifndef SRC_DIR
18+
#define SRC_DIR "."
19+
#endif
20+
#ifndef BUILD_DIR
21+
#define BUILD_DIR "."
22+
#endif
23+
24+
// Avoid symbol clash: rename masp main before including the implementation
25+
#define main masp_program_main
26+
#include "../../src/masp.c"
27+
#undef main
28+
29+
static int files_equal(const char *a, const char *b) {
30+
FILE *fa = fopen(a, "rb");
31+
FILE *fb = fopen(b, "rb");
32+
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return 0; }
33+
int eq = 1;
34+
const size_t BUFSZ = 8192;
35+
unsigned char *ba = (unsigned char*)malloc(BUFSZ);
36+
unsigned char *bb = (unsigned char*)malloc(BUFSZ);
37+
if (!ba || !bb) { eq = 0; goto done; }
38+
size_t ra, rb;
39+
do {
40+
ra = fread(ba, 1, BUFSZ, fa);
41+
rb = fread(bb, 1, BUFSZ, fb);
42+
if (ra != rb || memcmp(ba, bb, ra) != 0) { eq = 0; break; }
43+
} while (ra > 0 && rb > 0);
44+
done:
45+
if (ba) free(ba);
46+
if (bb) free(bb);
47+
fclose(fa);
48+
fclose(fb);
49+
return eq;
50+
}
51+
52+
static void print_diff_snippet(const char *out_path, const char *exp_path) {
53+
FILE *fo = fopen(out_path, "rb");
54+
FILE *fe = fopen(exp_path, "rb");
55+
if (!fo || !fe) {
56+
fprintf(stderr, "(diff) unable to open files for diff: out=%s exp=%s\n", out_path, exp_path);
57+
if (fo) fclose(fo);
58+
if (fe) fclose(fe);
59+
return;
60+
}
61+
char lo[4096];
62+
char le[4096];
63+
int line = 1;
64+
int shown = 0;
65+
fprintf(stderr, "===== Diff (expected vs actual) =====\n");
66+
while (fgets(le, sizeof(le), fe)) {
67+
char *po = fgets(lo, sizeof(lo), fo);
68+
if (!po) {
69+
fprintf(stderr, "L%05d | EXPECTED: %sL%05d | ACTUAL : <EOF>\n", line, le, line);
70+
shown++;
71+
break;
72+
}
73+
if (strcmp(le, lo) != 0) {
74+
// Trim trailing newlines for nicer printing
75+
size_t len_e = strlen(le); if (len_e && (le[len_e-1] == '\n' || le[len_e-1] == '\r')) le[len_e-1] = '\0';
76+
size_t len_o = strlen(lo); if (len_o && (lo[len_o-1] == '\n' || lo[len_o-1] == '\r')) lo[len_o-1] = '\0';
77+
fprintf(stderr, "L%05d | EXPECTED: %s\n", line, le);
78+
fprintf(stderr, "L%05d | ACTUAL : %s\n", line, lo);
79+
shown++;
80+
if (shown >= 50) break;
81+
}
82+
line++;
83+
}
84+
// If actual has extra lines
85+
if (shown == 0) {
86+
// Check for extra lines when content so far matched
87+
if (fgets(lo, sizeof(lo), fo)) {
88+
size_t len_o = strlen(lo); if (len_o && (lo[len_o-1] == '\n' || lo[len_o-1] == '\r')) lo[len_o-1] = '\0';
89+
fprintf(stderr, "L%05d | EXPECTED: <EOF>\n", line);
90+
fprintf(stderr, "L%05d | ACTUAL : %s\n", line, lo);
91+
}
92+
}
93+
fclose(fo);
94+
fclose(fe);
95+
}
96+
97+
static int run_vu1Triangle(void) {
98+
char out_path[1024];
99+
char src_path[1024];
100+
char expected_path[1024];
101+
char out_dir[1024];
102+
snprintf(out_path, sizeof(out_path), "%s/test_outputs/masp_vu1Triangle.unit.out", BUILD_DIR);
103+
snprintf(src_path, sizeof(src_path), "%s/test/vu1Triangle.vcl", SRC_DIR);
104+
snprintf(expected_path, sizeof(expected_path), "%s/test/vu1Triangle.vcl_masp", SRC_DIR);
105+
#ifdef _WIN32
106+
// Normalize to backslashes for Windows APIs
107+
for (char *p = out_path; *p; ++p) if (*p == '/') *p = '\\';
108+
for (char *p = src_path; *p; ++p) if (*p == '/') *p = '\\';
109+
for (char *p = expected_path; *p; ++p) if (*p == '/') *p = '\\';
110+
#endif
111+
112+
// Ensure output directory exists (CI may not have it yet)
113+
snprintf(out_dir, sizeof(out_dir), "%s/test_outputs", BUILD_DIR);
114+
if (MKDIR(out_dir) != 0 && errno != EEXIST) {
115+
fprintf(stderr, "Failed to create output dir: %s (errno=%d)\n", out_dir, errno);
116+
return 1;
117+
}
118+
119+
int rc = 0;
120+
#if defined(_WIN32)
121+
if (1) {
122+
char masp_path[1024];
123+
snprintf(masp_path, sizeof(masp_path), "%s\\src\\masp.exe", BUILD_DIR);
124+
const char *argvp[] = { masp_path, "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
125+
int sprc = _spawnv(_P_WAIT, masp_path, (const char* const*)argvp);
126+
if (sprc == -1) {
127+
fprintf(stderr, "spawn failed for %s (errno=%d)\n", masp_path, errno);
128+
return 1;
129+
}
130+
rc = sprc;
131+
} else
132+
#elif defined(__unix__)
133+
{
134+
// Prefer a clean subprocess exec on UNIX to avoid sanitizer/locale clashes on CI
135+
char masp_path[1024];
136+
snprintf(masp_path, sizeof(masp_path), "%s/src/masp", BUILD_DIR);
137+
pid_t pid = fork();
138+
if (pid == 0) {
139+
const char *argvp[] = { masp_path, "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
140+
execv(masp_path, (char* const*)argvp);
141+
_exit(127);
142+
} else if (pid > 0) {
143+
int status = 0;
144+
if (waitpid(pid, &status, 0) < 0) {
145+
perror("waitpid");
146+
return 1;
147+
}
148+
if (WIFEXITED(status)) {
149+
rc = WEXITSTATUS(status);
150+
} else if (WIFSIGNALED(status)) {
151+
int sig = WTERMSIG(status);
152+
fprintf(stderr, "masp subprocess terminated by signal %d\n", sig);
153+
// Fallback to in-process to surface better diagnostics
154+
const char *argv0[] = { "masp", "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
155+
int argc = 9;
156+
rc = masp_program_main(argc, (char**)argv0);
157+
} else {
158+
fprintf(stderr, "masp subprocess terminated abnormally\n");
159+
return 1;
160+
}
161+
} else {
162+
perror("fork");
163+
return 1;
164+
}
165+
}
166+
#else
167+
{
168+
const char *argv0[] = { "masp", "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
169+
int argc = 9;
170+
rc = masp_program_main(argc, (char**)argv0);
171+
}
172+
#endif
173+
if (rc != 0) {
174+
fprintf(stderr, "masp_program_main returned %d\n", rc);
175+
return 1;
176+
}
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+
}
182+
if (!files_equal(out_path, expected_path)) {
183+
struct stat st2; st2.st_size = -1; (void)stat(out_path, &st2);
184+
fprintf(stderr, "Output differs from expected: %s (size=%ld)\n", out_path, (long)st2.st_size);
185+
print_diff_snippet(out_path, expected_path);
186+
return 1;
187+
}
188+
return 0;
189+
}
190+
191+
int main(void) {
192+
int failures = 0;
193+
failures += run_vu1Triangle();
194+
if (failures) {
195+
fprintf(stderr, "Unit tests failed: %d\n", failures);
196+
return 1;
197+
}
198+
printf("All unit tests passed.\n");
199+
return 0;
200+
}
201+
202+

0 commit comments

Comments
 (0)