File size: 8,055 Bytes
78d2150 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
/* 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();
} |