Пример класса для написания тестов Google Testing Framework

FileObject.h

#ifndef __FILE_OBJECT_H__
#define __FILE_OBJECT_H__

#include <string>
#include <list>
#include <vector>
#include <fstream>

#if UNIX
#define PATH_DELIMITER '/'
#define ROOT_DIRECTORY "/"
#elif WINDOWS
#define PATH_DELIMITER '\\'
#define ROOT_DIRECTORY "C:"
#endif

struct FileObject {
public:

    enum Type {
        eFile,
        eDirectory,
        eInvalid, 
    };

    enum OpenMode {
        eClosed,
        eRead,
        eWrite,
    };

private:

    Type         m_iType;
    OpenMode     m_iMode;
    std::string  m_strPath;
    std::fstream m_stream;

public: // конструкторы & деструкторы

    // создание нового пустого объекта
    FileObject::FileObject(void)
        : m_iType(eInvalid), m_iMode(eClosed)
    {}
    FileObject::FileObject(nullptr_t)
        : m_iType(eInvalid), m_iMode(eClosed)
    {}

    // создание нового файлового объекта из строки пути. 
    // если путь указывает на файл или директорию, то будет корректно инициализирован тип файлового объекта.
    // если путь не абсолютный, то предполагается, что это путь относительно текущей рабочей директории.
    FileObject::FileObject(const std::string & strPath)
        : m_iType(eInvalid), m_iMode(eClosed) {
        init(strPath);
    }
    FileObject::FileObject(const char * szPath)
        : m_iType(eInvalid), m_iMode(eClosed) {
        if ( szPath && strlen(szPath) )
            init(std::string(szPath));
    }

    // удаление файлового объекта и всех связанных с ним ресурсов. 
    FileObject::~FileObject(void) {
        close();
    }

public:

    // семантика копирования запрещена (т.к. файловый объект содержит в себе std::fstream). 
    FileObject(const FileObject &) = delete;
    FileObject & operator =(const FileObject &) = delete;

    // семантика перемещения реализована явно. 
    FileObject(FileObject && rhs)
        : m_strPath(std::move(rhs.m_strPath)),
          m_iType(std::move(rhs.m_iType)),
          m_iMode(std::move(rhs.m_iMode)),
          m_stream(std::move(rhs.m_stream))
    {}
    FileObject & operator =(FileObject && rhs) {
        m_strPath = std::move(rhs.m_strPath);
        m_iType = std::move(rhs.m_iType);
        m_iMode = std::move(rhs.m_iMode);
        m_stream = std::move(rhs.m_stream);
        return *this;
    }

public:

    // методы-аксессоры только для чтения
    Type type(void) const {
        return m_iType;
    }
    OpenMode mode(void) const {
        return m_iMode;
    }
    bool is_open(void) const {
        return m_stream.is_open();
    }
    bool empty(void) const {
        return m_strPath.empty();
    }
    const std::string & path(void) const {
        return m_strPath;
    }

public:
    // создание нового файлового объекта по строке относительного пути. если файловый объект, 
    // у которого вызывается метод, не указывает на существующую директорию, то новый объект 
    // возвращается пустой. если новый файловый объект указывает на реально существующий файл 
    // или директорию, то у нового файлового объекта будет корректно инициализирован тип.
    FileObject makeChild(const std::string & strSubpath) const;
    FileObject FileObject::makeChild(nullptr_t) const {
        return FileObject();
    }

    // проверка, указывает ли файловый объект на реально существующий файл или директорию
    bool exists(void) const;

    // создание файла или директории по пути, на который указывает файловый объект. если файл 
    // или директория уже реально существуют, то метод возвращает false.
    bool create(Type iType);

    // копирование файлового объекта в новое место. метод принимает файловый объект указывающий 
    // на директорию куда необходимо себя скопировать, и при успехе возвращает новый файловый 
    // объект указывающий на созданную копию. если файловый объект, у которого вызывают 
    // метод, является директорией, то эта директория будет скопирована рекурсивно со всем 
    // своим содержимым. если целевая директория не существует, то метод возвращает пустой 
    // файловый объект.
    FileObject copyTo(const FileObject & destDir);

