| #include "../../unity/unity.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <ctype.h> |
| #include <locale.h> |
|
|
| |
| |
| |
| |
| |
|
|
| static char *capture_print_ascii(const unsigned char *bytes, |
| idx_t fields, idx_t blank, |
| int width, idx_t pad) |
| { |
| |
| FILE *tmp = tmpfile(); |
| if (!tmp) return NULL; |
|
|
| if (fflush(stdout) != 0) { fclose(tmp); return NULL; } |
|
|
| int out_fd = fileno(stdout); |
| int saved_fd = dup(out_fd); |
| if (saved_fd < 0) { fclose(tmp); return NULL; } |
|
|
| if (dup2(fileno(tmp), out_fd) < 0) { |
| int e = errno; (void)e; |
| close(saved_fd); |
| fclose(tmp); |
| return NULL; |
| } |
|
|
| |
| print_ascii(fields, blank, bytes, NULL, width, pad); |
|
|
| |
| fflush(stdout); |
|
|
| |
| if (dup2(saved_fd, out_fd) < 0) { |
| |
| close(saved_fd); |
| fclose(tmp); |
| return NULL; |
| } |
| close(saved_fd); |
|
|
| |
| if (fflush(tmp) != 0) { fclose(tmp); return NULL; } |
| if (fseek(tmp, 0, SEEK_END) != 0) { fclose(tmp); return NULL; } |
| long len = ftell(tmp); |
| if (len < 0) { fclose(tmp); return NULL; } |
| if (fseek(tmp, 0, SEEK_SET) != 0) { fclose(tmp); return NULL; } |
|
|
| char *buf = (char *)malloc((size_t)len + 1); |
| if (!buf) { fclose(tmp); return NULL; } |
| size_t nread = fread(buf, 1, (size_t)len, tmp); |
| buf[nread] = '\0'; |
| fclose(tmp); |
| return buf; |
| } |
|
|
| static void append_str(char **dst, size_t *cap, size_t *len, const char *s) |
| { |
| size_t sl = strlen(s); |
| if (*len + sl + 1 > *cap) { |
| size_t ncap = (*cap ? *cap : 64); |
| while (*len + sl + 1 > ncap) ncap *= 2; |
| char *nbuf = (char *)realloc(*dst, ncap); |
| if (!nbuf) { |
| free(*dst); |
| *dst = NULL; |
| *cap = *len = 0; |
| return; |
| } |
| *dst = nbuf; |
| *cap = ncap; |
| } |
| memcpy(*dst + *len, s, sl); |
| *len += sl; |
| (*dst)[*len] = '\0'; |
| } |
|
|
| static void append_spaces(char **dst, size_t *cap, size_t *len, int n) |
| { |
| if (n <= 0) return; |
| if (*len + (size_t)n + 1 > *cap) { |
| size_t ncap = (*cap ? *cap : 64); |
| while (*len + (size_t)n + 1 > ncap) ncap *= 2; |
| char *nbuf = (char *)realloc(*dst, ncap); |
| if (!nbuf) { |
| free(*dst); |
| *dst = NULL; |
| *cap = *len = 0; |
| return; |
| } |
| *dst = nbuf; |
| *cap = ncap; |
| } |
| memset(*dst + *len, ' ', (size_t)n); |
| *len += (size_t)n; |
| (*dst)[*len] = '\0'; |
| } |
|
|
| static const char *ascii_repr(unsigned char c, char tmp[4]) |
| { |
| switch (c) { |
| case '\0': return "\\0"; |
| case '\a': return "\\a"; |
| case '\b': return "\\b"; |
| case '\f': return "\\f"; |
| case '\n': return "\\n"; |
| case '\r': return "\\r"; |
| case '\t': return "\\t"; |
| case '\v': return "\\v"; |
| default: |
| if (isprint(c)) { |
| tmp[0] = (char)c; |
| tmp[1] = '\0'; |
| } else { |
| |
| sprintf(tmp, "%03o", c); |
| } |
| return tmp; |
| } |
| } |
|
|
| static char *build_expected_for_bytes(const unsigned char *bytes, |
| idx_t fields, idx_t blank, |
| int width, idx_t pad) |
| { |
| size_t cap = 0, len = 0; |
| char *out = NULL; |
|
|
| idx_t pad_remaining = pad; |
| idx_t j = 0; |
|
|
| for (idx_t i = fields; blank < i; i--) { |
| char tmp[4]; |
| const char *s = ascii_repr(bytes[j++], tmp); |
| size_t slen = strlen(s); |
| idx_t next_pad = pad_at(fields, i - 1, pad); |
| int adjusted_width = (int)(pad_remaining - next_pad) + width; |
| int spaces = adjusted_width - (int)slen; |
| if (spaces < 0) spaces = 0; |
|
|
| append_spaces(&out, &cap, &len, spaces); |
| if (!out) return NULL; |
| append_str(&out, &cap, &len, s); |
| if (!out) return NULL; |
|
|
| pad_remaining = next_pad; |
| } |
|
|
| if (!out) { |
| out = (char *)malloc(1); |
| if (out) out[0] = '\0'; |
| } |
| return out; |
| } |
|
|
| void setUp(void) |
| { |
| |
| setlocale(LC_ALL, "C"); |
| } |
|
|
| void tearDown(void) |
| { |
| } |
|
|
| void test_print_ascii_single_printable(void) |
| { |
| unsigned char bytes[] = { 'A' }; |
| char *got = capture_print_ascii(bytes, 1, 0, 3, 0); |
| TEST_ASSERT_NOT_NULL(got); |
|
|
| |
| TEST_ASSERT_EQUAL_STRING(" A", got); |
| free(got); |
| } |
|
|
| void test_print_ascii_octal_nonprintable(void) |
| { |
| unsigned char bytes[] = { 1u, 31u, 127u }; |
| char *got = capture_print_ascii(bytes, 3, 0, 3, 0); |
| TEST_ASSERT_NOT_NULL(got); |
|
|
| |
| TEST_ASSERT_EQUAL_STRING("001037177", got); |
| free(got); |
| } |
|
|
| void test_print_ascii_escapes(void) |
| { |
| unsigned char bytes[] = { '\0', '\a', '\b', '\f', '\n', '\r', '\t', '\v' }; |
| idx_t fields = 8; |
| int width = 3; |
| idx_t pad = 0; |
|
|
| char *got = capture_print_ascii(bytes, fields, 0, width, pad); |
| TEST_ASSERT_NOT_NULL(got); |
|
|
| char *expected = build_expected_for_bytes(bytes, fields, 0, width, pad); |
| TEST_ASSERT_NOT_NULL(expected); |
|
|
| TEST_ASSERT_EQUAL_STRING(expected, got); |
|
|
| free(expected); |
| free(got); |
| } |
|
|
| void test_print_ascii_blank_fields(void) |
| { |
| unsigned char bytes[] = { 'A', 'B', 'C', 'D' }; |
| |
| char *got = capture_print_ascii(bytes, 4, 2, 3, 0); |
| TEST_ASSERT_NOT_NULL(got); |
| TEST_ASSERT_EQUAL_STRING(" A B", got); |
| free(got); |
| } |
|
|
| void test_print_ascii_padding_distribution(void) |
| { |
| unsigned char bytes[] = { 'A', 'B', 'C', 'D' }; |
| idx_t fields = 4; |
| idx_t blank = 0; |
| int width = 3; |
| idx_t pad = 10; |
|
|
| char *got = capture_print_ascii(bytes, fields, blank, width, pad); |
| TEST_ASSERT_NOT_NULL(got); |
|
|
| char *expected = build_expected_for_bytes(bytes, fields, blank, width, pad); |
| TEST_ASSERT_NOT_NULL(expected); |
|
|
| TEST_ASSERT_EQUAL_STRING(expected, got); |
|
|
| |
| |
| idx_t pad_remaining = pad; |
| int widths[4]; |
| for (idx_t i = fields, k = 0; i > blank; i--, k++) { |
| idx_t next_pad = pad_at(fields, i - 1, pad); |
| int adjusted_width = (int)(pad_remaining - next_pad) + width; |
| widths[k] = adjusted_width; |
| pad_remaining = next_pad; |
| } |
| |
| int spaces_before[4] = {0,0,0,0}; |
| const char *p = got; |
| for (int idx = 0; idx < 4; idx++) { |
| int count = 0; |
| while (*p == ' ') { count++; p++; } |
| |
| |
| if (*p == '\0') break; |
| p++; |
| spaces_before[idx] = count; |
| } |
| |
| TEST_ASSERT_EQUAL_INT(widths[0] - 1, spaces_before[0]); |
| TEST_ASSERT_EQUAL_INT(widths[1] - 1, spaces_before[1]); |
| TEST_ASSERT_EQUAL_INT(widths[2] - 1, spaces_before[2]); |
| TEST_ASSERT_EQUAL_INT(widths[3] - 1, spaces_before[3]); |
|
|
| free(expected); |
| free(got); |
| } |
|
|
| void test_print_ascii_width_less_than_repr(void) |
| { |
| unsigned char bytes[] = { '\n' }; |
| |
| char *got = capture_print_ascii(bytes, 1, 0, 1, 0); |
| TEST_ASSERT_NOT_NULL(got); |
| TEST_ASSERT_EQUAL_STRING("\\n", got); |
| free(got); |
| } |
|
|
| int main(void) |
| { |
| UNITY_BEGIN(); |
| RUN_TEST(test_print_ascii_single_printable); |
| RUN_TEST(test_print_ascii_octal_nonprintable); |
| RUN_TEST(test_print_ascii_escapes); |
| RUN_TEST(test_print_ascii_blank_fields); |
| RUN_TEST(test_print_ascii_padding_distribution); |
| RUN_TEST(test_print_ascii_width_less_than_repr); |
| return UNITY_END(); |
| } |