//---------------------------------------------------------------------------
//	Greenplum Database
//	Copyright (C) 2011 EMC Corp.
//
//	@filename:
//		CFileTest.cpp
//
//	@doc:
//		Tests for CFileWriter and CFileReader
//---------------------------------------------------------------------------

#include "unittest/gpos/io/CFileTest.h"

#include "gpos/assert.h"
#include "gpos/error/CMessage.h"
#include "gpos/io/CFileReader.h"
#include "gpos/io/CFileWriter.h"
#include "gpos/io/ioutils.h"
#include "gpos/string/CStringStatic.h"
#include "gpos/task/CAutoTraceFlag.h"
#include "gpos/test/CUnittest.h"

using namespace gpos;


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::EresUnittest
//
//	@doc:
//		Driver for unittests
//---------------------------------------------------------------------------
GPOS_RESULT
CFileTest::EresUnittest()
{
	CUnittest rgut[] = {
		GPOS_UNITTEST_FUNC(CFileTest::EresUnittest_Invalid),
		GPOS_UNITTEST_FUNC(CFileTest::EresUnittest_FileContent),
		GPOS_UNITTEST_FUNC(CFileTest::EresUnittest_InconsistentSize),
	};

	return CUnittest::EresExecute(rgut, GPOS_ARRAY_SIZE(rgut));
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::EresUnittest_Invalid
//
//	@doc:
//		Test for open files with invalid operations
//
//---------------------------------------------------------------------------
GPOS_RESULT
CFileTest::EresUnittest_Invalid()
{
	CHAR szTmpDir[GPOS_FILE_NAME_BUF_SIZE];
	CHAR szTmpFile[GPOS_FILE_NAME_BUF_SIZE];

	const ULONG ulWrPerms = S_IWUSR | S_IRGRP | S_IROTH;
	const ULONG ulRdPerms = S_IRUSR | S_IRGRP | S_IROTH;


	// make a unique temporary file name
	Unittest_MkTmpFile(szTmpDir, szTmpFile);

	GPOS_TRY
	{
		// open a nonexistent file by CFileReader,
		// it will catch an exception
		CFileReader rd1;
		Unittest_CheckError(&rd1, &CFileReader::Open, (const CHAR *) szTmpFile,
							ulRdPerms, CException::ExmiIOError);

		// create, or truncate the file only permit to write
		CFileWriter wr;
		wr.Open(szTmpFile, ulWrPerms);

		// close file
		wr.Close();

		// open a write only file by CFileReader,
		// it will catch an exception
		CFileReader rd2;
		Unittest_CheckError(&rd2, &CFileReader::Open, (const CHAR *) szTmpFile,
							ulRdPerms, CException::ExmiIOError);
	}
	GPOS_CATCH_EX(ex)
	{
		// delete tmp file and dir
		Unittest_DeleteTmpDir(szTmpDir, szTmpFile);

		GPOS_RETHROW(ex);
	}
	GPOS_CATCH_END;

	// delete tmp file and dir
	Unittest_DeleteTmpDir(szTmpDir, szTmpFile);

	return GPOS_OK;
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::EresUnittest_FileContent
//
//	@doc:
//		Test for write data, and read the written data
//
//---------------------------------------------------------------------------
GPOS_RESULT
CFileTest::EresUnittest_FileContent()
{
	CHAR szTmpDir[GPOS_FILE_NAME_BUF_SIZE];
	CHAR szTmpFile[GPOS_FILE_NAME_BUF_SIZE];

	const ULONG ulWrPerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
	const ULONG ulRdPerms = S_IRUSR | S_IRGRP | S_IROTH;
	const CHAR szData[] = "Test file content for CFileTest_file\n";
	const INT iLineNum = 100;
	const ULONG ulLineLength = GPOS_ARRAY_SIZE(szData);

	CFileReader rd;
	CFileWriterInternal wr;

	// make a unique temporary file name
	Unittest_MkTmpFile(szTmpDir, szTmpFile);

	GPOS_TRY
	{
		// open file for writing by CFileWriter
		wr.Open(szTmpFile, ulWrPerms);

		// write file line by line
		for (INT i = 0; i < iLineNum; i++)
		{
			wr.Write((BYTE *) szData, ulLineLength);
		}

		// test file size
		GPOS_UNITTEST_ASSERT((wr.FileSize() == (ulLineLength * iLineNum)) &&
							 (wr.UllSizeInternal() == wr.FileSize()));

		// close file
		wr.Close();

		// read data written to file above line by line
		rd.Open(szTmpFile, ulRdPerms);

		CHAR szRdBuf[ulLineLength];
		CHAR szRdData[ulLineLength];
		ULONG_PTR ulpRdLen = 0;
		CStringStatic strRdData(szRdData, GPOS_ARRAY_SIZE(szRdData));

		// read file content line by line,
		// and test if it is equal to what is written
		for (INT i = 0; i < iLineNum; i++)
		{
			ulpRdLen = rd.ReadBytesToBuffer((BYTE *) szRdBuf, ulLineLength);
			strRdData.AppendBuffer((const CHAR *) szRdBuf);

			GPOS_UNITTEST_ASSERT(strRdData.Equals((CHAR *) szData));
			GPOS_UNITTEST_ASSERT(ulpRdLen == ulLineLength);

			strRdData.Reset();
		}

		GPOS_UNITTEST_ASSERT(rd.FileReadSize() == (ulLineLength * iLineNum));

		// when the EOF is reached, read again will return zero
		GPOS_UNITTEST_ASSERT(
			0 == rd.ReadBytesToBuffer((BYTE *) szRdBuf, ulLineLength));

		//close file
		rd.Close();
	}
	GPOS_CATCH_EX(ex)
	{
		// delete tmp file and dir
		Unittest_DeleteTmpDir(szTmpDir, szTmpFile);

		GPOS_RETHROW(ex);
	}
	GPOS_CATCH_END;

	// delete temporary file and dir
	Unittest_DeleteTmpDir(szTmpDir, szTmpFile);

	return GPOS_OK;
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::EresUnittest_InconsistentSize
//
//	@doc:
//		Test for write and read file with given length
//		not equal to buffer size
//
//---------------------------------------------------------------------------
GPOS_RESULT
CFileTest::EresUnittest_InconsistentSize()
{
	CHAR szTmpDir[GPOS_FILE_NAME_BUF_SIZE];
	CHAR szTmpFile[GPOS_FILE_NAME_BUF_SIZE];

	// make a unique temporary file name
	Unittest_MkTmpFile(szTmpDir, szTmpFile);

	GPOS_TRY
	{
		// write data with inconsistent size with write buffer
		Unittest_WriteInconsistentSize(szTmpFile);

		// read data with inconsistent size with read buffer
		Unittest_ReadInconsistentSize(szTmpFile);
	}
	GPOS_CATCH_EX(ex)
	{
		// delete tmp file and dir
		Unittest_DeleteTmpDir(szTmpDir, szTmpFile);

		GPOS_RETHROW(ex);
	}
	GPOS_CATCH_END;

	// delete tmp file and dir
	Unittest_DeleteTmpDir(szTmpDir, szTmpFile);

	return GPOS_OK;
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::Unittest_MkTmpFile
//
//	@doc:
//		Make a unique temporary dir and file
//
//---------------------------------------------------------------------------
void
CFileTest::Unittest_MkTmpFile(CHAR *szTmpDir, CHAR *szTmpFile)
{
	GPOS_UNITTEST_ASSERT(nullptr != szTmpDir);
	GPOS_UNITTEST_ASSERT(nullptr != szTmpFile);

	CHAR szDir[] = "/tmp/CFileTest.XXXXXX";
	const CHAR szFile[] = "/CFileTest_file.txt";

	// create unique temporary directory name under /tmp
	ioutils::CreateTempDir(szDir);

	// copy the temporary directory name into szTmpDir
	CStringStatic strTmpDir(szTmpDir, GPOS_FILE_NAME_BUF_SIZE, szDir);
	CStringStatic strTmpFile(szTmpFile, GPOS_FILE_NAME_BUF_SIZE);

	// unique temporary file name
	strTmpFile.AppendFormat("%s%s", szDir, szFile);
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::Unittest_DeleteTmpDir
//
//	@doc:
//		Delete test files and directory
//
//---------------------------------------------------------------------------
void
CFileTest::Unittest_DeleteTmpDir(const CHAR *szDir, const CHAR *szFile)
{
	GPOS_UNITTEST_ASSERT(nullptr != szDir);
	GPOS_UNITTEST_ASSERT(nullptr != szFile);

	if (ioutils::PathExists(szFile))
	{
		// delete temporary file
		ioutils::Unlink(szFile);
	}

	if (ioutils::PathExists(szDir))
	{
		// delete temporary dir
		ioutils::RemoveDir(szDir);
	}
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::Unittest_CheckError
//
//	@doc:
//		Check error for write, read and open
//
//---------------------------------------------------------------------------
template <typename T, typename R, typename ARG1, typename ARG2>
void
CFileTest::Unittest_CheckError(T *pt, R (T::*pfunc)(ARG1, ARG2), ARG1 argFirst,
							   ARG2 argSec, CException::ExMinor exmi)
{
	GPOS_UNITTEST_ASSERT(nullptr != pt);
	GPOS_UNITTEST_ASSERT(nullptr != pfunc);

	GPOS_TRY
	{
		(pt->*pfunc)(argFirst, argSec);
	}
	GPOS_CATCH_EX(ex)
	{
		if (!GPOS_MATCH_EX(ex, CException::ExmaSystem, (ULONG) exmi))
		{
			GPOS_RETHROW(ex);
		}

		GPOS_RESET_EX;
	}
	GPOS_CATCH_END;
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::Unittest_WriteInconsistentSize
//
//	@doc:
//		Write data with inconsistent size with write buffer
//
//---------------------------------------------------------------------------
void
CFileTest::Unittest_WriteInconsistentSize(const CHAR *szTmpFile)
{
	GPOS_UNITTEST_ASSERT(nullptr != szTmpFile);

	const ULONG ulWdPerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
	const BYTE szData[] = "Test file content for CFileTest_file\n";
	const ULONG length = GPOS_ARRAY_SIZE(szData);
	const ULONG ulShortfall = 5;

	ULONG ulExpectSize = length - ulShortfall;

	CFileWriterInternal wr;

	wr.Open(szTmpFile, ulWdPerms);

	// try to write data with length smaller than buffer size
	wr.Write(szData, ulExpectSize);

	CFileWriterInternal wrInternal;

	// test file size
	GPOS_UNITTEST_ASSERT(ulExpectSize == wr.FileSize());
	GPOS_UNITTEST_ASSERT(ulExpectSize == wr.UllSizeInternal());

	// try to write data with length larger than buffer size
	ulExpectSize += (length + ulShortfall);
	wr.Write(szData, length + ulShortfall);

	// test file size
	GPOS_UNITTEST_ASSERT(ulExpectSize == wr.FileSize());
	GPOS_UNITTEST_ASSERT(wr.UllSizeInternal() == wr.FileSize());

	wr.Close();
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::Unittest_ReadInconsistentSize
//
//	@doc:
//		Read data with inconsistent size with read buffer
//
//---------------------------------------------------------------------------
void
CFileTest::Unittest_ReadInconsistentSize(const CHAR *szTmpFile)
{
	GPOS_UNITTEST_ASSERT(nullptr != szTmpFile);

	const ULONG ulWrPerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
	const ULONG ulRdPerms = S_IRUSR | S_IRGRP | S_IROTH;
	const BYTE szData[] = "Test file content for CFileTest_file\n";
	const ULONG length = GPOS_ARRAY_SIZE(szData);
	const ULONG ulShortfall = 5;

	CFileWriter wr;

	// truncate file to zero, an then write specific data for reading
	wr.Open(szTmpFile, ulWrPerms);

	// write two lines
	wr.Write(szData, length);
	wr.Write(szData, length);

	// close file
	wr.Close();

	CFileReader rd;
	// read buffer is oversized, because below we attempt to read past the end of
	// the file
	CHAR szRdBuf[length + ulShortfall];

	// open file by reader
	rd.Open(szTmpFile, ulRdPerms);

	// read data with length smaller than read buffer size
	ULONG_PTR ulpRdSize =
		rd.ReadBytesToBuffer((BYTE *) szRdBuf, length - ulShortfall);
	szRdBuf[length - ulShortfall] = CHAR_EOS;

	CHAR strBuf[length];
	CStringStatic strRdData(strBuf, GPOS_ARRAY_SIZE(strBuf));
	strRdData.AppendBuffer((const CHAR *) szRdBuf);

	CHAR szExpectData[] = "Test file content for CFileTest_f";
	ULONG ulExpectSize = length - ulShortfall;

	// test read data, and read size
	GPOS_UNITTEST_ASSERT(ulExpectSize == ulpRdSize);
	GPOS_UNITTEST_ASSERT(ulExpectSize == rd.FileReadSize());

	BOOL fEqual GPOS_ASSERTS_ONLY = strRdData.Equals(szExpectData);

	GPOS_UNITTEST_ASSERT(fEqual);

	// close file
	rd.Close();

	// try to read more data than the read buffer size
	rd.Open(szTmpFile, ulRdPerms);
	ulExpectSize = length + ulShortfall;
	ulpRdSize = rd.ReadBytesToBuffer((BYTE *) szRdBuf, ulExpectSize);

	strRdData.Reset();
	strRdData.AppendBuffer((const CHAR *) szRdBuf);

	// test read size, and read data
	GPOS_UNITTEST_ASSERT(ulpRdSize == ulExpectSize);
	GPOS_UNITTEST_ASSERT(strRdData.Equals((const CHAR *) szData));

	// close file
	rd.Close();
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::CFileWriterInternal::UllSizeInternal
//
//	@doc:
//		Get file size by descriptor
//
//---------------------------------------------------------------------------
ULLONG
CFileTest::CFileWriterInternal::UllSizeInternal() const
{
	return ioutils::FileSize(GetFileDescriptor());
}


//---------------------------------------------------------------------------
//	@function:
//		CFileTest::CFileWriterInternal:: ~CFileWriterInternal
//
//	@doc:
//		Dtor
//
//---------------------------------------------------------------------------
CFileTest::CFileWriterInternal::~CFileWriterInternal() = default;

// EOF