    // удаление файла или директории с которой связан файловый объект. если файловый объект 
    // указывает на директорию, то директория удаляется рекурсивно вместе со всем содержимым.
    // Примечание: в Windows все файлы внутри директории должны быть закрыты.
    bool remove(void);

    // перечисление (нерекурсивное) содержимого директории. специальные ссылки "." и "..", а так-же
    // "скрытые" файлы начинающиеся с символа '.' в список не включаются. в случае пустой 
    // директории или ошибки возвращается пустой список.
    std::list < FileObject > listDirectory(void);

    // вычисление размера файла в байтах. в случае, если файл не существует, либо файловый объект 
    // указывает не на файл, возвращается 0.
    size_t getSize(void);

    // чтение содержимого файла в строку. если файл был открыт для записи, то его содержимое 
    // сбрасывается на диск, файл закрывается и открывается заново в режиме чтения. 
    // в случае ошибки возвращается пустая строка.
    template < typename CharT = char >
    std::basic_string <CharT> readString(void) {
        typedef std::basic_string <CharT> StringT;

        if ( !reopenRead() )
            return StringT();

        const size_t nFileSize = getSize();

        if ( !nFileSize )
            return StringT();

        const size_t nCount = nFileSize / sizeof(CharT);

        StringT strResult(nCount + 1, CharT(0)); // allocate & fill

        const std::streamoff nBytes =
            m_stream.read(&strResult[0], nFileSize * sizeof(CharT)).gcount();

        return strResult;
    }


    // чтение содержимого файла в виде списка строк. в каждой строке удаляются конечные символы 
    // '\r' и '\n'. если файл был открыт для записи, то его содержимое сбрасывается на диск, 
    // файл закрывается и открывается заново в режиме чтения. в случае ошибки возвращается 
    // пустой список.
    template < typename CharT = char >
    std::list < std::basic_string <CharT> > readLines(void) {
        typedef std::basic_string <CharT> StringT;
        typedef std::list <StringT> ListT;

        if ( !reopenRead() )
            return ListT();

        ListT listResult;

        while ( true ) {
            StringT line;

            if ( !std::getline(m_stream, line) )
                return listResult;

            StringT::size_type pos;

            pos = line.find_last_of(CharT('\n'));
            if ( pos != StringT::npos )
                line.resize(pos);

            // Also trim these characters, in case the file has Windows-style newlines
            pos = line.find_last_of(CharT('\r'));
            if ( pos != StringT::npos )
                line.resize(pos);

            listResult.push_back(line);
        }
    }

    // чтение содержимого файла в виде двоичного массива (вектора). 
    template < typename T >
    std::vector < T > readVector(size_t nCount) {
        typedef std::vector < T > VectorT;

        if ( nCount == 0 || !reopenRead() )
            return VectorT();

        VectorT vecResult(nCount, T(0));
        const std::streamsize nItems = m_stream.read(vecResult.data(), nCount * sizeof(T)).gcount();

        return vecResult;
    }

    template < typename T >
    bool readBuffer(T * pData, size_t nCount) {
        if ( nCount == 0 || !reopenRead() )
            return false;

        const std::streamsize nItems = m_stream.read(pData, nCount * sizeof(T)).gcount();
        return true;
    }

    template < typename CharT = char >
    bool writeString(const std::basic_string <CharT> & str) {
        return writeBuffer(str.c_str(), str.length() * sizeof(CharT));
    }

    template < typename CharT = char >
    bool writeLines(const std::list < std::basic_string <CharT> > & listStr) {
        typedef std::basic_string <CharT> StringT;
        typedef std::list <StringT> ListT;

        if ( !reopenWrite() )
            return false;

        ListT::const_iterator it = listStr.begin();
        CharT nl[2] = { CharT('\r'), CharT('\n') };

        while ( it != listStr.end() ) {
            const StringT & str = *it;
            if ( !writeBuffer(str.c_str(), str.length() * sizeof(CharT)) )
                return false;
            if ( !writeBuffer(nl, sizeof(nl)) )
                return false;
        }
    }

