#include "../../unity/unity.h" #include #include #include #include #include /* We rely on the program's static globals and the target function being visible here due to textual inclusion into the same translation unit. */ /* Helper to construct a line with given fields. Allocates memory for the fields and their contents. */ static void build_line(struct line *ln, const char *const *vals, const idx_t *lens, idx_t n) { memset(ln, 0, sizeof(*ln)); ln->nfields = n; ln->nfields_allocated = n; if (n == 0) { ln->fields = NULL; return; } ln->fields = (struct field *)malloc(sizeof(struct field) * n); for (idx_t i = 0; i < n; ++i) { idx_t L = lens ? lens[i] : (idx_t)strlen(vals[i]); char *buf = (char *)malloc((size_t)L); if (L > 0) { memcpy(buf, vals[i], (size_t)L); } ln->fields[i].beg = buf; ln->fields[i].len = L; } } /* Helper to free memory allocated by build_line. */ static void free_line(struct line *ln) { if (!ln) return; for (idx_t i = 0; i < ln->nfields; ++i) { free(ln->fields[i].beg); ln->fields[i].beg = NULL; ln->fields[i].len = 0; } free(ln->fields); ln->fields = NULL; ln->nfields = 0; ln->nfields_allocated = 0; } /* Capture stdout output produced by calling prfields(line, join_field, autocount). Returns a malloc'd NUL-terminated string on success, or NULL on failure. */ static char *capture_prfields_output(struct line const *line, idx_t join_field, idx_t autocount) { char *result = NULL; FILE *tmp = tmpfile(); if (!tmp) { return NULL; } int out_fd = fileno(stdout); int saved_fd = dup(out_fd); if (saved_fd < 0) { fclose(tmp); return NULL; } int tmp_fd = fileno(tmp); /* Redirect stdout to tmp. */ fflush(stdout); if (dup2(tmp_fd, out_fd) < 0) { close(saved_fd); fclose(tmp); return NULL; } /* Call the target function while stdout is redirected. */ prfields(line, join_field, autocount); /* Flush and read back the captured output. */ fflush(stdout); long size = 0; if (fseek(tmp, 0, SEEK_END) == 0) { long endpos = ftell(tmp); if (endpos >= 0) { size = endpos; (void)fseek(tmp, 0, SEEK_SET); } } if (size < 0) size = 0; result = (char *)malloc((size_t)size + 1); if (!result) { /* Restore stdout before returning. */ dup2(saved_fd, out_fd); close(saved_fd); fclose(tmp); return NULL; } if (size > 0) { size_t rd = fread(result, 1, (size_t)size, tmp); result[rd] = '\0'; if (rd != (size_t)size) { /* Restore stdout and report failure. */ dup2(saved_fd, out_fd); close(saved_fd); fclose(tmp); free(result); return NULL; } } else { result[0] = '\0'; } /* Restore stdout. */ dup2(saved_fd, out_fd); close(saved_fd); fclose(tmp); return result; } void setUp(void) { /* Default settings before each test. */ autoformat = false; output_separator = " "; output_seplen = 1; empty_filler = NULL; } void tearDown(void) { /* Nothing persistent to clean up across tests. */ } /* Test: basic behavior, skip the join field in the middle. */ static void test_prfields_basic_skip_join_middle(void) { const char *vals[] = { "A", "B", "C" }; struct line ln; build_line(&ln, vals, NULL, 3); output_separator = ","; output_seplen = (idx_t)strlen(output_separator); autoformat = false; empty_filler = NULL; char *out = capture_prfields_output(&ln, (idx_t)1, (idx_t)0); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING(",A,C", out); free(out); free_line(&ln); } /* Test: join_field out of range (greater than number of fields) -> no skip, all printed. */ static void test_prfields_join_field_out_of_range_high(void) { const char *vals[] = { "A", "B", "C" }; struct line ln; build_line(&ln, vals, NULL, 3); output_separator = ","; output_seplen = 1; autoformat = false; char *out = capture_prfields_output(&ln, (idx_t)5, (idx_t)0); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING(",A,B,C", out); free(out); free_line(&ln); } /* Test: no fields present -> no output. */ static void test_prfields_no_fields_no_output(void) { struct line ln; build_line(&ln, NULL, NULL, 0); output_separator = ","; output_seplen = 1; autoformat = false; char *out = capture_prfields_output(&ln, (idx_t)0, (idx_t)0); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("", out); free(out); free_line(&ln); } /* Test: empty field replaced by empty_filler when printed (non-join field). */ static void test_prfields_empty_field_with_empty_filler(void) { const char *vals[] = { "A", "", "C" }; idx_t lens[] = { 1, 0, 1 }; /* explicit lengths to create an empty field */ struct line ln; build_line(&ln, vals, lens, 3); output_separator = ","; output_seplen = 1; autoformat = false; empty_filler = ""; /* Use join_field = 0 so fields 1 and 2 are printed. */ char *out = capture_prfields_output(&ln, (idx_t)0, (idx_t)0); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING(",,C", out); free(out); free_line(&ln); } /* Test: autoformat extends beyond existing fields and uses empty_filler for missing ones. */ static void test_prfields_autoformat_extends_and_fills(void) { const char *vals[] = { "A", "B" }; struct line ln; build_line(&ln, vals, NULL, 2); output_separator = ","; output_seplen = 1; autoformat = true; empty_filler = ""; /* join_field = 0 -> print indices 1..3; ln has only index 1, so 2 and 3 use filler */ char *out = capture_prfields_output(&ln, (idx_t)0, (idx_t)4); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING(",B,,", out); free(out); free_line(&ln); } /* Test: autoformat limits output to autocount even if there are more fields; join field skipped. */ static void test_prfields_autoformat_limits(void) { const char *vals[] = { "A", "B", "C", "D" }; struct line ln; build_line(&ln, vals, NULL, 4); output_separator = ","; output_seplen = 1; autoformat = true; empty_filler = NULL; /* nfields considered = 3 (autocount). join_field = 2 => print 0 and 1 only. */ char *out = capture_prfields_output(&ln, (idx_t)2, (idx_t)3); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING(",A,B", out); free(out); free_line(&ln); } /* Test: custom multi-character separator honors output_seplen. */ static void test_prfields_custom_separator_multichar(void) { const char *vals[] = { "X", "Y" }; struct line ln; build_line(&ln, vals, NULL, 2); output_separator = "::"; output_seplen = (idx_t)strlen(output_separator); autoformat = false; /* join_field = 1 => print only field 0 */ char *out = capture_prfields_output(&ln, (idx_t)1, (idx_t)0); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("::X", out); free(out); free_line(&ln); } /* Test: autoformat with missing fields and no empty_filler prints only separators for those fields. */ static void test_prfields_autoformat_no_empty_filler_only_separators(void) { const char *vals[] = { "A" }; struct line ln; build_line(&ln, vals, NULL, 1); output_separator = ","; output_seplen = 1; autoformat = true; empty_filler = NULL; /* join_field = 0; autocount=3 -> attempt to print fields 1 and 2 (both missing): separators appear, but no filler text. */ char *out = capture_prfields_output(&ln, (idx_t)0, (idx_t)3); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING(",,", out); free(out); free_line(&ln); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_prfields_basic_skip_join_middle); RUN_TEST(test_prfields_join_field_out_of_range_high); RUN_TEST(test_prfields_no_fields_no_output); RUN_TEST(test_prfields_empty_field_with_empty_filler); RUN_TEST(test_prfields_autoformat_extends_and_fills); RUN_TEST(test_prfields_autoformat_limits); RUN_TEST(test_prfields_custom_separator_multichar); RUN_TEST(test_prfields_autoformat_no_empty_filler_only_separators); return UNITY_END(); }