Skip to content

Commit ab59f9f

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

6 files changed

Lines changed: 241 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: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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+
106+
// Ensure output directory exists (CI may not have it yet)
107+
snprintf(out_dir, sizeof(out_dir), "%s/test_outputs", BUILD_DIR);
108+
if (MKDIR(out_dir) != 0 && errno != EEXIST) {
109+
fprintf(stderr, "Failed to create output dir: %s (errno=%d)\n", out_dir, errno);
110+
return 1;
111+
}
112+
113+
int rc = 0;
114+
#if defined(_WIN32)
115+
if (1) {
116+
char masp_path[1024];
117+
snprintf(masp_path, sizeof(masp_path), "%s\\src\\masp.exe", BUILD_DIR);
118+
const char *argvp[] = { masp_path, "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
119+
int sprc = _spawnv(_P_WAIT, masp_path, (const char* const*)argvp);
120+
if (sprc == -1) {
121+
fprintf(stderr, "spawn failed for %s (errno=%d)\n", masp_path, errno);
122+
return 1;
123+
}
124+
rc = sprc;
125+
} else
126+
#elif defined(__unix__)
127+
{
128+
// Prefer a clean subprocess exec on UNIX to avoid sanitizer/locale clashes on CI
129+
char masp_path[1024];
130+
snprintf(masp_path, sizeof(masp_path), "%s/src/masp", BUILD_DIR);
131+
pid_t pid = fork();
132+
if (pid == 0) {
133+
const char *argvp[] = { masp_path, "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
134+
execv(masp_path, (char* const*)argvp);
135+
_exit(127);
136+
} else if (pid > 0) {
137+
int status = 0;
138+
if (waitpid(pid, &status, 0) < 0) {
139+
perror("waitpid");
140+
return 1;
141+
}
142+
if (WIFEXITED(status)) {
143+
rc = WEXITSTATUS(status);
144+
} else if (WIFSIGNALED(status)) {
145+
int sig = WTERMSIG(status);
146+
fprintf(stderr, "masp subprocess terminated by signal %d\n", sig);
147+
// Fallback to in-process to surface better diagnostics
148+
const char *argv0[] = { "masp", "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
149+
int argc = 9;
150+
rc = masp_program_main(argc, (char**)argv0);
151+
} else {
152+
fprintf(stderr, "masp subprocess terminated abnormally\n");
153+
return 1;
154+
}
155+
} else {
156+
perror("fork");
157+
return 1;
158+
}
159+
}
160+
#else
161+
{
162+
const char *argv0[] = { "masp", "-p", "-s", "-c", ";", "-o", out_path, "--", src_path, NULL };
163+
int argc = 9;
164+
rc = masp_program_main(argc, (char**)argv0);
165+
}
166+
#endif
167+
if (rc != 0) {
168+
fprintf(stderr, "masp_program_main returned %d\n", rc);
169+
return 1;
170+
}
171+
struct stat st;
172+
if (stat(out_path, &st) != 0) {
173+
fprintf(stderr, "Output file not created: %s\n", out_path);
174+
return 1;
175+
}
176+
if (!files_equal(out_path, expected_path)) {
177+
fprintf(stderr, "Output differs from expected: %s\n", out_path);
178+
print_diff_snippet(out_path, expected_path);
179+
return 1;
180+
}
181+
return 0;
182+
}
183+
184+
int main(void) {
185+
int failures = 0;
186+
failures += run_vu1Triangle();
187+
if (failures) {
188+
fprintf(stderr, "Unit tests failed: %d\n", failures);
189+
return 1;
190+
}
191+
printf("All unit tests passed.\n");
192+
return 0;
193+
}
194+
195+

0 commit comments

Comments
 (0)