You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-07-07 00:35:37 +02:00
These functions accepted const Buffer objects and returned non-const pointers which is definitely not a good idea. Add bufPtrConst() to handle cases where only a const return value is needed and update call sites. Use UNCONSTIFY() in cases where library code out of our control requires a non-const pointer. This includes the already-documented exception in command/backup/pageChecksum and input buffers in the gzCompress and gzDecompress filters.
610 lines
26 KiB
C
610 lines
26 KiB
C
/***********************************************************************************************************************************
|
|
Test IO
|
|
***********************************************************************************************************************************/
|
|
#include <fcntl.h>
|
|
|
|
#include "common/type/json.h"
|
|
|
|
#include "common/harnessFork.h"
|
|
|
|
/***********************************************************************************************************************************
|
|
Test functions for IoRead that are not covered by testing the IoBufferRead object
|
|
***********************************************************************************************************************************/
|
|
static bool
|
|
testIoReadOpen(void *driver)
|
|
{
|
|
if (driver == (void *)998)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static size_t
|
|
testIoRead(void *driver, Buffer *buffer, bool block)
|
|
{
|
|
ASSERT(driver == (void *)999);
|
|
(void)block;
|
|
|
|
bufCat(buffer, BUFSTRDEF("Z"));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool testIoReadCloseCalled = false;
|
|
|
|
static void
|
|
testIoReadClose(void *driver)
|
|
{
|
|
ASSERT(driver == (void *)999);
|
|
testIoReadCloseCalled = true;
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Test functions for IoWrite that are not covered by testing the IoBufferWrite object
|
|
***********************************************************************************************************************************/
|
|
static bool testIoWriteOpenCalled = false;
|
|
|
|
static void
|
|
testIoWriteOpen(void *driver)
|
|
{
|
|
ASSERT(driver == (void *)999);
|
|
testIoWriteOpenCalled = true;
|
|
}
|
|
|
|
static void
|
|
testIoWrite(void *driver, const Buffer *buffer)
|
|
{
|
|
ASSERT(driver == (void *)999);
|
|
ASSERT(strEq(strNewBuf(buffer), strNew("ABC")));
|
|
}
|
|
|
|
static bool testIoWriteCloseCalled = false;
|
|
|
|
static void
|
|
testIoWriteClose(void *driver)
|
|
{
|
|
ASSERT(driver == (void *)999);
|
|
testIoWriteCloseCalled = true;
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Test filter that counts total bytes
|
|
***********************************************************************************************************************************/
|
|
typedef struct IoTestFilterSize
|
|
{
|
|
MemContext *memContext;
|
|
size_t size;
|
|
} IoTestFilterSize;
|
|
|
|
static void
|
|
ioTestFilterSizeProcess(THIS_VOID, const Buffer *buffer)
|
|
{
|
|
THIS(IoTestFilterSize);
|
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM_P(VOID, this);
|
|
FUNCTION_LOG_PARAM(BUFFER, buffer);
|
|
FUNCTION_LOG_PARAM(SIZE, this->size);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(this != NULL);
|
|
ASSERT(buffer != NULL);
|
|
|
|
this->size += bufUsed(buffer);
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
}
|
|
|
|
static Variant *
|
|
ioTestFilterSizeResult(THIS_VOID)
|
|
{
|
|
THIS(IoTestFilterSize);
|
|
|
|
return varNewUInt64(this->size);
|
|
}
|
|
|
|
static IoFilter *
|
|
ioTestFilterSizeNew(const char *type)
|
|
{
|
|
IoFilter *this = NULL;
|
|
|
|
MEM_CONTEXT_NEW_BEGIN("IoTestFilterSize")
|
|
{
|
|
IoTestFilterSize *driver = memNew(sizeof(IoTestFilterSize));
|
|
|
|
*driver = (IoTestFilterSize)
|
|
{
|
|
.memContext = MEM_CONTEXT_NEW(),
|
|
};
|
|
|
|
this = ioFilterNewP(strNew(type), driver, NULL, .in = ioTestFilterSizeProcess, .result = ioTestFilterSizeResult);
|
|
}
|
|
MEM_CONTEXT_NEW_END();
|
|
|
|
return this;
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Test filter to multiply input to the output. It can also flush out a variable number of bytes at the end.
|
|
***********************************************************************************************************************************/
|
|
typedef struct IoTestFilterMultiply
|
|
{
|
|
MemContext *memContext;
|
|
unsigned int flushTotal;
|
|
bool writeZero;
|
|
char flushChar;
|
|
Buffer *multiplyBuffer;
|
|
unsigned int multiplier;
|
|
IoFilter *bufferFilter;
|
|
} IoTestFilterMultiply;
|
|
|
|
static void
|
|
ioTestFilterMultiplyProcess(THIS_VOID, const Buffer *input, Buffer *output)
|
|
{
|
|
THIS(IoTestFilterMultiply);
|
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
FUNCTION_LOG_PARAM_P(VOID, this);
|
|
FUNCTION_LOG_PARAM(BUFFER, input);
|
|
FUNCTION_LOG_PARAM(BUFFER, output);
|
|
FUNCTION_LOG_END();
|
|
|
|
ASSERT(this != NULL);
|
|
ASSERT(output != NULL && bufRemains(output) > 0);
|
|
|
|
if (input == NULL)
|
|
{
|
|
// Write nothing into the output buffer to make sure the filter processing will skip the remaining filters
|
|
if (!this->writeZero)
|
|
{
|
|
this->writeZero = true;
|
|
}
|
|
else
|
|
{
|
|
char flushZ[] = {this->flushChar, 0};
|
|
bufCat(output, bufNewC(flushZ, 1));
|
|
this->flushTotal--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this->multiplyBuffer == NULL)
|
|
{
|
|
this->multiplyBuffer = bufNew(bufUsed(input) * this->multiplier);
|
|
const unsigned char *inputPtr = bufPtrConst(input);
|
|
unsigned char *bufferPtr = bufPtr(this->multiplyBuffer);
|
|
|
|
for (unsigned int charIdx = 0; charIdx < bufUsed(input); charIdx++)
|
|
{
|
|
for (unsigned int multiplierIdx = 0; multiplierIdx < this->multiplier; multiplierIdx++)
|
|
bufferPtr[charIdx * this->multiplier + multiplierIdx] = inputPtr[charIdx];
|
|
}
|
|
|
|
bufUsedSet(this->multiplyBuffer, bufSize(this->multiplyBuffer));
|
|
}
|
|
|
|
ioFilterProcessInOut(this->bufferFilter, this->multiplyBuffer, output);
|
|
|
|
if (!ioFilterInputSame(this->bufferFilter))
|
|
this->multiplyBuffer = NULL;
|
|
}
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
}
|
|
|
|
static bool
|
|
ioTestFilterMultiplyDone(const THIS_VOID)
|
|
{
|
|
THIS(const IoTestFilterMultiply);
|
|
|
|
return this->flushTotal == 0;
|
|
}
|
|
|
|
static bool
|
|
ioTestFilterMultiplyInputSame(const THIS_VOID)
|
|
{
|
|
THIS(const IoTestFilterMultiply);
|
|
|
|
return ioFilterInputSame(this->bufferFilter);
|
|
}
|
|
|
|
static IoFilter *
|
|
ioTestFilterMultiplyNew(const char *type, unsigned int multiplier, unsigned int flushTotal, char flushChar)
|
|
{
|
|
IoFilter *this = NULL;
|
|
|
|
MEM_CONTEXT_NEW_BEGIN("IoTestFilterMultiply")
|
|
{
|
|
IoTestFilterMultiply *driver = memNew(sizeof(IoTestFilterMultiply));
|
|
|
|
*driver = (IoTestFilterMultiply)
|
|
{
|
|
.memContext = MEM_CONTEXT_NEW(),
|
|
.bufferFilter = ioBufferNew(),
|
|
.multiplier = multiplier,
|
|
.flushTotal = flushTotal,
|
|
.flushChar = flushChar,
|
|
};
|
|
|
|
VariantList *paramList = varLstNew();
|
|
varLstAdd(paramList, varNewStrZ(type));
|
|
varLstAdd(paramList, varNewUInt(multiplier));
|
|
varLstAdd(paramList, varNewUInt(flushTotal));
|
|
|
|
this = ioFilterNewP(
|
|
strNew(type), driver, paramList, .done = ioTestFilterMultiplyDone, .inOut = ioTestFilterMultiplyProcess,
|
|
.inputSame = ioTestFilterMultiplyInputSame);
|
|
}
|
|
MEM_CONTEXT_NEW_END();
|
|
|
|
return this;
|
|
}
|
|
|
|
/***********************************************************************************************************************************
|
|
Test Run
|
|
***********************************************************************************************************************************/
|
|
void
|
|
testRun(void)
|
|
{
|
|
FUNCTION_HARNESS_VOID();
|
|
|
|
// *****************************************************************************************************************************
|
|
if (testBegin("ioBufferSize() and ioBufferSizeSet()"))
|
|
{
|
|
TEST_RESULT_UINT(ioBufferSize(), 65536, "check initial buffer size");
|
|
TEST_RESULT_VOID(ioBufferSizeSet(16384), "set buffer size");
|
|
TEST_RESULT_UINT(ioBufferSize(), 16384, "check buffer size");
|
|
}
|
|
|
|
// *****************************************************************************************************************************
|
|
if (testBegin("IoRead, IoBufferRead, IoBuffer, IoSize, IoFilter, IoFilterGroup, and ioReadBuf()"))
|
|
{
|
|
IoRead *read = NULL;
|
|
Buffer *buffer = bufNew(2);
|
|
ioBufferSizeSet(2);
|
|
|
|
TEST_ASSIGN(
|
|
read, ioReadNewP((void *)998, .close = testIoReadClose, .open = testIoReadOpen, .read = testIoRead),
|
|
"create io read object");
|
|
|
|
TEST_RESULT_BOOL(ioReadOpen(read), false, " open io object");
|
|
|
|
TEST_ASSIGN(
|
|
read, ioReadNewP((void *)999, .close = testIoReadClose, .open = testIoReadOpen, .read = testIoRead),
|
|
"create io read object");
|
|
|
|
TEST_RESULT_BOOL(ioReadOpen(read), true, " open io object");
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 2, " read 2 bytes");
|
|
TEST_RESULT_BOOL(ioReadEof(read), false, " no eof");
|
|
TEST_RESULT_VOID(ioReadClose(read), " close io object");
|
|
TEST_RESULT_BOOL(testIoReadCloseCalled, true, " check io object closed");
|
|
|
|
TEST_RESULT_VOID(ioReadFree(read), " free read object");
|
|
|
|
// Read a zero-length buffer to be sure it is not passed on to the filter group
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
IoRead *bufferRead = NULL;
|
|
ioBufferSizeSet(2);
|
|
buffer = bufNew(2);
|
|
Buffer *bufferOriginal = bufNew(0);
|
|
|
|
TEST_ASSIGN(bufferRead, ioBufferReadNew(bufferOriginal), "create empty buffer read object");
|
|
TEST_RESULT_BOOL(ioReadOpen(bufferRead), true, " open");
|
|
TEST_RESULT_BOOL(ioReadEof(bufferRead), false, " not eof");
|
|
TEST_RESULT_UINT(ioRead(bufferRead, buffer), 0, " read 0 bytes");
|
|
TEST_RESULT_BOOL(ioReadEof(bufferRead), true, " now eof");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
ioBufferSizeSet(2);
|
|
buffer = bufNew(2);
|
|
bufferOriginal = bufNewC("123", 3);
|
|
|
|
TEST_ASSIGN(bufferRead, ioBufferReadNew(bufferOriginal), "create buffer read object");
|
|
|
|
TEST_RESULT_VOID(ioFilterGroupClear(ioReadFilterGroup(bufferRead)), " clear does nothing when no filters");
|
|
TEST_RESULT_VOID(ioFilterGroupAdd(ioReadFilterGroup(bufferRead), ioSizeNew()), " add filter to be cleared");
|
|
TEST_RESULT_VOID(ioFilterGroupClear(ioReadFilterGroup(bufferRead)), " clear size filter");
|
|
|
|
IoFilter *sizeFilter = ioSizeNew();
|
|
TEST_RESULT_VOID(
|
|
ioFilterGroupAdd(ioReadFilterGroup(bufferRead), ioTestFilterMultiplyNew("double", 2, 3, 'X')),
|
|
" add filter to filter group");
|
|
TEST_RESULT_PTR(
|
|
ioFilterGroupInsert(ioReadFilterGroup(bufferRead), 0, sizeFilter), bufferRead->filterGroup,
|
|
" add filter to filter group");
|
|
TEST_RESULT_VOID(ioFilterGroupAdd(ioReadFilterGroup(bufferRead), ioSizeNew()), " add filter to filter group");
|
|
IoFilter *bufferFilter = ioBufferNew();
|
|
TEST_RESULT_VOID(ioFilterGroupAdd(ioReadFilterGroup(bufferRead), bufferFilter), " add filter to filter group");
|
|
TEST_RESULT_PTR(ioFilterMove(NULL, memContextTop()), NULL, " move NULL filter to top context");
|
|
TEST_RESULT_STR_Z(
|
|
jsonFromVar(ioFilterGroupParamAll(ioReadFilterGroup(bufferRead))),
|
|
"[{\"size\":null},{\"double\":[\"double\",2,3]},{\"size\":null},{\"buffer\":null}]", " check filter params");
|
|
|
|
TEST_RESULT_BOOL(ioReadOpen(bufferRead), true, " open");
|
|
TEST_RESULT_INT(ioReadHandle(bufferRead), -1, " handle invalid");
|
|
TEST_RESULT_BOOL(ioReadEof(bufferRead), false, " not eof");
|
|
TEST_RESULT_UINT(ioRead(bufferRead, buffer), 2, " read 2 bytes");
|
|
TEST_RESULT_UINT(ioRead(bufferRead, buffer), 0, " read 0 bytes (full buffer)");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "11", " check read");
|
|
TEST_RESULT_STR_Z(ioFilterType(sizeFilter), "size", "check filter type");
|
|
TEST_RESULT_BOOL(ioReadEof(bufferRead), false, " not eof");
|
|
|
|
TEST_RESULT_VOID(bufUsedZero(buffer), " zero buffer");
|
|
TEST_RESULT_UINT(ioRead(bufferRead, buffer), 2, " read 2 bytes");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "22", " check read");
|
|
|
|
TEST_ASSIGN(buffer, bufNew(3), "change output buffer size to 3");
|
|
TEST_RESULT_UINT(ioRead(bufferRead, buffer), 3, " read 3 bytes");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "33X", " check read");
|
|
|
|
TEST_RESULT_VOID(bufUsedZero(buffer), " zero buffer");
|
|
TEST_RESULT_UINT(ioRead(bufferRead, buffer), 2, " read 2 bytes");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "XX", " check read");
|
|
TEST_RESULT_BOOL(ioReadEof(bufferRead), true, " eof");
|
|
TEST_RESULT_UINT(ioBufferRead(ioReadDriver(bufferRead), buffer, true), 0, " eof from driver");
|
|
TEST_RESULT_UINT(ioRead(bufferRead, buffer), 0, " read 0 bytes");
|
|
TEST_RESULT_VOID(ioReadClose(bufferRead), " close buffer read object");
|
|
TEST_RESULT_STR_Z(
|
|
jsonFromVar(ioFilterGroupResultAll(ioReadFilterGroup(bufferRead))),
|
|
"{\"buffer\":null,\"double\":null,\"size\":[3,9]}",
|
|
" check filter result all");
|
|
|
|
TEST_RESULT_PTR(ioReadFilterGroup(bufferRead), ioReadFilterGroup(bufferRead), " check filter group");
|
|
TEST_RESULT_UINT(
|
|
varUInt64(varLstGet(varVarLst(ioFilterGroupResult(ioReadFilterGroup(bufferRead), ioFilterType(sizeFilter))), 0)), 3,
|
|
" check filter result");
|
|
TEST_RESULT_PTR(
|
|
ioFilterGroupResult(ioReadFilterGroup(bufferRead), strNew("double")), NULL, " check filter result is NULL");
|
|
TEST_RESULT_UINT(
|
|
varUInt64(varLstGet(varVarLst(ioFilterGroupResult(ioReadFilterGroup(bufferRead), ioFilterType(sizeFilter))), 1)), 9,
|
|
" check filter result");
|
|
|
|
TEST_RESULT_PTR(ioFilterDriver(bufferFilter), bufferFilter->driver, " check filter driver");
|
|
TEST_RESULT_PTR(ioFilterInterface(bufferFilter), &bufferFilter->interface, " check filter interface");
|
|
|
|
TEST_RESULT_VOID(ioFilterFree(bufferFilter), " free buffer filter");
|
|
TEST_RESULT_VOID(ioFilterGroupFree(ioReadFilterGroup(bufferRead)), " free filter group object");
|
|
|
|
// Set filter group results
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
IoFilterGroup *filterGroup = ioFilterGroupNew();
|
|
filterGroup->opened = true;
|
|
TEST_RESULT_VOID(ioFilterGroupResultAllSet(filterGroup, NULL), "null result");
|
|
TEST_RESULT_VOID(ioFilterGroupResultAllSet(filterGroup, jsonToVar(strNew("{\"test\":777}"))), "add result");
|
|
filterGroup->closed = true;
|
|
TEST_RESULT_UINT(varUInt64(ioFilterGroupResult(filterGroup, strNew("test"))), 777, " check filter result");
|
|
|
|
// Read a zero-size buffer to ensure filters are still processed even when there is no input. Some filters (e.g. encryption
|
|
// and compression) will produce output even if there is no input.
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
ioBufferSizeSet(1024);
|
|
buffer = bufNew(1024);
|
|
bufferOriginal = bufNew(0);
|
|
|
|
TEST_ASSIGN(bufferRead, ioBufferReadNew(bufferOriginal), "create buffer read object");
|
|
TEST_RESULT_VOID(
|
|
ioFilterGroupAdd(ioReadFilterGroup(bufferRead), ioTestFilterMultiplyNew("double", 2, 5, 'Y')),
|
|
" add filter that produces output with no input");
|
|
TEST_RESULT_BOOL(ioReadOpen(bufferRead), true, " open read");
|
|
TEST_RESULT_UINT(ioRead(bufferRead, buffer), 5, " read 5 chars");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "YYYYY", " check buffer");
|
|
|
|
// Mixed line and buffer read
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
ioBufferSizeSet(5);
|
|
read = ioBufferReadNew(BUFSTRDEF("AAA123\n1234\n\n12\nBDDDEFF"));
|
|
ioReadOpen(read);
|
|
buffer = bufNew(3);
|
|
|
|
// Start with a buffer read
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 3, "read buffer");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "AAA", " check buffer");
|
|
|
|
// Do line reads of various lengths
|
|
TEST_RESULT_STR_Z(ioReadLine(read), "123", "read line");
|
|
TEST_RESULT_STR_Z(ioReadLine(read), "1234", "read line");
|
|
TEST_RESULT_STR_Z(ioReadLine(read), "", "read line");
|
|
TEST_RESULT_STR_Z(ioReadLine(read), "12", "read line");
|
|
|
|
// Read what was left in the line buffer
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 0, "read buffer");
|
|
bufUsedSet(buffer, 2);
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 1, "read buffer");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "AAB", " check buffer");
|
|
bufUsedSet(buffer, 0);
|
|
|
|
// Now do a full buffer read from the input
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 3, "read buffer");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "DDD", " check buffer");
|
|
|
|
// Read line doesn't work without a linefeed
|
|
TEST_ERROR(ioReadLine(read), FileReadError, "unexpected eof while reading line");
|
|
|
|
// But those bytes can be picked up by a buffer read
|
|
buffer = bufNew(2);
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 2, "read buffer");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "EF", " check buffer");
|
|
|
|
buffer = bufNew(1);
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 1, "read buffer");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "F", " check buffer");
|
|
|
|
// Nothing left to read
|
|
TEST_ERROR(ioReadLine(read), FileReadError, "unexpected eof while reading line");
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 0, "read buffer");
|
|
|
|
// Error if buffer is full and there is no linefeed
|
|
ioBufferSizeSet(10);
|
|
read = ioBufferReadNew(BUFSTRDEF("0123456789"));
|
|
ioReadOpen(read);
|
|
TEST_ERROR(ioReadLine(read), FileReadError, "unable to find line in 10 byte buffer");
|
|
|
|
// Read line without eof
|
|
read = ioBufferReadNew(BUFSTRDEF("1234"));
|
|
ioReadOpen(read);
|
|
TEST_RESULT_STR_Z(ioReadLineParam(read, true), "1234", "read line without eof");
|
|
|
|
// Read IO into a buffer
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
ioBufferSizeSet(8);
|
|
|
|
bufferRead = ioBufferReadNew(BUFSTRDEF("a test string"));
|
|
ioReadOpen(bufferRead);
|
|
|
|
TEST_RESULT_STR_Z(strNewBuf(ioReadBuf(bufferRead)), "a test string", "read into buffer");
|
|
|
|
// Drain read IO
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
bufferRead = ioBufferReadNew(BUFSTRDEF("a better test string"));
|
|
ioFilterGroupAdd(ioReadFilterGroup(bufferRead), ioSizeNew());
|
|
|
|
TEST_RESULT_BOOL(ioReadDrain(bufferRead), true, "drain read io");
|
|
TEST_RESULT_UINT(varUInt64(ioFilterGroupResult(ioReadFilterGroup(bufferRead), SIZE_FILTER_TYPE_STR)), 20, "check length");
|
|
|
|
// Cannot open file
|
|
TEST_ASSIGN(
|
|
read, ioReadNewP((void *)998, .close = testIoReadClose, .open = testIoReadOpen, .read = testIoRead),
|
|
"create io read object");
|
|
TEST_RESULT_BOOL(ioReadDrain(read), false, "cannot open");
|
|
}
|
|
|
|
// *****************************************************************************************************************************
|
|
if (testBegin("IoWrite, IoBufferWrite, IoBuffer, IoSize, IoFilter, and IoFilterGroup"))
|
|
{
|
|
IoWrite *write = NULL;
|
|
ioBufferSizeSet(3);
|
|
|
|
TEST_ASSIGN(
|
|
write, ioWriteNewP((void *)999, .close = testIoWriteClose, .open = testIoWriteOpen, .write = testIoWrite),
|
|
"create io write object");
|
|
|
|
TEST_RESULT_VOID(ioWriteOpen(write), " open io object");
|
|
TEST_RESULT_BOOL(testIoWriteOpenCalled, true, " check io object open");
|
|
TEST_RESULT_VOID(ioWriteStr(write, STRDEF("ABC")), " write 3 bytes");
|
|
TEST_RESULT_VOID(ioWriteClose(write), " close io object");
|
|
TEST_RESULT_BOOL(testIoWriteCloseCalled, true, " check io object closed");
|
|
|
|
TEST_RESULT_VOID(ioWriteFree(write), " free write object");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
ioBufferSizeSet(3);
|
|
IoWrite *bufferWrite = NULL;
|
|
Buffer *buffer = bufNew(0);
|
|
|
|
TEST_ASSIGN(bufferWrite, ioBufferWriteNew(buffer), "create buffer write object");
|
|
IoFilterGroup *filterGroup = ioWriteFilterGroup(bufferWrite);
|
|
IoFilter *sizeFilter = ioSizeNew();
|
|
TEST_RESULT_VOID(ioFilterGroupAdd(filterGroup, sizeFilter), " add filter to filter group");
|
|
TEST_RESULT_VOID(
|
|
ioFilterGroupAdd(filterGroup, ioTestFilterMultiplyNew("double", 2, 3, 'X')), " add filter to filter group");
|
|
TEST_RESULT_VOID(
|
|
ioFilterGroupAdd(filterGroup, ioTestFilterMultiplyNew("single", 1, 1, 'Y')),
|
|
" add filter to filter group");
|
|
TEST_RESULT_VOID(ioFilterGroupAdd(filterGroup, ioTestFilterSizeNew("size2")), " add filter to filter group");
|
|
|
|
TEST_RESULT_VOID(ioWriteOpen(bufferWrite), " open buffer write object");
|
|
TEST_RESULT_INT(ioWriteHandle(bufferWrite), -1, " handle invalid");
|
|
TEST_RESULT_VOID(ioWriteLine(bufferWrite, BUFSTRDEF("AB")), " write line");
|
|
TEST_RESULT_VOID(ioWrite(bufferWrite, bufNew(0)), " write 0 bytes");
|
|
TEST_RESULT_VOID(ioWrite(bufferWrite, NULL), " write 0 bytes");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "AABB\n\n", " check write");
|
|
|
|
TEST_RESULT_VOID(ioWriteStr(bufferWrite, STRDEF("Z")), " write string");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "AABB\n\n", " no change because output buffer is not full");
|
|
TEST_RESULT_VOID(ioWriteStr(bufferWrite, STRDEF("12345")), " write bytes");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "AABB\n\nZZ1122334455", " check write");
|
|
|
|
TEST_RESULT_VOID(ioWriteClose(bufferWrite), " close buffer write object");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "AABB\n\nZZ1122334455XXXY", " check write after close");
|
|
|
|
TEST_RESULT_PTR(ioWriteFilterGroup(bufferWrite), filterGroup, " check filter group");
|
|
TEST_RESULT_UINT(
|
|
varUInt64(ioFilterGroupResult(filterGroup, ioFilterType(sizeFilter))), 9, " check filter result");
|
|
TEST_RESULT_UINT(varUInt64(ioFilterGroupResult(filterGroup, strNew("size2"))), 22, " check filter result");
|
|
}
|
|
|
|
// *****************************************************************************************************************************
|
|
if (testBegin("IoHandleRead, IoHandleWrite, and ioHandleWriteOneStr()"))
|
|
{
|
|
ioBufferSizeSet(16);
|
|
|
|
HARNESS_FORK_BEGIN()
|
|
{
|
|
HARNESS_FORK_CHILD_BEGIN(0, true)
|
|
{
|
|
IoWrite *write = NULL;
|
|
|
|
TEST_ASSIGN(write, ioHandleWriteNew(strNew("write test"), HARNESS_FORK_CHILD_WRITE()), "move write");
|
|
ioWriteOpen(write);
|
|
TEST_RESULT_INT(ioWriteHandle(write), ((IoHandleWrite *)write->driver)->handle, "check write handle");
|
|
|
|
// Write a line to be read
|
|
TEST_RESULT_VOID(ioWriteStrLine(write, strNew("test string 1")), "write test string");
|
|
ioWriteFlush(write);
|
|
ioWriteFlush(write);
|
|
|
|
// Sleep so the other side will timeout
|
|
const Buffer *buffer = BUFSTRDEF("12345678");
|
|
TEST_RESULT_VOID(ioWrite(write, buffer), "write buffer");
|
|
sleepMSec(1250);
|
|
|
|
// Write a buffer in two parts and sleep in the middle so it will be read on the other side in two parts
|
|
TEST_RESULT_VOID(ioWrite(write, buffer), "write buffer");
|
|
sleepMSec(500);
|
|
TEST_RESULT_VOID(ioWrite(write, buffer), "write buffer");
|
|
ioWriteFlush(write);
|
|
}
|
|
HARNESS_FORK_CHILD_END();
|
|
|
|
HARNESS_FORK_PARENT_BEGIN()
|
|
{
|
|
IoRead *read = ioHandleReadNew(strNew("read test"), HARNESS_FORK_PARENT_READ_PROCESS(0), 1000);
|
|
|
|
ioReadOpen(read);
|
|
TEST_RESULT_INT(ioReadHandle(read), ((IoHandleRead *)ioReadDriver(read))->handle, "check handle");
|
|
TEST_RESULT_PTR(ioReadInterface(read), &read->interface, "check interface");
|
|
TEST_RESULT_PTR(ioReadDriver(read), read->driver, "check driver");
|
|
|
|
// Read a string
|
|
TEST_RESULT_STR_Z(ioReadLine(read), "test string 1", "read test string");
|
|
|
|
// Only part of the buffer is written before timeout
|
|
Buffer *buffer = bufNew(16);
|
|
|
|
TEST_ERROR(ioRead(read, buffer), FileReadError, "unable to read data from read test after 1000ms");
|
|
TEST_RESULT_UINT(bufSize(buffer), 16, "buffer is only partially read");
|
|
|
|
// Read a buffer that is transmitted in two parts with blocking on the read side
|
|
buffer = bufNew(16);
|
|
bufLimitSet(buffer, 12);
|
|
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 12, "read buffer");
|
|
bufLimitClear(buffer);
|
|
TEST_RESULT_UINT(ioRead(read, buffer), 4, "read buffer");
|
|
TEST_RESULT_STR_Z(strNewBuf(buffer), "1234567812345678", "check buffer");
|
|
|
|
// Check EOF
|
|
buffer = bufNew(16);
|
|
|
|
TEST_RESULT_UINT(ioHandleRead(ioReadDriver(read), buffer, true), 0, "read buffer at eof");
|
|
TEST_RESULT_UINT(ioHandleRead(ioReadDriver(read), buffer, true), 0, "read buffer at eof again");
|
|
}
|
|
HARNESS_FORK_PARENT_END();
|
|
}
|
|
HARNESS_FORK_END();
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_ERROR(
|
|
ioHandleWriteOneStr(999999, strNew("test")), FileWriteError,
|
|
"unable to write to handle: [9] Bad file descriptor");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
String *fileName = strNewFmt("%s/test.txt", testPath());
|
|
int fileHandle = open(strPtr(fileName), O_CREAT | O_TRUNC | O_WRONLY, 0700);
|
|
|
|
TEST_RESULT_VOID(ioHandleWriteOneStr(fileHandle, strNew("test1\ntest2")), "write string to file");
|
|
}
|
|
|
|
FUNCTION_HARNESS_RESULT_VOID();
|
|
}
|