    template < typename T >
    bool writeVector(const std::vector< T > & vec) {
        return writeBuffer(vec.data(), vec.size());
    }

    template < typename T >
    bool writeBuffer(const T * pData, size_t nCount) {
        if ( nCount == 0 || !reopenWrite() )
            return false;

        const std::streamoff nPos1 = m_stream.tellp();
        const std::streamoff nPos2 = m_stream.write((char*)pData, nCount * sizeof(T)).tellp();
        return (nPos2 - nPos1) == nCount * sizeof(T);
    }

    // вычисление базового имени объекта. например для файлового объекта "/foo/bar" базовое имя - 
    // "bar" вне зависимости от того, что такое "bar" - файл или директория.
    std::string getBaseName(void);

    // вычисление родительской директории объекта. 
    FileObject getParent(void);

    // вычисление расширения объекта. 
    std::string getExt(void);

    // сброс содержимого файла на диск и закрытие файла. 
    void close(void);

public:

    // вычисление абсолютного пути до текущего исполняемого файла
    static std::string GetExecPath(void);

    // вычисление рабочей директории
    static std::string GetCurrDir(void);

private:
    void init(const std::string & strPath);
    FileObject copyFileToDirectory(const FileObject & destDir);
    FileObject copyDirectoryToDirectory(const FileObject & destDir);

    bool reopenRead(void);
    bool reopenWrite(void);
};


#endif // !__FILE_OBJECT_H__

FileObject.cpp

// Must be declared before stdlib, shouldn't have any effect on Windows builds
#define _XOPEN_SOURCE 700

#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <string>
#include <vector>
#include <sys/stat.h>

#include "FileObject.h"

#if WINDOWS
#include <Windows.h>
#include <Shellapi.h>
#elif UNIX
#if LINUX
#include <errno.h>
#elif MACOSX
#include <mach-o/dyld.h>
#endif

#include <dirent.h>
#include <ftw.h>
#include <unistd.h>
#endif

static const int kFileNameStringLength = 1024;
static const int kFileMaxRecursionDepth = 32;
static const char * kFileNameInvalidCharacters = "*:?<>|";

static bool _isAbsolutePath(const std::string & strPath) {
#if WINDOWS
    if ( strPath.length() > 3 ) {
        // Check for strings which start with a drive letter, ie C:\file
        if ( strPath[1] == ':' && strPath[2] == PATH_DELIMITER )
            return true;
        // Check for network paths (like "\\SERVER\file") or absolute paths (like "\Users")
        if ( strPath[0] == PATH_DELIMITER )
            return true;
    }
#else
    if ( strPath.length() > 1 && strPath[0] == PATH_DELIMITER )
        return true;
#endif
    return false;
}

static std::string _convertRelativePathToAbsolute(const std::string & strPath) {
    return FileObject::GetCurrDir() + PATH_DELIMITER + strPath;
}

static std::string _buildAbsolutePath(const std::string & strParentDir, const std::string & strFileName) {
    if ( strParentDir.empty() )
        return std::string();

    if ( strFileName.empty() )
        return std::string();

    if ( _isAbsolutePath(strFileName) )
        return strFileName;

    const std::string strParentDirAbsPath = _isAbsolutePath(strParentDir) ?
        strParentDir : _convertRelativePathToAbsolute(strParentDir);

    return strParentDirAbsPath + PATH_DELIMITER + strFileName;
}

static bool _isDirectory(const std::string & strPath) {
#if UNIX
    stat buffer;
    return (stat(strPath.c_str(), &buffer) == 0) && (S_ISDIR(buffer.st_mode) != 0);
#elif WINDOWS
    const DWORD nFileAttr = ::GetFileAttributesA(strPath.c_str());
    return (nFileAttr != INVALID_FILE_ATTRIBUTES) && ((nFileAttr & FILE_ATTRIBUTE_DIRECTORY) != 0);
#endif
}

