|
|
#include "../../unity/unity.h" |
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <fcntl.h> |
|
|
#include <errno.h> |
|
|
#include <sys/stat.h> |
|
|
#include <sys/types.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool nl_file (char const *file); |
|
|
|
|
|
|
|
|
extern char const *FORMAT_RIGHT_NOLZ; |
|
|
static void reset_lineno(void); |
|
|
|
|
|
|
|
|
static char* make_print_no_line_fmt_buf(int width, const char* sep) |
|
|
{ |
|
|
size_t seplen = strlen(sep); |
|
|
char* s = (char*)malloc((size_t)width + seplen + 1); |
|
|
if (!s) return NULL; |
|
|
memset(s, ' ', (size_t)width); |
|
|
memcpy(s + width, sep, seplen); |
|
|
s[width + seplen] = '\0'; |
|
|
return s; |
|
|
} |
|
|
|
|
|
|
|
|
static int write_all(int fd, const char* data, size_t len) |
|
|
{ |
|
|
size_t off = 0; |
|
|
while (off < len) { |
|
|
ssize_t w = write(fd, data + off, len - off); |
|
|
if (w < 0) return -1; |
|
|
off += (size_t)w; |
|
|
} |
|
|
return 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static char* create_temp_file_with(const char* content) |
|
|
{ |
|
|
char tmpl[] = "/tmp/nl_test_XXXXXX"; |
|
|
int fd = mkstemp(tmpl); |
|
|
if (fd < 0) return NULL; |
|
|
if (content && write_all(fd, content, strlen(content)) < 0) { |
|
|
close(fd); |
|
|
unlink(tmpl); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
lseek(fd, 0, SEEK_SET); |
|
|
close(fd); |
|
|
char* path = strdup(tmpl); |
|
|
return path; |
|
|
} |
|
|
|
|
|
|
|
|
static char* read_entire_file(const char* path) |
|
|
{ |
|
|
FILE* f = fopen(path, "rb"); |
|
|
if (!f) return NULL; |
|
|
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } |
|
|
long sz = ftell(f); |
|
|
if (sz < 0) { fclose(f); return NULL; } |
|
|
if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return NULL; } |
|
|
char* buf = (char*)malloc((size_t)sz + 1); |
|
|
if (!buf) { fclose(f); return NULL; } |
|
|
size_t n = fread(buf, 1, (size_t)sz, f); |
|
|
buf[n] = '\0'; |
|
|
fclose(f); |
|
|
return buf; |
|
|
} |
|
|
|
|
|
|
|
|
static int redirect_stdout_to_temp(char** out_path) |
|
|
{ |
|
|
fflush(stdout); |
|
|
char tmpl[] = "/tmp/nl_out_XXXXXX"; |
|
|
int outfd = mkstemp(tmpl); |
|
|
if (outfd < 0) return -1; |
|
|
int saved = dup(STDOUT_FILENO); |
|
|
if (saved < 0) { close(outfd); unlink(tmpl); return -1; } |
|
|
if (dup2(outfd, STDOUT_FILENO) < 0) { |
|
|
close(outfd); |
|
|
close(saved); |
|
|
unlink(tmpl); |
|
|
return -1; |
|
|
} |
|
|
close(outfd); |
|
|
*out_path = strdup(tmpl); |
|
|
return saved; |
|
|
} |
|
|
|
|
|
|
|
|
static int redirect_stderr_to_temp(char** err_path) |
|
|
{ |
|
|
fflush(stderr); |
|
|
char tmpl[] = "/tmp/nl_err_XXXXXX"; |
|
|
int errfd = mkstemp(tmpl); |
|
|
if (errfd < 0) return -1; |
|
|
int saved = dup(STDERR_FILENO); |
|
|
if (saved < 0) { close(errfd); unlink(tmpl); return -1; } |
|
|
if (dup2(errfd, STDERR_FILENO) < 0) { |
|
|
close(errfd); |
|
|
close(saved); |
|
|
unlink(tmpl); |
|
|
return -1; |
|
|
} |
|
|
close(errfd); |
|
|
*err_path = strdup(tmpl); |
|
|
return saved; |
|
|
} |
|
|
|
|
|
|
|
|
static void restore_stdout(int saved_fd) |
|
|
{ |
|
|
fflush(stdout); |
|
|
dup2(saved_fd, STDOUT_FILENO); |
|
|
close(saved_fd); |
|
|
} |
|
|
|
|
|
|
|
|
static void restore_stderr(int saved_fd) |
|
|
{ |
|
|
fflush(stderr); |
|
|
dup2(saved_fd, STDERR_FILENO); |
|
|
close(saved_fd); |
|
|
} |
|
|
|
|
|
|
|
|
static int redirect_stdin_from_file(const char* path) |
|
|
{ |
|
|
int fd = open(path, O_RDONLY); |
|
|
if (fd < 0) return -1; |
|
|
int saved = dup(STDIN_FILENO); |
|
|
if (saved < 0) { close(fd); return -1; } |
|
|
if (dup2(fd, STDIN_FILENO) < 0) { |
|
|
close(fd); |
|
|
close(saved); |
|
|
return -1; |
|
|
} |
|
|
close(fd); |
|
|
return saved; |
|
|
} |
|
|
|
|
|
extern char const *separator_str; |
|
|
extern int lineno_width; |
|
|
extern char const *lineno_format; |
|
|
extern char *print_no_line_fmt; |
|
|
extern intmax_t starting_line_number; |
|
|
extern intmax_t page_incr; |
|
|
extern bool reset_numbers; |
|
|
extern char const *body_type; |
|
|
extern char const *header_type; |
|
|
extern char const *footer_type; |
|
|
extern char const *current_type; |
|
|
extern bool have_read_stdin; |
|
|
|
|
|
|
|
|
static char* allocated_print_no_line_fmt = NULL; |
|
|
|
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
separator_str = "\t"; |
|
|
lineno_width = 2; |
|
|
lineno_format = FORMAT_RIGHT_NOLZ; |
|
|
starting_line_number = 1; |
|
|
page_incr = 1; |
|
|
reset_numbers = true; |
|
|
|
|
|
|
|
|
body_type = "t"; |
|
|
header_type = "n"; |
|
|
footer_type = "n"; |
|
|
current_type = body_type; |
|
|
|
|
|
|
|
|
if (allocated_print_no_line_fmt) { |
|
|
free(allocated_print_no_line_fmt); |
|
|
allocated_print_no_line_fmt = NULL; |
|
|
} |
|
|
allocated_print_no_line_fmt = make_print_no_line_fmt_buf(lineno_width, separator_str); |
|
|
print_no_line_fmt = allocated_print_no_line_fmt; |
|
|
|
|
|
|
|
|
reset_lineno(); |
|
|
|
|
|
|
|
|
have_read_stdin = false; |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
if (allocated_print_no_line_fmt) { |
|
|
free(allocated_print_no_line_fmt); |
|
|
allocated_print_no_line_fmt = NULL; |
|
|
print_no_line_fmt = NULL; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static void test_nl_file_numbers_simple_file(void) |
|
|
{ |
|
|
const char* input = "line1\nline2\n"; |
|
|
char* in_path = create_temp_file_with(input); |
|
|
TEST_ASSERT_NOT_NULL(in_path); |
|
|
|
|
|
char* out_path = NULL; |
|
|
int saved_stdout = redirect_stdout_to_temp(&out_path); |
|
|
TEST_ASSERT_TRUE(saved_stdout >= 0); |
|
|
TEST_ASSERT_NOT_NULL(out_path); |
|
|
|
|
|
|
|
|
bool ok = nl_file(in_path); |
|
|
|
|
|
|
|
|
restore_stdout(saved_stdout); |
|
|
|
|
|
|
|
|
char* out = read_entire_file(out_path); |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
const char* expected = " 1\tline1\n 2\tline2\n"; |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
|
|
|
free(out); |
|
|
unlink(out_path); |
|
|
free(out_path); |
|
|
unlink(in_path); |
|
|
free(in_path); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_nl_file_t_mode_blank_lines(void) |
|
|
{ |
|
|
const char* input = "\nX\n\n"; |
|
|
char* in_path = create_temp_file_with(input); |
|
|
TEST_ASSERT_NOT_NULL(in_path); |
|
|
|
|
|
|
|
|
body_type = "t"; |
|
|
current_type = body_type; |
|
|
reset_lineno(); |
|
|
|
|
|
char* out_path = NULL; |
|
|
int saved_stdout = redirect_stdout_to_temp(&out_path); |
|
|
TEST_ASSERT_TRUE(saved_stdout >= 0); |
|
|
TEST_ASSERT_NOT_NULL(out_path); |
|
|
|
|
|
bool ok = nl_file(in_path); |
|
|
|
|
|
restore_stdout(saved_stdout); |
|
|
|
|
|
char* out = read_entire_file(out_path); |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
const char* expected = " \t\n 1\tX\n \t\n"; |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
|
|
|
free(out); |
|
|
unlink(out_path); |
|
|
free(out_path); |
|
|
unlink(in_path); |
|
|
free(in_path); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_nl_file_from_stdin_dash(void) |
|
|
{ |
|
|
const char* input = "a\nb\n"; |
|
|
char* in_path = create_temp_file_with(input); |
|
|
TEST_ASSERT_NOT_NULL(in_path); |
|
|
|
|
|
|
|
|
int saved_stdin = redirect_stdin_from_file(in_path); |
|
|
TEST_ASSERT_TRUE(saved_stdin >= 0); |
|
|
|
|
|
char* out_path = NULL; |
|
|
int saved_stdout = redirect_stdout_to_temp(&out_path); |
|
|
TEST_ASSERT_TRUE(saved_stdout >= 0); |
|
|
TEST_ASSERT_NOT_NULL(out_path); |
|
|
|
|
|
|
|
|
bool ok = nl_file("-"); |
|
|
|
|
|
|
|
|
restore_stdout(saved_stdout); |
|
|
dup2(saved_stdin, STDIN_FILENO); |
|
|
close(saved_stdin); |
|
|
|
|
|
char* out = read_entire_file(out_path); |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_TRUE(have_read_stdin); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
const char* expected = " 1\ta\n 2\tb\n"; |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
|
|
|
free(out); |
|
|
unlink(out_path); |
|
|
free(out_path); |
|
|
unlink(in_path); |
|
|
free(in_path); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_nl_file_nonexistent_returns_false(void) |
|
|
{ |
|
|
const char* bad_path = "/tmp/this_file_should_not_exist_nl_test_XXXX"; |
|
|
|
|
|
char* out_path = NULL; |
|
|
int saved_stdout = redirect_stdout_to_temp(&out_path); |
|
|
TEST_ASSERT_TRUE(saved_stdout >= 0); |
|
|
TEST_ASSERT_NOT_NULL(out_path); |
|
|
|
|
|
char* err_path = NULL; |
|
|
int saved_stderr = redirect_stderr_to_temp(&err_path); |
|
|
TEST_ASSERT_TRUE(saved_stderr >= 0); |
|
|
TEST_ASSERT_NOT_NULL(err_path); |
|
|
|
|
|
|
|
|
bool ok = nl_file(bad_path); |
|
|
|
|
|
|
|
|
restore_stdout(saved_stdout); |
|
|
restore_stderr(saved_stderr); |
|
|
|
|
|
|
|
|
char* out = read_entire_file(out_path); |
|
|
TEST_ASSERT_FALSE(ok); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING("", out); |
|
|
|
|
|
free(out); |
|
|
unlink(out_path); |
|
|
free(out_path); |
|
|
unlink(err_path); |
|
|
free(err_path); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_nl_file_starting_and_increment(void) |
|
|
{ |
|
|
starting_line_number = 5; |
|
|
page_incr = 3; |
|
|
reset_numbers = true; |
|
|
reset_lineno(); |
|
|
|
|
|
|
|
|
body_type = "t"; |
|
|
current_type = body_type; |
|
|
|
|
|
const char* input = "aa\nbb\n"; |
|
|
char* in_path = create_temp_file_with(input); |
|
|
TEST_ASSERT_NOT_NULL(in_path); |
|
|
|
|
|
char* out_path = NULL; |
|
|
int saved_stdout = redirect_stdout_to_temp(&out_path); |
|
|
TEST_ASSERT_TRUE(saved_stdout >= 0); |
|
|
TEST_ASSERT_NOT_NULL(out_path); |
|
|
|
|
|
bool ok = nl_file(in_path); |
|
|
|
|
|
restore_stdout(saved_stdout); |
|
|
|
|
|
char* out = read_entire_file(out_path); |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
const char* expected = " 5\taa\n 8\tbb\n"; |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
|
|
|
free(out); |
|
|
unlink(out_path); |
|
|
free(out_path); |
|
|
unlink(in_path); |
|
|
free(in_path); |
|
|
} |
|
|
|
|
|
|
|
|
static void test_nl_file_a_mode_blank_join(void) |
|
|
{ |
|
|
|
|
|
body_type = "a"; |
|
|
current_type = body_type; |
|
|
|
|
|
extern intmax_t blank_join; |
|
|
blank_join = 2; |
|
|
|
|
|
reset_numbers = true; |
|
|
starting_line_number = 1; |
|
|
reset_lineno(); |
|
|
|
|
|
const char* input = "\n\n\nx\n\n"; |
|
|
char* in_path = create_temp_file_with(input); |
|
|
TEST_ASSERT_NOT_NULL(in_path); |
|
|
|
|
|
char* out_path = NULL; |
|
|
int saved_stdout = redirect_stdout_to_temp(&out_path); |
|
|
TEST_ASSERT_TRUE(saved_stdout >= 0); |
|
|
TEST_ASSERT_NOT_NULL(out_path); |
|
|
|
|
|
bool ok = nl_file(in_path); |
|
|
|
|
|
restore_stdout(saved_stdout); |
|
|
|
|
|
char* out = read_entire_file(out_path); |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const char* expected = |
|
|
" \t\n" |
|
|
" 1\t\n" |
|
|
" \t\n" |
|
|
" 2\tx\n" |
|
|
" \t\n"; |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
|
|
|
free(out); |
|
|
unlink(out_path); |
|
|
free(out_path); |
|
|
unlink(in_path); |
|
|
free(in_path); |
|
|
} |
|
|
|
|
|
int main(void) |
|
|
{ |
|
|
UNITY_BEGIN(); |
|
|
RUN_TEST(test_nl_file_numbers_simple_file); |
|
|
RUN_TEST(test_nl_file_t_mode_blank_lines); |
|
|
RUN_TEST(test_nl_file_from_stdin_dash); |
|
|
RUN_TEST(test_nl_file_nonexistent_returns_false); |
|
|
RUN_TEST(test_nl_file_starting_and_increment); |
|
|
RUN_TEST(test_nl_file_a_mode_blank_join); |
|
|
return UNITY_END(); |
|
|
} |