/// @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();
#include "Process.hpp"
#ifdef _WIN32
-# error "TODO: windows implementation of Process"
+# include <tchar.h>
+# include <windows.h>
#else
-# include <cstdio>
# include <fcntl.h>
+# include <signal.h>
# include <unistd.h>
# include <sys/wait.h>
#endif
+#include <cstdio>
#include <stdexcept>
using namespace std;
size_t ReadOut(void *buffer, size_t max_len);
size_t ReadErr(void *buffer, size_t max_len);
+ void Terminate();
int Join();
#ifdef _WIN32
return impl->ReadErr(buffer, max_len);
}
+void Process::Terminate() {
+ if (!joined) {
+ impl->Terminate();
+ }
+}
+
int Process::Join() {
if (joined) {
return status;
#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]);
--- /dev/null
+#ifndef BLANK_IO_LINEBUFFER_HPP_
+#define BLANK_IO_LINEBUFFER_HPP_
+
+#include <algorithm>
+#include <cassert>
+#include <string>
+
+
+namespace blank {
+
+template<std::size_t size>
+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
void CommandBuffer::Error(const string &msg) {
+ // TODO: prefix each line in message/error/broadcast
write_buffer += " ! ";
write_buffer += msg;
write_buffer += '\n';
void ProcessTest::testExit() {
#ifdef __WIN32
-# error "TODO: implemente Process tests for windows"
+# error "TODO: implement Process tests for windows"
#else
{
--- /dev/null
+#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;
+}
+
+}
+}
--- /dev/null
+#ifndef BLANK_TEST_INTEGRATION_SERVERTEST_HPP_
+#define BLANK_TEST_INTEGRATION_SERVERTEST_HPP_
+
+#include <cppunit/extensions/HelperMacros.h>
+
+
+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
--- /dev/null
+#include "TestServer.hpp"
+
+#include <iostream>
+
+
+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;
+ }
+ }
+}
+
+}
+}
--- /dev/null
+#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<BUFSIZ> serv_buf;
+ LineBuffer<BUFSIZ> sock_buf;
+
+};
+
+}
+}
+
+#endif