From: Daniel Karbach Date: Fri, 18 Nov 2016 14:15:21 +0000 (+0100) Subject: first test for actual program binary X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=fd86376a8e7d3f1b09be3d018f772ef884937238;p=blank.git first test for actual program binary --- diff --git a/src/app/Process.hpp b/src/app/Process.hpp index 985b661..3efe963 100644 --- a/src/app/Process.hpp +++ b/src/app/Process.hpp @@ -33,6 +33,9 @@ public: /// @return the number of bytes read std::size_t ReadErr(void *buffer, std::size_t max_len); + /// ask the process nicely to terminate + /// (except on win32) + void Terminate(); /// wait until the process exits and fetch its exit status int Join(); diff --git a/src/app/proc.cpp b/src/app/proc.cpp index 33d1a14..3137b04 100644 --- a/src/app/proc.cpp +++ b/src/app/proc.cpp @@ -1,14 +1,16 @@ #include "Process.hpp" #ifdef _WIN32 -# error "TODO: windows implementation of Process" +# include +# include #else -# include # include +# include # include # include #endif +#include #include using namespace std; @@ -28,6 +30,7 @@ struct Process::Impl { size_t ReadOut(void *buffer, size_t max_len); size_t ReadErr(void *buffer, size_t max_len); + void Terminate(); int Join(); #ifdef _WIN32 @@ -72,6 +75,12 @@ size_t Process::ReadErr(void *buffer, size_t max_len) { return impl->ReadErr(buffer, max_len); } +void Process::Terminate() { + if (!joined) { + impl->Terminate(); + } +} + int Process::Join() { if (joined) { return status; @@ -266,6 +275,16 @@ size_t Process::Impl::ReadErr(void *buffer, size_t max_len) { #endif } +void Process::Impl::Terminate() { +#ifdef _WIN32 + if (!TerminateProcess(pi.hProcess, -1)) { +#else + if (kill(pid, SIGTERM) == -1) { +#endif + throw runtime_error("failed to terminate child process"); + } +} + int Process::Impl::Join() { #ifdef _WIN32 CloseHandle(fd_in[1]); diff --git a/src/io/LineBuffer.hpp b/src/io/LineBuffer.hpp new file mode 100644 index 0000000..4fcc3ca --- /dev/null +++ b/src/io/LineBuffer.hpp @@ -0,0 +1,71 @@ +#ifndef BLANK_IO_LINEBUFFER_HPP_ +#define BLANK_IO_LINEBUFFER_HPP_ + +#include +#include +#include + + +namespace blank { + +template +class LineBuffer { + +public: + explicit LineBuffer(char term = '\n') noexcept + : buffer{0} + , term(term) + , head(buffer) { } + + char *begin() noexcept { + return buffer; + } + const char *begin() const noexcept { + return buffer; + } + char *end() noexcept { + return head; + } + const char *end() const noexcept { + return head; + } + + /// extract one line from the buffer, terminator not included + /// @return false if the buffer does not contain a complete line + bool Extract(std::string &line) { + char *line_end = std::find(begin(), end(), term); + if (line_end == end()) { + return false; + } + line.assign(begin(), line_end); + ++line_end; + std::move(line_end, end(), begin()); + return true; + } + + /// get a pointer to append data to the buffer + /// it is safe to write at most Remain() bytes + char *WriteHead() noexcept { + return head; + } + + // call when data has been written to WriteHead() + void Update(std::size_t written) { + assert(written <= Remain()); + head += written; + } + + std::size_t Remain() const noexcept { + return std::distance(end(), buffer + size); + } + +private: + char buffer[size]; + char term; + char *head; + +}; + +} + +#endif diff --git a/src/shared/net.cpp b/src/shared/net.cpp index 17b2e8d..e0b7fc7 100644 --- a/src/shared/net.cpp +++ b/src/shared/net.cpp @@ -60,6 +60,7 @@ CommandBuffer::~CommandBuffer() { void CommandBuffer::Error(const string &msg) { + // TODO: prefix each line in message/error/broadcast write_buffer += " ! "; write_buffer += msg; write_buffer += '\n'; diff --git a/tst/app/ProcessTest.cpp b/tst/app/ProcessTest.cpp index 778b7f6..db181aa 100644 --- a/tst/app/ProcessTest.cpp +++ b/tst/app/ProcessTest.cpp @@ -21,7 +21,7 @@ void ProcessTest::tearDown() { void ProcessTest::testExit() { #ifdef __WIN32 -# error "TODO: implemente Process tests for windows" +# error "TODO: implement Process tests for windows" #else { diff --git a/tst/integration/ServerTest.cpp b/tst/integration/ServerTest.cpp new file mode 100644 index 0000000..fbec21a --- /dev/null +++ b/tst/integration/ServerTest.cpp @@ -0,0 +1,25 @@ +#include "ServerTest.hpp" + +#include "TestServer.hpp" + +CPPUNIT_TEST_SUITE_REGISTRATION(blank::test::ServerTest); + + +namespace blank { +namespace test { + +void ServerTest::setUp() { + +} + +void ServerTest::tearDown() { + +} + + +void ServerTest::testStartup() { + TestServer server; +} + +} +} diff --git a/tst/integration/ServerTest.hpp b/tst/integration/ServerTest.hpp new file mode 100644 index 0000000..bc8336d --- /dev/null +++ b/tst/integration/ServerTest.hpp @@ -0,0 +1,30 @@ +#ifndef BLANK_TEST_INTEGRATION_SERVERTEST_HPP_ +#define BLANK_TEST_INTEGRATION_SERVERTEST_HPP_ + +#include + + +namespace blank { +namespace test { + +class ServerTest +: public CppUnit::TestFixture { + +CPPUNIT_TEST_SUITE(ServerTest); + +CPPUNIT_TEST(testStartup); + +CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + + void testStartup(); + +}; + +} +} + +#endif diff --git a/tst/integration/TestServer.cpp b/tst/integration/TestServer.cpp new file mode 100644 index 0000000..cb5819d --- /dev/null +++ b/tst/integration/TestServer.cpp @@ -0,0 +1,73 @@ +#include "TestServer.hpp" + +#include + + +namespace blank { +namespace test { + +TestServer::TestServer() +: dir() +, proc( + "./blank" BLANK_SUFFIX, + { "blank", "--server", "--save-path", dir.Path(), "--cmd-port", "12354" }, + { }) +, conn() +, serv_buf() +, sock_buf() { + // wait for command service startup + // TODO: timeouts for reading from process + WaitOutputLine("listening on TCP port 12354"); + // connect to command service + conn = tcp::Socket("localhost", 12354); +} + +TestServer::~TestServer() { + proc.Terminate(); +} + + +void TestServer::WaitOutputLine(const std::string &expected) { + std::string line; + while (true) { + if (!serv_buf.Extract(line)) { + // buffer exhausted, fetch more data + serv_buf.Update(proc.ReadOut(serv_buf.WriteHead(), serv_buf.Remain())); + continue; + } + if (line == expected) { + return; + } else { + std::cerr << "ignoring line: " << line << std::endl; + } + } +} + +void TestServer::WaitCommandMessage(const std::string &line) { + WaitCommandLine(" > " + line); +} + +void TestServer::WaitCommandError(const std::string &line) { + WaitCommandLine(" ! " + line); +} + +void TestServer::WaitCommandBroadcast(const std::string &line) { + WaitCommandLine(" @ " + line); +} + +void TestServer::WaitCommandLine(const std::string &expected) { + std::string line; + while (true) { + if (!serv_buf.Extract(line)) { + // buffer exhausted, fetch more data + serv_buf.Update(conn.Recv(serv_buf.WriteHead(), serv_buf.Remain())); + continue; + } + if (line == expected) { + return; + } + } +} + +} +} diff --git a/tst/integration/TestServer.hpp b/tst/integration/TestServer.hpp new file mode 100644 index 0000000..9ac31d7 --- /dev/null +++ b/tst/integration/TestServer.hpp @@ -0,0 +1,48 @@ +#ifndef BLANK_TEST_INTEGRATION_TESTSERVER_HPP_ +#define BLANK_TEST_INTEGRATION_TESTSERVER_HPP_ + +#include "app/Process.hpp" +#include "io/filesystem.hpp" +#include "io/LineBuffer.hpp" +#include "net/tcp.hpp" + + +namespace blank { +namespace test { + +class TestServer { + +public: + TestServer(); + ~TestServer(); + +public: + /// wait until server writes given line to stdout + void WaitOutputLine(const std::string &line); + + /// wait for given message on the command service + void WaitCommandMessage(const std::string &line); + /// wait for given error on the command service + void WaitCommandError(const std::string &line); + /// wait for given broadcast on the command service + void WaitCommandBroadcast(const std::string &line); + /// wait for given line on the command service + void WaitCommandLine(const std::string &line); + + /// send command line to server + void SendCommand(const std::string &); + +private: + TempDir dir; + Process proc; + tcp::Socket conn; + size_t head; + LineBuffer serv_buf; + LineBuffer sock_buf; + +}; + +} +} + +#endif