/* * 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 #include /* 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); }