#include "../../unity/unity.h" #include #include #include #include #include /* Unity fixtures */ void setUp(void) { /* no-op */ } void tearDown(void) { /* no-op */ } /* Helper: capture output of writeline into outbuf. Returns NULL on success; otherwise returns a static error message string. This helper must not use Unity asserts because it redirects stdout. */ static const char* capture_writeline_output(const char* line_data, size_t line_len, int cls, bool enable_col1, bool enable_col2, bool enable_both, const char* sep, size_t sep_len, char* outbuf, size_t outcap, size_t* outlen) { static char errbuf[256]; /* Set the globals used by writeline. These are defined in the program source. */ only_file_1 = enable_col1; only_file_2 = enable_col2; both = enable_both; col_sep = sep; col_sep_len = sep_len; /* Prepare temporary sink for stdout. */ FILE* tmp = tmpfile(); if (!tmp) { snprintf(errbuf, sizeof errbuf, "tmpfile failed: %s", strerror(errno)); return errbuf; } int saved_fd = dup(fileno(stdout)); if (saved_fd < 0) { snprintf(errbuf, sizeof errbuf, "dup failed: %s", strerror(errno)); fclose(tmp); return errbuf; } if (fflush(stdout) != 0) { snprintf(errbuf, sizeof errbuf, "fflush(stdout) failed: %s", strerror(errno)); close(saved_fd); fclose(tmp); return errbuf; } if (dup2(fileno(tmp), fileno(stdout)) < 0) { snprintf(errbuf, sizeof errbuf, "dup2 failed: %s", strerror(errno)); close(saved_fd); fclose(tmp); return errbuf; } /* Construct the linebuffer expected by writeline. */ struct linebuffer lb; lb.buffer = (char*) line_data; lb.length = line_len; /* Call the function under test. */ writeline(&lb, cls); /* Flush and measure captured output. */ if (fflush(stdout) != 0) { snprintf(errbuf, sizeof errbuf, "fflush after writeline failed: %s", strerror(errno)); /* Attempt to restore stdout before returning. */ } long endpos = ftell(tmp); if (endpos < 0) { snprintf(errbuf, sizeof errbuf, "ftell failed: %s", strerror(errno)); /* Attempt to restore stdout before returning. */ } if (fseek(tmp, 0, SEEK_SET) != 0) { snprintf(errbuf, sizeof errbuf, "fseek failed: %s", strerror(errno)); /* Attempt to restore stdout before returning. */ } size_t toread = (endpos > 0) ? (size_t) endpos : 0; if (toread > outcap) { /* We will read only up to outcap to not overflow, but signal an error. */ toread = outcap; snprintf(errbuf, sizeof errbuf, "captured output larger than buffer"); } else { errbuf[0] = '\0'; /* No error so far */ } size_t nread = (toread > 0) ? fread(outbuf, 1, toread, tmp) : 0; if (nread != toread) { snprintf(errbuf, sizeof errbuf, "fread failed or short read: %s", ferror(tmp) ? strerror(errno) : "short read"); } /* Restore stdout. */ fflush(stdout); dup2(saved_fd, fileno(stdout)); close(saved_fd); fclose(tmp); *outlen = nread; if (errbuf[0] != '\0') { return errbuf; } return NULL; } /* Utility to check expected output easily. */ static void assert_capture_equals(const char* line, size_t line_len, int cls, bool c1, bool c2, bool cboth, const char* sep, size_t sep_len, const char* expected, size_t expected_len) { char buf[256]; size_t got_len = 0; const char* err = capture_writeline_output(line, line_len, cls, c1, c2, cboth, sep, sep_len, buf, sizeof buf, &got_len); TEST_ASSERT_NULL_MESSAGE(err, err ? err : "unexpected error capturing output"); TEST_ASSERT_EQUAL_UINT64(expected_len, got_len); TEST_ASSERT_EQUAL_MEMORY(expected, buf, expected_len); } /* Tests */ /* class=1: suppressed column -> no output */ void test_writeline_class1_suppressed_no_output(void) { const char line[] = "alpha\n"; assert_capture_equals(line, strlen(line), 1, false, /* only_file_1 disabled */ false, /* only_file_2 irrelevant */ false, /* both irrelevant */ "\t", 1, "", 0); } /* class=1: enabled -> prints line with no separators */ void test_writeline_class1_enabled_prints_line(void) { const char line[] = "alpha\n"; assert_capture_equals(line, strlen(line), 1, true, /* only_file_1 enabled */ false, false, "\t", 1, line, strlen(line)); } /* class=2: suppressed column -> no output */ void test_writeline_class2_suppressed_no_output(void) { const char line[] = "beta\n"; assert_capture_equals(line, strlen(line), 2, false, /* only_file_1 irrelevant for printing */ false, /* only_file_2 disabled */ false, "|", 1, "", 0); } /* class=2: enabled, col1 disabled -> no separator, just line */ void test_writeline_class2_enabled_no_col1_no_prefix(void) { const char line[] = "beta\n"; assert_capture_equals(line, strlen(line), 2, false, /* only_file_1 disabled -> no prefix */ true, /* only_file_2 enabled -> print */ false, "|", 1, line, strlen(line)); } /* class=2: enabled, col1 enabled -> one separator then line */ void test_writeline_class2_enabled_with_col1_prefix_once(void) { const char line[] = "beta\n"; const char sep[] = "|"; char expected[16]; size_t e_len = 0; memcpy(expected + e_len, sep, 1); e_len += 1; memcpy(expected + e_len, line, strlen(line)); e_len += strlen(line); assert_capture_equals(line, strlen(line), 2, true, /* only_file_1 enabled -> prefix once */ true, /* only_file_2 enabled -> print */ false, sep, strlen(sep), expected, e_len); } /* class=3: both enabled, neither col1 nor col2 -> no prefix, just line */ void test_writeline_class3_enabled_no_columns_no_prefix(void) { const char line[] = "gamma\n"; assert_capture_equals(line, strlen(line), 3, false, /* only_file_1 disabled */ false, /* only_file_2 disabled */ true, /* both enabled -> print */ "\t", 1, line, strlen(line)); } /* class=3: both enabled, only col1 enabled -> one prefix then line */ void test_writeline_class3_enabled_with_col1_one_prefix(void) { const char line[] = "delta\n"; const char sep[] = "+++"; char expected[32]; size_t e_len = 0; memcpy(expected + e_len, sep, strlen(sep)); e_len += strlen(sep); memcpy(expected + e_len, line, strlen(line)); e_len += strlen(line); assert_capture_equals(line, strlen(line), 3, true, /* only_file_1 enabled */ false, /* only_file_2 disabled */ true, /* both enabled */ sep, strlen(sep), expected, e_len); } /* class=3: both enabled, only col2 enabled -> one prefix then line */ void test_writeline_class3_enabled_with_col2_one_prefix(void) { const char line[] = "epsilon\n"; const char sep[] = "::"; char expected[32]; size_t e_len = 0; memcpy(expected + e_len, sep, strlen(sep)); e_len += strlen(sep); memcpy(expected + e_len, line, strlen(line)); e_len += strlen(line); assert_capture_equals(line, strlen(line), 3, false, /* only_file_1 disabled */ true, /* only_file_2 enabled */ true, /* both enabled */ sep, strlen(sep), expected, e_len); } /* class=3: both enabled, col1 and col2 enabled -> two prefixes then line */ void test_writeline_class3_enabled_with_both_columns_two_prefixes(void) { const char line[] = "zeta\n"; const char sep[] = "::"; char expected[64]; size_t e_len = 0; memcpy(expected + e_len, sep, strlen(sep)); e_len += strlen(sep); memcpy(expected + e_len, sep, strlen(sep)); e_len += strlen(sep); memcpy(expected + e_len, line, strlen(line)); e_len += strlen(line); assert_capture_equals(line, strlen(line), 3, true, /* only_file_1 enabled -> first prefix */ true, /* only_file_2 enabled -> second prefix */ true, /* both enabled -> print */ sep, strlen(sep), expected, e_len); } /* Ensure col_sep_len controls actual emitted bytes (sep non-empty but length 0 => no prefix) */ void test_writeline_separator_len_zero_emits_no_prefix(void) { const char line[] = "theta\n"; /* sep string is non-empty, but we pass sep_len=0 explicitly */ assert_capture_equals(line, strlen(line), 2, true, /* would normally cause prefix for class=2 */ true, /* print class 2 */ false, "XYZ", 0 /* col_sep_len zero */, line, strlen(line)); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_writeline_class1_suppressed_no_output); RUN_TEST(test_writeline_class1_enabled_prints_line); RUN_TEST(test_writeline_class2_suppressed_no_output); RUN_TEST(test_writeline_class2_enabled_no_col1_no_prefix); RUN_TEST(test_writeline_class2_enabled_with_col1_prefix_once); RUN_TEST(test_writeline_class3_enabled_no_columns_no_prefix); RUN_TEST(test_writeline_class3_enabled_with_col1_one_prefix); RUN_TEST(test_writeline_class3_enabled_with_col2_one_prefix); RUN_TEST(test_writeline_class3_enabled_with_both_columns_two_prefixes); RUN_TEST(test_writeline_separator_len_zero_emits_no_prefix); return UNITY_END(); }