/* * Stream wrappers: aksl_fopen / aksl_fread / aksl_fwrite / aksl_fclose. * * TODO.md section 1.2. Covered here: the happy paths, the round trip, the NULL * guards that exist, and the two error statuses the wrappers can actually * produce today -- AKERR_EOF from a short read and AKERR_IO from a stream whose * error indicator is set. * * Not covered, because the behaviour is a documented gap rather than a * contract: aksl_fopen(NULL, ...) and aksl_fopen(path, NULL, ...) are unchecked * (2.2.2), aksl_fread/aksl_fwrite never check ptr and report a short transfer * that is neither EOF nor error as complete success (2.2.3). * * Temp files come from aksl_temp_file() and are unlinked by the test that made * them, so a failing test leaves nothing behind but the file it was mid-way * through. */ #include "aksl_capture.h" #include static int test_fopen_writes_stream_pointer(void) { char path[AKSL_TMP_MAX]; FILE *fp = NULL; AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); AKSL_CHECK_OK(aksl_fopen(path, "w", &fp)); AKSL_CHECK(fp != NULL); AKSL_CHECK_OK(aksl_fclose(fp)); AKSL_CHECK(unlink(path) == 0); return 0; } static int test_fopen_reports_missing_path(void) { FILE *fp = NULL; /* The pathname belongs in the message; the status is the errno fopen saw. */ AKSL_CHECK_STATUS_MSG_CONTAINS( aksl_fopen("/nonexistent/aksl/stream", "r", &fp), ENOENT, "/nonexistent/aksl/stream"); return 0; } static int test_fopen_rejects_null_stream_out(void) { char path[AKSL_TMP_MAX]; AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, "r", NULL), AKERR_NULLPOINTER, "NULL"); AKSL_CHECK(unlink(path) == 0); return 0; } /* fopen -> fwrite -> fclose -> fopen -> fread -> compare, all through the wrappers. */ static int test_write_read_round_trip(void) { char path[AKSL_TMP_MAX]; char payload[] = "libakstdlib round trip"; char readback[sizeof(payload)]; FILE *fp = NULL; AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); AKSL_CHECK_OK(aksl_fopen(path, "w", &fp)); AKSL_CHECK_OK(aksl_fwrite(payload, 1, sizeof(payload), fp)); AKSL_CHECK_OK(aksl_fclose(fp)); fp = NULL; memset(readback, 0x00, sizeof(readback)); AKSL_CHECK_OK(aksl_fopen(path, "r", &fp)); AKSL_CHECK_OK(aksl_fread(readback, 1, sizeof(readback), fp)); AKSL_CHECK_OK(aksl_fclose(fp)); AKSL_CHECK(memcmp(payload, readback, sizeof(payload)) == 0); AKSL_CHECK(unlink(path) == 0); return 0; } /* Asking for more members than the file holds sets feof, which is AKERR_EOF. */ static int test_fread_short_read_is_eof(void) { char path[AKSL_TMP_MAX]; char buf[32]; FILE *fp = NULL; AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); AKSL_CHECK_OK(aksl_fopen(path, "w", &fp)); AKSL_CHECK_OK(aksl_fwrite("abcd", 1, 4, fp)); AKSL_CHECK_OK(aksl_fclose(fp)); fp = NULL; memset(buf, 0x00, sizeof(buf)); AKSL_CHECK_OK(aksl_fopen(path, "r", &fp)); AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp), AKERR_EOF, "EOF"); AKSL_CHECK_OK(aksl_fclose(fp)); /* The bytes that did arrive are still in the buffer. */ AKSL_CHECK(memcmp(buf, "abcd", 4) == 0); AKSL_CHECK(unlink(path) == 0); return 0; } /* * A stream opened "w" has no read permission, so fread sets the error indicator * rather than the EOF one: AKERR_IO, not AKERR_EOF. */ static int test_fread_from_write_only_stream_is_io_error(void) { char path[AKSL_TMP_MAX]; char buf[4]; FILE *fp = NULL; AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); AKSL_CHECK_OK(aksl_fopen(path, "w", &fp)); memset(buf, 0x00, sizeof(buf)); AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp), AKERR_IO, "Error reading file"); AKSL_CHECK_OK(aksl_fclose(fp)); AKSL_CHECK(unlink(path) == 0); return 0; } static int test_fread_rejects_null_stream(void) { char buf[4]; memset(buf, 0x00, sizeof(buf)); AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), NULL), AKERR_NULLPOINTER, "NULL"); return 0; } /* Mirror image of the fread case: a "r" stream cannot be written to. */ static int test_fwrite_to_read_only_stream_is_io_error(void) { char path[AKSL_TMP_MAX]; FILE *fp = NULL; AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); AKSL_CHECK_OK(aksl_fopen(path, "r", &fp)); AKSL_CHECK_STATUS(aksl_fwrite("xy", 1, 2, fp), AKERR_IO); AKSL_CHECK_OK(aksl_fclose(fp)); AKSL_CHECK(unlink(path) == 0); return 0; } static int test_fwrite_rejects_null_stream(void) { AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fwrite("xy", 1, 2, NULL), AKERR_NULLPOINTER, "NULL"); return 0; } static int test_fclose_rejects_null_stream(void) { AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fclose(NULL), AKERR_NULLPOINTER, "NULL"); return 0; } int main(void) { int failures = 0; akerr_init(); AKSL_RUN(failures, test_fopen_writes_stream_pointer); AKSL_RUN(failures, test_fopen_reports_missing_path); AKSL_RUN(failures, test_fopen_rejects_null_stream_out); AKSL_RUN(failures, test_write_read_round_trip); AKSL_RUN(failures, test_fread_short_read_is_eof); AKSL_RUN(failures, test_fread_from_write_only_stream_is_io_error); AKSL_RUN(failures, test_fread_rejects_null_stream); AKSL_RUN(failures, test_fwrite_to_read_only_stream_is_io_error); AKSL_RUN(failures, test_fwrite_rejects_null_stream); AKSL_RUN(failures, test_fclose_rejects_null_stream); AKSL_REPORT(failures); }