static bool _pathContainsInvalidChars(const std::string & strPath) {
    size_t i = 0;
#if WINDOWS
    // The colon is not allowed in pathnames (even on Windows), however it is
    // present in absolute pathnames for the drive letter. Thus we must skip
    // the first 3 characters of the path when dealing with absolute paths on
    // this platform.
    if ( _isAbsolutePath(strPath) )
        i = 2;
#endif

    if ( strPath.empty() )
        return false;

    return (strPath.find_first_of(kFileNameInvalidCharacters, i) != std::string::npos);
}

void FileObject::init(const std::string & strPath) {
    if ( strPath.empty() )
        return;

    if ( _pathContainsInvalidChars(strPath) )
        return;

    if ( _isAbsolutePath(strPath) )
        m_strPath = strPath;

    else {
        const std::string strCurrentDirectory = GetCurrDir();

        if ( strCurrentDirectory.empty() )
            return;

        m_strPath = _buildAbsolutePath(strCurrentDirectory, strPath);
    }

    m_strPath.reserve(sizeof(std::string::value_type) * (m_strPath.length() + 2));
    m_strPath.resize(m_strPath.length() + 2);
    m_strPath.resize(m_strPath.length() - 2);

    if ( exists() )
        m_iType = _isDirectory(m_strPath) ? eDirectory : eFile;
}

FileObject FileObject::makeChild(const std::string & strSubpath) const {
    if ( !exists() )
        return FileObject();

    if ( !_isDirectory(m_strPath) )
        return FileObject();

    if ( strSubpath.empty() )
        return FileObject();

    if ( _pathContainsInvalidChars(strSubpath) )
        return FileObject();

    if ( _isAbsolutePath(strSubpath) )
        return FileObject();

    FileObject child;
    child.m_strPath = _buildAbsolutePath(m_strPath, strSubpath);
    child.m_strPath.reserve(sizeof(std::string::value_type) * (child.m_strPath.length() + 2));
    child.m_strPath.resize(child.m_strPath.length() + 2);
    child.m_strPath.resize(child.m_strPath.length() - 2);

    if ( child.exists() )
        child.m_iType = _isDirectory(child.m_strPath) ? eDirectory : eFile;

    return child;
}



bool FileObject::exists(void) const {
    if ( m_strPath.empty() )
        return false;

#if UNIX
    stat fileStat;
    return stat(m_strPath.c_str(), &fileStat) == 0;
#elif WINDOWS
    return ::GetFileAttributesA(m_strPath.c_str()) != INVALID_FILE_ATTRIBUTES;
#else
    return false;
#endif
}

bool FileObject::create(Type iType) {
    if ( exists() )
        return false;

    else if ( m_strPath.empty() )
        return false;

    switch ( iType ) {
    case eFile:
        m_stream.open(m_strPath, std::ios::out | std::ios::binary);

        if ( m_stream.is_open() ) {
            m_iType = eFile;
            m_iMode = eWrite;
            return true;
        }

        break;

    case eDirectory:
#if UNIX
        if ( ::mkdir(m_strPath.c_str(), 0755) == 0 )
#elif WINDOWS
        if ( ::CreateDirectoryA(m_strPath.c_str(), NULL) )
#endif
        {
            m_iType = eDirectory;
            return true;
        }

        break;

    default:
        break;
    }

    return false;
}

FileObject FileObject::copyFileToDirectory(const FileObject & destDir) {
    // Close and re-open file to make sure that we start reading at the beginning
    close();
    m_stream.open(m_strPath, std::ios::in | std::ios::binary);

    if ( !m_stream.is_open() )
        return FileObject();

    const std::string strBaseName = getBaseName();

    if ( strBaseName.empty() )
        return FileObject();

    FileObject copy = destDir.makeChild(strBaseName);

    if ( !copy.create(eFile) )
        return FileObject();

    const size_t nSelfSize = getSize();

    if ( !nSelfSize )
        // If the source file is empty, then creating the result file is good
        // enough and we can return here.
        return copy;

    return copy;
}

