#include "../../unity/unity.h" #include #include #include #include #include /* Helper that captures what write_pending writes to stdout. It performs stdout redirection, calls write_pending(outbuf, bpout), restores stdout, then reads and returns the captured data. On success, returns a malloc'd buffer (possibly empty but non-NULL) with its length in *out_len. On failure, returns NULL. Never uses Unity assertions while stdout is redirected. */ static char* capture_write_pending_output(char *outbuf, char **bpout, size_t *out_len) { int saved_stdout = -1; int p[2] = {-1, -1}; char *result = NULL; size_t result_len = 0; saved_stdout = dup(STDOUT_FILENO); if (saved_stdout < 0) return NULL; if (pipe(p) != 0) { close(saved_stdout); return NULL; } if (dup2(p[1], STDOUT_FILENO) < 0) { close(saved_stdout); close(p[0]); close(p[1]); return NULL; } close(p[1]); /* stdout now refers to the pipe's write end */ /* Call the target while stdout is redirected: no Unity asserts here. */ write_pending(outbuf, bpout); /* Restore stdout before any assertions. */ if (dup2(saved_stdout, STDOUT_FILENO) < 0) { /* Even if restore fails, attempt to close fds to minimize leaks. */ close(saved_stdout); close(p[0]); return NULL; } close(saved_stdout); /* Read all output from the pipe. */ { char tmp[1024]; ssize_t n; size_t cap = 0; while ((n = read(p[0], tmp, sizeof tmp)) > 0) { if (result_len + (size_t)n > cap) { size_t new_cap = cap ? cap * 2 : 1024; while (new_cap < result_len + (size_t)n) new_cap *= 2; char *new_buf = (char*)realloc(result, new_cap); if (!new_buf) { free(result); close(p[0]); return NULL; } result = new_buf; cap = new_cap; } memcpy(result + result_len, tmp, (size_t)n); result_len += (size_t)n; } close(p[0]); if (n < 0) { free(result); return NULL; } } /* Ensure non-NULL return even for zero-length capture, to simplify tests. */ if (!result) { result = (char*)malloc(1); if (!result) return NULL; } if (out_len) *out_len = result_len; return result; } void setUp(void) { /* No global setup needed */ } void tearDown(void) { /* No global teardown needed */ } /* Test: When there is no pending data (bpout == outbuf), nothing is written and bpout is unchanged. */ void test_write_pending_no_pending_data(void) { char outbuf[8] = {0}; char *bp = outbuf; /* no data pending */ size_t captured_len = 0; char *captured = capture_write_pending_output(outbuf, &bp, &captured_len); TEST_ASSERT_NOT_NULL_MESSAGE(captured, "Failed to capture output"); TEST_ASSERT_EQUAL_size_t(0, captured_len); TEST_ASSERT_EQUAL_PTR(outbuf, bp); /* pointer should remain unchanged */ free(captured); } /* Test: With pending data, exactly those bytes are written and bpout is reset to outbuf. */ void test_write_pending_writes_and_resets_pointer(void) { const char *msg = "Hello, world!"; size_t msg_len = strlen(msg); char outbuf[64]; memcpy(outbuf, msg, msg_len); char *bp = outbuf + msg_len; size_t captured_len = 0; char *captured = capture_write_pending_output(outbuf, &bp, &captured_len); TEST_ASSERT_NOT_NULL_MESSAGE(captured, "Failed to capture output"); TEST_ASSERT_EQUAL_size_t(msg_len, captured_len); TEST_ASSERT_EQUAL_MEMORY(msg, captured, msg_len); TEST_ASSERT_EQUAL_PTR(outbuf, bp); /* reset to start */ free(captured); } /* Test: Two independent flushes write their own content and reset bp each time. */ void test_write_pending_multiple_independent_flushes(void) { char outbuf[16]; /* First flush: "ABC" */ memcpy(outbuf, "ABC", 3); char *bp1 = outbuf + 3; size_t len1 = 0; char *cap1 = capture_write_pending_output(outbuf, &bp1, &len1); TEST_ASSERT_NOT_NULL_MESSAGE(cap1, "Failed to capture output (first)"); TEST_ASSERT_EQUAL_size_t(3, len1); TEST_ASSERT_EQUAL_MEMORY("ABC", cap1, 3); TEST_ASSERT_EQUAL_PTR(outbuf, bp1); /* Second flush: "DEF" */ memcpy(outbuf, "DEF", 3); char *bp2 = outbuf + 3; size_t len2 = 0; char *cap2 = capture_write_pending_output(outbuf, &bp2, &len2); TEST_ASSERT_NOT_NULL_MESSAGE(cap2, "Failed to capture output (second)"); TEST_ASSERT_EQUAL_size_t(3, len2); TEST_ASSERT_EQUAL_MEMORY("DEF", cap2, 3); TEST_ASSERT_EQUAL_PTR(outbuf, bp2); /* Combined logical result should be concatenation if viewed together. */ TEST_ASSERT_EQUAL_size_t(3, len1); TEST_ASSERT_EQUAL_size_t(3, len2); free(cap1); free(cap2); } /* Test: A moderately large write (8 KiB) succeeds and resets the pointer. */ void test_write_pending_large_payload(void) { const size_t N = 8192; /* keep below typical pipe capacity to avoid blocking */ char *outbuf = (char*)malloc(N); TEST_ASSERT_NOT_NULL(outbuf); for (size_t i = 0; i < N; i++) outbuf[i] = (char)('a' + (i % 26)); char *bp = outbuf + N; size_t captured_len = 0; char *captured = capture_write_pending_output(outbuf, &bp, &captured_len); TEST_ASSERT_NOT_NULL_MESSAGE(captured, "Failed to capture output"); TEST_ASSERT_EQUAL_size_t(N, captured_len); TEST_ASSERT_EQUAL_MEMORY(outbuf, captured, N); TEST_ASSERT_EQUAL_PTR(outbuf, bp); free(captured); free(outbuf); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_write_pending_no_pending_data); RUN_TEST(test_write_pending_writes_and_resets_pointer); RUN_TEST(test_write_pending_multiple_independent_flushes); RUN_TEST(test_write_pending_large_payload); return UNITY_END(); }