#include "../../unity/unity.h" #include #include #include #include /* Unity requires these, even if empty. */ void setUp(void) { /* Setup code here, or leave empty */ } void tearDown(void) { /* Cleanup code here, or leave empty */ } #ifdef EVAL_TRACE /* Helper: capture the output of trace(fxn) given a specific args[]. Returns a newly allocated NUL-terminated buffer on success, or NULL on error. Ensures stdout is restored before returning. Does not use Unity macros. */ static char *capture_trace_output(const char *fxn, char **test_args) { /* Save and set args for the duration of the call. */ char ***args_ptr = &args; /* args is from expr.c (static char **args) */ char **saved_args = *args_ptr; *args_ptr = test_args; /* Prepare a temporary FILE to redirect stdout. */ FILE *tmp = tmpfile(); if (!tmp) { *args_ptr = saved_args; return NULL; } fflush(stdout); int stdout_fd = fileno(stdout); int saved_fd = dup(stdout_fd); if (saved_fd == -1) { fclose(tmp); *args_ptr = saved_args; return NULL; } if (dup2(fileno(tmp), stdout_fd) == -1) { close(saved_fd); fclose(tmp); *args_ptr = saved_args; return NULL; } /* Call the function under test while stdout is redirected. */ trace((char *)fxn); /* Flush everything to the temp file. */ fflush(stdout); /* Determine size and read back. */ long end = ftell(tmp); if (end < 0) { /* Restore stdout before returning. */ dup2(saved_fd, stdout_fd); close(saved_fd); fclose(tmp); *args_ptr = saved_args; return NULL; } if (fseek(tmp, 0, SEEK_SET) != 0) { dup2(saved_fd, stdout_fd); close(saved_fd); fclose(tmp); *args_ptr = saved_args; return NULL; } char *buf = (char *)malloc((size_t)end + 1); if (!buf) { dup2(saved_fd, stdout_fd); close(saved_fd); fclose(tmp); *args_ptr = saved_args; return NULL; } size_t nread = fread(buf, 1, (size_t)end, tmp); buf[nread] = '\0'; /* Restore stdout and cleanup. */ fflush(stdout); dup2(saved_fd, stdout_fd); close(saved_fd); fclose(tmp); *args_ptr = saved_args; return buf; } static void assert_trace_equals(const char *fxn, char **test_args, const char *expected) { char *out = capture_trace_output(fxn, test_args); TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture trace output"); TEST_ASSERT_EQUAL_STRING(expected, out); free(out); } void test_trace_empty_args(void) { char *a0[] = { NULL }; assert_trace_equals("eval", a0, "eval:\n"); } void test_trace_single_arg(void) { char *a1[] = { "foo", NULL }; assert_trace_equals("walk", a1, "walk: foo\n"); } void test_trace_multiple_args(void) { char *a2[] = { "arg1", "arg2", "arg3", NULL }; assert_trace_equals("parse", a2, "parse: arg1 arg2 arg3\n"); } void test_trace_empty_string_arg(void) { char *a3[] = { "", "X", NULL }; /* Note the double space after ':' due to empty first argument. */ assert_trace_equals("step", a3, "step: X\n"); } void test_trace_utf8_args(void) { /* UTF-8: "\xCE\xB1" is Greek alpha, "\xE2\x9D\xA7" is U+2767. */ char *a4[] = { "\xCE\xB1bc", "\xE2\x9D\xA7", NULL }; assert_trace_equals("mb", a4, "mb: \xCE\xB1bc \xE2\x9D\xA7\n"); } #else /* !EVAL_TRACE */ void test_trace_unavailable(void) { TEST_IGNORE_MESSAGE("EVAL_TRACE not defined; trace() not compiled, skipping tests."); } #endif /* EVAL_TRACE */ int main(void) { UNITY_BEGIN(); #ifdef EVAL_TRACE RUN_TEST(test_trace_empty_args); RUN_TEST(test_trace_single_arg); RUN_TEST(test_trace_multiple_args); RUN_TEST(test_trace_empty_string_arg); RUN_TEST(test_trace_utf8_args); #else RUN_TEST(test_trace_unavailable); #endif return UNITY_END(); }