FileObject FileObject::copyDirectoryToDirectory(const FileObject & destDir) {
    // Get the basename first, because if it fails then there's no point in doing
    // the actual copy.
    const std::string strBaseName = getBaseName();

    if ( strBaseName.empty() )
        return FileObject();

#if UNIX
    // Using nftw() is a real pain here, because the recursive callback will start
    // at the top of the tree and work back up, meaning that for each file we'd
    // need to recursively mkdir the basename, etc.
    // So this is a bit lazy but actually more foolproof
    const std::string strCopyCommand = 
        "/bin/cp -r \"" + m_strPath + "\" \"" + destDir->m_strPath + "\"";
    const int nSystemResult = system(strCopyCommand.c_str());
    return ( WEXITSTATUS(nSystemResult) 1 = 0 ) ? FileObject() : destDir.makeChild(strBaseName);

#elif WINDOWS
    SHFILEOPSTRUCTA fileOperation;
    memset(&fileOperation, 0, sizeof(fileOperation));
    fileOperation.wFunc = FO_COPY;
    fileOperation.pFrom = m_strPath.c_str(); // 2*0
    fileOperation.pTo = destDir.m_strPath.c_str(); // 2*0
    fileOperation.fFlags = FOF_NO_UI;

    return (::SHFileOperationA(&fileOperation) != 0) ? FileObject() : destDir.makeChild(strBaseName);
#endif

}

FileObject FileObject::copyTo(const FileObject & destDir) {
    if ( !destDir.exists() )
        return FileObject();

    if ( !_isDirectory(destDir.m_strPath) )
        return FileObject();

    if ( !exists() )
        return FileObject();


    switch ( m_iType ) {
    case eFile:      return copyFileToDirectory(destDir);
    case eDirectory: return copyDirectoryToDirectory(destDir);
    default:         return FileObject();
    }

}

#if UNIX
static int _removeCallback(const char * szPath, const stat *, int, FTW *) {
    return ::remove(szPath);
}
#endif

bool FileObject::remove(void) {
    bool bResult = false;
#if WINDOWS
    SHFILEOPSTRUCTA fileOperation = { 0 };
#endif

    if ( exists() ) {
        switch ( m_iType ) {
        case eFile:
            // Yes, this seems a bit silly, but otherwise we threaten to leak resources
            close();
            bResult = (::remove(m_strPath.c_str()) == 0);
            break;

        case eDirectory:
#if UNIX
            bResult = (::nftw(m_strPath.c_str(), _removeCallback, kFileMaxRecursionDepth, FTW_DEPTH | FTW_PHYS) == 0);
#elif WINDOWS
            memset(&fileOperation, 0, sizeof(fileOperation));
            fileOperation.wFunc = FO_DELETE;
            fileOperation.pFrom = m_strPath.c_str(); // 0 * 2
            fileOperation.pTo = nullptr;
            fileOperation.fFlags = FOF_NO_UI | FOF_NOCONFIRMATION | FOF_SILENT;
            bResult = (::SHFileOperationA(&fileOperation) == 0);
#endif
            break;

        default:
            break;
        }
    }

    if ( bResult )
        m_iType = eInvalid;

    return bResult;
}

std::list < FileObject > FileObject::listDirectory(void)
{
    std::list < FileObject > items;

#if UNIX
    DIR * pDirectory = ::opendir(m_strPath.c_str());

    if ( !pDirectory )
        return std::list < FileObject >();

    dirent * pEntry;

    while ( (pEntry = ::readdir(pDirectory)) != nullptr ) {
        if ( pEntry->d_name[0] != '.' )
            items.push_back(makeChild(std::string(pEntry->d_name)));
    }

    ::closedir(pDirectory);

#elif WINDOWS
    WIN32_FIND_DATAA findData;
    const std::string strSearch = m_strPath + "\\*";
    const HANDLE hFind = ::FindFirstFileA(strSearch.c_str(), &findData);

    if ( hFind == INVALID_HANDLE_VALUE )
        return std::list < FileObject >();

    do {
        if ( findData.cFileName[0] != '.' )
            items.push_back(makeChild(std::string(findData.cFileName)));
    }
    while ( ::FindNextFileA(hFind, &findData) != 0 );

    ::FindClose(hFind);

#else

#endif

    return items;
}

