File size: 7,153 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
#include "../../unity/unity.h"
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

/* Globals from cut.c are visible since this file is included into cut.c */
extern struct field_range_pair *frp;

/* Helper: build selection ranges and set frp.
   'pairs' is an array of {lo, hi} with 'count' entries.
   Returns the allocated array to be freed by the caller. */
static struct field_range_pair* set_frp_from_pairs(const uintmax_t (*pairs)[2], size_t count) {
  struct field_range_pair *arr = (struct field_range_pair*) malloc(sizeof(*arr) * (count + 1));
  if (!arr) return NULL;
  for (size_t i = 0; i < count; i++) {
    arr[i].lo = pairs[i][0];
    arr[i].hi = pairs[i][1];
  }
  /* Sentinel to ensure no stepping beyond valid ranges. */
  arr[count].lo = UINTMAX_MAX;
  arr[count].hi = UINTMAX_MAX;
  frp = arr;
  return arr;
}

/* Helper: run cut_fields on provided input string with given configuration,
   capturing stdout and returning the captured output in a malloc'ed string.
   The caller must free the returned string.
   out_delim_str==NULL => use default (input delimiter). */
static char* run_cut_fields_capture(
    const char *input,
    const uintmax_t (*pairs)[2], size_t npairs,
    unsigned char in_delim,
    const char *out_delim_str,
    bool only_delimited)
{
  /* Configure globals used by cut_fields. */
  suppress_non_delimited = only_delimited;

  /* Set input delimiter. */
  delim = in_delim;

  /* Configure default and actual output delimiter. */
  output_delimiter_default[0] = delim;
  if (out_delim_str == NULL) {
    output_delimiter_string = output_delimiter_default;
    output_delimiter_length = 1;
  } else {
    output_delimiter_string = (char*)out_delim_str;
    output_delimiter_length = strlen(out_delim_str);
  }

  /* Ensure line delimiter is standard newline for these tests. */
  line_delim = '\n';

  /* Set selection ranges. */
  struct field_range_pair *local_frp = set_frp_from_pairs(pairs, npairs);
  if (!local_frp) {
    /* Allocation failure – return a copy of empty string to avoid NULL. */
    char *empty = (char*)malloc(1);
    if (empty) empty[0] = '\0';
    return empty;
  }

  /* Prepare input stream. */
  FILE *in = tmpfile();
  if (!in) {
    free(local_frp);
    char *empty = (char*)malloc(1);
    if (empty) empty[0] = '\0';
    return empty;
  }
  if (input && *input) {
    fwrite(input, 1, strlen(input), in);
  }
  fflush(in);
  fseek(in, 0, SEEK_SET);

  /* Capture stdout to a temporary file. */
  fflush(stdout);
  int saved_stdout_fd = dup(fileno(stdout));
  FILE *cap = tmpfile();
  if (cap == NULL) {
    fclose(in);
    free(local_frp);
    char *empty = (char*)malloc(1);
    if (empty) empty[0] = '\0';
    return empty;
  }
  dup2(fileno(cap), fileno(stdout));
  clearerr(stdout);

  /* Call target function. */
  cut_fields(in);

  /* Stop capturing and restore stdout. */
  fflush(stdout);
  dup2(saved_stdout_fd, fileno(stdout));
  close(saved_stdout_fd);

  /* Read captured output into a buffer. */
  fflush(cap);
  fseek(cap, 0, SEEK_END);
  long sz = ftell(cap);
  if (sz < 0) sz = 0;
  fseek(cap, 0, SEEK_SET);
  char *out = (char*)malloc((size_t)sz + 1);
  if (!out) {
    out = (char*)malloc(1);
    if (out) out[0] = '\0';
  } else {
    size_t nread = fread(out, 1, (size_t)sz, cap);
    out[nread] = '\0';
  }

  /* Cleanup. */
  fclose(cap);
  fclose(in);
  free(local_frp);

  return out;
}

