Files
libakstdlib/tests/test_streamio.c
Andrew Kesterson 3ad3994762 Test aksl_vscanf, and drop a check that could not fail
Coverage caught both. aksl_vscanf was declared, documented and never
called by anything, which is what 100% function coverage is for -- it is
the only metric here that notices a whole function nobody exercises.

aksl_vasprintf had a second vsnprintf whose return it compared against
the first, and a CLEANUP block to free the buffer when they disagreed.
They cannot disagree: the buffer was sized by vsnprintf from the same
format string and the same arguments a few lines earlier. So that was a
failure branch nothing can reach and a free() beneath it that nothing
can execute -- exactly the dead code TODO.md 2.2.11 recorded against the
old aksl_memset and aksl_memcpy, and removing those was the point. The
invariant is a comment now, where it can be read.

Back to 99.5% of lines (1708/1716) and 100% of functions (154/154), with
the eight uncovered lines the ones TODO.md already accounts for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:07:25 -04:00

711 lines
23 KiB
C

/*
* Stream wrappers beyond open/read/write/close -- src/stream.c, TODO.md 3.1.
*
* tests/test_stream.c covers fopen/fread/fwrite/fclose. This one covers
* positioning, flushing, character and line I/O, stream state, and formatted
* input.
*
* The recurring assertion is the one the section exists for: where stdio folds
* "no more data" and "something broke" into a single sentinel -- EOF from
* fgetc, NULL from fgets, -1L from ftell -- the wrapper separates them, so
* AKERR_EOF ends a read loop and anything else is a fault.
*/
#include "aksl_capture.h"
#include <errno.h>
#include <fcntl.h>
/* A temp file containing exactly `text`, opened for reading. */
static int open_with_text(const char *text, char *path, size_t pathlen, FILE **fp)
{
FILE *out = NULL;
if ( aksl_temp_file(path, pathlen) != 0 ) {
return 1;
}
out = fopen(path, "w");
if ( out == NULL ) {
return 1;
}
if ( text[0] != '\0' && fputs(text, out) == EOF ) {
fclose(out);
return 1;
}
if ( fclose(out) != 0 ) {
return 1;
}
*fp = fopen(path, "r");
return (*fp == NULL) ? 1 : 0;
}
/* ---------------------------------------------------------------------- */
/* Positioning */
/* ---------------------------------------------------------------------- */
static int test_seek_and_tell(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
long pos = -1;
int c = 0;
AKSL_CHECK(open_with_text("0123456789", path, sizeof(path), &fp) == 0);
AKSL_CHECK_OK(aksl_ftell(fp, &pos));
AKSL_CHECK(pos == 0);
AKSL_CHECK_OK(aksl_fseek(fp, 4, SEEK_SET));
AKSL_CHECK_OK(aksl_ftell(fp, &pos));
AKSL_CHECK(pos == 4);
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK(c == '4');
AKSL_CHECK_OK(aksl_fseek(fp, -1, SEEK_END));
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK(c == '9');
/* rewind is the fseek whose failure rewind(3) throws away. */
AKSL_CHECK_OK(aksl_rewind(fp));
AKSL_CHECK_OK(aksl_ftell(fp, &pos));
AKSL_CHECK(pos == 0);
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
return 0;
}
static int test_seeko_and_tello(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
off_t pos = -1;
AKSL_CHECK(open_with_text("0123456789", path, sizeof(path), &fp) == 0);
AKSL_CHECK_OK(aksl_fseeko(fp, 6, SEEK_SET));
AKSL_CHECK_OK(aksl_ftello(fp, &pos));
AKSL_CHECK(pos == 6);
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
return 0;
}
static int test_getpos_and_setpos(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
fpos_t saved;
int c = 0;
AKSL_CHECK(open_with_text("abcdef", path, sizeof(path), &fp) == 0);
AKSL_CHECK_OK(aksl_fseek(fp, 2, SEEK_SET));
AKSL_CHECK_OK(aksl_fgetpos(fp, &saved));
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK(c == 'c');
AKSL_CHECK_OK(aksl_fsetpos(fp, &saved));
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK(c == 'c');
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
return 0;
}
static int test_positioning_rejects_null(void)
{
long pos = 0;
off_t opos = 0;
fpos_t fpos;
AKSL_CHECK_STATUS(aksl_fseek(NULL, 0, SEEK_SET), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_ftell(NULL, &pos), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_ftell(stdin, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_rewind(NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fseeko(NULL, 0, SEEK_SET), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_ftello(NULL, &opos), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_ftello(stdin, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fgetpos(NULL, &fpos), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fgetpos(stdin, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fsetpos(NULL, &fpos), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fsetpos(stdin, NULL), AKERR_NULLPOINTER);
return 0;
}
/* Seeking a pipe is ESPIPE, which is the failure rewind(3) would have hidden. */
static int test_seek_on_a_pipe_reports_espipe(void)
{
FILE *fp = popen("echo hello", "r");
long pos = 0;
AKSL_CHECK(fp != NULL);
AKSL_CHECK_STATUS(aksl_fseek(fp, 0, SEEK_SET), ESPIPE);
AKSL_CHECK_STATUS(aksl_ftell(fp, &pos), ESPIPE);
AKSL_CHECK_STATUS(aksl_rewind(fp), ESPIPE);
pclose(fp);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Character and line I/O */
/* ---------------------------------------------------------------------- */
/*
* fgetc(3) returns EOF for both the end of the file and a read error. Split
* apart, a read loop needs no ferror/feof dance at the bottom of it.
*/
static int test_fgetc_separates_eof_from_error(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
int c = 0;
int seen = 0;
AKSL_CHECK(open_with_text("ab", path, sizeof(path), &fp) == 0);
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK(c == 'a');
seen++;
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK(c == 'b');
seen++;
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fgetc(fp, &c), AKERR_EOF, "end of stream");
AKSL_CHECK(seen == 2);
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
/* A stream with no read permission is the error half of the same sentinel. */
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
AKSL_CHECK_OK(aksl_fopen(path, "w", &fp));
AKSL_CHECK_STATUS(aksl_fgetc(fp, &c), EBADF);
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
AKSL_CHECK_STATUS(aksl_fgetc(NULL, &c), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fgetc(stdin, NULL), AKERR_NULLPOINTER);
return 0;
}
static int test_fputc_and_ungetc(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
int c = 0;
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
AKSL_CHECK_OK(aksl_fopen(path, "w", &fp));
AKSL_CHECK_OK(aksl_fputc('x', fp));
AKSL_CHECK_OK(aksl_fputc('y', fp));
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK(c == 'x');
/* Put it back and read it again. */
AKSL_CHECK_OK(aksl_ungetc(c, fp));
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK(c == 'x');
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
AKSL_CHECK_STATUS(aksl_fputc('x', NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_ungetc('x', NULL), AKERR_NULLPOINTER);
return 0;
}
static int test_fgets_and_fputs(void)
{
char path[AKSL_TMP_MAX];
char line[64];
FILE *fp = NULL;
size_t len = 0;
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
AKSL_CHECK_OK(aksl_fopen(path, "w", &fp));
AKSL_CHECK_OK(aksl_fputs("first\n", fp));
AKSL_CHECK_OK(aksl_fputs("second\n", fp));
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
AKSL_CHECK_OK(aksl_fgets(line, sizeof(line), fp, &len));
AKSL_CHECK(strcmp(line, "first\n") == 0);
AKSL_CHECK(len == 6);
AKSL_CHECK_OK(aksl_fgets(line, sizeof(line), fp, &len));
AKSL_CHECK(strcmp(line, "second\n") == 0);
/* The end of the input is AKERR_EOF, which is how the loop terminates. */
AKSL_CHECK_STATUS(aksl_fgets(line, sizeof(line), fp, &len), AKERR_EOF);
AKSL_CHECK(len == 0);
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
return 0;
}
/*
* A line longer than the buffer is a short read, not an error -- that is what
* fgets does and what a caller reading fixed chunks wants. The reported length
* is how the caller notices: a full buffer with no newline is exactly this case.
*/
static int test_fgets_reports_a_long_line_as_a_short_read(void)
{
char path[AKSL_TMP_MAX];
char line[4];
FILE *fp = NULL;
size_t len = 0;
AKSL_CHECK(open_with_text("abcdefgh\n", path, sizeof(path), &fp) == 0);
AKSL_CHECK_OK(aksl_fgets(line, sizeof(line), fp, &len));
AKSL_CHECK(len == 3);
AKSL_CHECK(strcmp(line, "abc") == 0);
AKSL_CHECK(line[len - 1] != '\n');
/* The rest of the line is still there. */
AKSL_CHECK_OK(aksl_fgets(line, sizeof(line), fp, &len));
AKSL_CHECK(strcmp(line, "def") == 0);
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
AKSL_CHECK_STATUS(aksl_fgets(NULL, 4, stdin, &len), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fgets(line, 4, NULL, &len), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fgets(line, 4, stdin, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fgets(line, 0, stdin, &len), AKERR_VALUE);
return 0;
}
/*
* getline grows the buffer, so the caller starts with NULL/0 and frees once at
* the end -- not once per line. The reported length is what distinguishes an
* embedded NUL from the end of the line; strlen cannot.
*/
static int test_getline_grows_its_buffer(void)
{
char path[AKSL_TMP_MAX];
char *line = NULL;
size_t cap = 0;
size_t len = 0;
FILE *fp = NULL;
int lines = 0;
AKSL_CHECK(open_with_text("short\na much longer line than the first one\n",
path, sizeof(path), &fp) == 0);
while ( 1 ) {
akerr_ErrorContext *raised = aksl_getline(&line, &cap, fp, &len);
int status = aksl_take(raised);
if ( status == AKERR_EOF ) {
break;
}
AKSL_CHECK(status == 0);
AKSL_CHECK(len > 0);
AKSL_CHECK(line != NULL);
lines++;
}
AKSL_CHECK(lines == 2);
/* One buffer for the whole file, grown as needed. */
AKSL_CHECK(cap >= 42);
AKSL_CHECK_OK(aksl_freep((void **)&line));
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
return 0;
}
static int test_getdelim_splits_on_any_byte(void)
{
char path[AKSL_TMP_MAX];
char *field = NULL;
size_t cap = 0;
size_t len = 0;
FILE *fp = NULL;
int fields = 0;
AKSL_CHECK(open_with_text("a:b:c", path, sizeof(path), &fp) == 0);
while ( 1 ) {
akerr_ErrorContext *raised = aksl_getdelim(&field, &cap, ':', fp, &len);
int status = aksl_take(raised);
if ( status == AKERR_EOF ) {
break;
}
AKSL_CHECK(status == 0);
fields++;
}
AKSL_CHECK(fields == 3);
AKSL_CHECK_OK(aksl_freep((void **)&field));
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
AKSL_CHECK_STATUS(aksl_getline(NULL, &cap, stdin, &len), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_getline(&field, NULL, stdin, &len), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_getline(&field, &cap, NULL, &len), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_getline(&field, &cap, stdin, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_getdelim(NULL, &cap, ':', stdin, &len), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_getdelim(&field, &cap, ':', stdin, NULL), AKERR_NULLPOINTER);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Stream state */
/* ---------------------------------------------------------------------- */
static int test_stream_state(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
int flag = 99;
int fd = -1;
int c = 0;
AKSL_CHECK(open_with_text("a", path, sizeof(path), &fp) == 0);
AKSL_CHECK_OK(aksl_feof(fp, &flag));
AKSL_CHECK(flag == 0);
AKSL_CHECK_OK(aksl_ferror(fp, &flag));
AKSL_CHECK(flag == 0);
AKSL_CHECK_OK(aksl_fileno(fp, &fd));
AKSL_CHECK(fd >= 0);
/* Read past the end to set the EOF indicator, then clear it. */
AKSL_CHECK_OK(aksl_fgetc(fp, &c));
AKSL_CHECK_STATUS(aksl_fgetc(fp, &c), AKERR_EOF);
AKSL_CHECK_OK(aksl_feof(fp, &flag));
AKSL_CHECK(flag != 0);
AKSL_CHECK_OK(aksl_clearerr(fp));
AKSL_CHECK_OK(aksl_feof(fp, &flag));
AKSL_CHECK(flag == 0);
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
AKSL_CHECK_STATUS(aksl_feof(NULL, &flag), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_feof(stdin, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_ferror(NULL, &flag), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_ferror(stdin, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_clearerr(NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fileno(NULL, &fd), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fileno(stdin, NULL), AKERR_NULLPOINTER);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Flushing, buffering and opening */
/* ---------------------------------------------------------------------- */
static int test_fflush_and_setvbuf(void)
{
char path[AKSL_TMP_MAX];
char buffer[BUFSIZ];
FILE *fp = NULL;
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
AKSL_CHECK_OK(aksl_fopen(path, "w", &fp));
AKSL_CHECK_OK(aksl_setvbuf(fp, buffer, _IOFBF, sizeof(buffer)));
AKSL_CHECK_OK(aksl_fputs("buffered", fp));
AKSL_CHECK_OK(aksl_fflush(fp));
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
/* NULL means "every output stream" and is not an error, unlike everywhere else. */
AKSL_CHECK_OK(aksl_fflush(NULL));
AKSL_CHECK_STATUS(aksl_setvbuf(NULL, NULL, _IONBF, 0), AKERR_NULLPOINTER);
return 0;
}
static int test_tmpfile_fdopen_and_freopen(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
FILE *reopened = NULL;
int fd = -1;
size_t moved = 0;
/* tmpfile: no name, gone when it is closed. */
AKSL_CHECK_OK(aksl_tmpfile(&fp));
AKSL_CHECK(fp != NULL);
AKSL_CHECK_OK(aksl_fwrite("x", 1, 1, fp, &moved));
AKSL_CHECK_OK(aksl_fclose(fp));
/* fdopen: wrap a descriptor this test owns. */
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
fd = open(path, O_RDONLY);
AKSL_CHECK(fd >= 0);
AKSL_CHECK_OK(aksl_fdopen(fd, "r", &fp));
AKSL_CHECK_OK(aksl_fclose(fp)); /* closes fd too */
/* freopen: point an existing stream at a different file. */
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
AKSL_CHECK_OK(aksl_freopen(path, "r", fp, &reopened));
AKSL_CHECK(reopened != NULL);
AKSL_CHECK_OK(aksl_fclose(reopened));
AKSL_CHECK(unlink(path) == 0);
AKSL_CHECK_STATUS(aksl_tmpfile(NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fdopen(-1, "r", &fp), AKERR_VALUE);
AKSL_CHECK_STATUS(aksl_fdopen(0, NULL, &fp), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fdopen(0, "r", NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_freopen("/tmp", "r", NULL, &fp), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_freopen("/tmp", NULL, stdin, &fp), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_freopen("/tmp", "r", stdin, NULL), AKERR_NULLPOINTER);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Formatted input */
/* ---------------------------------------------------------------------- */
/*
* The reason the scanf wrappers take an expected count: scanf(3) returns how
* many conversions succeeded, and comparing that against the number written in
* the format string is a check every caller has to do by hand and eventually
* forgets. Forgetting leaves the unassigned arguments holding whatever they
* held before.
*/
static int test_sscanf_enforces_the_expected_count(void)
{
int a = 0;
int b = 0;
int assigned = 0;
AKSL_CHECK_OK(aksl_sscanf("12 34", "%d %d", 2, &assigned, &a, &b));
AKSL_CHECK(assigned == 2);
AKSL_CHECK(a == 12 && b == 34);
/* One conversion short: an error rather than a stale second variable. */
a = 0;
b = 999;
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_sscanf("12 xy", "%d %d", 2, &assigned, &a, &b),
AKERR_VALUE, "1 of 2 conversions");
AKSL_CHECK(assigned == 1);
AKSL_CHECK(b == 999);
/* expected 0 opts out and hands the count back for the caller to judge. */
AKSL_CHECK_OK(aksl_sscanf("12 xy", "%d %d", 0, &assigned, &a, &b));
AKSL_CHECK(assigned == 1);
/* Nothing matched at all. */
AKSL_CHECK_STATUS(aksl_sscanf("", "%d", 1, &assigned, &a), AKERR_VALUE);
AKSL_CHECK_STATUS(aksl_sscanf(NULL, "%d", 1, &assigned, &a), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_sscanf("1", NULL, 1, &assigned), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_sscanf("1", "%d", 1, NULL, &a), AKERR_NULLPOINTER);
return 0;
}
static int test_fscanf_reads_from_a_stream(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
int a = 0;
int b = 0;
int assigned = 0;
AKSL_CHECK(open_with_text("7 8\n", path, sizeof(path), &fp) == 0);
AKSL_CHECK_OK(aksl_fscanf(fp, "%d %d", 2, &assigned, &a, &b));
AKSL_CHECK(assigned == 2);
AKSL_CHECK(a == 7 && b == 8);
/* Nothing left: the end of the stream, not a conversion failure. */
AKSL_CHECK_STATUS(aksl_fscanf(fp, "%d", 1, &assigned, &a), AKERR_EOF);
AKSL_CHECK_OK(aksl_fclose(fp));
AKSL_CHECK(unlink(path) == 0);
AKSL_CHECK_STATUS(aksl_fscanf(NULL, "%d", 1, &assigned, &a), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fscanf(stdin, NULL, 1, &assigned), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fscanf(stdin, "%d", 1, NULL, &a), AKERR_NULLPOINTER);
return 0;
}
/*
* aksl_scanf reads stdin, so stdin is pointed at a temp file for the duration of
* the call and restored through a dup of the original descriptor -- the same
* dance tests/test_format.c does for aksl_printf and stdout, and for the same
* reason: nothing between the freopen and the dup2 may return early.
*/
static int test_scanf_reads_stdin(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
akerr_ErrorContext *err = NULL;
int a = 0;
int b = 0;
int assigned = 0;
int saved = -1;
int restored = -1;
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
fp = fopen(path, "w");
AKSL_CHECK(fp != NULL);
AKSL_CHECK(fputs("11 22\n", fp) != EOF);
AKSL_CHECK(fclose(fp) == 0);
saved = dup(fileno(stdin));
AKSL_CHECK(saved >= 0);
if ( freopen(path, "r", stdin) == NULL ) {
close(saved);
AKSL_CHECK(0);
}
err = aksl_scanf("%d %d", 2, &assigned, &a, &b);
restored = dup2(saved, fileno(stdin));
close(saved);
clearerr(stdin);
AKSL_CHECK(restored >= 0);
AKSL_CHECK(aksl_take(err) == 0);
AKSL_CHECK(assigned == 2);
AKSL_CHECK(a == 11 && b == 22);
AKSL_CHECK(unlink(path) == 0);
AKSL_CHECK_STATUS(aksl_scanf(NULL, 1, &assigned), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_scanf("%d", 1, NULL, &a), AKERR_NULLPOINTER);
return 0;
}
/* The va_list form, used the way a consumer building its own wrapper would. */
static akerr_ErrorContext AKERR_NOIGNORE *consumer_scan(const char *fmt, int expected,
int *assigned, ...)
{
va_list args;
akerr_ErrorContext *raised = NULL;
va_start(args, assigned);
raised = aksl_vscanf(fmt, expected, assigned, args);
va_end(args);
return raised;
}
static int test_vscanf_reads_stdin(void)
{
char path[AKSL_TMP_MAX];
FILE *fp = NULL;
akerr_ErrorContext *err = NULL;
int a = 0;
int assigned = 0;
int saved = -1;
int restored = -1;
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
fp = fopen(path, "w");
AKSL_CHECK(fp != NULL);
AKSL_CHECK(fputs("33\n", fp) != EOF);
AKSL_CHECK(fclose(fp) == 0);
saved = dup(fileno(stdin));
AKSL_CHECK(saved >= 0);
if ( freopen(path, "r", stdin) == NULL ) {
close(saved);
AKSL_CHECK(0);
}
err = consumer_scan("%d", 1, &assigned, &a);
restored = dup2(saved, fileno(stdin));
close(saved);
clearerr(stdin);
AKSL_CHECK(restored >= 0);
AKSL_CHECK(aksl_take(err) == 0);
AKSL_CHECK(assigned == 1);
AKSL_CHECK(a == 33);
AKSL_CHECK(unlink(path) == 0);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Files */
/* ---------------------------------------------------------------------- */
static int test_remove_and_rename(void)
{
char path[AKSL_TMP_MAX];
char moved[AKSL_TMP_MAX + 8];
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
AKSL_CHECK((size_t)snprintf(moved, sizeof(moved), "%s.moved", path) < sizeof(moved));
AKSL_CHECK_OK(aksl_rename(path, moved));
AKSL_CHECK(access(path, F_OK) != 0);
AKSL_CHECK(access(moved, F_OK) == 0);
AKSL_CHECK_OK(aksl_remove(moved));
AKSL_CHECK(access(moved, F_OK) != 0);
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_remove("/nonexistent/aksl/file"),
ENOENT, "/nonexistent/aksl/file");
AKSL_CHECK_STATUS(aksl_remove(NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_rename(NULL, "b"), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_rename("a", NULL), AKERR_NULLPOINTER);
return 0;
}
/*
* mkstemp and mkdtemp rewrite the template in place, so a template that is not
* a writable buffer of the right shape is a crash waiting to happen. The shape
* is checked here rather than left to the kernel.
*/
static int test_mkstemp_and_mkdtemp(void)
{
char file_template[] = "/tmp/aksl_streamio_XXXXXX";
char dir_template[] = "/tmp/aksl_streamio_dir_XXXXXX";
char bad[] = "/tmp/aksl_no_placeholder";
int fd = -1;
AKSL_CHECK_OK(aksl_mkstemp(file_template, &fd));
AKSL_CHECK(fd >= 0);
AKSL_CHECK(strcmp(file_template, "/tmp/aksl_streamio_XXXXXX") != 0);
AKSL_CHECK(close(fd) == 0);
AKSL_CHECK_OK(aksl_remove(file_template));
AKSL_CHECK_OK(aksl_mkdtemp(dir_template));
AKSL_CHECK(access(dir_template, F_OK) == 0);
AKSL_CHECK(rmdir(dir_template) == 0);
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_mkstemp(bad, &fd), AKERR_VALUE, "six literal X");
AKSL_CHECK(fd == -1);
AKSL_CHECK_STATUS(aksl_mkdtemp(bad), AKERR_VALUE);
AKSL_CHECK_STATUS(aksl_mkstemp(NULL, &fd), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_mkstemp(file_template, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_mkdtemp(NULL), AKERR_NULLPOINTER);
return 0;
}
int main(void)
{
int failures = 0;
akerr_init();
AKSL_RUN(failures, test_seek_and_tell);
AKSL_RUN(failures, test_seeko_and_tello);
AKSL_RUN(failures, test_getpos_and_setpos);
AKSL_RUN(failures, test_positioning_rejects_null);
AKSL_RUN(failures, test_seek_on_a_pipe_reports_espipe);
AKSL_RUN(failures, test_fgetc_separates_eof_from_error);
AKSL_RUN(failures, test_fputc_and_ungetc);
AKSL_RUN(failures, test_fgets_and_fputs);
AKSL_RUN(failures, test_fgets_reports_a_long_line_as_a_short_read);
AKSL_RUN(failures, test_getline_grows_its_buffer);
AKSL_RUN(failures, test_getdelim_splits_on_any_byte);
AKSL_RUN(failures, test_stream_state);
AKSL_RUN(failures, test_fflush_and_setvbuf);
AKSL_RUN(failures, test_tmpfile_fdopen_and_freopen);
AKSL_RUN(failures, test_sscanf_enforces_the_expected_count);
AKSL_RUN(failures, test_fscanf_reads_from_a_stream);
AKSL_RUN(failures, test_scanf_reads_stdin);
AKSL_RUN(failures, test_vscanf_reads_stdin);
AKSL_RUN(failures, test_remove_and_rename);
AKSL_RUN(failures, test_mkstemp_and_mkdtemp);
AKSL_REPORT(failures);
}