Requirements:
Following are the functions I need to test, along with the cases where they can fail:
* readlines_fread() can fail due to:
- resize_buf() failure.
- alloc_and_append_line() failure.
- fread() failure.
* readlines_getline() can fail due to:
- getline() failure.
- resize_buf() failure.
- append_line() failure.
* readlines_mmap_memchr() can fail due to:
- fstat() failure.
- mmap() failure.
- resize_buf() failure.
- append_line() failure.
* readlines_mmap_getline() can fail due to:
- fstat() failure.
- mmap() failure.
- fmemopen() failure.
- readlines_getline() failure.
* readlines_read() can fail due to:
- fileno() failure.
- read() failure.
- resize_buf() failure.
- alloc_and_append_line() failure.
Now I have to simulate out-of-memory conditions, read errors, invalid stream errors, et cetera. I have written stubs (I hope that's the term, correct me if I am wrong) for the above functions:
stubs.c:
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "../src/common.h"
#ifdef RESIZE_FBUF
#define resize_fbuf resize_fbuf_stub
bool resize_fbuf_stub(FileBuf *) { return false; }
#endif /* RESIZE_FBUF */
#ifdef ALLOC_AND_APPEND_LINE
#define alloc_and_append_line alloc_and_append_line_stub
bool alloc_and_append_line_stub(FileBuf *, size_t, char *) { return false; }
#endif /* ALLOC_AND_APPEND_LINE */
#ifdef APPEND_LINE
#define append_line append_line_stub
bool append_line_stub(FileBuf *, size_t, char *) { return false; }
#endif /* APPEND_LINE */
#ifdef FEOF
#define feof feof_stub
int feof_stub(FILE *) { return 0; }
#endif /* FEOF */
#ifdef FILENO
#define fileno fileno_stub
int fileno_stub(FILE *) { return -1; }
#endif /* FILENO */
#ifdef GETLINE
#define getline getline_stub
ssize_t getline_stub(char **, size_t *, FILE *) { return -1; }
#endif /* GETLINE */
#ifdef FSTAT
#define fstat fstat_stub
int fstat_stub(int, struct stat *) { return -1; }
#endif /* FSTAT */
#ifdef MMAP
#define mmap mmap_stub
void *mmap_stub(void *, size_t, int, int, int, off_t ) { return MAP_FAILED; }
#endif /* MMAP */
#ifdef FMEMOPEN
#define fmemopen fmemopen_stub
FILE *fmemopen_stub(void *, size_t, const char *) { return nullptr; }
#endif /* FMEMOPEN */
#ifdef READ
#define read read_stub
ssize_t read_stub(int, void *, size_t) { return -1; }
#endif /* READ */
Now in the test source file, I follow this pattern:
- Define the stub I require.
- Include
"stubs.c". - Then include the relevant source file for testing, say
"readlines_fread.c".
So basically, define macros for the original functions and replace them with the stubs that merely return the error values for those functions.
As a result, each failure case is tested for in separate source files, so for readlines_fread(), I have:
readlines_fread_test1.c:
#include <stdio.h>
#include "acutest.h"
#include "../src/common.h"
#define RESIZE_FBUF
#include "stubs.c"
#include "../src/FileBuf.h"
#include "../src/readlines_fread.c"
void test_oom_and_overflow_error(void)
{
FILE *const fp = fopen("test/stubs.c", "r");
TEST_ASSERT(fp);
TEST_CHECK(!readlines_fread(fp, &(FileBuf) {}));
}
TEST_LIST = {
{ "test_oom_and_overflow_error", test_oom_and_overflow_error },
{ nullptr, nullptr }
};
and, readlines_fread_test2.c
#include <stdio.h>
#include "acutest.h"
#define ALLOC_AND_APPEND_LINE
#include "stubs.c"
#include "../src/FileBuf.h"
#include "../src/readlines_fread.c"
void test_oom_and_overflow_condition(void)
{
FILE *const fp = fopen("test/stubs.c", "r");
TEST_ASSERT(fp);
TEST_CHECK(!readlines_fread(fp, &(FileBuf) {}));
}
TEST_LIST = {
{ "test_oom_and_overflow_condition", test_oom_and_overflow_condition},
{ nullptr, nullptr }
};
and, readlines_fread_test3.c:
#include <stdio.h>
#include "acutest.h"
#define FEOF
#include "stubs.c"
#include "../src/FileBuf.h"
#include "../src/readlines_fread.c"
void test_read_error(void)
{
FILE *const fp = fopen("test/stubs.c", "r");
TEST_ASSERT(fp);
TEST_CHECK(!readlines_fread(fp, &(FileBuf) {}));
}
TEST_LIST = {
{ "test_read_error", test_read_error},
{ nullptr, nullptr }
};
and same for other 4 routines.
Review Request:
After much searching, this is the method I found on StackOverflow. It does not seem very tidy, and has a lot of duplication, but works just fine.
Are there any problems with what I am doing? Are there better ways?
Note: Unnamed parameters in function definitions are allowed in C23. Furthermore, I do not wish to modify the source code for testing. So no TESTING macro that'd define the functions to the corresponding stubs, nor do I wish to play with the linker.
#ifdef FEOF #define feof feof_test #endif), and build the corresponding test object with-DFEOF. Requires a bit more of the build infrastructure though. \$\endgroup\$