diff --git a/env/assert.cpp b/env/assert.cpp new file mode 100644 index 0000000..a866715 --- /dev/null +++ b/env/assert.cpp @@ -0,0 +1,19 @@ +#include "assert.hpp" + +#include "../std/iostream.hpp" + +#include // for assert + + +namespace dbg +{ + void FireAssert(SourceAddress const & sa, string const & msg) + { + cerr << sa.ToString() << msg << endl; +#ifdef DEBUG + assert(false); +#else + abort(); +#endif + } +} diff --git a/env/assert.hpp b/env/assert.hpp new file mode 100644 index 0000000..1b519af --- /dev/null +++ b/env/assert.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "message_list.hpp" +#include "source_address.hpp" + + +namespace dbg +{ + void FireAssert(SourceAddress const & sa, string const & msg); +} + +#define CHECK(x, msg) do { if (x) {} else { ::dbg::FireAssert(SRC(), ::msg::MessageList msg) } } while (false) + +#ifdef DEBUG + #define ASSERT(x, msg) CHECK(x, msg) +#else + #define ASSERT(x, msg) +#endif diff --git a/env/condition.hpp b/env/condition.hpp new file mode 100644 index 0000000..8c6391c --- /dev/null +++ b/env/condition.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "mutex.hpp" + + +namespace env +{ + +class Condition : private noncopyable +{ +public: + Condition(); + ~Condition(); + + void Lock() { m_mutex.Lock(); } + void TryLock() { m_mutex.TryLock(); } + void Unlock() { m_mutex.Unlock(); } + + void Signal(); + void SignalAll(); + void Wait(); + + class Guard + { + Condition & m_cond; + public: + Guard(Condition & c) : m_cond(c) { m_cond.Lock(); } + ~Guard() { m_cond.Unlock(); } + }; + +private: + Mutex m_mutex; + pthread_cond_t m_condition; +}; + +} diff --git a/env/condition_posix.cpp b/env/condition_posix.cpp new file mode 100644 index 0000000..b18e057 --- /dev/null +++ b/env/condition_posix.cpp @@ -0,0 +1,33 @@ +#include "condition.hpp" +#include "posix.hpp" + + +namespace env +{ + +Condition::Condition() +{ + CHECK_POSIX(::pthread_cond_init(&m_condition, 0)); +} + +Condition::~Condition() +{ + CHECK_POSIX(::pthread_cond_destroy(&m_condition)); +} + +void Condition::Signal() +{ + CHECK_POSIX(::pthread_cond_signal(&m_condition)); +} + +void Condition::SignalAll() +{ + CHECK_POSIX(::pthread_cond_broadcast(&m_condition)); +} + +void Condition::Wait() +{ + CHECK_POSIX(::pthread_cond_wait(&m_condition, &m_mutex.m_mutex)); +} + +} diff --git a/env/env.pro b/env/env.pro new file mode 100644 index 0000000..fa241e5 --- /dev/null +++ b/env/env.pro @@ -0,0 +1,38 @@ +TEMPLATE = app +TARGET = env_tests +CONFIG += console warn_on +CONFIG -= app_bundle + +INCLUDEPATH += ../3rdparty/boost ../3rdparty/googletest/include + +HEADERS += \ + assert.hpp \ + condition.hpp \ + exception.hpp \ + file_handle.hpp \ + file_system.hpp \ + logging.hpp \ + message_list.hpp \ + mutex.hpp \ + posix.hpp \ + source_address.hpp \ + strings.hpp \ + writer.hpp \ + thread.hpp \ + +SOURCES += \ + assert.cpp \ + condition_posix.cpp \ + file_handle.cpp \ + file_system.cpp \ + logging.cpp \ + mutex_posix.cpp \ + posix.cpp \ + source_address.cpp \ + thread_posix.cpp \ + +# unit tests +SOURCES += \ + ../3rdparty/googletest/src/gtest-all.cc \ + ../3rdparty/googletest/src/gtest_main.cc \ + tests/smoke.cpp \ diff --git a/env/exception.hpp b/env/exception.hpp new file mode 100644 index 0000000..d138c93 --- /dev/null +++ b/env/exception.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "message_list.hpp" + +namespace ex +{ + +class Exception +{ + string m_msg; +public: + Exception(string const & msg) : m_msg(msg) {} + string const & Msg() const { return m_msg; } +}; + +} + +#define THROWEX(klass, message) throw klass(::msg::MessageList message) diff --git a/env/file_handle.cpp b/env/file_handle.cpp new file mode 100644 index 0000000..8adf9ee --- /dev/null +++ b/env/file_handle.cpp @@ -0,0 +1,114 @@ +#include "file_handle.hpp" +#include "logging.hpp" +#include "assert.hpp" +#include "posix.hpp" + + +namespace file +{ + +FileHandle::FileHandle(string const & name, Mode mode) + : m_name(name), m_mode(mode) +{ + char const * const modes [] = {"rb", "wb", "r+b", "ab"}; + m_file = fopen(name.c_str(), modes[mode]); + if (m_file) + return; + + if (mode == WRITE_EXISTING) + { + // if file doesn't exist "r+b" fails + m_file = fopen(name.c_str(), "wb"); + if (m_file) + return; + } + + THROWEX(FileException, (E2S())); +} + +FileHandle::~FileHandle() +{ + if (m_file && fclose(m_file)) + LOG(WARNING, ("Error closing file", E2S())); +} + +string FileHandle::E2S() const +{ + char const * s; + switch (m_mode) + { + case READ: s = "Read"; break; + case WRITE_TRUNCATE: s = "Write truncate"; break; + case WRITE_EXISTING: s = "Write existing"; break; + case APPEND: s = "Append"; break; + } + + return m_name + "; " + s + "; " + env::GetCError(); +} + +static int64_t const INVALID_POS = -1; + +uint64_t FileHandle::Size() const +{ + int64_t const pos = ftell64(m_file); + if (pos == INVALID_POS) + THROWEX(FileException, (E2S(), pos)); + + if (fseek64(m_file, 0, SEEK_END)) + THROWEX(FileException, (E2S())); + + int64_t const size = ftell64(m_file); + if (size == INVALID_POS) + THROWEX(FileException, (E2S(), size)); + + if (fseek64(m_file, pos, SEEK_SET)) + THROWEX(FileException, (E2S(), pos)); + + ASSERT(size >= 0, ()); + return static_cast(size); +} + +void FileHandle::Read(void * p, size_t size) +{ + size_t const readed = fread(p, 1, size, m_file); + if (readed != size || ferror(m_file)) + THROWEX(FileException, (E2S(), readed, size)); +} + +uint64_t FileHandle::Pos() const +{ + int64_t const pos = ftell64(m_file); + if (pos == INVALID_POS) + THROWEX(FileException, (E2S())); + + ASSERT(pos >= 0, ()); + return static_cast(pos); +} + +void FileHandle::Seek(uint64_t pos) +{ + if (fseek64(m_file, pos, SEEK_SET)) + THROWEX(FileException, (E2S(), pos)); +} + +void FileHandle::Write(void const * p, size_t size) +{ + size_t const written = fwrite(p, 1, size, m_file); + if (written != size || ferror(m_file)) + THROWEX(FileException, (E2S(), written, size)); +} + +void FileHandle::Flush() +{ + if (fflush(m_file)) + THROWEX(FileException, (E2S())); +} + +void FileHandle::Truncate(uint64_t size) +{ + int const res = ftruncate(fileno(m_file), size); + if (res) + THROWEX(FileException, (E2S(), size)); +} + +} diff --git a/env/file_handle.hpp b/env/file_handle.hpp new file mode 100644 index 0000000..c4a46a1 --- /dev/null +++ b/env/file_handle.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "exception.hpp" + +#include "../std/cstdio.hpp" +#include "../std/stdint.hpp" +#include "../std/noncopyable.hpp" + + +namespace file +{ + +struct FileException : public ex::Exception +{ + FileException(string const & msg) : ex::Exception(msg) {} +}; + +inline string ToString(FileException const & ex) +{ + return ex.Msg(); +} + +class FileHandle : private noncopyable +{ +public: + /// Do not change order (@see cpp FileHandle::FileHandle) + enum Mode { READ = 0, WRITE_TRUNCATE, WRITE_EXISTING, APPEND }; + + FileHandle(string const & fileName, Mode mode); + ~FileHandle(); + + uint64_t Size() const; + uint64_t Pos() const; + + void Seek(uint64_t pos); + + void Read(void * p, size_t size); + void Write(void const * p, size_t size); + + void Flush(); + void Truncate(uint64_t sz); + + string GetName() const { return m_name; } + +private: + FILE * m_file; + string m_name; + Mode m_mode; + + /// Convert last error to string. + string E2S() const; +}; + +} diff --git a/env/file_system.cpp b/env/file_system.cpp new file mode 100644 index 0000000..0446e93 --- /dev/null +++ b/env/file_system.cpp @@ -0,0 +1,12 @@ +#include "file_system.hpp" + + +namespace fs +{ + +bool DeleteFile(string const & path) +{ + return (0 == remove(path.c_str())); +} + +} diff --git a/env/file_system.hpp b/env/file_system.hpp new file mode 100644 index 0000000..520b9e1 --- /dev/null +++ b/env/file_system.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "../std/string.hpp" + + +namespace fs +{ + +bool DeleteFile(string const & path); + +} diff --git a/env/logging.cpp b/env/logging.cpp new file mode 100644 index 0000000..8a9b421 --- /dev/null +++ b/env/logging.cpp @@ -0,0 +1,20 @@ +#include "logging.hpp" + +#include "../std/iostream.hpp" + + +namespace dbg +{ + +string ToString(LogPriority pr) +{ + static char const * arr[] = { "DEBUG", "INFO", "WARNING", "ERROR" }; + return arr[pr]; +} + +void Print(LogPriority pr, SourceAddress const & sa, string const & msg) +{ + cout << ToString(pr) << " " << sa.ToString() << msg << endl; +} + +} diff --git a/env/logging.hpp b/env/logging.hpp new file mode 100644 index 0000000..c37d517 --- /dev/null +++ b/env/logging.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "message_list.hpp" +#include "source_address.hpp" + + +namespace dbg +{ + +enum LogPriority { LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR }; +string ToString(LogPriority pr); + +void Print(LogPriority pr, SourceAddress const & sa, string const & msg); + +} + +#define LOG(pr, message) ::dbg::Print(::dbg::LOG_##pr, SRC(), ::msg::MessageList message) diff --git a/env/message_list.hpp b/env/message_list.hpp new file mode 100644 index 0000000..126d594 --- /dev/null +++ b/env/message_list.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "strings.hpp" + + +namespace msg +{ + +inline string ToString(char const * s) { return s; } +inline string ToString(string const & s) { return s; } + +/// Override ToString function for your custom class in it's namespace. +/// Your function will be called according to the ADL lookup. +/// This is the default "last chance" implementation. +template +inline string ToString(T const & t) +{ + return str::ToString(t); +} + +inline string MessageList() +{ + return string(); +} + +template +inline string MessageList(T1 const & t1) +{ + return ToString(t1); +} + +template +inline string MessageList(T1 const & t1, T2 const & t2) +{ + return ToString(t1) + " " + ToString(t2); +} + +template +inline string MessageList(T1 const & t1, T2 const & t2, T3 const & t3) +{ + return MessageList(t1, t2) + " " + ToString(t3); +} + +} diff --git a/env/mutex.hpp b/env/mutex.hpp new file mode 100644 index 0000000..b43c842 --- /dev/null +++ b/env/mutex.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "../std/noncopyable.hpp" + + +namespace env +{ + +class Condition; + +class Mutex : private noncopyable +{ +public: + Mutex(); + ~Mutex(); + + void Lock(); + bool TryLock(); + void Unlock(); + + class Guard + { + Mutex & m_mutex; + public: + Guard(Mutex & m) : m_mutex(m) { m_mutex.Lock(); } + ~Guard() { m_mutex.Unlock(); } + }; + +private: + pthread_mutex_t m_mutex; + + friend class Condition; +}; + +} diff --git a/env/mutex_posix.cpp b/env/mutex_posix.cpp new file mode 100644 index 0000000..2282212 --- /dev/null +++ b/env/mutex_posix.cpp @@ -0,0 +1,33 @@ +#include "mutex.hpp" +#include "posix.hpp" + + +namespace env +{ + +Mutex::Mutex() +{ + CHECK_POSIX(::pthread_mutex_init(&m_mutex, 0)); +} + +Mutex::~Mutex() +{ + CHECK_POSIX(::pthread_mutex_destroy(&m_mutex)); +} + +void Mutex::Lock() +{ + CHECK_POSIX(::pthread_mutex_lock(&m_mutex)); +} + +bool Mutex::TryLock() +{ + return (0 == ::pthread_mutex_trylock(&m_mutex)); +} + +void Mutex::Unlock() +{ + CHECK_POSIX(::pthread_mutex_unlock(&m_mutex)); +} + +} diff --git a/env/posix.cpp b/env/posix.cpp new file mode 100644 index 0000000..04f9112 --- /dev/null +++ b/env/posix.cpp @@ -0,0 +1,21 @@ +#include "posix.hpp" +#include "logging.hpp" + +#include "../std/cstring.hpp" + +#include // for errno + + +namespace env +{ + void CheckPosixResult(int res) + { + if (res != 0) + LOG(WARNING, (strerror(res))); + } + + char const * GetCError() + { + return strerror(errno); + } +} diff --git a/env/posix.hpp b/env/posix.hpp new file mode 100644 index 0000000..052c6fa --- /dev/null +++ b/env/posix.hpp @@ -0,0 +1,10 @@ +#pragma once + + +namespace env +{ + void CheckPosixResult(int res); + char const * GetCError(); +} + +#define CHECK_POSIX(x) env::CheckPosixResult(x) diff --git a/env/source_address.cpp b/env/source_address.cpp new file mode 100644 index 0000000..26871eb --- /dev/null +++ b/env/source_address.cpp @@ -0,0 +1,16 @@ +#include "source_address.hpp" + +#include "../std/sstream.hpp" + + +namespace dbg +{ + +string SourceAddress::ToString() const +{ + ostringstream ss; + ss << m_file << ", " << m_func << ", " << m_line << ": "; + return ss.str(); +} + +} diff --git a/env/source_address.hpp b/env/source_address.hpp new file mode 100644 index 0000000..8c5c2c9 --- /dev/null +++ b/env/source_address.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "../std/string.hpp" + + +namespace dbg +{ + +class SourceAddress +{ + char const * m_file; + char const * m_func; + int m_line; + +public: + SourceAddress(char const * file, char const * func, int line) + : m_file(file), m_func(func), m_line(line) + { + } + + string ToString() const; +}; + +inline string ToString(SourceAddress const & sa) +{ + return sa.ToString(); +} + +} + +#define SRC() ::dbg::SourceAddress(__FILE__, __FUNCTION__, __LINE__) diff --git a/env/strings.hpp b/env/strings.hpp new file mode 100644 index 0000000..4151330 --- /dev/null +++ b/env/strings.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "../std/string.hpp" +#include "../std/sstream.hpp" + + +namespace str +{ + +template string ToString(T const & t) +{ + ostringstream ss; + ss << t; + return ss.str(); +} + +} diff --git a/env/tests/smoke.cpp b/env/tests/smoke.cpp new file mode 100644 index 0000000..6360188 --- /dev/null +++ b/env/tests/smoke.cpp @@ -0,0 +1,70 @@ +#include + +#include "../file_handle.hpp" +#include "../file_system.hpp" +#include "../logging.hpp" + +#include "../../std/algorithm.hpp" +#include "../../std/vector.hpp" + + +/// @note Do not edit formatting here (SRC() test): +//@{ +namespace +{ + string GetSourceAddress() + { + return SRC().ToString(); + } +} + +TEST(EnvSmoke, SourceAddress) +{ + string s = GetSourceAddress(); + size_t const beg = s.find_last_of('/'); + EXPECT_NE(beg, string::npos); + s = s.substr(beg + 1); + + size_t const end = s.find_last_of(','); + EXPECT_NE(end, string::npos); + string const test = s.substr(0, end); + EXPECT_EQ(test, "smoke.cpp, GetSourceAddress"); + + ostringstream ss; + ss << test << ", " << (__LINE__ - 17) << ": "; // magic constant + EXPECT_EQ(s, ss.str()); +} +//@} + + +TEST(EnvSmoke, FileHandle) +{ + typedef file::FileHandle HandleT; + + char const * name = "file.bin"; + char const * buffer = "Some fellows believe in a dream until merry one!"; + + try + { + { + HandleT file(name, HandleT::WRITE_TRUNCATE); + file.Write(buffer, strlen(buffer)); + } + + { + HandleT file(name, HandleT::READ); + + size_t const n = strlen(buffer); + vector v(n); + file.Read(v.data(), n); + + EXPECT_TRUE(equal(v.begin(), v.end(), buffer)); + } + } + catch (file::FileException const & ex) + { + LOG(ERROR, (ex)); + } + + EXPECT_TRUE(fs::DeleteFile(name)); +} diff --git a/env/thread.hpp b/env/thread.hpp new file mode 100644 index 0000000..ef1aaa7 --- /dev/null +++ b/env/thread.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "../std/noncopyable.hpp" + + +namespace env +{ + +class Thread : private noncopyable +{ +public: + class Runnable : private noncopyable + { + bool m_cancelled; + + public: + Runnable() : m_cancelled(false) {} + bool IsCancelled() const { return m_cancelled; } + + virtual void Run() = 0; + virtual void Cancel() { m_cancelled = true; } + }; + + Thread() : m_runnable(0) {} + + void Create(Runnable * runnable); + void Join(); + void Cancel(); + +private: + Runnable * m_runnable; + pthread_t m_handle; +}; + +} diff --git a/env/thread_posix.cpp b/env/thread_posix.cpp new file mode 100644 index 0000000..56c256f --- /dev/null +++ b/env/thread_posix.cpp @@ -0,0 +1,37 @@ +#include "thread.hpp" +#include "posix.hpp" + + +namespace env +{ + +void * PThreadProc(void * p) +{ + Thread::Runnable * runnable = reinterpret_cast(p); + runnable->Run(); + + ::pthread_exit(0); + return 0; +} + +void Thread::Create(Runnable * runnable) +{ + m_runnable = runnable; + CHECK_POSIX(::pthread_create(&m_handle, 0, &PThreadProc, reinterpret_cast(runnable))); +} + +void Thread::Join() +{ + CHECK_POSIX(::pthread_join(m_handle, 0)); +} + +void Thread::Cancel() +{ + if (m_runnable) + { + m_runnable->Cancel(); + Join(); + } +} + +} diff --git a/env/writer.hpp b/env/writer.hpp new file mode 100644 index 0000000..adf708e --- /dev/null +++ b/env/writer.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "file_handle.hpp" + + +namespace wr +{ + +class Writer +{ +public: + virtual ~Writer() {} + virtual void Write(void const * p, size_t size) = 0; +}; + +class FileWriter : public Writer +{ + typedef file::FileHandle HandleT; + HandleT m_file; + +public: + FileWriter(string const & name) + : m_file(name, HandleT::WRITE_TRUNCATE) + { + } + + virtual void Write(void const * p, size_t size) + { + m_file.Write(p, size); + } + + uint64_t Size() + { + m_file.Flush(); + return m_file.Size(); + } +}; + +} diff --git a/std/cstring.hpp b/std/cstring.hpp new file mode 100644 index 0000000..810f4df --- /dev/null +++ b/std/cstring.hpp @@ -0,0 +1,5 @@ +#pragma once + +// Used for memcpy, memcmp, strcpy, ... + +#include diff --git a/tests.pro b/tests.pro new file mode 100644 index 0000000..b83f653 --- /dev/null +++ b/tests.pro @@ -0,0 +1,6 @@ +cache() + +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS = env \