From 9db1f4d93712edb68315d6ccad7c131e8088faba Mon Sep 17 00:00:00 2001 From: hama Date: Tue, 7 Jun 2016 20:52:06 +0200 Subject: [PATCH] initial state --- .gitignore | 2 + Server/Makefile | 35 +++++ Server/client.cpp | 44 ++++++ Server/cpidjinn.cpp | 22 +++ Server/cpidjinn.hpp | 5 + Server/dynbuffer.cpp | 1 + Server/dynbuffer.hpp | 1 + Server/gpioprocessor.cpp | 117 ++++++++++++++++ Server/gpioprocessor.hpp | 21 +++ Server/logger.cpp | 1 + Server/logger.hpp | 1 + Server/tcpclient.cpp | 89 ++++++++++++ Server/tcpclient.hpp | 24 ++++ Server/tcpserver.cpp | 187 +++++++++++++++++++++++++ Server/tcpserver.hpp | 43 ++++++ Server/thread.cpp | 1 + Server/thread.hpp | 1 + Server/timer.cpp | 1 + Server/timer.hpp | 1 + Server/timeutils.cpp | 1 + Server/timeutils.hpp | 1 + Server/trace.hpp | 1 + Server/util.hpp | 1 + util/Makefile | 25 ++++ util/cudynbuffer.cpp | 291 +++++++++++++++++++++++++++++++++++++++ util/cutimer.cpp | 142 +++++++++++++++++++ util/cutimeutils.cpp | 71 ++++++++++ util/dynbuffer.cpp | 181 ++++++++++++++++++++++++ util/dynbuffer.hpp | 152 ++++++++++++++++++++ util/logger.cpp | 41 ++++++ util/logger.hpp | 56 ++++++++ util/test.cpp | 14 ++ util/test.hpp | 6 + util/thread.cpp | 256 ++++++++++++++++++++++++++++++++++ util/thread.hpp | 102 ++++++++++++++ util/timer.cpp | 67 +++++++++ util/timer.hpp | 35 +++++ util/timeutils.cpp | 63 +++++++++ util/timeutils.hpp | 50 +++++++ util/trace.hpp | 16 +++ util/unittest.cpp | 114 +++++++++++++++ util/unittest.hpp | 37 +++++ util/util.hpp | 19 +++ 43 files changed, 2339 insertions(+) create mode 100644 .gitignore create mode 100644 Server/Makefile create mode 100644 Server/client.cpp create mode 100644 Server/cpidjinn.cpp create mode 100644 Server/cpidjinn.hpp create mode 120000 Server/dynbuffer.cpp create mode 120000 Server/dynbuffer.hpp create mode 100644 Server/gpioprocessor.cpp create mode 100644 Server/gpioprocessor.hpp create mode 120000 Server/logger.cpp create mode 120000 Server/logger.hpp create mode 100644 Server/tcpclient.cpp create mode 100644 Server/tcpclient.hpp create mode 100644 Server/tcpserver.cpp create mode 100644 Server/tcpserver.hpp create mode 120000 Server/thread.cpp create mode 120000 Server/thread.hpp create mode 120000 Server/timer.cpp create mode 120000 Server/timer.hpp create mode 120000 Server/timeutils.cpp create mode 120000 Server/timeutils.hpp create mode 120000 Server/trace.hpp create mode 120000 Server/util.hpp create mode 100644 util/Makefile create mode 100644 util/cudynbuffer.cpp create mode 100644 util/cutimer.cpp create mode 100644 util/cutimeutils.cpp create mode 100644 util/dynbuffer.cpp create mode 100644 util/dynbuffer.hpp create mode 100644 util/logger.cpp create mode 100644 util/logger.hpp create mode 100644 util/test.cpp create mode 100644 util/test.hpp create mode 100644 util/thread.cpp create mode 100644 util/thread.hpp create mode 100644 util/timer.cpp create mode 100644 util/timer.hpp create mode 100644 util/timeutils.cpp create mode 100644 util/timeutils.hpp create mode 100644 util/trace.hpp create mode 100644 util/unittest.cpp create mode 100644 util/unittest.hpp create mode 100644 util/util.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..874c63c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o + diff --git a/Server/Makefile b/Server/Makefile new file mode 100644 index 0000000..c07910b --- /dev/null +++ b/Server/Makefile @@ -0,0 +1,35 @@ +CC = g++ +CL = g++ +CFLAGS = -Wall +LDFLAGS = -lbcm2835 -lpthread + +OBJ_UTIL = timer.o thread.o dynbuffer.o logger.o +OBJ = cpidjinn.o $(OBJ_UTIL) tcpserver.o gpioprocessor.o +PROG = cpidjinn + +OBJ_CLIENT = $(OBJ_UTIL) tcpclient.o client.o +CLIENT = client + +all: $(PROG) + +$(CLIENT) : $(OBJ_CLIENT) + $(CL) -o $(CLIENT) $(OBJ_CLIENT) + +debug:all + $(CC) $(CFLAGS) -g -o $(PROG) $(OBJ) +stable:all + $(CC) $(CFLAGS) -o cipdjinn $(OBJ) + +.PHONY: clean +clean: + rm -vfr *~ $(PROG) $(OBJ) + +$(PROG): $(OBJ) + $(CL) $(LDFLAGS) -o $(PROG) $(OBJ) + +%.hpp: %.cpp + $(CC) $(CFLAGS) -g -c $< + +%.o: %.cpp + $(CC) $(CFLAGS) -g -c $< + diff --git a/Server/client.cpp b/Server/client.cpp new file mode 100644 index 0000000..f914f50 --- /dev/null +++ b/Server/client.cpp @@ -0,0 +1,44 @@ +#include "util.hpp" +#include "tcpclient.hpp" + +#if 0 +static void prepareBlink(DynBuffer& buffer){ + +} +#endif + +static void addTone(int duration, int millihz, DynBuffer& buffer){ + int high, low; + high = low = 1000*1000*1000 / millihz / 2 + 1; + buffer.appendAsLE(duration, 2); + buffer.appendAsLE(high, 3); + buffer.appendAsLE(low, 3); +} + +static void prepareMelody(DynBuffer& buffer){ + int duration = 2000; + int pin = 0; + int countTones = 3; + buffer.clear().append("MELO"); + buffer.appendAsLE(pin, 1); + buffer.appendAsLE(countTones, 1); + // tone 'a' 440 Hz + addTone(duration, 440000, buffer); + // tone c#: 554,365 Hz + addTone(duration, 554365, buffer); + // tone e: 659,255 Hz + addTone(duration, 659255, buffer); +} +int main (int argc, char **argv) { + const char* host = "127.0.0.1"; + int port = 15000; + Logger logger; + DynBuffer buffer; + TcpClient client(&logger); + if (client.connect(host, port)){ + prepareMelody(buffer); + client.sendAndReceive(buffer); + } + client.disconnect(); + return 0; +} diff --git a/Server/cpidjinn.cpp b/Server/cpidjinn.cpp new file mode 100644 index 0000000..4ab53d5 --- /dev/null +++ b/Server/cpidjinn.cpp @@ -0,0 +1,22 @@ +#include "cpidjinn.hpp" + +/** + * Main function. + * + * @param argc number of arguments + * @param argv argument vector + * @return exit code + */ +int main(int argc, char** argv){ + Logger logger; + int port = argc < 2 ? 15000 : atol(argv[1]); + logger.say(LOG_INFO, "start"); + GPIOProcessor processor; + TcpServer server(port, &logger); + server.addProcessor(processor); + // interpreter for the standard commands: + server.addProcessor(server); + server.listen(); + return 0; +} + diff --git a/Server/cpidjinn.hpp b/Server/cpidjinn.hpp new file mode 100644 index 0000000..49cb5c1 --- /dev/null +++ b/Server/cpidjinn.hpp @@ -0,0 +1,5 @@ +#ifndef CPIDJINN_HPP +#include "util.hpp" +#include "tcpserver.hpp" +#include "gpioprocessor.hpp" +#endif diff --git a/Server/dynbuffer.cpp b/Server/dynbuffer.cpp new file mode 120000 index 0000000..45fdb14 --- /dev/null +++ b/Server/dynbuffer.cpp @@ -0,0 +1 @@ +../util/dynbuffer.cpp \ No newline at end of file diff --git a/Server/dynbuffer.hpp b/Server/dynbuffer.hpp new file mode 120000 index 0000000..e367f33 --- /dev/null +++ b/Server/dynbuffer.hpp @@ -0,0 +1 @@ +../util/dynbuffer.hpp \ No newline at end of file diff --git a/Server/gpioprocessor.cpp b/Server/gpioprocessor.cpp new file mode 100644 index 0000000..d2ca966 --- /dev/null +++ b/Server/gpioprocessor.cpp @@ -0,0 +1,117 @@ +#include "cpidjinn.hpp" +#include "bcm2835.h" + +/** + * Constructor. + */ +GPIOProcessor::GPIOProcessor() +{ +} +/** + * Destructor. + */ +GPIOProcessor::~GPIOProcessor() +{ + +} +/** + * Lets a GPIO pin blink. + * + * Syntax: BLNK[pin][period_high][period_low]
+ * [pin]: 1 byte. Numbering like WiringPi: 0 is GPIO17...
+ * [period_x]: 4 byte little endian, in microseconds (< 35 minutes) + * + * @param buffer IN: contains the blink command
+ * OUT: answer for the client + */ +void GPIOProcessor::blink(DynBuffer& buffer){ + if (buffer.length() != 4 + 1 + 2*4){ + buffer.clear().appendFormatted("ERR wrong packet length %d instead of %d", + buffer.length(), 4 + 1 + 2*4); + } else { + int pin = buffer.str()[4]; + int periodHigh = buffer.valueOfLE(4, 5); + int periodLow = buffer.valueOfLE(4, 5 + 4); + printf("Pin: %d high: %d low: %d\n", pin, periodHigh, periodLow); + buffer.set("OK "); + } +} + +/** + * "Writes" a melody to a GPIO pin. + * + * Syntax: MELO[pin][count_tones][duration_1][period_high_1][period_high_1]...
+ * [pin]: 1 byte. Numbering like WiringPi: 0 is GPIO17...
+ * [count_tones]: 1 byte
+ * [duration_y]: 2 byte little endian, number of millisec (1 msec - 65 sec)
+ * [period_x_y]: 3 byte little endian, unit: microsec (1 usec - 16.8 sec) + * + * @param buffer IN: contains the command describing a melody to play
+ * OUT: answer for the client + */ +void GPIOProcessor::melody(DynBuffer& buffer){ + if (buffer.length() < 4 + 1 + 1 + 2 + 2*3){ + buffer.clear().appendFormatted("ERR wrong packet length %d instead of %d", + buffer.length(), 4 + 1 + 2*4); + } else { + int pin = buffer.at(4); + int countTones = buffer.at(5); + if ( (buffer.length() - 4 - 1 - 1) % (2 + 2*3) != 0){ + buffer.clear().appendFormatted("ERR incomplete tones definitions %d count: %d", + buffer.length(), countTones); + } else { + int offset = 4 + 1 + 1; + printf("Pin: %d count: %d\n", pin, countTones); + while(countTones-- > 0) { + int periodHigh, periodLow; + int duration = buffer.valueOfLE(2, offset); + offset += 2; + periodHigh = buffer.valueOfLE(3, offset); + offset += 3; + periodLow = buffer.valueOfLE(3, offset); + offset += 3; + int count = duration * 1000 / (periodHigh + periodLow); + printf("high: %d low: %d duration: %d msec count: %d\n", + periodHigh, periodLow, duration, count); + } + buffer.set("OK ", 4); + } + } +} +/** + * "Writes" a PWM code to the a pin. + * + * Syntax: PWM [pin][divider][countValues][pause][size_values][value1]...
+ * [pin]: '1' (constant): GPIO18 is the only available pin for PWM
+ * [divider]: 1 byte, 1 << [divider] is the real value
+ * [countValues]: 2 byte little endian: number of values to write as PWM
+ * [pause]: 4 byte little endian in microseconds (< 35 minutes), pause between 2 data
+ * + * + * @param buffer IN: the command describing a PulseWidthModulation command
+ * OUT: answer for the client + */ +void GPIOProcessor::pulseWidthModulation(DynBuffer& buffer){ + buffer.set("ERROR not implemented"); +} + +/** + * Processes the received buffer content. + * + * @param buffer IN: the client command
+ * OUT: the answer to th client + */ +TcpProcessor::State GPIOProcessor::process(DynBuffer& buffer){ + State rc = stProcessed; + + if (buffer.startsWith("BLNK", 4)){ + blink(buffer); + } else if (buffer.startsWith("MELO", 4)){ + melody(buffer); + } else if (buffer.startsWith("PWM ", 4)){ + pulseWidthModulation(buffer); + } else { + rc = stIgnored; + } + return rc; +} diff --git a/Server/gpioprocessor.hpp b/Server/gpioprocessor.hpp new file mode 100644 index 0000000..d8b855b --- /dev/null +++ b/Server/gpioprocessor.hpp @@ -0,0 +1,21 @@ +#ifndef GPIOPROCESSOR_H +#define GPIOPROCESSOR_H + +class GPIOProcessor : public TcpProcessor +{ +public: + GPIOProcessor(); + ~GPIOProcessor(); +private: + // not implemented, private use only + GPIOProcessor(const GPIOProcessor& other); + GPIOProcessor& operator=(const GPIOProcessor& other); + void blink(DynBuffer& buffer); + void melody(DynBuffer& buffer); + void pulseWidthModulation(DynBuffer& buffer); +public: + virtual State process(DynBuffer& buffer); + +}; + +#endif // GPIOPROCESSOR_H diff --git a/Server/logger.cpp b/Server/logger.cpp new file mode 120000 index 0000000..6522cd3 --- /dev/null +++ b/Server/logger.cpp @@ -0,0 +1 @@ +../util/logger.cpp \ No newline at end of file diff --git a/Server/logger.hpp b/Server/logger.hpp new file mode 120000 index 0000000..b81183c --- /dev/null +++ b/Server/logger.hpp @@ -0,0 +1 @@ +../util/logger.hpp \ No newline at end of file diff --git a/Server/tcpclient.cpp b/Server/tcpclient.cpp new file mode 100644 index 0000000..afb095c --- /dev/null +++ b/Server/tcpclient.cpp @@ -0,0 +1,89 @@ +#include "util.hpp" +#include "tcpclient.hpp" +#include +#include +#include +#include + +/** + * Constructor. + */ +TcpClient::TcpClient(Logger* logger) : + m_socket(socket (AF_INET, SOCK_STREAM, 0)), + m_buffer(64*1024), + m_logger(logger), + m_connected(false) +{ +} +/** + * Destructor. + */ +TcpClient::~TcpClient() { + disconnect(); +} +/** + * Connects to a server. + * + * @param host the host as numerical iP address + * @param port the port + * @return true: success + */ +bool TcpClient::connect(const char* host, int port){ + bool rc = true; + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_port = htons (port); + inet_aton (host, &address.sin_addr); + if (m_connected) + disconnect(); + if (::connect ( m_socket, (struct sockaddr *) &address, sizeof (address)) != 0){ + m_logger->sayf(LOG_ERROR, "connect() failed: %d", errno); + rc = false; + } else { + m_connected = true; + } + return rc; +} +/** + * Closes the connection. + */ +void TcpClient::disconnect(){ + if (m_connected){ + close(m_socket); + m_connected = false; + } +} +/** + * Sends a message to the server. + * + * @param buffer the message to send + */ +void TcpClient::send(DynBuffer& buffer){ + if (::send(m_socket, buffer.str(), buffer.length(), 0) != 0){ + m_logger->sayf(LOG_ERROR, "send(%.4s, %d) failed: %d", + buffer.str(), buffer.length(), errno); + } +} +/** + * Sends a message to the server and receives the answer. + * + * @param buffer IN: the message to send
+ * OUT: the answer from the server + */ +void TcpClient::sendAndReceive(DynBuffer& buffer){ + if (::send(m_socket, buffer.str(), buffer.length(), 0) <= 0){ + m_logger->sayf(LOG_ERROR, "send(%.4s, %d) failed: %d", + buffer.str(), buffer.length(), errno); + } else { + int length = ::recv(m_socket, buffer.buffer(), buffer.size(), 0); + if (length < 0){ + m_logger->sayf(LOG_ERROR, "receive() failed: %d", errno); + buffer.clear(); + } else { + buffer.setLength(length); + if (buffer.startsWith("ERRO", 4)){ + m_logger->say(LOG_ERROR, buffer.str()); + } + } + } +} diff --git a/Server/tcpclient.hpp b/Server/tcpclient.hpp new file mode 100644 index 0000000..26571b8 --- /dev/null +++ b/Server/tcpclient.hpp @@ -0,0 +1,24 @@ +#ifndef TCPCLIENT_H +#define TCPCLIENT_H + +class TcpClient { +public: + TcpClient(Logger* logger); + ~TcpClient(); +private: + // not implemented, only private use + TcpClient ( const TcpClient& other ); + TcpClient& operator= ( const TcpClient& other ); +public: + bool connect(const char* host, int port); + void disconnect(); + void send(DynBuffer& buffer); + void sendAndReceive(DynBuffer& buffer); +private: + int m_socket; + DynBuffer m_buffer; + Logger* m_logger; + bool m_connected; +}; + +#endif // TCPCLIENT_H diff --git a/Server/tcpserver.cpp b/Server/tcpserver.cpp new file mode 100644 index 0000000..8a92e1c --- /dev/null +++ b/Server/tcpserver.cpp @@ -0,0 +1,187 @@ +#include "cpidjinn.hpp" +#include +#include +#include +#include +#include + +//#define SIGPIPE 13 +volatile sig_atomic_t s_brokenPipes = 0; +typedef void (*sighandler_t)(int); + +static sighandler_t prepareSignal (int signalNo, sighandler_t signalHandler) { +#if 1 + sighandler_t oldHandler = signal(signalNo, signalHandler); + (void) oldHandler; + return NULL; +#else + struct sigaction newSignal, oldSignal; + newSignal.sa_handler = signalHandler; + sigemptyset (&newSignal.sa_mask); + newSignal.sa_flags = SA_RESTART; + if (sigaction (signalNo, &newSignal, &oldSignal) < 0) + return SIG_ERR; + return oldSignal.sa_handler; +#endif +} +static void catchSignal(int signal) { + if (signal == SIGPIPE) + s_brokenPipes++; + prepareSignal(signal, &catchSignal); +} + +/** + * Constructor. + * + * @param port the port to listen + */ +TcpServer::TcpServer(int port, Logger* logger) : + m_port(port), + m_buffer(1024), + m_listeningSocket( socket (AF_INET, SOCK_STREAM, 0)), + m_logger(logger), + m_processors(), + m_processorCount(0) + +{ + int set = 1; + setsockopt(m_listeningSocket, SOL_SOCKET, MSG_NOSIGNAL, (void *)&set, sizeof(int)); + prepareSignal(SIGPIPE, catchSignal); + +} +/** + * Destructor. + */ +TcpServer::~TcpServer() +{ +} + +/** + * Adds a processor for the client messages. + * + * @param processor processor to add + */ +void TcpServer::addProcessor(TcpProcessor& processor){ + if ((size_t) m_processorCount >= sizeof m_processors / sizeof m_processors[0]) + m_logger->sayf(LOG_ERROR, "too many processors: %d", m_processorCount); + else { + m_processors[m_processorCount++] = &processor; + } +} + +/** + * Processes standard messages. + * + * @param buffer IN: the client message
+ * OUT: the answer for the client + * @return stIgnored: unknown message
+ * stProcessed: command recognized and processed. + * stStopQUIT recognized + */ +TcpProcessor::State TcpServer::process(DynBuffer& buffer){ + State rc = stProcessed; + if (buffer.startsWith("ECHO", 4)){ + // nothing to do: the buffer is returned unchanged. + } else if (buffer.startsWith("QUIT", 4)){ + rc = stStop; + buffer.clear(); + } else if (buffer.startsWith("TIME", 4)){ + buffer.resize(64); + struct tm* tm_info; + struct timeval tv; + + gettimeofday(&tv, NULL); + tm_info = localtime(&tv.tv_sec); + strftime(buffer.buffer(), 64, "TIME: %Y.%m:%d %H:%M:%S", tm_info); + buffer.appendInt(tv.tv_usec/1000, ".%03d"); + } else { + rc = stIgnored; + } + return rc; +} +/** + * Starts the listening state. + */ +void TcpServer::listen(){ + socklen_t addrlen; + size_t length; + bool again = true; + struct sockaddr_in address; + const int dummy = 1; + setsockopt( m_listeningSocket, SOL_SOCKET, SO_REUSEADDR, &dummy, sizeof(int)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons (m_port); + int lastBrokenPipe = s_brokenPipes; + if (bind ( m_listeningSocket, (struct sockaddr *) &address, sizeof (address)) != 0) { + m_logger->sayf(LOG_ERROR, "port in use: %d", m_port); + } else { + m_logger->sayf(LOG_INFO, "listen on port %d...", m_port); + ::listen (m_listeningSocket, 5); + addrlen = sizeof (struct sockaddr_in); + while (again) { + int connectionSocket = accept ( m_listeningSocket, (struct sockaddr *) &address, &addrlen ); + if (connectionSocket <= 0) + m_logger->sayf (LOG_ERROR, "accept() failed: %d", errno); + else { + int set = 1; + setsockopt(connectionSocket, SOL_SOCKET, MSG_NOSIGNAL, (void *)&set, sizeof(int)); + m_logger->sayf (LOG_INFO, "new connection: %s ...", inet_ntoa (address.sin_addr)); + State state = stUndef; + do { + length = ::recv (connectionSocket, m_buffer.buffer(), m_buffer.size()-1, 0); + if (lastBrokenPipe != s_brokenPipes && length <= 0){ + m_logger->sayf(LOG_INFO, "connection lost"); + lastBrokenPipe = s_brokenPipes; + break; + } + if (length > 0) { + m_buffer.setLength(length); + m_logger->sayf(LOG_DEBUG, "received: %d bytes, %.4s", length, m_buffer.str()); + } else if (length == 0){ + int errorNo = errno; + if (errorNo == ENOPROTOOPT){ + m_logger->say(LOG_INFO, "connection lost."); + break; + } else { + m_logger->sayf(LOG_ERROR, "recv() returns 0: %d", errorNo); + } + } else { + m_logger->sayf(LOG_ERROR, "recv() returns %d: %d", length, errno); + } + state = stUndef; + if (length >= 4){ + bool again = true; + for (int ix = 0; again && ix < m_processorCount; ix++){ + State state = m_processors[ix]->process(m_buffer); + switch(state){ + case stUndef: + case stIgnored: + break; + case stProcessed: + again = false; + break; + case stStop: + break; + } + } + } + if (state == stIgnored){ + char buf[5]; + memcpy(buf, m_buffer.str(), 4); + buf[4] = '\n'; + m_buffer.clear().appendFormatted("ERROR: unknown command: %s", buf); + m_logger->say(LOG_ERROR, m_buffer.str()); + } + if (m_buffer.length() > 0){ + ::send (connectionSocket, m_buffer.str(), m_buffer.length(), 0); + } + } while (state != stStop); + close (connectionSocket); + } + } + } + close (m_listeningSocket); +} + + diff --git a/Server/tcpserver.hpp b/Server/tcpserver.hpp new file mode 100644 index 0000000..d64516c --- /dev/null +++ b/Server/tcpserver.hpp @@ -0,0 +1,43 @@ +#ifndef TCPSERVER_H +#define TCPSERVER_H +typedef unsigned char ubyte_t; + +class TcpProcessor { +public: + enum State { + stUndef = 0, + /// buffer content recognized and processed: + stProcessed, + /// unknown buffer content, not processed + stIgnored, + // server should stop + stStop + }; +public: + virtual State process(DynBuffer& buffer) = 0; +}; + +class TcpServer : public TcpProcessor +{ +public: + TcpServer(int port, Logger* logger); + virtual ~TcpServer(); +private: + // not implemented! Fails if used! + TcpServer(const TcpServer& other); + TcpServer& operator=(const TcpServer& other); + bool operator==(const TcpServer& other) const; +public: + void addProcessor(TcpProcessor& processor); + void listen(); + virtual State process(DynBuffer& buffer); +private: + int m_port; + DynBuffer m_buffer; + int m_listeningSocket; + Logger* m_logger; + TcpProcessor* m_processors[8]; + int m_processorCount; +}; + +#endif // TCPSERVER_H diff --git a/Server/thread.cpp b/Server/thread.cpp new file mode 120000 index 0000000..f37fbb6 --- /dev/null +++ b/Server/thread.cpp @@ -0,0 +1 @@ +../util/thread.cpp \ No newline at end of file diff --git a/Server/thread.hpp b/Server/thread.hpp new file mode 120000 index 0000000..a68448a --- /dev/null +++ b/Server/thread.hpp @@ -0,0 +1 @@ +../util/thread.hpp \ No newline at end of file diff --git a/Server/timer.cpp b/Server/timer.cpp new file mode 120000 index 0000000..2a0d81a --- /dev/null +++ b/Server/timer.cpp @@ -0,0 +1 @@ +../util/timer.cpp \ No newline at end of file diff --git a/Server/timer.hpp b/Server/timer.hpp new file mode 120000 index 0000000..04dacbc --- /dev/null +++ b/Server/timer.hpp @@ -0,0 +1 @@ +../util/timer.hpp \ No newline at end of file diff --git a/Server/timeutils.cpp b/Server/timeutils.cpp new file mode 120000 index 0000000..e07e439 --- /dev/null +++ b/Server/timeutils.cpp @@ -0,0 +1 @@ +../util/timeutils.cpp \ No newline at end of file diff --git a/Server/timeutils.hpp b/Server/timeutils.hpp new file mode 120000 index 0000000..4f43923 --- /dev/null +++ b/Server/timeutils.hpp @@ -0,0 +1 @@ +../util/timeutils.hpp \ No newline at end of file diff --git a/Server/trace.hpp b/Server/trace.hpp new file mode 120000 index 0000000..4360f00 --- /dev/null +++ b/Server/trace.hpp @@ -0,0 +1 @@ +../util/trace.hpp \ No newline at end of file diff --git a/Server/util.hpp b/Server/util.hpp new file mode 120000 index 0000000..ea72e00 --- /dev/null +++ b/Server/util.hpp @@ -0,0 +1 @@ +../util/util.hpp \ No newline at end of file diff --git a/util/Makefile b/util/Makefile new file mode 100644 index 0000000..a385304 --- /dev/null +++ b/util/Makefile @@ -0,0 +1,25 @@ +CC = g++ +CFLAGS = "-Wall" +LDFLAGS = -lm -lpthread +CL = g++ + +OBJ = cutimeutils.o timeutils.o cutimer.o cudynbuffer.o thread.o timer.o dynbuffer.o \ + logger.o unittest.o test.o +PROG = test + +all : $(PROG) + +$(PROG): $(OBJ) + $(CC) $(LDFLAGS) -g -o $(PROG) $(OBJ) + +%.o: %.cpp + $(CC) $(CFLAGS) -g -c $< + +debug: all + $(CC) $(LDFLAGS) -g -o $(PROG) $(OBJ) +stable: all + $(CL) $(LDFLAGS) -o $(PROG) $(OBJ) + +.PHONY: clean +clean: + rm -vfr *~ $(PROG) $(OBJ) diff --git a/util/cudynbuffer.cpp b/util/cudynbuffer.cpp new file mode 100644 index 0000000..aa90fce --- /dev/null +++ b/util/cudynbuffer.cpp @@ -0,0 +1,291 @@ +#include "test.hpp" + +class TestDynBuffer : public UnitTest { +public: + TestDynBuffer() : + UnitTest("dynbuffer") + { + + } +public: + virtual void run(){ + testBasic(); + testAppend(); + testAppendFormatted(); + testAppendInt(); + testAppendLittleEndian(); + testAt(); + testBufferClearLength(); + testResize(); + testSet(); + testSetLength(); + testStartsWith(); + testValueOfLE(); + testMemory(); + } + void testBasic(){ + DynBuffer buf("Hi", 0, 2); + buf.setBlocksize(2); + DynBuffer buf2(buf); + checkE("Hi", buf2); + DynBuffer buf3("hi!", 2); + checkE("hi", buf3); + DynBuffer buf4(4, 8); + buf4.append("1234"); + checkE(4, buf4.length()); + checkE(4, buf4.size()); + buf4.append("5"); + checkE(5, buf4.length()); + checkE("12345", buf4); + checkE(4+8, buf4.size()); + checkT(buf.length() <= buf.size()); + + buf = "abc"; + checkE("abc", buf); + checkE(3, buf.length()); + checkT(buf.length() <= buf.size()); + + buf = DynBuffer("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + checkE("ABCDEFGHIJKLMNOPQRSTUVWXYZ", buf); + checkE(26, buf.length()); + checkT(buf.length() <= buf.size()); + } + void testAppend(){ + DynBuffer buf("1", 1, 1); + checkE(1, buf.length()); + checkT(buf.length() <= buf.size()); + buf.append("2x", 1); + checkE("12", buf); + checkE(2, buf.length()); + checkT(buf.length() <= buf.size()); + DynBuffer buf2("xy"); + buf.append(buf2); + checkE("12xy", buf); + checkE(4, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.clear(); + checkE(0, buf.length()); + buf.append("a"); + checkE("a", buf); + checkE(1, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.append("zzz", 2); + checkE("azz", buf); + checkE(3, buf.length()); + checkT(buf.length() <= buf.size()); + } + void testAppendFormatted(){ + DynBuffer buf(2, 4); + buf.appendFormatted("%03d%c%s", 7, 'x', "Z.").appendFormatted("%.2s", "+++++"); + checkE("007xZ.++", buf); + checkE(8, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.clear(); + buf.appendFormatted("%x", 15); + checkE("f", buf); + checkE(1, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.appendFormatted("%c", '!'); + checkE("f!", buf); + checkE(2, buf.length()); + checkT(buf.length() <= buf.size()); + } + void testAppendInt(){ + DynBuffer buf(2,2); + buf.appendInt(123); + checkE("123", buf); + checkE(3, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.appendInt(64, "%4x"); + checkE("123 40", buf); + checkE(3 + 4, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.clear(); + buf.appendInt(2); + checkE("2", buf); + checkE(1, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.appendInt(8, "%02d"); + checkE("208", buf); + checkE(3, buf.length()); + checkT(buf.length() <= buf.size()); + } + void testAppendLittleEndian(){ + DynBuffer buf(2,2); + buf.appendAsLE(0x31323334, 4); + checkE("4321", buf); + checkE(4, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.appendAsLE(0x414243, 3); + checkE("4321CBA", buf); + checkE(7, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.clear(); + buf.appendAsLE(0x6162, 2); + checkE("ba", buf); + checkE(2, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.appendAsLE(0x21); + checkE("ba!", buf); + checkE(3, buf.length()); + checkT(buf.length() <= buf.size()); + } + void testAt(){ + DynBuffer buf("abcd"); + checkT('a' == buf.at(0)); + checkT('b' == buf.at(1)); + checkT('c' == buf.at(2)); + checkT('d' == buf.at(3)); + checkT(0 == buf.at(-1)); + checkT(0 == buf.at(4)); + } + + void testBufferClearLength(){ + DynBuffer buf(2, 2); + checkE(2, buf.size()); + checkE(0, buf.length()); + const char* longString = "123456789 123456789 123456789"; + int len = strlen(longString); + strcpy(buf.buffer(len), longString); + buf.setLength(len); + checkE(longString, buf); + checkE(29, buf.length()); + checkT(buf.length() <= buf.size()); + + buf.clear(); + checkE("", buf); + checkE(0, buf.length()); + checkT(buf.length() <= buf.size()); + } + void testResize(){ + DynBuffer buf(2, 2); + checkE(2, buf.size()); + checkE(0, buf.length()); + // blocksize is 2: + buf.resize(3); + checkE(4, buf.size()); + checkE(0, buf.length()); + // reserve more than blocksize: + buf.resize(7); + checkE(7, buf.size()); + checkE(0, buf.length()); + } + void testSet(){ + DynBuffer buf("123"); + checkE("123", buf); + checkE(3, buf.length()); + checkT(buf.length() <= buf.size()); + + // a shorter string: + buf.set("ab"); + checkE("ab", buf); + checkE(2, buf.length()); + checkT(buf.length() <= buf.size()); + + // a longer string: + buf.set("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + checkE("ABCDEFGHIJKLMNOPQRSTUVWXYZ", buf); + checkE(26, buf.length()); + checkT(buf.length() <= buf.size()); + } + void testSetBlocksize(){ + DynBuffer buf(2, 4); + checkE(2, buf.size()); + checkE(0, buf.length()); + checkE(4, buf.blocksize()); + + buf.setBlocksize(23); + checkE(2, buf.size()); + checkE(0, buf.length()); + checkE(23, buf.blocksize()); + } + void testSetLength(){ + DynBuffer buf("12345"); + buf.setBlocksize(2); + checkE("12345", buf); + checkE(5, buf.size()); + checkE(5, buf.length()); + + // make it shorter: + buf.setLength(4); + checkE("1234", buf); + checkE(4, buf.length()); + + // make it larger: + buf.setLength(8); + checkT(buf.startsWith("1234")); + checkE(8, buf.length()); + checkE(8, buf.size()); + } + // testSize() not implemented + void testStartsWith(){ + DynBuffer buf("abc"); + checkE(3, buf.length()); + + checkT(buf.startsWith("abc")); + checkT(buf.startsWith("ab")); + checkT(buf.startsWith("a")); + + checkF(buf.startsWith("A")); + checkF(buf.startsWith("aBc")); + + buf.clear().appendAsLE(0x32310041, 4); + checkE(4, buf.length()); + checkE(0x41, buf.at(0)); + checkE(0, buf.at(1)); + checkE(0x31, buf.at(2)); + checkE(0x32, buf.at(3)); + + checkT(buf.startsWith("A")); + checkE(4, buf.length()); + DynBuffer buf2(buf); + checkT(buf.startsWith(buf2.str())); + for (int len = 3; len > 0; len--){ + buf2.append("x"); + checkF(buf.startsWith(buf2.str(), buf2.length())); + buf2.setLength(len); + checkT(buf.startsWith(buf2.str())); + } + } + void testValueOfLE(){ + // int valueOfLE(int size, int index = 0, int defaultValue = -1); + DynBuffer buf("abc"); + buf.appendAsLE(0x01020304, 4).append("xyz"); + checkE(0x04, buf.valueOfLE(1, 3)); + checkE(0x0304, buf.valueOfLE(2, 3)); + checkE(0x020304, buf.valueOfLE(3, 3)); + checkE(0x01020304, buf.valueOfLE(4, 3)); + buf.buffer()[0] = 0x7f; + buf.buffer()[1] = 0xab; + buf.buffer()[2] = 0x77; + checkE(0x7f, buf.valueOfLE(1)); + checkE(0xab7f, buf.valueOfLE(2)); + checkE(0x77ab7f, buf.valueOfLE(3)); + } + void testMemory(){ + clock_t start = clock(); + int count = 1000*1000; + int size = 10*1024*1024; + for (int ix = 0; ix < 1000; ix++){ + DynBuffer big(size); + big.setLength(size + 10); + } + double duration = (clock() - start) / CLOCKS_PER_SEC; + printf("allocation of %d blocks with %d MiByte: %.3f sec\n", count, size / 1024 / 1024, duration); + } +}; + +void testDynBuffer(){ + TestDynBuffer test; + test.run(); +} diff --git a/util/cutimer.cpp b/util/cutimer.cpp new file mode 100644 index 0000000..47d86a6 --- /dev/null +++ b/util/cutimer.cpp @@ -0,0 +1,142 @@ +#include "test.hpp" +//#define TRACE_ON +#include "trace.hpp" + +static Logger s_logger; +static ThreadPool s_timerPool(256, &s_logger); + +class Counter : public Timer{ +public: + Counter(Unit unit, int count, int* result, Announcer* logger, ThreadPool* pool) : + Timer(count, 10, 1, unit, true, logger, pool), + m_counter(result) + { + + } +public: + virtual void timerTask(){ + trace1("Counter::timerTask %d\n", threadId()); + m_pool.lock(); + ++*m_counter; + m_pool.unlock(); + } +public: + int* m_counter; +}; +class TestTimer : public UnitTest { +public: + TestTimer() : + UnitTest("cutimer") + { + + } +public: + virtual void run(){ + testMany(); + test2Timers(); + testBasic(); + testTimer(); + } + void testTimer(){ + m_logger.say(LOG_INFO, "Start testTimer"); + + TimeMeasurement m; + int max = 1000000; + for (int ix = max; ix > 0; --ix){ + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + } + m.stop(); + m.logDuration(max, &m_logger, "clock_gettime()"); + + m.start(); + for (int ix = 50; ix > 0; --ix){ + Thread::microSleep(10*1000); + } + m.stop(); + m.logDuration(50, &m_logger, "microsleep(10*1000):"); + + m.start(); + for (int ix = 500; ix > 0; --ix){ + Thread::microSleep(1000); + } + m.stop(); + m.logDuration(500, &m_logger, "microsleep(1000): "); + + //extern int gv_calls, gv_rounds; + //m_logger.sayf(LOG_INFO, "calls: %d rounds: %d", gv_calls, gv_rounds); + + m.start(); + for (int ix = 10000; ix > 0; --ix){ + Thread::microSleep(10); + } + m.stop(); + m.logDuration(10000, &m_logger, "microsleep(10): "); + + m.start(); + for (int ix = 10000; ix > 0; --ix){ + Thread::microSleep(1); + } + m.stop(); + m.logDuration(10000, &m_logger, "microsleep(1): "); + + } + + void testBasic(){ + int count = 0; + TimeMeasurement m; + Counter* counter1 = new Counter(Timer::uMilliSeconds, 50, &count, &s_logger, &s_timerPool); + int id1 = counter1->threadId(); + counter1->start(); + s_timerPool.join(id1); + m.stop(); + m.logDuration(&m_logger); + checkT(m.m_durationReal >= 0.06); + checkE(50, count); + } + void test2Timers(){ + int count = 0; + TimeMeasurement m; + Counter* counter1 = new Counter(Timer::uMilliSeconds, 10, &count, &s_logger, &s_timerPool); + int id1 = counter1->threadId(); + Counter* counter2 = new Counter(Timer::uMilliSeconds, 10, &count, &s_logger, &s_timerPool); + int id2 = counter2->threadId(); + counter1->start(); + counter2->start(); + m_logger.say(LOG_INFO, "counter1 + 2 started"); + s_timerPool.join(id1); + s_timerPool.join(id2); + m.stop(); + m.logDuration(&m_logger, "test2timers:"); + checkT(m.m_durationReal >= 0.02); + checkE(20, count); + } + void testMany(){ + TimeMeasurement m; + int count = 0; + const int MAX = 2000; + int id = 1; + while(id <= MAX){ + if (s_timerPool.count() >= 256){ + Thread::microSleep(100); + } else { + Counter* counter = new Counter(Timer::uMicroSeconds, 5, &count, + &s_logger, &s_timerPool); + counter->start(); + id++; + } + } + + for (int id = 1; id <= MAX; id++){ + s_timerPool.join(id); + } + m.stop(); + m.logDuration(MAX, &m_logger, "testMany:"); + checkE(MAX*5, count); + } +}; + +void testTimer(){ + TestTimer test; + test.run(); +} diff --git a/util/cutimeutils.cpp b/util/cutimeutils.cpp new file mode 100644 index 0000000..5efbd1e --- /dev/null +++ b/util/cutimeutils.cpp @@ -0,0 +1,71 @@ +#include "test.hpp" +#include "math.h" + +static Logger s_logger; +static ThreadPool s_timerPool(2, &s_logger); + +class TestTimeUtils : public UnitTest { +public: + TestTimeUtils() : + UnitTest("cutimeutils") + { + + } +public: + virtual void run(){ + testBasic(); + testTimeMeasurement(); + } + void testBasic(){ + const uint64_t MRD = 1000000000; + time_t start = time(NULL); + while(time(NULL) == start){ + // do nothing + } + start++; + double dummy = 0.0; + uint64_t startReal = TimeUtils::nanosecSinceEpoche(); + uint64_t startCpu = TimeUtils::nanosecSinceBoot(); + int count; + uint64_t current; + while(startReal + MRD > (current = TimeUtils::nanosecSinceEpoche())){ + dummy = dummy * 12.45 + 1.12345 * sin(dummy) + tan(dummy*7); + if (dummy > 1 || dummy < -1) + dummy = 1 / dummy; + ++count; + } + double durationCpu = (TimeUtils::nanosecSinceBoot() - startCpu) / (double) MRD; + double durationReal = (current - startReal) / (double) MRD; + int duration = time(NULL) - start; + m_logger.sayf(LOG_INFO, "duration: %.3f / %.3f", durationReal, durationCpu); + checkT(durationReal >= 1.0); + m_logger.sayf(LOG_INFO, "diff real-cpu: %f", durationReal - durationCpu); + checkT(durationCpu <= durationReal); + checkE(1, duration); + } + void testTimeMeasurement(){ + time_t start = time(NULL); + while(time(NULL) == start){ + // do nothing + } + TimeMeasurement m; + ++start; + double dummy = 0.0; + int count = 0; + while(time(NULL) == start){ + dummy = dummy * 12.45 + 1.12345 * sin(dummy) + tan(dummy*7); + if (dummy > 1 || dummy < -1) + dummy = 1 / dummy; + ++count; + } + m.stop(); + m.logDuration(&m_logger); + m.logDuration(count, &m_logger); + } + +}; + +void testTimerUtils(){ + TestTimeUtils test; + test.run(); +} diff --git a/util/dynbuffer.cpp b/util/dynbuffer.cpp new file mode 100644 index 0000000..6dd1773 --- /dev/null +++ b/util/dynbuffer.cpp @@ -0,0 +1,181 @@ +#include "util.hpp" + +/** + * Constructor. + * + * @param bufferSize initial size of the buffer + * @param blocksize minimal increment of size when reallocation is needed
+ * 0: use bufferSize as block size + */ +DynBuffer::DynBuffer(size_t bufferSize, size_t blocksize) : + m_size(bufferSize == 0 ? 16 : bufferSize), + m_length(0), + m_blocksize(blocksize == 0 ? m_size : blocksize), + m_buffer(new char[m_size + 1]){ + m_buffer[0] = '\0'; +} +/** + * Constructor. + * + * @param value initial value + * @param length 0: strlen(value) will be taken
+ * otherwise: the length of value + * @param size the initial buffer size
+ * 0: size depends on length + */ +DynBuffer::DynBuffer(const char* value, size_t length, size_t size) : + m_size(0), + m_length(length == 0 ? strlen(value) : length), + m_blocksize(256), + m_buffer(NULL){ + m_size = size == 0 ? m_length : size < m_length ? m_length : size; + m_buffer = new char[m_size + 1]; + memcpy(m_buffer, value, m_length); + m_buffer[m_length] = '\0'; + } + +/** + * Destructor. + */ +DynBuffer::~DynBuffer(){ + if (m_buffer != NULL){ + delete[] m_buffer; + } + m_buffer = NULL; + m_length = 0; + m_size = 0; +} +/** + * Copy constructor. + * + * @param source the source to copy + */ +DynBuffer::DynBuffer(const DynBuffer& source) : + m_size(source.m_length < 16 ? 16 : source.m_length), + m_length(source.m_length), + m_blocksize(256), + m_buffer(new char[m_size + 1]) +{ + // copy including the trailing '\0': + memcpy(m_buffer, source.m_buffer, m_length + 1); +} +/** + * Assign operator. + * + * @param source the source to copy + */ +DynBuffer& DynBuffer::operator =(const DynBuffer& source){ + if (source.m_length == 0){ + clear(); + } else if (m_size >= source.m_length){ + // copy with '\0': + memcpy(m_buffer, source.m_buffer, (m_length = source.m_length) + 1); + } else { + delete[] m_buffer; + m_buffer = new char[(m_size = m_length = source.m_length) + 1]; + // copy with '\0': + memcpy(m_buffer, source.m_buffer, m_length + 1); + } + return *this; +} +/** + * Appends a string to the buffer. + * + * @param string the string to append + * @param size the length of the string. 0: use strlen(string) + * @return *this (for chaining) + */ +DynBuffer& DynBuffer::append(const char* string, size_t length){ + if (length == 0) + length = strlen(string); + resize(m_length + length); + memcpy(m_buffer + m_length, string, length); + m_length += length; + m_buffer[m_length] = '\0'; + return *this; +} +/** + * Appends a formatted string to the buffer. + * + * @param format the format like sprintf() + * @param ... the argument matching the format + * @return *this (for chaining) + */ +DynBuffer& DynBuffer::appendFormatted(const char* format, ...) +{ + char buffer[1024 * 64]; + va_list argptr; + va_start(argptr, format); + vsnprintf(buffer, sizeof buffer, format, argptr); + va_end(argptr); + return append(buffer, strlen(buffer)); +} +/** + * Appends an integer as string. + * + * @param value the value to add + * @param format the format like sprintf() + * @return *this (for chaining) + */ +DynBuffer& DynBuffer::appendInt(int value, const char* format){ + char buffer[256]; + snprintf(buffer, sizeof buffer, format, value); + return append(buffer, strlen(buffer)); +} +/** + * Appends an integer as little endian binary number. + * + * @param value the value to append + * @param size the number of bytes to store: 1..sizeof(int) + * @return *this (for chaining) + */ +DynBuffer& DynBuffer::appendAsLE(int value, size_t size){ + resize(m_length + size); + while(size-- > 0){ + m_buffer[m_length++] = (char) value; + value >>= 8; + } + m_buffer[m_length] = '\0'; + return *this; +} +/** + * Ensures that the size is greater or equal a given size. + * + * @param size the wanted size (not including the implicite trailing '\0') + * @return *this (for chaining) + */ +DynBuffer& DynBuffer::resize(size_t size){ + if (m_length + size > m_size){ + m_size = m_size + m_blocksize; + if (size > m_size) + m_size = size; + char* newBuffer = new char[m_size + 1]; + // copy with '\0': + memcpy(newBuffer, m_buffer, m_length + 1); + delete[] m_buffer; + m_buffer = newBuffer; + } + return *this; +} +/** + * Gets a binary number in little endian format from the buffer. + * + * @param size the size of the number: 1..8 + * @param index the first index + * @param defaultValue the return value if error occurs + * @return defaultValue: the index is wrong
+ * otherwise: the integer from the given position + */ +int DynBuffer::valueOfLE(int size, int index, int defaultValue){ + int ix; + int rc = 0; + if (index < 0 || size_t(index + size) > m_length) + rc = defaultValue; + else { + for (ix = index + size - 1; ix >= index; ix--){ + rc = (rc << 8) + (ubyte_t) m_buffer[ix]; + } + } + return rc; +} + diff --git a/util/dynbuffer.hpp b/util/dynbuffer.hpp new file mode 100644 index 0000000..ea7e39a --- /dev/null +++ b/util/dynbuffer.hpp @@ -0,0 +1,152 @@ +#ifndef DYNBUFFER_H +#define DYNBUFFER_H + +/** + * Implements a dynamic growing buffer. + * Can be used as "poor man's string implementation": + * the buffer always is terminated with a '\0' (like a C string). + * This ending zero is not counted by m_length. + */ +class DynBuffer { +public: + DynBuffer(size_t bufferSize = 16, size_t blocksize = 0); + DynBuffer(const char* value, size_t length = 0, size_t size = 0); + ~DynBuffer(); + DynBuffer(const DynBuffer& source); + DynBuffer& operator =(const DynBuffer& source); +public: + DynBuffer& append(const char* string, size_t length = 0); + inline DynBuffer& append(const DynBuffer& source){ + return append(source.str(), source.length()); + } + DynBuffer& appendFormatted(const char* format, ...); + DynBuffer& appendInt(int value, const char* format = "%d"); + DynBuffer& appendAsLE(int value, size_t size = 1); + /** + * Returns the character at a given position. + * + * @param index the index of the wanted character + * @return '\0': wrong index
+ * otherwise: the character at the position index + */ + inline char at(int index) const { + return index < 0 || index >= (int) m_length ? '\0' : m_buffer[index]; + } + /** + * Returns the current blocksize. + * + * @return the current blocksize + */ + inline size_t blocksize(){ + return m_blocksize; + } + /** + * Returns the address of the internal buffer for writing to it. + * + * @param neededSize 0: do not change the size
+ * otherwise: the buffer is resized to this value + * @return the internal buffer + */ + inline char* buffer(size_t neededSize = 0){ + if (neededSize > 0) + resize(neededSize); + return m_buffer; + } + /** + * Remove the buffer contents. + * + * @return *this (for chaining) + */ + inline DynBuffer& clear(){ + m_length = 0; + m_buffer[0] = '\0'; + return *this; + } + /** + * Returns the current buffer length. + * + * @return the count of bytes in the buffer + */ + inline size_t length() const { + return m_length; + } + DynBuffer& resize(size_t length); + /** + * Replace the buffer contents with a given string. + * + * @param source the string to set + * @param length 0: strlen(source) will be used
+ * otherwise: the length of source + * @return *this (for chaining) + */ + DynBuffer& set(const char* source, size_t length = 0){ + return clear().append(source, length); + } + /** + * Sets the block size. + * + * @param blocksize the new block size, the minimal increment when reallocation + */ + void setBlocksize(size_t blocksize){ + m_blocksize = blocksize; + } + /** + * Sets the length of the buffer. + * + * @param length the new length. If larger than the size the size is increased + * but the content is not changed. + * @return *this (for chaining) + */ + inline DynBuffer& setLength(int length){ + if (length > (int) m_size) + resize(length); + m_buffer[m_length = length] = '\0'; + return *this; + } + /** + * Returns the current size of the buffer. + * + * @return the current size + */ + inline size_t size() const{ + return m_size; + } + /** + * Tests whether the buffer starts with a given string. + * + * @param text text to inspect + * @param length length of length
+ * 0: use strlen(text) + * @return true: the text is found at the beginning of the buffer + */ + inline bool startsWith(const char* text, size_t length = 0){ + return memcmp(m_buffer, text, length == 0 ? strlen(text) : length) == 0; + } + /** + * Returns the buffer content as constant string. + */ + inline const char* str() const { + return m_buffer; + } + /** + * Returns a binary stored integer (stored with appendAsLE()). + * + * @param size the size of the stored integer: 1..8 + * @param index the index of the first byte of the integer + * @param defaultValue the value returned if the index is out of bounds + * @return defaultValue: index is wrong
+ * otherwise: the integer stored at the given position + */ + int valueOfLE(int size, int index = 0, int defaultValue = -1); +private: + /// size of the buffer (without the trailing '\0') + size_t m_size; + /// length without the trailing '\0' + size_t m_length; + /// the minimal increment when reallocation is done + size_t m_blocksize; + /// Holds the buffer contents + char* m_buffer; +}; + +#endif // DYNBUFFER_H diff --git a/util/logger.cpp b/util/logger.cpp new file mode 100644 index 0000000..d0c1c7c --- /dev/null +++ b/util/logger.cpp @@ -0,0 +1,41 @@ +#include "util.hpp" + +/** + * Constructor. + */ +Logger::Logger() : + m_buffer(1024, 2048) +{ + +} + +/** + * Destructor. + */ +Logger::~Logger() +{ +} + +/** + * Writes a log message. + * + * @param level type of the message: LOG_ERROR, LOG_WARNING, LOG_INFO + * @param message the message to Writes + */ +void Logger::say(LogLevel level, const char* message){ + const char* prefix; + switch(level){ + case LOG_ERROR: + m_errors++; + prefix = "+++ "; + break; + case LOG_WARNING: + m_warnings++; + prefix = "*** "; + break; + default: + prefix = ""; + break; + } + printf("%s%s\n", prefix, message); +} diff --git a/util/logger.hpp b/util/logger.hpp new file mode 100644 index 0000000..627ba96 --- /dev/null +++ b/util/logger.hpp @@ -0,0 +1,56 @@ +#ifndef LOGGER_H +#define LOGGER_H + +enum LogLevel { + LOG_ERROR = 1, + LOG_WARNING, + LOG_INFO, + LOG_DEBUG +}; +class Announcer{ +public: + Announcer() : + m_errors(0), + m_warnings(0) + {} +public: + virtual void say(LogLevel level, const char* message) = 0; +public: + void say(LogLevel level, const DynBuffer& message){ + say(level, message.str()); + } + void sayf(LogLevel level, const char* format, ...){ + char dest[1024 * 64]; + va_list argptr; + va_start(argptr, format); + vsnprintf(dest, sizeof dest, format, argptr); + va_end(argptr); + say(level, dest); + } + int errors() const { + return m_errors; + } + int warnings() const { + return m_warnings; + } +protected: + int m_errors; + int m_warnings; +}; + +class Logger : public Announcer +{ +public: + Logger(); + ~Logger(); +private: + // no implementation, no external usage! + Logger(const Logger& other); + Logger& operator=(const Logger& other); +public: + void say(LogLevel level, const char* message); +private: + DynBuffer m_buffer; +}; + +#endif // LOGGER_H diff --git a/util/test.cpp b/util/test.cpp new file mode 100644 index 0000000..96f6b5a --- /dev/null +++ b/util/test.cpp @@ -0,0 +1,14 @@ +#include +#include + +int main(int argc, char **argv) { + + extern void testDynBuffer(); + extern void testTimer(); + extern void testTimerUtils(); + + testTimer(); + testTimerUtils(); + testDynBuffer(); + return 0; +} diff --git a/util/test.hpp b/util/test.hpp new file mode 100644 index 0000000..49095a0 --- /dev/null +++ b/util/test.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_HPP +#define __TEST_HPP +#include "util.hpp" +#include "unittest.hpp" + +#endif diff --git a/util/thread.cpp b/util/thread.cpp new file mode 100644 index 0000000..41ac1e6 --- /dev/null +++ b/util/thread.cpp @@ -0,0 +1,256 @@ +#include "util.hpp" +//#define TRACE_ON +#include "trace.hpp" + +int ThreadPool::m_currentMaxThreads = 128; +ThreadPool* ThreadPool::m_instance = NULL; +const pthread_mutex_t ThreadPool::m_mutexInitializer = PTHREAD_MUTEX_INITIALIZER; + +int gv_rounds = 0; +int gv_calls; + +/** + * Connects the posix thread to the class instance. + */ +void* threadStarter(void *param){ + trace("threadstarter"); + Thread* thread = reinterpret_cast(param); + thread->execute(); + return param; +} +/** + * Constructor. + * + * @param pool the thread pool + * @param autoDelete true: the instance destroys itself after run() + * If true the new operator must be used for creation + * @param logger the logger + */ +Thread::Thread(Announcer* logger, bool autoDelete, ThreadPool* pool) : + m_threadId(0), + m_shouldStop(false), + m_pool(pool == NULL ? *ThreadPool::instance(logger) : *pool), + m_logger(logger), + m_pthread(0), + m_autoDelete(autoDelete), + m_running(false) +{ + pool->append(*this); + trace1("Thread(%d)\n", m_threadId); +} + +/** + * Destructor. + */ +Thread::~Thread() { + trace1("~Thread(%d)\n", m_threadId); +} +/** + * Executes the "run" of the thread and deactivates itself. + */ +void Thread::execute(){ + trace1("Thread::execute(%d)\n", m_threadId); + m_running = true; + run(); + m_pool.remove(*this); + if (m_autoDelete){ + trace1("Thread::execute(%d) autodelete\n", m_threadId); + delete this; + } +} + +/** + * Waits at least a given amount of microseconds. + * + * @param microseconds the time to wait + */ +void Thread::microSleep(uint64_t microseconds){ + struct timespec wait; + wait.tv_sec = microseconds / 1000000; + wait.tv_nsec = microseconds % 1000000 * 1000; + nanosleep(&wait , NULL); +} + +/** + * Lets the thread run. + */ +void Thread::start(){ + pthread_create(&m_pthread, NULL, &threadStarter, this); + trace2("Thread::start(%d): %lx\n", m_threadId, m_pthread); +} +/** + * Introduces the termination of the thread. + * + * Cooperative mode: Sets only a flag. The thread must test this flag + * and terminate if set. + */ +void Thread::terminate(){ + trace1("Thread::terminate(%d)\n", m_threadId); + m_shouldStop = true; +} +/** + * Returns the thread id. + * + * @return the thread id + */ +int Thread::threadId() const{ + return m_threadId; +} + +/** + * Constructor. + * + * @param maxThreads the maximum of active threads + * @param factory a factory for creating threads + * @param logger the logger + */ +ThreadPool::ThreadPool(int maxThreads, Announcer* logger) : + m_maxThreads(maxThreads), + m_list(new Thread*[m_currentMaxThreads]), + m_count(0), + m_nextId(1), + m_maxExitTime(50), + m_logger(logger), + m_mutex(m_mutexInitializer) +{ +} +/** + * Destructor. + */ +ThreadPool::~ThreadPool() { + trace1("~ThreadPool: %d\n", m_count); + terminateAll(); + int maxMillisec = m_maxExitTime * 1000 + 500; + while (m_count != 0 && maxMillisec > 0){ + usleep(10*1000); + maxMillisec -= 10; + } + // Force the termination of the non terminated: + pthread_t pthread; + trace1("~ThreadPool: force: %d\n", m_count); + while (m_count-- > 0){ + lock(); + Thread* thread = m_list[m_count]; + pthread = thread->m_pthread; + unlock(); + pthread_cancel(pthread); + } + +} +/** + * Appends a thread to the pool. + * + * @param thread the thred to add + */ +void ThreadPool::append(Thread& thread){ + lock(); + thread.m_threadId = m_nextId++; + if (m_count == m_maxThreads){ + unlock(); + m_logger->sayf(LOG_ERROR, "too many threads: %d", m_maxThreads); + } else { + m_list[m_count++] = &thread; + unlock(); + trace1("ThreadPool::append(): id: %d\n", thread.m_threadId); + } +} + +/** + * Waits for the finishing of a given thread. + * + * @param threadId the thread identifier + * @return NULL: the thread does not already exist
+ * otherwise: the exit code of the thread + */ +void* ThreadPool::join(int threadId){ + trace1("ThreadPool::join(%d):\n", threadId); + void* rc = NULL; + Thread* thread = NULL; + pthread_t pthread = -1; + bool running = false; + do { + lock(); + thread = findById(threadId); + if(thread == NULL){ + unlock(); + break; + } else { + pthread = thread->m_pthread; + running = thread->m_running; + } + unlock(); + if (! running) + Thread::microSleep(10*1000); + } while(! running); + trace2("join: thread: %c %lx\n", thread != NULL ? 't' : 'f', pthread); + // Does the thread already exist? + if (pthread != (pthread_t) -1) + pthread_join(pthread, &rc); + trace1("join: rc: %c\n", rc == NULL ? 't' : 'f'); + return rc; +} + +/** + * Returns a singleton instance of the ThreadPool. + * + * @param logger the logger + * @return the singleton instance + */ +ThreadPool* ThreadPool::instance(Announcer* logger) +{ + if (m_instance == NULL) + m_instance = new ThreadPool(m_currentMaxThreads, logger); + return m_instance; +} + +/** + * Deactivates a thread. + * + * @param thread the thread to deactivate. + */ +void ThreadPool::remove(Thread& thread){ + // Destructor already called? + trace1("ThreadPool::remove(%d)\n", thread.threadId()); + lock(); + for (int ix = 0; ix < m_count; ix++){ + if (m_list[ix] == &thread){ + // 012x45 + // (count - ix - 1) + memmove(m_list + ix, m_list + ix + 1, + (--m_count - ix) * sizeof m_list[0]); + thread.m_threadId = 0; + break; + } + } + unlock(); +} + +/** + * Search a thread given by its id. + * + * @param id the thread id + * @return NULL: not found
+ * the thread with the given id + */ +Thread* ThreadPool::findById(int id){ + Thread* rc = NULL; + for (int ix = 0; ix < m_count; ix++){ + if (m_list[ix]->threadId() == id){ + rc = m_list[ix]; + break; + } + } + trace2("ThreadPool::findBy(%d): %c\n", id, rc == NULL ? 'f' : 't'); + return rc; +} +/** + * Deactivates a thread. + * + * @param thread the thread to deactivate. + */ +void ThreadPool::terminateAll(){ + trace("terminateAll()\n"); + for (int ix = m_count - 1; ix >= 0; ix--){ + m_list[ix]->terminate(); + } +} diff --git a/util/thread.hpp b/util/thread.hpp new file mode 100644 index 0000000..484b9d3 --- /dev/null +++ b/util/thread.hpp @@ -0,0 +1,102 @@ +#ifndef THREAD_H +#define THREAD_H +#include "pthread.h" +class ThreadPool; + +/** + * Implements a base class of a posix thread. + */ +class Thread { + friend ThreadPool; +public: + Thread(Announcer* logger, bool autoDelete = true, ThreadPool* pool = NULL); + virtual ~Thread(); +private: + // not implemented, private avoids usage. + Thread ( const Thread& other ); + Thread& operator= ( const Thread& other ); +public: + int threadId() const; + /** + * Does the real things. + */ + virtual void run() = 0; + void start(); + void terminate(); +public: + static void microSleep(uint64_t microseconds); +private: + friend void* threadStarter(void *); + void execute(); +protected: + int m_threadId; + bool m_shouldStop; + ThreadPool& m_pool; + Announcer* m_logger; + pthread_t m_pthread; + bool m_autoDelete; + bool m_running; +}; + +/** + * Manages a pool of threads. + * + * Allows waiting for all threads... + * Can (but not must) be used as singleton. + */ +class ThreadPool { +public: + ThreadPool(int maxThreads, Announcer* logger); + virtual ~ThreadPool(); +private: + // not implemented, private avoids usage: + ThreadPool(const ThreadPool& source); + ThreadPool& operator =(const ThreadPool& source); +public: + static ThreadPool* instance(Announcer* logger); + static void microSleep(int microseconds); +public: + void append(Thread& thread); + /** + * Returns the number of threads. + * + * @return the number of threads in the pool + */ + inline int count() const { + return m_count; + } + void* join(int threadId); + /** + * Locks the pool specific mutual exclusion object. + */ + inline void lock(){ + pthread_mutex_lock(&m_mutex); + } + void remove(Thread& thread); + void terminateAll(); + /** + * Unlocks the pool specific mutual exclusion object. + */ + inline void unlock(){ + pthread_mutex_unlock(&m_mutex); + } +private: + Thread* findById(int id); +public: + /// Change this value before calling the constructor. + /// The constructor can be called implicite by instance(). + static int m_currentMaxThreads; + static const pthread_mutex_t m_mutexInitializer; +private: + static ThreadPool* m_instance; +private: + int m_maxThreads; + Thread** m_list; + int m_count; + int m_nextId; + /// maximal waiting time for deactivating active threads while destructing (in 10 msec) + int m_maxExitTime; + Announcer* m_logger; + pthread_mutex_t m_mutex; +}; +#endif // THREAD_H diff --git a/util/timer.cpp b/util/timer.cpp new file mode 100644 index 0000000..92fff64 --- /dev/null +++ b/util/timer.cpp @@ -0,0 +1,67 @@ +#include "util.hpp" +//#define TRACE_ON +#include "trace.hpp" +/** + * Constructor. + * + * @param count number of actions + * @param delay the number of units to wait after the action + * @param startDelay number of units to wait before the first action + * @param unit the unit used for the times: uMicroSecond... + * @param autoDelete true: the instance destroys itself after the actions. + * If true the new operator must be used for creation + * @param logger the logger + * @param pool the thread pool. If NULL the global singleton is used + */ +Timer::Timer(int count, int delay, int m_startDelay, Unit unit, + bool autoDelete, Announcer* logger, ThreadPool* pool) : + Thread(logger, autoDelete, pool), + m_count(count), + m_delay(delay), + m_startDelay(delay), + m_unit(unit) +{ + trace2("Timer(%d, %d)\n", count, delay); +} + +Timer::~Timer() { + +} + +/** + * The action called by Thread. + */ +void Timer::run(){ + trace1("Timer::run(%d)\n", threadId()); + if (m_startDelay > 0){ + switch (m_unit){ + case uSeconds: + sleep(m_startDelay); + break; + case uMilliSeconds: + microSleep(m_startDelay * 1000); + break; + case uMicroSeconds: + default: + microSleep(m_startDelay); + break; + } + } + while(m_count-- > 0){ + trace2("Timer::run(%d): %d\n", threadId(), m_count); + timerTask(); + switch (m_unit){ + case Timer::uSeconds: + sleep(m_delay); + break; + case Timer::uMilliSeconds: + microSleep(m_delay * 1000); + break; + case Timer::uMicroSeconds: + usleep(m_delay); + break; + default: + break; + } + } +} diff --git a/util/timer.hpp b/util/timer.hpp new file mode 100644 index 0000000..95e1da2 --- /dev/null +++ b/util/timer.hpp @@ -0,0 +1,35 @@ +#ifndef TIMER_H +#define TIMER_H + +/** + * Administrates threads for delayed actions. + * + * This is a abstract base class. + */ +class Timer : public Thread{ +public: + enum Unit { + uMicroSeconds, + uMilliSeconds, + uSeconds, + }; +public: + Timer(int count, int delay, int m_startDelay, Unit unit, + bool autoDelete, Announcer* logger, ThreadPool* pool = NULL); + ~Timer(); +private: + // not implemented, private to avoid usage: + Timer ( const Timer& other ); + Timer& operator= ( const Timer& other ); +public: + virtual void timerTask() = 0; +private: + virtual void run(); +private: + int m_count; + int m_delay; + int m_startDelay; + Unit m_unit; +}; + +#endif // TIMER_H diff --git a/util/timeutils.cpp b/util/timeutils.cpp new file mode 100644 index 0000000..5266689 --- /dev/null +++ b/util/timeutils.cpp @@ -0,0 +1,63 @@ +#include "util.hpp" + +/** + * Constructor. + */ +TimeMeasurement::TimeMeasurement() : + m_startReal(0), + m_startCpu(0) +{ + start(); +} + +/** + * Destructor. + */ +TimeMeasurement::~TimeMeasurement() { + +} + +/** + * Logs the duration. + * + * @param logger the logger + * @param format the format (like in sprintf()) with 2 double placeholders
+ * if NULL: "duration real: %.3f cpu: %3f" + */ +void TimeMeasurement::logDuration(Announcer* logger, const char* format){ + static const char* stdFormat1 = "duration real: %.3f sec cpu: %3f sec"; + static const char* stdFormat2 = "duration real: %.6f sec cpu: %6f sec"; + DynBuffer format2; + if (format == NULL) + format = m_durationReal >= 1.0 ? stdFormat1 : stdFormat2; + else if (strchr(format, '%') == NULL){ + format2.append(format).append(" ").append(m_durationReal >= 1 ? stdFormat1 : stdFormat2); + format = format2.str(); + } + logger->sayf(LOG_INFO, format, m_durationReal, m_durationCpu); +} +/** + * Logs the duration of a mass event measurement. + * + * @param count number of events + * @param logger the logger + * @param format the format (like in sprintf()) with 1 integer + * and 4 double placeholders
+ * if the format does not contain a placeholder ('%') it will be used + * as format2 to the standard format
+ * if NULL: "count: %d duration real: %.3f (%.1f/sec) cpu: %3f (%.1f/s)" + */ +void TimeMeasurement::logDuration(int count, Announcer* logger, const char* format){ + DynBuffer format2; + static const char* stdFormat1 = "count: %d duration real: %.3f sec (%.3g/sec) cpu: %.3f sec (%.3g/sec)"; + static const char* stdFormat2 = "count: %d duration real: %.6f sec (%.3g/sec) cpu: %.6f sec (%.3g/sec)"; + if (format == NULL) + format = m_durationReal >= 1.0 ? stdFormat1 : stdFormat2; + else if (strchr(format, '%') == NULL){ + format2.append(format).append(" ").append(m_durationReal >= 1 ? stdFormat1 : stdFormat2); + format = format2.str(); + } + logger->sayf(LOG_INFO, format, + count, m_durationReal, m_durationReal == 0 ? 0.0 : count / m_durationReal, + m_durationCpu, m_durationCpu == 0 ? 0.0 : count / m_durationCpu); +} diff --git a/util/timeutils.hpp b/util/timeutils.hpp new file mode 100644 index 0000000..37eaaa2 --- /dev/null +++ b/util/timeutils.hpp @@ -0,0 +1,50 @@ +#ifndef TIME_UTILS_HPP +#define TIME_UTILS_HPP + +class TimeUtils { +public: + inline static uint64_t nanosecSinceEpoche(){ + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + return time.tv_sec * (uint64_t)1000000000 + time.tv_nsec; + } + inline static uint64_t nanosecSinceBoot(){ + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + return time.tv_sec * (uint64_t)1000000000 + time.tv_nsec; + } +}; + +class Announcer; +class TimeMeasurement{ +public: + TimeMeasurement(); + ~TimeMeasurement(); +private: + // not implemented, private to avoid usage: + TimeMeasurement ( const TimeMeasurement& other ); + TimeMeasurement& operator= ( const TimeMeasurement& other ); +public: + void logDuration(Announcer* logger, const char* format = NULL); + void logDuration(int count, Announcer* logger, const char* format = NULL); + /** + * Starts a measurement event. + */ + inline void start(){ + m_startReal = TimeUtils::nanosecSinceEpoche(); + m_startCpu = TimeUtils::nanosecSinceBoot(); + } + /** + * Stops a measurement event. + */ + inline void stop(){ + m_durationCpu = (TimeUtils::nanosecSinceBoot() - m_startCpu) * 1E-9; + m_durationReal = (TimeUtils::nanosecSinceEpoche() - m_startReal) * 1E-9; + } +public: + uint64_t m_startReal; + uint64_t m_startCpu; + double m_durationReal; + double m_durationCpu; +}; +#endif // TIME_UTILS_HPP diff --git a/util/trace.hpp b/util/trace.hpp new file mode 100644 index 0000000..a298ac7 --- /dev/null +++ b/util/trace.hpp @@ -0,0 +1,16 @@ +#ifndef TRACE_HPP +#define TRACE_HPP + +#ifdef TRACE_ON +#define trace(msg) printf(msg) +#define trace1(format, arg1) printf(format, arg1) +#define trace2(format, arg1, arg2) printf(format, arg1, arg2) +#define tracef(arg) printf(arg) +#else +#define trace(msg) +#define trace1(format, arg1) +#define trace2(format, arg1, arg2) +#define tracef(arg) +#endif // TRACE_ON + +#endif // TRACE_HPP diff --git a/util/unittest.cpp b/util/unittest.cpp new file mode 100644 index 0000000..dbfb355 --- /dev/null +++ b/util/unittest.cpp @@ -0,0 +1,114 @@ +#include "test.hpp" + +/** + * Constructor. + * + * @param name name of the tested module + */ +UnitTest::UnitTest(const char* name) : + m_name(name), + m_logger() +{ + m_logger.sayf(LOG_INFO, "%s:", name); +} + +/** + * Destructor. + */ +UnitTest::~UnitTest() { + if (m_logger.errors() > 0) + m_logger.sayf(LOG_ERROR, "%s has %d error(s)", m_name.str(), m_logger.errors()); + else + m_logger.sayf(LOG_INFO, "%s successfully finished", m_name.str()); +} +/** + * Tests, whether two strings are equals. + * + * @param expected the expected value + * @param current the current value + * @param line line number for the error mesage + */ +void UnitTest::equals(const char* expected, const char* current, int line){ + if (strcmp(expected, current) != 0){ + char divider = strlen(expected) > 20 ? '\n' : ' '; + error("%s-%d different:%c'%s'%c'%s'", m_name.str(), line, + divider, expected, divider, current); + } +} +/** + * Tests, whether two strings are equals. + * + * @param expected the expected value + * @param current the current value + * @param line line number for the error mesage + */ +void UnitTest::equals(const char* expected, const DynBuffer& current, int line){ + equals(expected, current.str(), line); +} +/** + * Tests, whether two numbers are equals. + * + * @param expected the expected value + * @param current the current value + * @param line line number for the error mesage + */ +void UnitTest::equals(int expected, int current, int line){ + if (expected != current){ + error("%s-%d: different:%d/%d [%x/%x]", m_name.str(), line, + expected, current, expected, current); + } +} +/** + * Tests, whether the condition is false + * + * @param condition the condition to test + * @param line line number for the error mesage + */ +void UnitTest::isFalse(bool condition, int line){ + if (condition) + error("%s-%d: is true", m_name.str(), line); +} +/** + * Tests, whether the pointer is not null + * + * @param pointer the pointer to test + * @param line line number for the error mesage + */ +void UnitTest::isNotNull(void* ptr, int line){ + if (ptr == NULL) + error("%s-%d: is null", m_name.str(), line); +} +/** + * Tests, whether the pointer is null + * + * @param pointer the pointer to test + * @param line line number for the error mesage + */ +void UnitTest::isNull(void* ptr, int line){ + if (ptr != NULL) + error("%s-%d: is not null", m_name.str(), line); +} +/** + * Tests, whether the condition is false + * + * @param condition the condition to test + * @param line line number for the error mesage + */ +void UnitTest::isTrue(bool condition, int line){ + if (! condition) + error("%s-%d: is false", m_name.str(), line); +} +/** + * Tests, whether the condition is false + * + * @param condition the condition to test + * @param line line number for the error mesage + */ +void UnitTest::error(const char* format, ...){ + char buffer[64000]; + va_list argptr; + va_start(argptr, format); + vsnprintf(buffer, sizeof buffer, format, argptr); + va_end(argptr); + m_logger.say(LOG_ERROR, buffer); +} diff --git a/util/unittest.hpp b/util/unittest.hpp new file mode 100644 index 0000000..6154e89 --- /dev/null +++ b/util/unittest.hpp @@ -0,0 +1,37 @@ +#ifndef UNITTEST_HPP +#define UNITTEST_HPP + +/** + * Unit tests like JUnit (java) + */ +class UnitTest { +public: + UnitTest(const char* name); + ~UnitTest(); +private: + // not implemented, only private use. + UnitTest ( const UnitTest& other ); + UnitTest& operator= ( const UnitTest& other ); +public: + void equals(const char* expected, const char* current, int line); + void equals(const char* expected, const DynBuffer& current, int line); + void equals(int expected, int current, int line); + void isFalse(bool condition, int line); + void isNotNull(void* ptr, int line); + void isNull(void* ptr, int line); + void isTrue(bool condition, int line); + virtual void run() = 0; +protected: + void error(const char* format, ...); +protected: + DynBuffer m_name; + Logger m_logger; +}; + +#define checkE(expected, current) equals(expected, current, __LINE__) +#define checkF(condition) isFalse(condition, __LINE__) +#define checkT(condition) isTrue(condition, __LINE__) +#define checkNN(condition) isNotNull(pointer, __LINE__) +#define checkN(condition) isNull(pointer, __LINE__) + +#endif // UNITTEST_H diff --git a/util/util.hpp b/util/util.hpp new file mode 100644 index 0000000..b3e983b --- /dev/null +++ b/util/util.hpp @@ -0,0 +1,19 @@ +#ifndef UTIL_HPP +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef unsigned char ubyte_t; +typedef long long unsigned uint64_t; +#include "dynbuffer.hpp" +#include "timeutils.hpp" +#include "logger.hpp" +#include "thread.hpp" +#include "timer.hpp" +#endif -- 2.39.5