#include #include #include #include #include #include #include #include #include #include #include #include "util.h" static void _stdio_open_switch(ft_test *t) { int fd, rc; FILE *fp; /* Create a basic file */ DO_E(fd, creat("ft_stdio.txt", 0755), t, "%d"); ft_assert(t, fd >= 0); DO_E(rc, write(fd, "Hello, stdio!\n", 14), t, "%d"); ft_assert(t, rc == 14); DO_E(rc, close(fd), t, "%d"); ft_assert(t, rc == 0); /* Try to open it with */ DO_E(fp, fopen("ft_stdio.txt", "r"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); /* Change buffer settings a couple times */ DO_E(rc, setvbuf(fp, NULL, _IONBF, 0), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IOLBF, 1024), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IOFBF, BUFSIZ), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); /* Reopen with different permissions */ DO_E(fp, freopen("ft_stdio.txt", "w+", fp), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(fp, freopen("ft_stdio.txt", "rb+", fp), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(fp, freopen("ft_stdio.txt", "ab", fp), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(fp, freopen("ft_stdio.txt", "w", fp), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _stdio_open(ft_test *t) { gint_world_switch(GINT_CALL(_stdio_open_switch, (void *)t)); } ft_test ft_stdio_open = { .name = "Opening files", .function = _stdio_open, }; static char const *filler = "The fread function reads, into the array pointed to by ptr, up to nmemb " "elements whose size is specified by size, from the stream pointed to by " "stream. For each object, size calls are made to the fgetc function and the " "results stored, in the order read, in an array of unsigned char exactly " "overlaying the object. The file position indicator for the stream (if " "defined) is advanced by the number of characters successfully read. If an " "error occurs, the resulting value of the file position indicator for the " "stream is indeterminate. If a partial element is read, its value is " "indeterminate.\n"; /* ISO/IEC 9899:1999, 7.19.8.1ยง2 (length 591) */ static void _ft_stdio_simple_read_switch(ft_test *t) { int fd, rc; FILE *fp; char *str = malloc(384); fpos_t pos1, pos2; /* Create a basic large file (compared to its buffer size) */ DO_E(fd, creat("ft_stdio.txt", 0755), t, "%d"); ft_assert(t, fd >= 0); DO_E(rc, write(fd, filler, 591), t, "%d"); ft_assert(t, rc == 591); DO_E(rc, close(fd), t, "%d"); ft_assert(t, rc == 0); /* Read it through a buffer of size 128 */ DO_E(fp, fopen("ft_stdio.txt", "r"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IOFBF, 128), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 0); /* Perform a first read of 16 bytes */ DO_E(rc, fread(str, 1, 16, fp), t, "%d"); ft_assert(t, rc == 16 && !memcmp(str, "The fread functi", 16)); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_READ); ft_assert(t, fp->fdpos == 128); ft_assert(t, fp->bufread == 128); ft_assert(t, fp->bufpos == 16); ft_assert(t, fp->eof == 0); ft_assert(t, feof(fp) == 0); /* Perform several reads that are longer than the buffer */ DO_E(rc, fread(str, 1, 384, fp), t, "%d"); ft_assert(t, rc == 384 && !memcmp(str, filler+16, 384)); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_READ); ft_assert(t, fp->fdpos == 512); ft_assert(t, fp->bufread == 128); ft_assert(t, fp->bufpos == 16); ft_assert(t, fp->eof == 0); ft_assert(t, feof(fp) == 0); /* Flush that buffer and check that we remain in place */ DO_E(rc, fflush(fp), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_READ); ft_assert(t, fp->fdpos == 400); ft_assert(t, fp->bufread == 0); ft_assert(t, fp->bufpos == 0); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 400); /* Do another standard read that will reach end-of-file on the fd */ DO_E(rc, fread(str, 1, 150, fp), t, "%d"); ft_assert(t, rc == 150); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_READ); ft_assert(t, fp->fdpos == 591); ft_assert(t, fp->bufread == 63); ft_assert(t, fp->bufpos == 22); ft_assert(t, fp->eof == 1); ft_assert(t, feof(fp) == 0); /* Do one last read that will not be entirely fulfilled */ DO_E(rc, fread(str, 1, 64, fp), t, "%d"); ft_assert(t, rc == 41); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_READ); ft_assert(t, fp->fdpos == 591); ft_assert(t, fp->bufread == 0); ft_assert(t, fp->bufpos == 0); ft_assert(t, fp->eof == 1); ft_assert(t, feof(fp) == 1); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 591); /* Rewind and move around */ rewind(fp); ft_log(t, "rewind(fp)\n"); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fseek(fp, -20, SEEK_END), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 571); ft_assert(t, fp->eof == 0); ft_assert(t, feof(fp) == 0); /* Move around with fgetpos() and fsetpos() */ DO_E(rc, fgetpos(fp, &pos1), t, "%d"); ft_assert(t, rc == 0); ft_assert(t, (size_t)pos1 == 571); DO_E(rc, fseek(fp, 12, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fgetpos(fp, &pos2), t, "%d"); ft_assert(t, rc == 0); ft_assert(t, (size_t)pos2 == 12); DO_E(rc, fsetpos(fp, &pos1), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 571); DO_E(rc, fsetpos(fp, &pos2), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 12); /* Seek at end of file, the EOF flag should be cleared */ DO_E(rc, fseek(fp, 0, SEEK_END), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 591); ft_assert(t, fp->eof == 0); ft_assert(t, feof(fp) == 0); /* Attempt to read and get 0+EOF but no error */ DO_E(rc, fread(str, 8, 1, fp), t, "%d"); ft_assert(t, rc == 0); ft_assert(t, fp->eof == 1); ft_assert(t, feof(fp) == 1); /* Read into the buffer, then flush by seeking */ DO_E(rc, fseek(fp, 0, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fread(str, 1, 64, fp), t, "%d"); ft_assert(t, rc == 64); ft_assert(t, !memcmp(str, filler, 64)); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_READ); ft_assert(t, fp->fdpos == 128); ft_assert(t, fp->bufread == 128); ft_assert(t, fp->bufpos == 64); ft_assert(t, fp->eof == 0); ft_assert(t, feof(fp) == 0); DO_E(rc, fseek(fp, 16, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 16); ft_assert(t, fp->bufread == 0); ft_assert(t, fp->bufpos == 0); ft_assert(t, fp->eof == 0); ft_assert(t, feof(fp) == 0); DO_E(rc, fread(str, 1, 64, fp), t, "%d"); ft_assert(t, rc == 64); ft_assert(t, !memcmp(str, filler+16, 64)); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_READ); ft_assert(t, fp->fdpos == 144); ft_assert(t, fp->bufread == 128); ft_assert(t, fp->bufpos == 64); ft_assert(t, fp->eof == 0); ft_assert(t, feof(fp) == 0); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); free(str); } static void _ft_stdio_simple_read(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_simple_read_switch, (void *)t)); } ft_test ft_stdio_simple_read = { .name = "Simple file reading", .function = _ft_stdio_simple_read, }; static void _ft_stdio_simple_write_switch(ft_test *t) { FILE *fp; int rc; /* Open a new file */ DO_E(fp, fopen("ft_stdio.txt", "w"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IOFBF, 128), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 0); /* Write small parts into the buffer until it flushes */ DO_E(rc, fwrite(filler, 1, 32, fp), t, "%d"); ft_assert(t, rc == 32); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->fdpos == 0); ft_assert(t, fp->bufpos == 32); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 32); DO_E(rc, fwrite(filler, 1, 32, fp), t, "%d"); ft_assert(t, rc == 32); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->fdpos == 0); ft_assert(t, fp->bufpos == 64); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 64); DO_E(rc, fwrite(filler, 1, 32, fp), t, "%d"); ft_assert(t, rc == 32); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->fdpos == 0); ft_assert(t, fp->bufpos == 96); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 96); DO_E(rc, fwrite(filler, 1, 32, fp), t, "%d"); ft_assert(t, rc == 32); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->fdpos == 128); ft_assert(t, fp->bufpos == 0); /* Write small bits and flush them immediately */ DO_E(rc, fwrite(filler, 1, 9, fp), t, "%d"); ft_assert(t, rc == 9); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->fdpos == 128); ft_assert(t, fp->bufpos == 9); DO_E(rc, fflush(fp), t, "%d"); ft_assert(t, rc == 0); ft_assert(t, fp->fdpos == 137); ft_assert(t, fp->bufpos == 0); /* Write a large section which will flush several times */ DO_E(rc, fwrite(filler, 1, 591, fp), t, "%d"); ft_assert(t, rc == 591); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->fdpos == 649); ft_assert(t, fp->bufpos == 79); /* Flush by seeking somewhere else */ DO_E(rc, fseek(fp, -16, SEEK_END), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 712); ft_assert(t, fp->bufpos == 0); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 712); /* Now overwrite the last 16 bytes and extend the file */ DO_E(rc, fwrite(filler, 1, 32, fp), t, "%d"); ft_assert(t, rc == 32); ft_log_FILE(t, "", fp); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->fdpos == 712); ft_assert(t, fp->bufpos == 32); /* Close the file, which should flush */ DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); /* Check the new file size */ DO_E(fp, fopen("ft_stdio.txt", "r"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; DO_E(rc, fseek(fp, 0, SEEK_END), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 744); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _ft_stdio_simple_write(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_simple_write_switch, (void *)t)); } ft_test ft_stdio_simple_write = { .name = "Simple file writing", .function = _ft_stdio_simple_write, }; static void _ft_stdio_line_buffering_switch(ft_test *t) { FILE *fp; int rc; /* Open a new line-buffered file */ DO_E(fp, fopen("ft_stdio.txt", "w"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IOLBF, 128), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); /* Write some short strings, which should flush due to newlines */ DO_E(rc, fwrite("Line #1\n", 1, 8, fp), t, "%d"); ft_assert(t, rc == 8); ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 8); ft_assert(t, fp->bufpos == 0); DO_E(rc, fwrite("A longer line #2\n", 1, 17, fp), t, "%d"); ft_assert(t, rc == 17); ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 25); ft_assert(t, fp->bufpos == 0); DO_E(rc, fwrite("Partial line #", 1, 14, fp), t, "%d"); ft_assert(t, rc == 14); ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 25); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->bufpos == 14); DO_E(rc, fwrite("3\nand something on the-- ", 1, 25, fp), t, "%d"); ft_assert(t, rc == 25); ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 41); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->bufpos == 23); /* The buffer should still flush when full */ DO_E(rc, fwrite(filler, 1, 250, fp), t, "%d"); ft_assert(t, rc == 250); ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 297); ft_assert(t, fp->bufdir == __FILE_BUF_WRITE); ft_assert(t, fp->bufpos == 17); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _ft_stdio_line_buffering(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_line_buffering_switch, (void *)t)); } ft_test ft_stdio_line_buffering = { .name = "Writing with line buffering", .function = _ft_stdio_line_buffering, }; static void _ft_stdio_update_ungetc_switch(ft_test *t) { FILE *fp; int rc; char str[128]; /* Create a file in update mode with w+ */ DO_E(fp, fopen("ft_stdio.txt", "w+"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); /* Write some data, then re-read it, then re-write it */ DO_E(rc, fwrite(filler, 1, 64, fp), t, "%d"); ft_assert(t, rc == 64); ft_log_FILE(t, "", fp); DO_E(rc, fseek(fp, 0, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); DO_E(rc, fread(str, 1, 16, fp), t, "%d"); ft_assert(t, rc == 16); ft_log_FILE(t, "", fp); DO_E(rc, fread(str, 1, 48, fp), t, "%d"); ft_assert(t, rc == 48); ft_log_FILE(t, "", fp); ft_assert(t, ftell(fp) == 64); DO_E(rc, fseek(fp, -16, SEEK_CUR), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fwrite(str, 1, 48, fp), t, "%d"); ft_assert(t, rc == 48); ft_log_FILE(t, "", fp); DO_E(rc, fwrite(str, 1, 16, fp), t, "%d"); ft_assert(t, rc == 16); ft_log_FILE(t, "", fp); ft_assert(t, ftell(fp) == 112); /* Re-read it and check it */ rewind(fp); ft_log(t, "rewind(fp)\n"); ft_log_FILE(t, "", fp); DO_E(rc, fread(str, 1, 112, fp), t, "%d"); ft_assert(t, rc == 112); ft_log_FILE(t, "", fp); ft_assert(t, !memcmp(str, filler, 48)); ft_assert(t, !memcmp(str+48, filler+16, 48)); ft_assert(t, !memcmp(str+96, filler+16, 16)); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); ft_log(t, "\n"); /* Reopen that file with r+ */ DO_E(fp, fopen("ft_stdio.txt", "r+"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); /* ungetc() some characters before position 0 */ DO_E(rc, ungetc('#', fp), t, "'%c'"); ft_assert(t, rc == '#'); ft_log_FILE(t, "", fp); DO_E(rc, ungetc('!', fp), t, "'%c'"); ft_assert(t, rc == '!'); ft_log_FILE(t, "", fp); DO_E(rc, ungetc('$', fp), t, "'%c'"); ft_assert(t, rc == '$'); ft_log_FILE(t, "", fp); /* Read them back along with some actual data */ DO_E(rc, fread(str, 1, 51, fp), t, "%d"); ft_assert(t, rc == 51); ft_log_FILE(t, "", fp); ft_assert(t, !memcmp(str, "$!#", 3) && !memcmp(str+3, filler, 48)); /* Do it again after position 0 */ DO_E(rc, ungetc('#', fp), t, "'%c'"); ft_assert(t, rc == '#'); ft_log_FILE(t, "", fp); DO_E(rc, ungetc('!', fp), t, "'%c'"); ft_assert(t, rc == '!'); ft_log_FILE(t, "", fp); DO_E(rc, ungetc('$', fp), t, "'%c'"); ft_assert(t, rc == '$'); ft_log_FILE(t, "", fp); /* Read them back as well as the second segment */ DO_E(rc, fread(str, 1, 51, fp), t, "%d"); ft_assert(t, rc == 51); ft_log_FILE(t, "", fp); ft_assert(t, !memcmp(str, "$!#", 3) && !memcmp(str+3, filler+16, 48)); /* Push some characters but discard them by rewind() */ DO_E(rc, ungetc('#', fp), t, "'%c'"); ft_assert(t, rc == '#'); ft_log_FILE(t, "", fp); DO_E(rc, fseek(fp, 0, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); DO_E(rc, fread(str, 1, 48, fp), t, "%d"); ft_assert(t, rc == 48); ft_log_FILE(t, "", fp); ft_assert(t, !memcmp(str, filler, 48)); /* Discard buffered data by a write (nonstandard but supported) */ DO_E(rc, fwrite(filler, 1, 48, fp), t, "%d"); ft_assert(t, rc == 48); ft_log_FILE(t, "", fp); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); /* Try ungetc() like previousy but with a non-buffered stream */ DO_E(fp, fopen("ft_stdio.txt", "r+"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IONBF, 0), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); DO_E(rc, fseek(fp, 0, SEEK_END), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 112); DO_E(rc, fseek(fp, 0, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); /* ungetc() some characters before position 0 */ DO_E(rc, ungetc('#', fp), t, "'%c'"); ft_assert(t, rc == '#'); ft_log_FILE(t, "", fp); DO_E(rc, ungetc('!', fp), t, "'%c'"); ft_assert(t, rc == '!'); ft_log_FILE(t, "", fp); DO_E(rc, ungetc('$', fp), t, "'%c'"); ft_assert(t, rc == '$'); ft_log_FILE(t, "", fp); /* Read them back along with some actual data */ DO_E(rc, fread(str, 1, 51, fp), t, "%d"); ft_assert(t, rc == 51); ft_log_FILE(t, "", fp); ft_assert(t, !memcmp(str, "$!#", 3) && !memcmp(str+3, filler, 48)); /* Do it again after position 0 */ DO_E(rc, ungetc('#', fp), t, "'%c'"); ft_assert(t, rc == '#'); ft_log_FILE(t, "", fp); DO_E(rc, ungetc('!', fp), t, "'%c'"); ft_assert(t, rc == '!'); ft_log_FILE(t, "", fp); DO_E(rc, ungetc('$', fp), t, "'%c'"); ft_assert(t, rc == '$'); ft_log_FILE(t, "", fp); /* Read them back as well as the second segment */ DO_E(rc, fread(str, 1, 51, fp), t, "%d"); ft_assert(t, rc == 51); ft_log_FILE(t, "", fp); ft_assert(t, !memcmp(str, "$!#", 3) && !memcmp(str+3, filler, 48)); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _ft_stdio_update_ungetc(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_update_ungetc_switch, (void *)t)); } ft_test ft_stdio_update_ungetc = { .name = "Update modes and ungetc()", .function = _ft_stdio_update_ungetc, }; static void _ft_stdio_append_switch(ft_test *t) { FILE *fp; int rc; char str[40]; /* Remove the file if it exists */ DO_E(rc, remove("ft_append.txt"), t, "%d"); ft_assert(t, rc == 0 || (rc == -1 && errno == ENOENT)); /* Append some data with mode a */ DO_E(fp, fopen("ft_append.txt", "a"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, fwrite("Line #1\n", 1, 8, fp), t, "%d"); ft_assert(t, rc == 8); ft_log_FILE(t, "", fp); DO_E(rc, fseek(fp, 0, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); ft_assert(t, ftell(fp) == 0); DO_E(rc, fwrite("Line #2\n", 1, 8, fp), t, "%d"); ft_assert(t, rc == 8); ft_log_FILE(t, "", fp); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 16); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); /* Append more with a+ */ DO_E(fp, fopen("ft_append.txt", "a+"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; DO_E(rc, fseek(fp, 0, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); ft_assert(t, ftell(fp) == 0); DO_E(rc, fread(str, 1, 8, fp), t, "%d"); ft_assert(t, rc == 8); ft_assert(t, ftell(fp) == 8); ft_log_FILE(t, "", fp); DO_E(rc, fwrite(str, 1, 8, fp), t, "%d"); ft_assert(t, rc == 8); ft_log_FILE(t, "", fp); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 24); DO_E(rc, fseek(fp, 8, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fread(str, 1, 16, fp), t, "%d"); ft_assert(t, rc == 16); ft_log_FILE(t, "", fp); DO_E(rc, fseek(fp, -8, SEEK_END), t, "%d"); ft_assert(t, rc == 0); ft_assert(t, ftell(fp) == 16); DO_E(rc, fwrite(str, 1, 16, fp), t, "%d"); ft_assert(t, rc == 16); ft_log_FILE(t, "", fp); DO_E(rc, ftell(fp), t, "%d"); ft_assert(t, rc == 40); DO_E(rc, fseek(fp, 0, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fread(str, 1, 40, fp), t, "%d"); ft_assert(t, rc == 40); ft_assert(t, !memcmp(str, "Line #1\nLine #2\nLine #1\nLine #2\nLine #1\n", 40)); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _ft_stdio_append(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_append_switch, (void *)t)); } ft_test ft_stdio_append = { .name = "Append modes", .function = _ft_stdio_append, }; static void _ft_stdio_fdopen_switch(ft_test *t) { int fd, rc; FILE *fp; char str[128]; /* Write some stuff through fdopen() */ DO_E(fd, creat("ft_fdopen.txt", 0755), t, "%d"); ft_assert(t, fd >= 0); if(fd < 0) return; DO_E(fp, fdopen(fd, "w"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) { close(fd); return; } DO_E(rc, fwrite(filler, 1, 128, fp), t, "%d"); ft_assert(t, rc == 128); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); /* Check that the contents are correct */ DO_E(fp, fopen("ft_fdopen.txt", "r"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; DO_E(rc, fread(str, 1, 128, fp), t, "%d"); ft_assert(t, rc == 128); ft_assert(t, !memcmp(str, filler, 128)); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _ft_stdio_fdopen(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_fdopen_switch, (void *)t)); } ft_test ft_stdio_fdopen = { .name = "FILE wrapper with fdopen()", .function = _ft_stdio_fdopen, }; static void _ft_stdio_write_chars_switch(ft_test *t) { FILE *fp; int rc; /* Create a buffered file with basic data */ DO_E(fp, fopen("ft_fput.txt", "w"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IOFBF, 32), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fputs("Test!", fp), t, "%d"); ft_assert(t, rc >= 0); ft_log_FILE(t, "",fp); ft_assert(t, fp->fdpos == 0); ft_assert(t, fp->bufpos == 5); DO_E(rc, fputs("Testing fputs() on _IOFBF streams\n", fp), t, "%d"); ft_assert(t, rc >= 0); ft_log_FILE(t, "",fp); ft_assert(t, fp->fdpos == 32); ft_assert(t, fp->bufpos == 7); for(int i = 0; i < 26; i++) { DO_E(rc, fputc('A'+i, fp), t, "'%c'"); ft_assert(t, rc == 'A'+i); DO_E(rc, fputc('a'+i, fp), t, "'%c'"); ft_assert(t, rc == 'a'+i); } ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 64); ft_assert(t, fp->bufpos == 27); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); /* Same, but make it line-buffered */ DO_E(fp, fopen("ft_fput.txt", "w"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IOLBF, 32), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fputs("Testing fputs() on _IOLBF streams\n", fp), t, "%d"); ft_assert(t, rc >= 0); ft_log_FILE(t, "",fp); ft_assert(t, fp->fdpos == 34); ft_assert(t, fp->bufpos == 0); for(int i = 0; i < 26; i++) { DO_E(rc, fputc('a'+i, fp), t, "'%c'"); ft_assert(t, rc == 'a'+i); } ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 34); ft_assert(t, fp->bufpos == 26); DO_E(rc, putc('\n', fp), t, "%d"); ft_assert(t, rc == '\n'); ft_log_FILE(t, "", fp); ft_assert(t, fp->fdpos == 61); ft_assert(t, fp->bufpos == 0); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); /* Same with unbuffered */ DO_E(fp, fopen("ft_fput.txt", "w"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; ft_log_FILE(t, "", fp); DO_E(rc, setvbuf(fp, NULL, _IONBF, 0), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, fputs("Testing fputs() on _IONBF streams\n", fp), t, "%d"); ft_assert(t, rc >= 0); ft_log_FILE(t, "",fp); /* Urgh this is going to be so slow. Don't do that kids */ for(int i = 0; i < 26; i++) { DO_E(rc, putc('a'+i, fp), t, "'%c'"); ft_assert(t, rc == 'a'+i); } ft_log_FILE(t, "", fp); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _ft_stdio_write_chars(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_write_chars_switch, (void *)t)); } ft_test ft_stdio_write_chars = { .name = "Character output functions", .function = _ft_stdio_write_chars, }; static void _ft_stdio_read_chars_switch(ft_test *t) { FILE *fp; int rc; char str[20], *retstr; /* Create an initial file with some filler */ DO_E(fp, fopen("ft_fget.txt", "w"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; DO_E(rc, fputs("Line #1\nLine #2\nLine #3", fp), t, "%d"); ft_assert(t, rc >= 0); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); /* Read it with fgets() and fgetc() */ DO_E(fp, fopen("ft_fget.txt", "r"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; DO_E(rc, setvbuf(fp, NULL, _IOFBF, 14), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); memset(str, 0xff, sizeof str); DO_E(retstr, fgets(str, 20, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log_FILE(t, "", fp); ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "Line #1\n")); memset(str, 0xff, sizeof str); DO_E(retstr, fgets(str, 6, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log_FILE(t, "", fp); ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "Line ")); memset(str, 0xff, sizeof str); DO_E(retstr, fgets(str, 20, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log_FILE(t, "", fp); ft_assert(t, !strcmp(str, "#2\n")); ft_log(t, "Reading to EOF with fgetc()...\n"); for(int i = 0; !feof(fp); i++) { int c = fgetc(fp); if(c == EOF) break; str[i] = c; str[i+1] = 0; } ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "Line #3")); /* Test fgets() at end of file */ DO_E(rc, fseek(fp, 16, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); DO_E(retstr, fgets(str, 20, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "Line #3")); /* Test interactions of fgets() and fgetc() with ungetc() */ DO_E(rc, fseek(fp, 8, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, ungetc('#', fp), t, "'%c'"); ft_assert(t, rc == '#'); DO_E(rc, ungetc('!', fp), t, "'%c'"); ft_assert(t, rc == '!'); DO_E(rc, ungetc('$', fp), t, "'%c'"); ft_assert(t, rc == '$'); ft_log_FILE(t, "", fp); DO_E(retstr, fgets(str, 20, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log_FILE(t, "", fp); ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "$!#Line #2\n")); DO_E(rc, ungetc('#', fp), t, "'%c'"); ft_assert(t, rc == '#'); DO_E(rc, ungetc('!', fp), t, "'%c'"); ft_assert(t, rc == '!'); DO_E(rc, ungetc('$', fp), t, "'%c'"); ft_assert(t, rc == '$'); ft_log_FILE(t, "", fp); ft_log(t, "Reading to EOF with fgetc()...\n"); for(int i = 0; !feof(fp); i++) { int c = getc(fp); if(c == EOF) break; str[i] = c; str[i+1] = 0; } ft_log_FILE(t, "", fp); ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "$!#Line #3")); /* Try again without buffering */ rewind(fp); ft_log(t, "rewind(fp)\n"); DO_E(rc, setvbuf(fp, NULL, _IONBF, 0), t, "%d"); ft_assert(t, rc == 0); memset(str, 0xff, sizeof str); DO_E(retstr, fgets(str, 20, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log_FILE(t, "", fp); ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "Line #1\n")); memset(str, 0xff, sizeof str); DO_E(retstr, fgets(str, 6, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log_FILE(t, "", fp); ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "Line ")); memset(str, 0xff, sizeof str); DO_E(retstr, fgets(str, 20, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log_FILE(t, "", fp); ft_assert(t, !strcmp(str, "#2\n")); ft_log(t, "Reading to EOF with fgetc()...\n"); for(int i = 0; !feof(fp); i++) { int c = fgetc(fp); if(c == EOF) break; str[i] = c; str[i+1] = 0; } ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "Line #3")); /* Re-test interactions with ungetc() */ DO_E(rc, fseek(fp, 8, SEEK_SET), t, "%d"); ft_assert(t, rc == 0); DO_E(rc, ungetc('#', fp), t, "'%c'"); ft_assert(t, rc == '#'); DO_E(rc, ungetc('!', fp), t, "'%c'"); ft_assert(t, rc == '!'); DO_E(rc, ungetc('$', fp), t, "'%c'"); ft_assert(t, rc == '$'); ft_log_FILE(t, "", fp); DO_E(retstr, fgets(str, 20, fp), t, "%p"); ft_assert(t, retstr != NULL); ft_log_FILE(t, "", fp); ft_log(t, "str = '%s'\n", str); ft_assert(t, !strcmp(str, "$!#Line #2\n")); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _ft_stdio_read_chars(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_read_chars_switch, (void *)t)); } ft_test ft_stdio_read_chars = { .name = "Character input functions", .function = _ft_stdio_read_chars, }; static int _ft_stdio_stdstreams_input_pos; static ssize_t _ft_stdio_stdstreams_input(void *data, size_t request_size) { int available = strlen(filler) - _ft_stdio_stdstreams_input_pos; int size = min(available, request_size); if(size <= 0) return 0; memcpy(data, filler + _ft_stdio_stdstreams_input_pos, size); _ft_stdio_stdstreams_input_pos += size; return size; } static ft_test *_ft_stdio_stdstreams_output_t; static ssize_t _ft_stdio_stdstreams_output(void const *data, size_t size) { if(!_ft_stdio_stdstreams_output_t) return -1; ft_log_raw(_ft_stdio_stdstreams_output_t, " ", 10); ft_log_raw(_ft_stdio_stdstreams_output_t, data, size); return size; } static void delayed_printf(char const *fmt, ...) { va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); } static void delayed_dprintf(int fd, char const *fmt, ...) { va_list args; va_start(args, fmt); vdprintf(fd, fmt, args); va_end(args); } static void _ft_stdio_stdstreams(ft_test *t) { ft_log(t, "For this test, stdin will read from a fixed string, and " "stdout and stderr will output here, with a prefix before " "every write.\n\n"); _ft_stdio_stdstreams_input_pos = 0; ft_streams_input = _ft_stdio_stdstreams_input; /* Get rid of previous data left in the buffer... */ fflush(stdin); _ft_stdio_stdstreams_output_t = t; ft_streams_output = _ft_stdio_stdstreams_output; fputs("Test string!\n", stdout); puts("Take this!"); printf("pos = %d\n", _ft_stdio_stdstreams_input_pos); delayed_printf("best number = %d\n", 73); puts("Let's read stuff from stdin..."); char str[33]; for(int i = 0; i < 32; i++) str[i] = getchar(); str[32] = 0; puts(str); fprintf(stderr, "This on stderr"); fprintf(stderr, "You can tell because it's not _IOLBF!\n"); fgets(str, 33, stdin); puts(str); putchar('p'); putchar('u'); putchar('t'); putchar('c'); putchar('h'); putchar('a'); putchar('r'); putchar('\n'); errno = ENOMEM; perror(NULL); errno = 0; perror("but now"); /* Yes, this is bad practice */ fflush(stdout); dprintf(STDOUT_FILENO, "dprintf = %p\n", dprintf); delayed_dprintf(STDOUT_FILENO, "vdprintf = %p\n", vdprintf); ft_streams_input = NULL; ft_streams_output = NULL; ft_log(t, "\n-- end --"); } ft_test ft_stdio_stdstreams = { .name = "stdin, stdout, and stderr", .function = _ft_stdio_stdstreams, }; static void _ft_stdio_getdelim_switch(ft_test *t) { FILE *fp; int rc; size_t n=0; ssize_t result; char *lineptr = NULL; /* Create an initial file with some filler */ DO_E(fp, fopen("ft_getdelim.txt", "w"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; DO_E(rc, fputs("Line #1\nLine #2\n\nAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ\nLine #3", fp), t, "%d"); ft_assert(t, rc >= 0); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); /* Read it with getline() and getdelim() */ DO_E(fp, fopen("ft_getdelim.txt", "r"), t, "%p"); ft_assert(t, fp != NULL); if(fp == NULL) return; DO_E(rc, setvbuf(fp, NULL, _IOFBF, 14), t, "%d"); ft_assert(t, rc == 0); ft_log_FILE(t, "", fp); /* also test automatic alloc if lineptr=NULL */ DO_E(result, getline(&lineptr, &n, fp), t, "%ld"); ft_assert(t, result == 8); ft_log_FILE(t, "", fp); ft_log(t, "lineptr = '%s'\n", lineptr); ft_assert(t, !strcmp(lineptr, "Line #1\n")); DO_E(result, getdelim(&lineptr, &n, ' ', fp), t, "%ld"); ft_assert(t, result == 5); ft_log_FILE(t, "", fp); ft_log(t, "lineptr = '%s'\n", lineptr); ft_assert(t, !strcmp(lineptr, "Line ")); DO_E(result, getline(&lineptr, &n, fp), t, "%ld"); ft_assert(t, result == 3); ft_log_FILE(t, "", fp); ft_assert(t, !strcmp(lineptr, "#2\n")); /* Test getline with 0 char line */ DO_E(result, getline(&lineptr, &n, fp), t, "%ld"); ft_assert(t, result == 1); ft_log_FILE(t, "", fp); ft_log(t, "lineptr = '%s'\n", lineptr); ft_assert(t, !strcmp(lineptr, "\n")); /* Test getline with long line */ DO_E(result, getline(&lineptr, &n, fp), t, "%ld"); ft_assert(t, result == 105); ft_log_FILE(t, "", fp); ft_log(t, "lineptr = '%s'\n", lineptr); ft_log(t, "n = '%u'\n", n); ft_assert(t, !strcmp(lineptr, "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ\n")); /* test that we don't get -1 in case delim iss last symbol in file */ DO_E(result, getdelim(&lineptr, &n, '3', fp), t, "%ld"); ft_assert(t, result == 7); ft_log_FILE(t, "", fp); ft_log(t, "lineptr = '%s'\n", lineptr); ft_log(t, "n = '%u'\n", n); ft_assert(t, !strcmp(lineptr, "Line #3")); ft_log(t, "Reading to EOF with getdelim(delim not in file)...\n"); rewind(fp); ft_log(t, "rewind(fp)\n"); DO_E(result, getdelim(&lineptr, &n, '5', fp), t, "%ld"); ft_assert(t, result == -1); ft_log(t, "lineptr = '%s'\n", lineptr); ft_assert(t, !strcmp(lineptr, "Line #1\nLine #2\n\nAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ\nLine #3")); DO_E(rc, fclose(fp), t, "%d"); ft_assert(t, rc == 0); } static void _ft_stdio_getdelim(ft_test *t) { gint_world_switch(GINT_CALL(_ft_stdio_getdelim_switch, (void *)t)); } ft_test ft_stdio_getdelim = { .name = "delimited string input", .function = _ft_stdio_getdelim, };