#include "../../unity/unity.h" #include #include #include #include #include /* The following globals/types are declared earlier in the cut.c TU and are used here: - struct field_range_pair - static unsigned char line_delim; - static char *output_delimiter_string; - static size_t output_delimiter_length; - static char output_delimiter_default[1]; - extern struct field_range_pair *frp; - static void cut_bytes (FILE *stream); */ static int write_bytes(FILE *f, const void *buf, size_t len) { if (len == 0) return 0; size_t nw = fwrite(buf, 1, len, f); return (nw == len) ? 0 : -1; } static struct field_range_pair *make_frp(const uintmax_t (*pairs)[2], size_t n_pairs) { /* Allocate n_pairs + 1 for a sentinel */ struct field_range_pair *arr = (struct field_range_pair *)malloc((n_pairs + 1) * sizeof(*arr)); TEST_ASSERT_NOT_NULL(arr); for (size_t i = 0; i < n_pairs; i++) { arr[i].lo = pairs[i][0]; arr[i].hi = pairs[i][1]; } /* Sentinel to avoid out-of-bounds access in helpers */ arr[n_pairs].lo = UINTMAX_MAX; arr[n_pairs].hi = UINTMAX_MAX; return arr; } static void free_frp(struct field_range_pair *arr) { free(arr); } static void set_output_delim_default(void) { /* Make pointer-equality match default so cut_bytes inserts no delimiters between ranges */ output_delimiter_string = output_delimiter_default; output_delimiter_length = 1; /* length value is not used when pointer == default */ } static void set_output_delim_custom(const char *s) { output_delimiter_string = (char *)s; output_delimiter_length = strlen(s); } /* Run cut_bytes on given input bytes with provided field range pairs. Capture stdout and compare to expected output. */ static void run_cut_bytes_case(const void *input, size_t in_len, const uintmax_t (*ranges)[2], size_t n_ranges, const void *expected, size_t exp_len, unsigned char line_delim_ch) { /* Configure line delimiter and ranges */ line_delim = line_delim_ch; struct field_range_pair *saved_frp = frp; struct field_range_pair *arr = make_frp(ranges, n_ranges); frp = arr; /* Prepare input stream */ FILE *in = tmpfile(); TEST_ASSERT_NOT_NULL(in); if (write_bytes(in, input, in_len) != 0) { TEST_FAIL_MESSAGE("Failed to write input to tmpfile"); } fflush(in); fseek(in, 0, SEEK_SET); /* Capture stdout */ fflush(stdout); int saved_stdout_fd = dup(fileno(stdout)); TEST_ASSERT_TRUE_MESSAGE(saved_stdout_fd >= 0, "dup(stdout) failed"); FILE *cap = tmpfile(); TEST_ASSERT_NOT_NULL(cap); int cap_fd = fileno(cap); TEST_ASSERT_TRUE_MESSAGE(cap_fd >= 0, "fileno(cap) failed"); if (dup2(cap_fd, fileno(stdout)) < 0) { /* Can't use TEST_ASSERT while redirected; but we haven't redirected yet. */ TEST_FAIL_MESSAGE("dup2 to redirect stdout failed"); } /* Call function under test while stdout is redirected */ cut_bytes(in); /* Flush and restore stdout BEFORE assertions */ fflush(stdout); if (dup2(saved_stdout_fd, fileno(stdout)) < 0) { /* Now stdout points to capture; error handling minimal */ /* Best effort: close and return */ } close(saved_stdout_fd); /* Read captured output */ fflush(cap); fseek(cap, 0, SEEK_END); long got_len_l = ftell(cap); TEST_ASSERT_TRUE_MESSAGE(got_len_l >= 0, "ftell on capture failed"); size_t got_len = (size_t)got_len_l; fseek(cap, 0, SEEK_SET); char *got = (char *)malloc(got_len ? got_len : 1); TEST_ASSERT_NOT_NULL(got); size_t nr = fread(got, 1, got_len, cap); TEST_ASSERT_EQUAL_SIZE_T(got_len, nr); /* Close files */ fclose(in); fclose(cap); /* Restore frp and free */ frp = saved_frp; free_frp(arr); /* Assertions */ TEST_ASSERT_EQUAL_SIZE_T(exp_len, got_len); if (exp_len == got_len) { TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, got, exp_len); } free(got); } void setUp(void) { /* Default to newline-terminated lines and default output delimiter. */ line_delim = '\n'; set_output_delim_default(); } void tearDown(void) { /* Nothing to clean up globally */ } /* Tests */ void test_cut_bytes_select_first_byte_simple_line(void) { const char *input = "abcdef\n"; const char *expected = "a\n"; const uintmax_t ranges[][2] = { {1,1} }; set_output_delim_default(); run_cut_bytes_case(input, strlen(input), ranges, 1, expected, strlen(expected), '\n'); } void test_cut_bytes_multi_ranges_no_custom_delim(void) { const char *input = "abcde\n"; /* Select bytes 1 and 3-4; default output delimiter => no insertion */ const char *expected = "acd\n"; const uintmax_t ranges[][2] = { {1,1}, {3,4} }; set_output_delim_default(); run_cut_bytes_case(input, strlen(input), ranges, 2, expected, strlen(expected), '\n'); } void test_cut_bytes_multi_ranges_with_custom_delim(void) { const char *input = "abcde\n"; /* Select bytes 1 and 3-4, with custom delimiter ',' between discontiguous ranges */ const char *expected = "a,cd\n"; const uintmax_t ranges[][2] = { {1,1}, {3,4} }; static const char comma[] = ","; set_output_delim_custom(comma); run_cut_bytes_case(input, strlen(input), ranges, 2, expected, strlen(expected), '\n'); } void test_cut_bytes_appends_newline_on_eof_without_newline(void) { const char input[] = { 'a','b','c' }; /* No trailing newline */ const char expected[] = { 'b','\n' }; /* Select byte 2; cut_bytes adds newline at EOF */ const uintmax_t ranges[][2] = { {2,2} }; set_output_delim_default(); run_cut_bytes_case(input, sizeof(input), ranges, 1, expected, sizeof(expected), '\n'); } void test_cut_bytes_zero_terminated_lines(void) { /* Two records: "ab\0" and "cde\0", select second byte of each record. */ const char input[] = { 'a','b','\0','c','d','e','\0' }; const char expected[] = { 'b','\0','d','\0' }; const uintmax_t ranges[][2] = { {2,2} }; set_output_delim_default(); run_cut_bytes_case(input, sizeof(input), ranges, 1, expected, sizeof(expected), '\0'); } void test_cut_bytes_resets_between_lines_and_inserts_delim(void) { /* Two lines: "abc\n" and "def\n", select bytes 1 and 3 with custom ":" between ranges. Expect "a:c\n" then "d:f\n". */ const char *input = "abc\ndef\n"; const char *expected = "a:c\nd:f\n"; const uintmax_t ranges[][2] = { {1,1}, {3,3} }; static const char colon[] = ":"; set_output_delim_custom(colon); run_cut_bytes_case(input, strlen(input), ranges, 2, expected, strlen(expected), '\n'); } void test_cut_bytes_empty_input_produces_no_output(void) { const char *input = ""; const char *expected = ""; const uintmax_t ranges[][2] = { {1,1} }; set_output_delim_default(); run_cut_bytes_case(input, 0, ranges, 1, expected, 0, '\n'); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_cut_bytes_select_first_byte_simple_line); RUN_TEST(test_cut_bytes_multi_ranges_no_custom_delim); RUN_TEST(test_cut_bytes_multi_ranges_with_custom_delim); RUN_TEST(test_cut_bytes_appends_newline_on_eof_without_newline); RUN_TEST(test_cut_bytes_zero_terminated_lines); RUN_TEST(test_cut_bytes_resets_between_lines_and_inserts_delim); RUN_TEST(test_cut_bytes_empty_input_produces_no_output); return UNITY_END(); }