void setUp(void) {
  /* Ensure a clean starting state for globals influenced by previous calls. */
  suppress_non_delimited = false;
  /* Default delimiters */
  delim = '\t';
  line_delim = '\n';
  output_delimiter_default[0] = delim;
  output_delimiter_string = output_delimiter_default;
  output_delimiter_length = 1;
}

void tearDown(void) {
  /* Release buffer possibly allocated by cut_fields. */
  if (field_1_buffer) {
    free(field_1_buffer);
    field_1_buffer = NULL;
    field_1_bufsize = 0;
  }
}

/* Test: Basic selection with default delimiter (TAB): select fields 2-3. */
void test_cut_fields_basic_selection_default_delim(void) {
  const char *input = "a\tb\tc\n1\t2\t3\n";
  const uintmax_t pairs[][2] = { {2,3} };
  char *out = run_cut_fields_capture(input, pairs, 1, '\t', NULL, false);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("b\tc\n2\t3\n", out);
  free(out);
}

/* Test: Non-delimited line without -s should be printed unchanged. */
void test_cut_fields_nondelimited_without_s(void) {
  const char *input = "abc\n";
  const uintmax_t pairs[][2] = { {2,2} }; /* select field 2 */
  char *out = run_cut_fields_capture(input, pairs, 1, ',', NULL, false);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("abc\n", out);
  free(out);
}

/* Test: Non-delimited line with -s should be suppressed entirely. */
void test_cut_fields_nondelimited_with_s(void) {
  const char *input = "abc\n";
  const uintmax_t pairs[][2] = { {1,1} }; /* select field 1 */
  char *out = run_cut_fields_capture(input, pairs, 1, ',', NULL, true);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("", out);
  free(out);
}

/* Test: Custom output delimiter between selected fields (select 1 and 3). */
void test_cut_fields_custom_output_delimiter(void) {
  const char *input = "a,b,c\nx,y,z\n";
  const uintmax_t pairs[][2] = { {1,1}, {3,3} };
  char *out = run_cut_fields_capture(input, pairs, 2, ',', ":", false);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("a:c\nx:z\n", out);
  free(out);
}

/* Test: Line has delimiters but not enough fields; output should be empty line(s). */
void test_cut_fields_missing_field_produces_empty_line(void) {
  const char *input = "a,b\nx,y\n";
  const uintmax_t pairs[][2] = { {3,3} }; /* third field does not exist */
  char *out = run_cut_fields_capture(input, pairs, 1, ',', NULL, false);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("\n\n", out);
  free(out);
}

/* Test: Input without trailing newline – ensure output is newline-terminated. */
void test_cut_fields_no_trailing_newline(void) {
  const char *input = "a,b,c";
  const uintmax_t pairs[][2] = { {2,2} }; /* select field 2 */
  char *out = run_cut_fields_capture(input, pairs, 1, ',', NULL, false);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("b\n", out);
  free(out);
}

/* Test: Empty selected field (adjacent delimiters) should yield empty output for that line. */
void test_cut_fields_empty_selected_field(void) {
  const char *input = "a,,c\n";
  const uintmax_t pairs[][2] = { {2,2} }; /* select empty field 2 */
  char *out = run_cut_fields_capture(input, pairs, 1, ',', NULL, false);
  TEST_ASSERT_NOT_NULL(out);
  TEST_ASSERT_EQUAL_STRING("\n", out);
  free(out);
}

int main(void) {
  UNITY_BEGIN();
  RUN_TEST(test_cut_fields_basic_selection_default_delim);
  RUN_TEST(test_cut_fields_nondelimited_without_s);
  RUN_TEST(test_cut_fields_nondelimited_with_s);
  RUN_TEST(test_cut_fields_custom_output_delimiter);
  RUN_TEST(test_cut_fields_missing_field_produces_empty_line);
  RUN_TEST(test_cut_fields_no_trailing_newline);
  RUN_TEST(test_cut_fields_empty_selected_field);
  return UNITY_END();
}