Я давно хотел более подробно разобраться с механизмом IPC, но как-то руки не доходили. Но буквально позавчера я посетил конференцию agilecamp, и после данной конференции у меня возникло желание что нибудь пописать, также хотел попробовать новую штуку - TDD. В общем - одним выстрелом убиваем несколько зайцев.
Для работы нам потребуется:
└── tests
├── CMakeLists.txt
├── ipc
│ ├── CMakeLists.txt
│ ├── ClientThreadEmulator.h
│ ├── IoBaseMock.h
│ ├── IpcBoostSharedMemoryImplTests.cpp
│ ├── IpcIoBaseTests.cpp
│ └── IpcSharedMemoryIoTests.cpp
└── TestsMain.cpp
На данный момент мы рассматриваем только директории: ipc и tests
CMakelists.txt для сборки библиотеки ipc:
CMakeLists.txt для тестов:
tests/ipc/CMakeLists.txt:
В CMake 2.8.2 не входит скрипт для поиска библиотеки googlemock, поэтому я нашел его на просторах интернета и немного модифицировал, здесь я его приводить не буду, его можно легко написать на основе FindGTest.cmake, который входит в стандартную поставку CMake.
Сначала немного о получившейся библиотеке: данная библиотека намеренно предоставляет упрощенную реализацию IPC взаимодействия, является своего рода фасадом к библиотеке Boost.interprocess:
Понятно, что данная реализация является упрощенной, я вижу несколько путей развития данной реализации:
Итак, поехали! Описание интерфейса:
Данный интерфейс предоставляет всего две функции для самого взаимодействия: send и recv. Предполагается, что данные функции будут осуществлять блокирующий механизм для чтения и записи. Также есть функция, которая прерывает блокирующую операцию. Операция, которая была прервана, должна бросить исключение. Также мы видим, что данный объект конструируется либо в режиме Server, либо в режиме Client. Это сделано для того, чтобы сервер мог создать необходимые сущности для IPC взаимодействия (и корректно их удалить).
Реализация проста, и мы не будем на ней заострять внимание:
Мы попробуем реализовать IPC механизм на основе Shared memory - разделяемой памяти. Более подробно данный механизм описан в официальной документации.
Переходим к интерфейсу класса SharedMemoryIo:
Данный класс имеет статическую функцию для безусловного удаления IPC сущностей. Это необходимо в том случае, если наш IPC сервер завершился аварийно и не удалил за собой. Функция remove подчищает оставшиеся IPC объекты. Также есть функция для проверки, что сервер с указанным именем существует.
Данный класс использует идиому PImpl, чтобы скрыть детали реализации. Обратите внимание, что мы также объявляем деструктор, это связано с тем, что шаблон scoped_ptr проверяет, что тип полностью объявлен (в отличие, от того же auto_ptr), и поэтому для корректного уничтожения scoped_ptr необходим нетривиальный деструктор.
Реализация класса тоже проста, мы перенаправляем все запросы к имплементации:
Проверку, что сервис с указанным именем существует, мы осуществляем просто: пытаемся создать клиента с заданным именем, если это проходит успешно, то значит сервис есть - иначе его нет.
Теперь переходим к внутреннему устройству SharedMemory механизма - к описанию класса SharedMemoryIoImpl:
Отдельно стоит рассмотреть структуру SharedMemoryMapping - это специальная структура, которая предоставляет буфер для сообщения, статус IPC, и примитивы для синхронизации:
С помощью данной структуры мы сможем осуществлять безопасную доставку сообщения между процессами, а также осуществлять безопасное прерывание любой операции.
Теперь осталось рассмотреть реализацию класса SharedMemoryIoImpl:
Конструктор вызывает создание IPC механизма для сервера или клиента в зависимости от OpenMode.
Функция createServer создает shared memory object, важное замечание - мы должны правильным образом инициализировать mutex и conditional_variable, для этого мы вызываем placement new и конструируем объект в указанной памяти. Изначально, placement new не вызывался, но когда я писал тест для прерывания блокирующей операции, то мой тест работал неправильно. Благодаря написанным ранее тестам, я быстро локализовал ошибку (это плюс в копилку TDD :)).
Функция createClient просто пытается открыть существующий IPC сервер, если ей это не удается, то кидаем исключение.
Функция doRecv осуществляет блокирующее чтение из shared memory: сначала захватываем мьютекс для монопольного доступа к разделяемой памяти, затем запускаем цикл ожидания сообщения (или сигнала прерывания) с помощью условной переменной и блокировки. После того как сигнал получен, мы его анализируем и кидаем исключение (если это сигнал прерывания), либо читаем сообщение из буфера и посылаем сигнал готовности записи.
Аналогично работает функция doSend: она ждет сигнал о том, что очередь пуста (либо операция прервана), и генерирует исключение, либо копирует сообщение и посылает сигнал о его доступности.
Как я уже говорил, я пытался писать данную библиотеку в стиле TDD, ниже я привожу список тестов для всех компонент библиотеки:
Итак, первая группа тестов для интерфейса IoBase:
Первый тест был написан до описания интерфейса IoBase. Здесь мы видим, что первый тест требует наличие функций - то есть первый тест предназначен для описания интерфейса.
Следующие тесты были написан для проверки публичных не-виртуальных методов, эти тесты также были написаны до реализации соответствующих методов.
Декларация mock класса не интересна, она полностью повторяет документацию.
Переходим к тестам для SharedMemoryIo:
Тесты в данном модуле задают различные аспекты поведения объекта SharedMemoryIo:
Для работы нам потребуется:
- Boost - я использовал версию 1.47.0 (самосборная, на базе gcc-4.4.5)
- CMake - версия 2.8.2 (из репозитория Debian squeeze)
- gtest - версия 1.5.0 (из репозитория Debian squeeze)
- gmoсk - версия 1.4.0 (из репозитория Debian squeeze)
- QtCreator - версия 2.3.0 (Вручную инсталлированный) - он имеет plugin для поддержки CMake сборки (на самом деле - мой любимый редактор :)).
Итак поехали
Я собираю данную библиотеку в составе более крупного примера (о нем будет в одном из следущих постов), поэтому приведу только часть CMakeLists, которая относится непосредственно к нашему примеру. Для начала - иерархия проекта:
Я собираю данную библиотеку в составе более крупного примера (о нем будет в одном из следущих постов), поэтому приведу только часть CMakeLists, которая относится непосредственно к нашему примеру. Для начала - иерархия проекта:
.
├── CMakeLists.txt
├── cmake
│ └── FindGMock.cmake
├── service
│ ├── CMakeLists.txt
│ └── ...
├── client
│ ├── CMakeLists.txt
│ └── ...
├── common
│ ├── CMakeLists.txt
│ └── ...
├── ipc
│ ├── boost_ipc
│ │ ├── impl
│ │ │ ├── IpcSharedMemoryHeader.h
│ │ │ ├── SharedMemoryIoImpl.cpp
│ │ │ └── SharedMemoryIoImpl.h
│ │ ├── SharedMemoryIo.cpp
│ │ └── SharedMemoryIo.h
│ ├── CMakeLists.txt
│ ├── IoBase.cpp
│ └── IoBase.h
└── tests
├── CMakeLists.txt
├── ipc
│ ├── CMakeLists.txt
│ ├── ClientThreadEmulator.h
│ ├── IoBaseMock.h
│ ├── IpcBoostSharedMemoryImplTests.cpp
│ ├── IpcIoBaseTests.cpp
│ └── IpcSharedMemoryIoTests.cpp
└── TestsMain.cpp
На данный момент мы рассматриваем только директории: ipc и tests
Вот корневой CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
project (asio_service)
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_MULTITHREADED ON)
set (CMAKE_FIND_LIBRARY_SUFFIXES ".a")
set(Boost_ADDITIONAL_VERSIONS "1.47" "1.47.0")
find_package(Boost COMPONENTS thread system REQUIRED)
include_directories(${Boost_INCLUDE_DIR})
add_definitions(${Boost_DEFINITIONS})
find_package(Threads)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
########################################################################
add_subdirectory(common)
add_subdirectory(ipc)
add_subdirectory(client)
add_subdirectory(service)
set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
add_subdirectory(tests)
|
_Winnie C++ Colorizer |
CMakelists.txt для сборки библиотеки ipc:
set (ipc_srcs
IoBase.cpp
IoBase.h
boost_ipc/SharedMemoryIo.cpp
boost_ipc/SharedMemoryIo.h
boost_ipc/impl/SharedMemoryIoImpl.cpp
boost_ipc/impl/SharedMemoryIoImpl.h
boost_ipc/impl/IpcSharedMemoryHeader.h
)
add_library(ipc ${ipc_srcs})
|
_Winnie C++ Colorizer |
CMakeLists.txt для тестов:
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
find_package(GMock REQUIRED)
include_directories(${GMOCK_INCLUDE_DIRS})
add_subdirectory(ipc) |
_Winnie C++ Colorizer |
tests/ipc/CMakeLists.txt:
set (ipc_test_sources
IpcIoBaseTests.cpp
IpcSharedMemoryIoTests.cpp
IpcBoostSharedMemoryImplTests.cpp
IoBaseMock.h
ClientThreadEmulator.h
)
add_executable(ipc_test ${ipc_test_sources} ../TestsMain.cpp)
target_link_libraries(ipc_test
${CMAKE_THREAD_LIBS_INIT}
${Boost_LIBRARIES}
${GTEST_LIBRARIES}
${GMOCK_LIBRARIES}
ipc
rt
)
add_test(ipc_test ipc_test)
|
_Winnie C++ Colorizer |
В CMake 2.8.2 не входит скрипт для поиска библиотеки googlemock, поэтому я нашел его на просторах интернета и немного модифицировал, здесь я его приводить не буду, его можно легко написать на основе FindGTest.cmake, который входит в стандартную поставку CMake.
Сначала немного о получившейся библиотеке: данная библиотека намеренно предоставляет упрощенную реализацию IPC взаимодействия, является своего рода фасадом к библиотеке Boost.interprocess:
- Мы будем использовать блокирующий ввод/вывод с возможностью безопасного прерывания.
- В качестве передаваемых данных - обычные строки (std::string).
- За один раз можно передать (принять) только одно сообщение, размером не больше 1024 символа.
- Упрощаем создание IPC сущностей, вводя понятие клиента и сервера: клиент может соединиться только к запущенному серверу
- IPC сервер идентифицируется по имени, при создании сервера мы указываем это имя, клиент соединяется с сервером, используя имя (Можно одновременно создать несколько серверов).
Понятно, что данная реализация является упрощенной, я вижу несколько путей развития данной реализации:
- К серверу может подсоединяться несколько клиентов.
- Сервер может пул Shared memory сущностей, при получении запроса, он может перемещать соединение в одну из свободных Shared memory сущностей, и работать с клиентом в ней. Данный механизм похож на работу с сокетами.
- Сделать асинхронный механизм чтения записи, сделать timed механизм чтения-записи.
- Убрать ограничение в 1024 символа на одно сообщения:
- Эту задачу можно решить, сделав еще один слой абстракции над уже существующим решением: разбивать сообщение и посылать сообщение по блокам. Возможно есть и другое решение, но я пока про него не знаю.
Итак, поехали! Описание интерфейса:
#ifndef IO_BASE_H
#define IO_BASE_H
#include <boost/noncopyable.hpp>
#include <string>
#include <stdexcept>
namespace ipc
{
class exception: public std::runtime_error
{
public:
exception(const std::string & msg)
: std::runtime_error("ipc error: " + msg) { } }; class IoBase: private boost::noncopyable { public: enum OpenMode { ServerMode, ClientMode }; IoBase(OpenMode mode, const std::string & name); virtual ~IoBase(); OpenMode openMode() const; std::string name() const; std::string recv(); void send(const std::string & msg); void interrupt(); private: OpenMode mOpenMode; std::string mName; virtual std::string doRecv() = 0; virtual void doSend(const std::string & msg) = 0; virtual void doInterrupt() = 0; }; } #endif // IO_BASE_H |
_Winnie C++ Colorizer |
Данный интерфейс предоставляет всего две функции для самого взаимодействия: send и recv. Предполагается, что данные функции будут осуществлять блокирующий механизм для чтения и записи. Также есть функция, которая прерывает блокирующую операцию. Операция, которая была прервана, должна бросить исключение. Также мы видим, что данный объект конструируется либо в режиме Server, либо в режиме Client. Это сделано для того, чтобы сервер мог создать необходимые сущности для IPC взаимодействия (и корректно их удалить).
Реализация проста, и мы не будем на ней заострять внимание:
#include "IoBase.h"
namespace ipc
{
IoBase::IoBase(IoBase::OpenMode openMode, const std::string & name)
: mOpenMode(openMode), mName(name) { }
IoBase::~IoBase() { }
IoBase::OpenMode IoBase::openMode() const
{
return mOpenMode;
}
std::string IoBase::name() const
{
return mName;
}
std::string IoBase::recv()
{
return this->doRecv();
}
void IoBase::send(const std::string & msg)
{
this->doSend(msg);
}
void IoBase::interrupt()
{
this->doInterrupt();
}
}
|
_Winnie C++ Colorizer |
Мы попробуем реализовать IPC механизм на основе Shared memory - разделяемой памяти. Более подробно данный механизм описан в официальной документации.
Переходим к интерфейсу класса SharedMemoryIo:
#ifndef SHAREDMEMORYIO_H
#define SHAREDMEMORYIO_H
#include "ipc/IoBase.h"
#include <boost/smart_ptr/scoped_ptr.hpp>
#include <string>
namespace ipc
{
class SharedMemoryIo: public IoBase
{
public:
SharedMemoryIo(IoBase::OpenMode openMode, const std::string & name);
virtual ~SharedMemoryIo();
static void remove(const std::string & name);
static bool exists(const std::string & name);
private:
boost::scoped_ptr<class SharedMemoryIoImpl> mImpl;
virtual std::string doRecv();
virtual void doSend(const std::string & msg);
virtual void doInterrupt();
};
} /* namespace ipc */
#endif /* SHAREDMEMORYIO_H */
|
_Winnie C++ Colorizer |
Данный класс имеет статическую функцию для безусловного удаления IPC сущностей. Это необходимо в том случае, если наш IPC сервер завершился аварийно и не удалил за собой. Функция remove подчищает оставшиеся IPC объекты. Также есть функция для проверки, что сервер с указанным именем существует.
Данный класс использует идиому PImpl, чтобы скрыть детали реализации. Обратите внимание, что мы также объявляем деструктор, это связано с тем, что шаблон scoped_ptr проверяет, что тип полностью объявлен (в отличие, от того же auto_ptr), и поэтому для корректного уничтожения scoped_ptr необходим нетривиальный деструктор.
Реализация класса тоже проста, мы перенаправляем все запросы к имплементации:
#include "SharedMemoryIo.h"
#include "ipc/boost_ipc/impl/SharedMemoryIoImpl.h"
namespace ipc
{
SharedMemoryIo::SharedMemoryIo(IoBase::OpenMode openMode, const std::string & name)
: IoBase(openMode, name), mImpl(0)
{
mImpl.reset(new SharedMemoryIoImpl(this));
}
SharedMemoryIo::~SharedMemoryIo() { }
std::string SharedMemoryIo::doRecv()
{
return mImpl->doRecv();
}
void SharedMemoryIo::doSend(const std::string & msg)
{
mImpl->doSend(msg);
}
void SharedMemoryIo::doInterrupt()
{
mImpl->doInterrupt();
}
void SharedMemoryIo::remove(const std::string & name)
{
SharedMemoryIoImpl::remove(name);
}
bool SharedMemoryIo::exists(const std::string & name)
{
try
{
SharedMemoryIo checker(IoBase::ClientMode, name);
return true;
}
catch (const std::exception & /* e */)
{
return false;
}
}
} /* namespace ipc */
|
_Winnie C++ Colorizer |
Проверку, что сервис с указанным именем существует, мы осуществляем просто: пытаемся создать клиента с заданным именем, если это проходит успешно, то значит сервис есть - иначе его нет.
Теперь переходим к внутреннему устройству SharedMemory механизма - к описанию класса SharedMemoryIoImpl:
#ifndef SHAREDMEMORYIOIMPL_H
#define SHAREDMEMORYIOIMPL_H
#include <string>
#include <boost/noncopyable.hpp>
#include <boost/interprocess/interprocess_fwd.hpp>
#include <boost/smart_ptr/scoped_ptr.hpp>
namespace ipc
{
class IoBase;
namespace internal
{
struct SharedMemoryMapping;
}
class SharedMemoryIoImpl: private boost::noncopyable
{
public:
SharedMemoryIoImpl(ipc::IoBase * io);
~SharedMemoryIoImpl();
std::string doRecv();
void doSend(const std::string & msg);
void doInterrupt();
static void remove(const std::string & name);
private:
ipc::IoBase * mIo;
boost::scoped_ptr<boost::interprocess::shared_memory_object> mMemoryObject;
boost::scoped_ptr<boost::interprocess::mapped_region> mRegion;
void createServer();
void createClient();
internal::SharedMemoryMapping * getMapping();
};
}
#endif // SHAREDMEMORYIOIMPL_H
|
_Winnie C++ Colorizer |
#ifndef IPCSHAREDMEMORYHEADER_H
#define IPCSHAREDMEMORYHEADER_H
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
namespace ipc
{
namespace internal
{
enum SharedMemoryState
{
ReadyWrite,
ReadyRead,
ReadyInterrupt
};
const size_t MessageBufferSize = 1024;
struct SharedMemoryMapping
{
SharedMemoryMapping()
: mutex(), condition(), state(ReadyWrite), messageSize(0) { }
boost::interprocess::interprocess_mutex mutex;
boost::interprocess::interprocess_condition condition;
SharedMemoryState state;
size_t messageSize;
char msgBuff[MessageBufferSize];
};
}
}
#endif // IPCSHAREDMEMORYHEADER_H
|
_Winnie C++ Colorizer |
Теперь осталось рассмотреть реализацию класса SharedMemoryIoImpl:
#include "SharedMemoryIoImpl.h"
#include "ipc/IoBase.h"
#include "ipc/boost_ipc/impl/IpcSharedMemoryHeader.h"
#include <boost/interprocess/mapped_region.hpp>
#include <boost/bind.hpp>
using namespace boost::interprocess;
namespace ipc
{
SharedMemoryIoImpl::SharedMemoryIoImpl(ipc::IoBase * io): mIo(io)
{
BOOST_ASSERT(io && "io object must NOT be NULL");
switch (io->openMode())
{
case IoBase::ServerMode:
createServer();
break;
case IoBase::ClientMode:
createClient();
break;
default:
BOOST_ASSERT(0 && "unknown OpenMode");
break;
}
}
SharedMemoryIoImpl::~SharedMemoryIoImpl()
{
if (mIo->openMode() == IoBase::ServerMode)
{
remove(mIo->name());
}
}
void SharedMemoryIoImpl::createServer()
{
try
{
mMemoryObject.reset(new shared_memory_object(
create_only,
mIo->name().c_str(),
read_write));
mMemoryObject->truncate(sizeof(internal::SharedMemoryMapping));
mRegion.reset(new mapped_region(*mMemoryObject, read_write));
void * addr = mRegion->get_address();
/**
* @note This placement new is important,
* because we must properly initialise mutex and cond_var
*/
internal::SharedMemoryMapping * mapping =
new (addr) internal::SharedMemoryMapping();
}
catch (const interprocess_exception & e)
{
throw exception(std::string("failed to create server: ") + e.what());
}
}
void SharedMemoryIoImpl::createClient()
{
try
{
mMemoryObject.reset(new shared_memory_object(
open_only,
mIo->name().c_str(),
read_write));
mRegion.reset(new mapped_region(*mMemoryObject, read_write));
}
catch (const interprocess_exception & e)
{
throw exception(std::string("failed to create client: ") + e.what());
}
}
void SharedMemoryIoImpl::remove(const std::string & name)
{
shared_memory_object::remove(name.c_str());
}
internal::SharedMemoryMapping * SharedMemoryIoImpl::getMapping()
{
void * addr = mRegion->get_address();
return static_cast<internal::SharedMemoryMapping * >(addr);
}
std::string SharedMemoryIoImpl::doRecv()
{
internal::SharedMemoryMapping * mapping = getMapping();
scoped_lock<interprocess_mutex> locker(mapping->mutex);
while (mapping->state == internal::ReadyWrite)
{
mapping->condition.wait(locker);
}
if (mapping->state == internal::ReadyInterrupt)
{
throw exception("interrupt requested");
}
BOOST_ASSERT((mapping->messageSize <= internal::MessageBufferSize)
&& "shared object corrupted, ipc impossible");
std::string result;
std::copy(mapping->msgBuff,
mapping->msgBuff + mapping->messageSize,
std::back_inserter(result));
mapping->state = internal::ReadyWrite;
mapping->condition.notify_one();
return result;
}
void SharedMemoryIoImpl::doSend(const std::string & msg)
{
internal::SharedMemoryMapping * mapping = getMapping();
scoped_lock<interprocess_mutex> locker(mapping->mutex);
while (mapping->state == internal::ReadyRead)
{
mapping->condition.wait(locker);
}
if (mapping->state == internal::ReadyInterrupt)
{
throw exception("interrupt requested");
}
if (msg.size() > internal::MessageBufferSize)
{
throw exception("message too long");
}
std::copy(msg.begin(), msg.end(), mapping->msgBuff);
mapping->messageSize = msg.size();
mapping->state = internal::ReadyRead;
mapping->condition.notify_one();
}
void SharedMemoryIoImpl::doInterrupt()
{
internal::SharedMemoryMapping * mapping = getMapping();
scoped_lock<interprocess_mutex> locker(mapping->mutex);
mapping->state = internal::ReadyInterrupt;
mapping->condition.notify_one();
}
}
|
_Winnie C++ Colorizer |
Конструктор вызывает создание IPC механизма для сервера или клиента в зависимости от OpenMode.
Функция createServer создает shared memory object, важное замечание - мы должны правильным образом инициализировать mutex и conditional_variable, для этого мы вызываем placement new и конструируем объект в указанной памяти. Изначально, placement new не вызывался, но когда я писал тест для прерывания блокирующей операции, то мой тест работал неправильно. Благодаря написанным ранее тестам, я быстро локализовал ошибку (это плюс в копилку TDD :)).
Функция createClient просто пытается открыть существующий IPC сервер, если ей это не удается, то кидаем исключение.
Функция doRecv осуществляет блокирующее чтение из shared memory: сначала захватываем мьютекс для монопольного доступа к разделяемой памяти, затем запускаем цикл ожидания сообщения (или сигнала прерывания) с помощью условной переменной и блокировки. После того как сигнал получен, мы его анализируем и кидаем исключение (если это сигнал прерывания), либо читаем сообщение из буфера и посылаем сигнал готовности записи.
Аналогично работает функция doSend: она ждет сигнал о том, что очередь пуста (либо операция прервана), и генерирует исключение, либо копирует сообщение и посылает сигнал о его доступности.
Как я уже говорил, я пытался писать данную библиотеку в стиле TDD, ниже я привожу список тестов для всех компонент библиотеки:
Итак, первая группа тестов для интерфейса IoBase:
#include <gtest/gtest.h>
#include "IoBaseMock.h"
using ::testing::_;
TEST(IpcIoBaseTest, DeclareInterfaceForIoBase)
{
mocks::IoBaseMock ioBaseMock(ipc::IoBase::ServerMode, "some_io_name");
EXPECT_CALL(ioBaseMock, doRecv());
EXPECT_CALL(ioBaseMock, doSend(_));
EXPECT_CALL(ioBaseMock, doInterrupt());
ioBaseMock.recv();
ioBaseMock.send("unused message");
ioBaseMock.interrupt();
ioBaseMock.name();
}
TEST(IpcIoBaseTest, ExpectServerMode)
{
mocks::IoBaseMock ioBaseMock(ipc::IoBase::ServerMode, "some_io_name");
EXPECT_EQ(ipc::IoBase::ServerMode, ioBaseMock.openMode());
}
TEST(IpcIoBaseTest, ExpectClientMode)
{
mocks::IoBaseMock ioBaseMock(ipc::IoBase::ClientMode, "some_io_name");
EXPECT_EQ(ipc::IoBase::ClientMode, ioBaseMock.openMode());
}
TEST(IpcIoBaseTest, ExpectTwoDifferentIoNames)
{
mocks::IoBaseMock ioBaseMock1(ipc::IoBase::ServerMode, "io_name1");
mocks::IoBaseMock ioBaseMock2(ipc::IoBase::ServerMode, "io_name2");
EXPECT_EQ("io_name1", ioBaseMock1.name());
EXPECT_EQ("io_name2", ioBaseMock2.name());
}
|
_Winnie C++ Colorizer |
Первый тест был написан до описания интерфейса IoBase. Здесь мы видим, что первый тест требует наличие функций - то есть первый тест предназначен для описания интерфейса.
Следующие тесты были написан для проверки публичных не-виртуальных методов, эти тесты также были написаны до реализации соответствующих методов.
Декларация mock класса не интересна, она полностью повторяет документацию.
Переходим к тестам для SharedMemoryIo:
#include <gtest/gtest.h>
#include <iostream>
#include <boost/thread.hpp>
#include "ipc/boost_ipc/SharedMemoryIo.h"
#include "ClientThreadEmulator.h"
TEST(IpcSharedMemoryIoTest, ExpectStaticMethodsOfSharedMemoryObject)
{
ipc::SharedMemoryIo::remove("unused_server_name");
ipc::SharedMemoryIo::exists("unused_server_name");
}
TEST(IpcSharedMemoryIoTest, TryToConstructServerSharedMemoryObject)
{
const std::string name = "unused_server_name";
ipc::SharedMemoryIo::remove(name);
ASSERT_FALSE(ipc::SharedMemoryIo::exists(name));
ipc::SharedMemoryIo memIo(ipc::IoBase::ServerMode, name);
ASSERT_TRUE(ipc::SharedMemoryIo::exists(name));
}
TEST(IpcSharedMemoryIoTest, ExpectToThrowOnNonExistendSharedMemoryObject)
{
const std::string name = "nonexistent_server_name";
ipc::SharedMemoryIo::remove(name);
EXPECT_THROW(ipc::SharedMemoryIo client(ipc::IoBase::ClientMode, name),
ipc::exception);
}
TEST(IpcSharedMemoryIoTest, ExpectToConnectToExistentSharedMemoryObject)
{
const std::string name = "test_server";
ipc::SharedMemoryIo::remove(name);
{
ipc::SharedMemoryIo server(ipc::IoBase::ServerMode, name);
ipc::SharedMemoryIo client(ipc::IoBase::ClientMode, name);
/**
* we does not check any method,
* we expect, that both ctors does not throw any exception
*/
}
}
TEST(IpcSharedMemoryIoTest, ExpectToSendAndRecvData)
{
const std::string name = "test_server";
ipc::SharedMemoryIo::remove(name);
{
ipc::SharedMemoryIo server(ipc::IoBase::ServerMode, name);
ipc::SharedMemoryIo client(ipc::IoBase::ClientMode, name);
std::string message = "some message";
client.send(message);
std::string receivedMessage = server.recv();
EXPECT_EQ(message, receivedMessage);
}
}
TEST(IpcSharedMemoryIoTest, TestForInterruptionOfServerThread)
{
const std::string name = "test_server";
ipc::SharedMemoryIo::remove(name);
ipc::SharedMemoryIo server(ipc::IoBase::ServerMode, name);
ClientThreadEmulator client(name);
EXPECT_THROW(server.recv(), ipc::exception);
}
|
_Winnie C++ Colorizer |
Тесты в данном модуле задают различные аспекты поведения объекта SharedMemoryIo:
- Сначала мы требуем, чтобы у класса SharedMemoryIo были две статические функции: remove и exists.
- Затем мы пытаемся создать SharedMemoryIo в режиме Server: здесь мы сначала пытаемся удалить IPC объект для того чтобы наш тест не сломался, если такой объект был случайно создан ранее. После того как сервер создался - мы проверяем, что он есть с помощью функции exists.
- В третьем тесте мы проверяем, что клиент не сможет законнектиться к несуществующему серверу : ожидаем исключение.
- В четвертом тесте мы последовательно создаем сервер, и клиент. Клиент должен не бросить исключение при попытке соединения с сервером.
Первые 4 теста задавали поведение объектов без самого IPC взаимодействия, следующие два теста как раз описывают передачу данных и прерывание операции:
- В пятом тесте мы создаем сервер и клиент, посылаем сообщение серверу. Хотя все операции блокирующие, у нас не возникает блокировки, потому что изначально очередь сообщений пуста, и поэтому клиент без проблем кладет свое сообщение. Когда сервер запускает чтение, то он сразу получает статус сообщения без входа в цикл ожидания.
- В последнем тесте мы запускаем сервер в главном потоке, и клиента во вспомогательном потоке. Здесь ожидание сообщения может блокироваться (в зависимости от того, кто раньше стартует: сервер или клиент), но в любом случае мы должны поймать исключение.
Переходим к тесту на имплементацию SharedmemoryIoImpl:
#include <gtest/gtest.h>
#include "ipc/boost_ipc/impl/SharedMemoryIoImpl.h"
TEST(IpcBoostSharedMemoryImplTests, ExpectAssertionFailureOnNullIoObject)
{
/// @todo Move this test to single exe without any threads
ASSERT_DEATH(ipc::SharedMemoryIoImpl ioImpl(0),
".*io object must NOT be NULL.*"); } |
_Winnie C++ Colorizer |
В данном модуле всего один тест (пока что :)), но здесь я запускаю т.н. death test, то есть тест на то что программа закончится фатальной ошибкой: в данном случае я ожидаю, что программа выдаст assertion failure с сообщением io object must NOT be NULL. Это очень удобный механизм для проверки ассертов, рекомендую им пользоваться.
Итак, мы создали библиотеку для IPC взаимодействия на базе механизма shared memory. Данная библиоткека получилась простой в использовании (паттерн Фасад). Необходимо заметить, что boost является кроссплатформенной библиотекой, поэтому данное решение скорее всего будет работать без изменений под другими платформами и операционными системами: win32, sparc-solaris и пр.
Итак, мы создали библиотеку для IPC взаимодействия на базе механизма shared memory. Данная библиоткека получилась простой в использовании (паттерн Фасад). Необходимо заметить, что boost является кроссплатформенной библиотекой, поэтому данное решение скорее всего будет работать без изменений под другими платформами и операционными системами: win32, sparc-solaris и пр.
Комментариев нет:
Отправить комментарий