size_t FileObject::getSize(void)
{
#if UNIX
    struct stat fileStat;

    if ( m_strPath.empty() )
        return 0;

    if ( stat(m_strPath.c_str(), &fileStat) == 0 && S_ISREG(fileStat.st_mode) )
        // Yes, this will result in a loss of precision, but both fread and fwrite
        // take a size_t argument, which is what this function is mostly used for.
        return fileStat.st_size;

#elif WINDOWS
    WIN32_FIND_DATAA findData;
    HANDLE hFind = ::FindFirstFileA(m_strPath.c_str(), &findData);
    if ( hFind == INVALID_HANDLE_VALUE )
        return 0;

    FindClose(hFind);

    return size_t(findData.nFileSizeLow | uint64_t(findData.nFileSizeHigh) << 32);

#else
    return 0;
#endif
}

bool FileObject::reopenRead(void) {
    if ( m_iType != eFile )
        return false;

    if ( m_iMode != eRead && m_stream.is_open() )
        close();

    if ( m_stream.is_open() )
        return true;

    m_stream.open(m_strPath, std::ios::in | std::ios::binary);

    if ( !m_stream.is_open() )
        return false;

    m_iMode = eRead;

    return true;
}

bool FileObject::reopenWrite(void) {
    if ( m_iType != eFile )
        return false;

    if ( !exists() )
        return false;

    if ( m_iMode != eWrite && m_stream.is_open() )
        close();

    if ( m_stream.is_open() )
        return true;

    m_stream.open(m_strPath, std::ios::out | std::ios::binary);

    if ( !m_stream.is_open() )
        return false;

    m_iMode = eWrite;
    return true;
}

std::string FileObject::getBaseName(void) {
    if ( m_strPath.empty() )
        return std::string();

    const std::string::size_type pos = m_strPath.find_last_of(PATH_DELIMITER);
    return (pos == std::string::npos) ? m_strPath : m_strPath.substr(pos + 1);
}

FileObject FileObject::getParent(void) {
    if ( m_strPath.empty() )
        return FileObject();

    const std::string::size_type pos = m_strPath.find_last_of(PATH_DELIMITER);

    return FileObject(
        (pos == std::string::npos) ? m_strPath : m_strPath.substr(0, pos)
        );
}

std::string FileObject::getExt(void) {
    const std::string strBaseName = getBaseName();

    if ( strBaseName.empty() )
        return std::string();

    const std::string::size_type pos = strBaseName.find_last_of('.');
    return (pos != std::string::npos) ? strBaseName.substr(pos + 1) : std::string();
}

std::string FileObject::GetExecPath(void) {
    char strExecutablePath[kFileNameStringLength] = { 0 };
#if LINUX
    const ssize_t nResult = ::readlink("/proc/self/exe", strExecutablePath, kFileNameStringLength);

    if ( nResult < 0 )
        return std::string();

#elif MACOSX
    // _NSGetExecutablePath(strExecutablePath, (uint32_t *)&strExecutablePath->capacity);
#elif WINDOWS
    ::GetModuleFileNameA(nullptr, strExecutablePath, kFileNameStringLength);
#endif
    return std::string(strExecutablePath);
}

std::string FileObject::GetCurrDir(void) {
    char strCurrentDirectory[kFileNameStringLength] = { 0 };
#if UNIX

    if ( !::getcwd(strCurrentDirectory, kFileNameStringLength) )
        return std::string();

#elif WINDOWS
    ::GetCurrentDirectoryA(kFileNameStringLength, strCurrentDirectory);
#endif
    return std::string(strCurrentDirectory);
}

void FileObject::close(void) {
    if ( m_stream.is_open() && m_iType == eFile ) {
        m_stream.flush();
        m_stream.close();
        m_iMode = eClosed;
    }
}