|
|
#include "../../unity/unity.h" |
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <errno.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool fmt (FILE *f, char const *file); |
|
|
static void set_prefix (char *p); |
|
|
|
|
|
|
|
|
|
|
|
static void test_set_width(int w) |
|
|
{ |
|
|
|
|
|
|
|
|
max_width = w; |
|
|
goal_width = (int)((long long)w * (100 - LEEWAY) / 100); |
|
|
if (goal_width <= 0) goal_width = w; |
|
|
} |
|
|
|
|
|
static void test_reset_options(void) |
|
|
{ |
|
|
|
|
|
crown = false; |
|
|
tagged = false; |
|
|
split = false; |
|
|
uniform = false; |
|
|
|
|
|
|
|
|
prefix = ""; |
|
|
prefix_full_length = 0; |
|
|
prefix_lead_space = 0; |
|
|
prefix_length = 0; |
|
|
|
|
|
|
|
|
in_column = 0; |
|
|
out_column = 0; |
|
|
tabs = false; |
|
|
prefix_indent = 0; |
|
|
first_indent = 0; |
|
|
other_indent = 0; |
|
|
next_char = '\0'; |
|
|
next_prefix_indent = 0; |
|
|
last_line_length = 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char *run_fmt_and_capture(const char *input, const char *file_label, bool *out_ok) |
|
|
{ |
|
|
|
|
|
FILE *in = tmpfile(); |
|
|
if (!in) return NULL; |
|
|
size_t in_len = strlen(input); |
|
|
if (in_len && fwrite(input, 1, in_len, in) != in_len) { |
|
|
fclose(in); |
|
|
return NULL; |
|
|
} |
|
|
fflush(in); |
|
|
rewind(in); |
|
|
|
|
|
|
|
|
FILE *cap = tmpfile(); |
|
|
if (!cap) { |
|
|
fclose(in); |
|
|
return NULL; |
|
|
} |
|
|
int saved_stdout_fd = dup(fileno(stdout)); |
|
|
if (saved_stdout_fd < 0) { |
|
|
fclose(in); |
|
|
fclose(cap); |
|
|
return NULL; |
|
|
} |
|
|
fflush(stdout); |
|
|
if (dup2(fileno(cap), fileno(stdout)) < 0) { |
|
|
fclose(in); |
|
|
fclose(cap); |
|
|
close(saved_stdout_fd); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
|
|
|
bool ok = fmt(in, file_label); |
|
|
|
|
|
|
|
|
fflush(stdout); |
|
|
if (dup2(saved_stdout_fd, fileno(stdout)) < 0) { |
|
|
|
|
|
} |
|
|
close(saved_stdout_fd); |
|
|
|
|
|
|
|
|
fflush(cap); |
|
|
fseek(cap, 0, SEEK_END); |
|
|
long cap_len = ftell(cap); |
|
|
if (cap_len < 0) cap_len = 0; |
|
|
fseek(cap, 0, SEEK_SET); |
|
|
|
|
|
char *buf = (char *)malloc((size_t)cap_len + 1); |
|
|
if (!buf) { |
|
|
fclose(cap); |
|
|
return NULL; |
|
|
} |
|
|
size_t nread = fread(buf, 1, (size_t)cap_len, cap); |
|
|
buf[nread] = '\0'; |
|
|
fclose(cap); |
|
|
|
|
|
if (out_ok) *out_ok = ok; |
|
|
return buf; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static char *normalize_words(const char *s) |
|
|
{ |
|
|
size_t len = strlen(s); |
|
|
|
|
|
char *out = (char *)malloc(len + 1); |
|
|
if (!out) return NULL; |
|
|
size_t oi = 0; |
|
|
int in_word = 0; |
|
|
for (size_t i = 0; i < len; i++) { |
|
|
unsigned char c = (unsigned char)s[i]; |
|
|
int is_ws = (c == ' ' || c == '\t' || c == '\n' || c == '\r'); |
|
|
if (!is_ws) { |
|
|
out[oi++] = c; |
|
|
in_word = 1; |
|
|
} else { |
|
|
if (in_word) { |
|
|
out[oi++] = ' '; |
|
|
in_word = 0; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (oi > 0 && out[oi-1] == ' ') |
|
|
oi--; |
|
|
out[oi] = '\0'; |
|
|
return out; |
|
|
} |
|
|
|
|
|
|
|
|
static size_t line_visible_length(const char *start, const char *end) |
|
|
{ |
|
|
|
|
|
size_t n = 0; |
|
|
for (const char *p = start; p < end; p++) { |
|
|
if (*p == '\n') break; |
|
|
n++; |
|
|
} |
|
|
return n; |
|
|
} |
|
|
|
|
|
|
|
|
void setUp(void) { |
|
|
test_reset_options(); |
|
|
test_set_width(80); |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void test_fmt_basic_no_wrap(void) |
|
|
{ |
|
|
test_reset_options(); |
|
|
test_set_width(80); |
|
|
|
|
|
const char *input = |
|
|
"Hello world\n" |
|
|
"Second line\n" |
|
|
"\n" |
|
|
"Third paragraph line\n"; |
|
|
|
|
|
bool ok = false; |
|
|
char *out = run_fmt_and_capture(input, "basic_no_wrap", &ok); |
|
|
|
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture fmt output"); |
|
|
TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed for simple input"); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_STRING(input, out); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
void test_fmt_wrap_lines_constraints(void) |
|
|
{ |
|
|
test_reset_options(); |
|
|
test_set_width(10); |
|
|
|
|
|
const char *input = "one two three four five\n"; |
|
|
bool ok = false; |
|
|
char *out = run_fmt_and_capture(input, "wrap_constraints", &ok); |
|
|
|
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture fmt output"); |
|
|
TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed"); |
|
|
|
|
|
|
|
|
const char *p = out; |
|
|
while (*p) { |
|
|
const char *line_end = strchr(p, '\n'); |
|
|
if (!line_end) line_end = p + strlen(p); |
|
|
size_t vis = line_visible_length(p, line_end); |
|
|
TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE((unsigned)max_width, (unsigned)max_width, "sanity"); |
|
|
TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE((unsigned)max_width, (unsigned)vis); |
|
|
|
|
|
TEST_ASSERT_TRUE_MESSAGE(vis <= (size_t)max_width, "A line exceeds max_width"); |
|
|
p = (*line_end == '\n') ? line_end + 1 : line_end; |
|
|
} |
|
|
|
|
|
|
|
|
char *norm_out = normalize_words(out); |
|
|
char *norm_in = normalize_words(input); |
|
|
TEST_ASSERT_NOT_NULL(norm_out); |
|
|
TEST_ASSERT_NOT_NULL(norm_in); |
|
|
TEST_ASSERT_EQUAL_STRING_MESSAGE(norm_in, norm_out, "Words/content changed by formatting"); |
|
|
|
|
|
free(norm_out); |
|
|
free(norm_in); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
void test_fmt_prefix_trimming_and_reattach(void) |
|
|
{ |
|
|
test_reset_options(); |
|
|
test_set_width(80); |
|
|
|
|
|
|
|
|
char *pf = (char *)malloc(8); |
|
|
TEST_ASSERT_NOT_NULL(pf); |
|
|
strcpy(pf, " > "); |
|
|
set_prefix(pf); |
|
|
|
|
|
const char *input = |
|
|
" >Hello\n" |
|
|
" >world\n" |
|
|
"\n"; |
|
|
|
|
|
bool ok = false; |
|
|
char *out = run_fmt_and_capture(input, "prefix_trim", &ok); |
|
|
|
|
|
|
|
|
prefix = ""; |
|
|
prefix_full_length = 0; |
|
|
prefix_lead_space = 0; |
|
|
prefix_length = 0; |
|
|
free(pf); |
|
|
|
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed with prefixed lines"); |
|
|
|
|
|
|
|
|
const char *expected = |
|
|
" >Hello\n" |
|
|
" >world\n" |
|
|
"\n"; |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
void test_fmt_returns_false_on_read_error(void) |
|
|
{ |
|
|
test_reset_options(); |
|
|
test_set_width(80); |
|
|
|
|
|
|
|
|
FILE *in = tmpfile(); |
|
|
TEST_ASSERT_NOT_NULL(in); |
|
|
const char *input = "data\n"; |
|
|
fwrite(input, 1, strlen(input), in); |
|
|
fflush(in); |
|
|
rewind(in); |
|
|
|
|
|
int fd = fileno(in); |
|
|
|
|
|
close(fd); |
|
|
|
|
|
bool ok = fmt(in, "read_error_expected"); |
|
|
|
|
|
TEST_ASSERT_FALSE_MESSAGE(ok, "fmt should return false on read error"); |
|
|
} |
|
|
|
|
|
|
|
|
int main(void) |
|
|
{ |
|
|
UNITY_BEGIN(); |
|
|
|
|
|
RUN_TEST(test_fmt_basic_no_wrap); |
|
|
RUN_TEST(test_fmt_wrap_lines_constraints); |
|
|
RUN_TEST(test_fmt_prefix_trimming_and_reattach); |
|
|
RUN_TEST(test_fmt_returns_false_on_read_error); |
|
|
|
|
|
return UNITY_END(); |
|
|
} |