summaryrefslogtreecommitdiff
path: root/ept/utils
diff options
context:
space:
mode:
Diffstat (limited to 'ept/utils')
-rw-r--r--ept/utils/string.cc437
-rw-r--r--ept/utils/string.h301
-rw-r--r--ept/utils/sys.cc786
-rw-r--r--ept/utils/sys.h468
-rw-r--r--ept/utils/tests-main.cc139
-rw-r--r--ept/utils/tests.cc578
-rw-r--r--ept/utils/tests.h804
7 files changed, 3513 insertions, 0 deletions
diff --git a/ept/utils/string.cc b/ept/utils/string.cc
new file mode 100644
index 0000000..95d24fd
--- /dev/null
+++ b/ept/utils/string.cc
@@ -0,0 +1,437 @@
+#include "string.h"
+#include <vector>
+
+using namespace std;
+
+namespace ept {
+namespace str {
+
+std::string basename(const std::string& pathname)
+{
+ size_t pos = pathname.rfind("/");
+ if (pos == std::string::npos)
+ return pathname;
+ else
+ return pathname.substr(pos+1);
+}
+
+std::string dirname(const std::string& pathname)
+{
+ if (pathname.empty()) return ".";
+
+ // Skip trailing separators
+ size_t end = pathname.size();
+ while (end > 0 && pathname[end - 1] == '/')
+ --end;
+
+ // If the result is empty again, then the string was only / characters
+ if (!end) return "/";
+
+ // Find the previous separator
+ end = pathname.rfind("/", end - 1);
+
+ if (end == std::string::npos)
+ // No previous separator found, everything should be chopped
+ return std::string(".");
+ else
+ {
+ while (end > 0 && pathname[end - 1] == '/')
+ --end;
+ if (!end) return "/";
+ return pathname.substr(0, end);
+ }
+}
+
+void appendpath(std::string& dest, const char* path2)
+{
+ if (!*path2)
+ return;
+
+ if (dest.empty())
+ {
+ dest = path2;
+ return;
+ }
+
+ if (dest[dest.size() - 1] == '/')
+ if (path2[0] == '/')
+ dest += (path2 + 1);
+ else
+ dest += path2;
+ else
+ if (path2[0] == '/')
+ dest += path2;
+ else
+ {
+ dest += '/';
+ dest += path2;
+ }
+}
+
+void appendpath(std::string& dest, const std::string& path2)
+{
+ if (path2.empty())
+ return;
+
+ if (dest.empty())
+ {
+ dest = path2;
+ return;
+ }
+
+ if (dest[dest.size() - 1] == '/')
+ if (path2[0] == '/')
+ dest += path2.substr(1);
+ else
+ dest += path2;
+ else
+ if (path2[0] == '/')
+ dest += path2;
+ else
+ {
+ dest += '/';
+ dest += path2;
+ }
+}
+
+std::string joinpath(const std::string& path1, const std::string& path2)
+{
+ string res = path1;
+ appendpath(res, path2);
+ return res;
+}
+
+std::string normpath(const std::string& pathname)
+{
+ vector<string> st;
+ if (pathname[0] == '/')
+ st.push_back("/");
+
+ Split split(pathname, "/");
+ for (const auto& i: split)
+ {
+ if (i == "." || i.empty()) continue;
+ if (i == "..")
+ if (st.back() == "..")
+ st.emplace_back(i);
+ else if (st.back() == "/")
+ continue;
+ else
+ st.pop_back();
+ else
+ st.emplace_back(i);
+ }
+
+ if (st.empty())
+ return ".";
+
+ string res;
+ for (const auto& i: st)
+ appendpath(res, i);
+ return res;
+}
+
+Split::const_iterator::const_iterator(const Split& split)
+ : split(&split)
+{
+ // Ignore leading separators if skip_end is true
+ if (split.skip_empty) skip_separators();
+ ++*this;
+}
+
+Split::const_iterator::~const_iterator()
+{
+}
+
+std::string Split::const_iterator::remainder() const
+{
+ if (end == std::string::npos)
+ return std::string();
+ else
+ return split->str.substr(end);
+};
+
+void Split::const_iterator::skip_separators()
+{
+ const std::string& str = split->str;
+ const std::string& sep = split->sep;
+
+ while (end + sep.size() <= str.size())
+ {
+ unsigned i = 0;
+ for ( ; i < sep.size(); ++i)
+ if (str[end + i] != sep[i])
+ break;
+ if (i < sep.size())
+ break;
+ else
+ end += sep.size();
+ }
+}
+
+Split::const_iterator& Split::const_iterator::operator++()
+{
+ if (!split) return *this;
+
+ const std::string& str = split->str;
+ const std::string& sep = split->sep;
+ bool skip_empty = split->skip_empty;
+
+ /// Convert into an end iterator
+ if (end == std::string::npos)
+ {
+ split = nullptr;
+ return *this;
+ }
+
+ /// The string ended with an iterator, and we do not skip empty tokens:
+ /// return it
+ if (end == str.size())
+ {
+ cur = string();
+ end = std::string::npos;
+ return *this;
+ }
+
+ /// Position of the first character past the token that starts at 'end'
+ size_t tok_end;
+ if (sep.empty())
+ /// If separator is empty, advance one character at a time
+ tok_end = end + 1;
+ else
+ {
+ /// The token ends at the next separator
+ tok_end = str.find(sep, end);
+ }
+
+ /// No more separators found, return from end to the end of the string
+ if (tok_end == std::string::npos)
+ {
+ cur = str.substr(end);
+ end = std::string::npos;
+ return *this;
+ }
+
+ /// We have the boundaries of the current token
+ cur = str.substr(end, tok_end - end);
+
+ /// Skip the separator
+ end = tok_end + sep.size();
+
+ /// Skip all the following separators if skip_empty is true
+ if (skip_empty)
+ {
+ skip_separators();
+ if (end == str.size())
+ {
+ end = std::string::npos;
+ return *this;
+ }
+ }
+
+ return *this;
+}
+
+const std::string& Split::const_iterator::operator*() const { return cur; }
+const std::string* Split::const_iterator::operator->() const { return &cur; }
+
+bool Split::const_iterator::operator==(const const_iterator& ti) const
+{
+ if (!split && !ti.split) return true;
+ if (split != ti.split) return false;
+ return end == ti.end;
+}
+
+bool Split::const_iterator::operator!=(const const_iterator& ti) const
+{
+ if (!split && !ti.split) return false;
+ if (split != ti.split) return true;
+ return end != ti.end;
+}
+
+
+std::string encode_cstring(const std::string& str)
+{
+ string res;
+ for (string::const_iterator i = str.begin(); i != str.end(); ++i)
+ if (*i == '\n')
+ res += "\\n";
+ else if (*i == '\t')
+ res += "\\t";
+ else if (*i == 0 || iscntrl(*i))
+ {
+ char buf[5];
+ snprintf(buf, 5, "\\x%02x", (unsigned int)*i);
+ res += buf;
+ }
+ else if (*i == '"' || *i == '\\')
+ {
+ res += "\\";
+ res += *i;
+ }
+ else
+ res += *i;
+ return res;
+}
+
+std::string decode_cstring(const std::string& str, size_t& lenParsed)
+{
+ string res;
+ string::const_iterator i = str.begin();
+ for ( ; i != str.end() && *i != '"'; ++i)
+ if (*i == '\\' && (i+1) != str.end())
+ {
+ switch (*(i+1))
+ {
+ case 'n': res += '\n'; break;
+ case 't': res += '\t'; break;
+ case 'x': {
+ size_t j;
+ char buf[5] = "0x\0\0";
+ // Read up to 2 extra hex digits
+ for (j = 0; j < 2 && i+2+j != str.end() && isxdigit(*(i+2+j)); ++j)
+ buf[2+j] = *(i+2+j);
+ i += j;
+ res += (char)atoi(buf);
+ break;
+ }
+ default:
+ res += *(i+1);
+ break;
+ }
+ ++i;
+ } else
+ res += *i;
+ if (i != str.end() && *i == '"')
+ ++i;
+ lenParsed = i - str.begin();
+ return res;
+}
+
+std::string encode_url(const std::string& str)
+{
+ string res;
+ for (string::const_iterator i = str.begin(); i != str.end(); ++i)
+ {
+ if ( (*i >= '0' && *i <= '9') || (*i >= 'A' && *i <= 'Z')
+ || (*i >= 'a' && *i <= 'z') || *i == '-' || *i == '_'
+ || *i == '!' || *i == '*' || *i == '\'' || *i == '(' || *i == ')')
+ res += *i;
+ else {
+ char buf[4];
+ snprintf(buf, 4, "%%%02x", static_cast<unsigned>(static_cast<unsigned char>(*i)));
+ res += buf;
+ }
+ }
+ return res;
+}
+
+std::string decode_url(const std::string& str)
+{
+ string res;
+ for (size_t i = 0; i < str.size(); ++i)
+ {
+ if (str[i] == '%')
+ {
+ // If there's a partial %something at the end, ignore it
+ if (i >= str.size() - 2)
+ return res;
+ res += static_cast<char>(strtoul(str.substr(i+1, 2).c_str(), 0, 16));
+ i += 2;
+ }
+ else
+ res += str[i];
+ }
+ return res;
+}
+
+static const char* base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+template<typename T>
+static const char invbase64(const T& idx)
+{
+ static const char data[] = {62,0,0,0,63,52,53,54,55,56,57,58,59,60,61,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,0,0,0,0,0,0,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
+ if (idx < 43) return 0;
+ if (static_cast<unsigned>(idx) > 43 + (sizeof(data)/sizeof(data[0]))) return 0;
+ return data[idx - 43];
+}
+
+std::string encode_base64(const std::string& str)
+{
+ std::string res;
+
+ for (size_t i = 0; i < str.size(); i += 3)
+ {
+ // Pack every triplet into 24 bits
+ unsigned int enc;
+ if (i + 3 < str.size())
+ enc = ((unsigned char)str[i] << 16) | ((unsigned char)str[i + 1] << 8) | (unsigned char)str[i + 2];
+ else
+ {
+ enc = ((unsigned char)str[i] << 16);
+ if (i + 1 < str.size())
+ enc |= (unsigned char)str[i + 1] << 8;
+ if (i + 2 < str.size())
+ enc |= (unsigned char)str[i + 2];
+ }
+
+ // Divide in 4 6-bit values and use them as indexes in the base64 char
+ // array
+ for (int j = 18; j >= 0; j -= 6)
+ res += base64[(enc >> j) & 63];
+ }
+
+ // Replace padding characters with '='
+ if (str.size() % 3)
+ for (size_t i = 0; i < 3 - (str.size() % 3); ++i)
+ res[res.size() - i - 1] = '=';
+
+ return res;
+}
+
+std::string decode_base64(const std::string& str)
+{
+ std::string res;
+
+ for (size_t i = 0; i < str.size(); i += 4)
+ {
+ // Pack every quadruplet into 24 bits
+ unsigned int enc;
+ if (i+4 < str.size())
+ {
+ enc = (invbase64(str[i]) << 18)
+ + (invbase64(str[i+1]) << 12)
+ + (invbase64(str[i+2]) << 6)
+ + (invbase64(str[i+3]));
+ } else {
+ enc = (invbase64(str[i]) << 18);
+ if (i+1 < str.size())
+ enc += (invbase64(str[i+1]) << 12);
+ if (i+2 < str.size())
+ enc += (invbase64(str[i+2]) << 6);
+ if (i+3 < str.size())
+ enc += (invbase64(str[i+3]));
+ }
+
+ // Divide in 3 8-bit values and append them to the result
+ res += enc >> 16 & 0xff;
+ res += enc >> 8 & 0xff;
+ res += enc & 0xff;
+ }
+
+ // Remove trailing padding
+ if (str.size() > 0)
+ for (size_t i = str.size() - 1; str[i] == '='; --i)
+ {
+ if (res.size() > 0)
+ res.resize(res.size() - 1);
+ if (i == 0 || res.size() == 0 )
+ break;
+ }
+
+ return res;
+}
+
+
+}
+}
diff --git a/ept/utils/string.h b/ept/utils/string.h
new file mode 100644
index 0000000..5988365
--- /dev/null
+++ b/ept/utils/string.h
@@ -0,0 +1,301 @@
+#ifndef EPT_STRING_H
+#define EPT_STRING_H
+
+/**
+ * @author Enrico Zini <enrico@enricozini.org>
+ * @brief String functions
+ *
+ * Copyright (C) 2007--2015 Enrico Zini <enrico@debian.org>
+ */
+
+#include <string>
+#include <functional>
+#include <sstream>
+#include <cctype>
+
+namespace ept {
+namespace str {
+
+/// Check if a string starts with the given substring
+inline bool startswith(const std::string& str, const std::string& part)
+{
+ if (str.size() < part.size())
+ return false;
+ return str.substr(0, part.size()) == part;
+}
+
+/// Check if a string ends with the given substring
+inline bool endswith(const std::string& str, const std::string& part)
+{
+ if (str.size() < part.size())
+ return false;
+ return str.substr(str.size() - part.size()) == part;
+}
+
+/**
+ * Stringify and join a sequence of objects
+ */
+template<typename ITER>
+std::string join(const std::string& sep, const ITER& begin, const ITER& end)
+{
+ std::stringstream res;
+ bool first = true;
+ for (ITER i = begin; i != end; ++i)
+ {
+ if (first)
+ first = false;
+ else
+ res << sep;
+ res << *i;
+ }
+ return res.str();
+}
+
+/**
+ * Stringify and join an iterable container
+ */
+template<typename ITEMS>
+std::string join(const std::string& sep, const ITEMS& items)
+{
+ std::stringstream res;
+ bool first = true;
+ for (const auto& i: items)
+ {
+ if (first)
+ first = false;
+ else
+ res << sep;
+ res << i;
+ }
+ return res.str();
+}
+
+/**
+ * Return the substring of 'str' without all leading characters for which
+ * 'classifier' returns true.
+ */
+template<typename FUN>
+inline std::string lstrip(const std::string& str, const FUN& classifier)
+{
+ if (str.empty())
+ return str;
+
+ size_t beg = 0;
+ while (beg < str.size() && classifier(str[beg]))
+ ++beg;
+
+ return str.substr(beg, str.size() - beg + 1);
+}
+
+/**
+ * Return the substring of 'str' without all leading spaces.
+ */
+inline std::string lstrip(const std::string& str)
+{
+ return lstrip(str, ::isspace);
+}
+
+/**
+ * Return the substring of 'str' without all trailing characters for which
+ * 'classifier' returns true.
+ */
+template<typename FUN>
+inline std::string rstrip(const std::string& str, const FUN& classifier)
+{
+ if (str.empty())
+ return str;
+
+ size_t end = str.size();
+ while (end > 0 && classifier(str[end - 1]))
+ --end;
+
+ if (end == 0)
+ return std::string();
+ else
+ return str.substr(0, end);
+}
+
+/**
+ * Return the substring of 'str' without all trailing spaces.
+ */
+inline std::string rstrip(const std::string& str)
+{
+ return rstrip(str, ::isspace);
+}
+
+/**
+ * Return the substring of 'str' without all leading and trailing characters
+ * for which 'classifier' returns true.
+ */
+template<typename FUN>
+inline std::string strip(const std::string& str, const FUN& classifier)
+{
+ if (str.empty())
+ return str;
+
+ size_t beg = 0;
+ size_t end = str.size() - 1;
+ while (beg < end && classifier(str[beg]))
+ ++beg;
+ while (end >= beg && classifier(str[end]))
+ --end;
+
+ return str.substr(beg, end-beg+1);
+}
+
+/**
+ * Return the substring of 'str' without all leading and trailing spaces.
+ */
+inline std::string strip(const std::string& str)
+{
+ return strip(str, ::isspace);
+}
+
+/// Return an uppercased copy of str
+inline std::string upper(const std::string& str)
+{
+ std::string res;
+ res.reserve(str.size());
+ for (std::string::const_iterator i = str.begin(); i != str.end(); ++i)
+ res += ::toupper(*i);
+ return res;
+}
+
+/// Return a lowercased copy of str
+inline std::string lower(const std::string& str)
+{
+ std::string res;
+ res.reserve(str.size());
+ for (std::string::const_iterator i = str.begin(); i != str.end(); ++i)
+ res += ::tolower(*i);
+ return res;
+}
+
+/// Given a pathname, return the file name without its path
+std::string basename(const std::string& pathname);
+
+/// Given a pathname, return the directory name without the file name
+std::string dirname(const std::string& pathname);
+
+/// Append path2 to path1, adding slashes when appropriate
+void appendpath(std::string& dest, const char* path2);
+
+/// Append path2 to path1, adding slashes when appropriate
+void appendpath(std::string& dest, const std::string& path2);
+
+/// Append an arbitrary number of path components to \a dest
+template<typename S1, typename S2, typename... Args>
+void appendpath(std::string& dest, S1 first, S2 second, Args... next)
+{
+ appendpath(dest, first);
+ appendpath(dest, second, next...);
+}
+
+/// Join two or more paths, adding slashes when appropriate
+template<typename... Args>
+std::string joinpath(Args... components)
+{
+ std::string res;
+ appendpath(res, components...);
+ return res;
+}
+
+/**
+ * Normalise a pathname.
+ *
+ * For example, A//B, A/./B and A/foo/../B all become A/B.
+ */
+std::string normpath(const std::string& pathname);
+
+/**
+ * Split a string where a given substring is found
+ *
+ * This does a similar work to the split functions of perl, python and ruby.
+ *
+ * Example code:
+ * \code
+ * str::Split splitter(my_string, "/");
+ * vector<string> split;
+ * std::copy(splitter.begin(), splitter.end(), back_inserter(split));
+ * \endcode
+ */
+struct Split
+{
+ /// String to split
+ std::string str;
+ /// Separator
+ std::string sep;
+ /**
+ * If true, skip empty tokens, effectively grouping consecutive separators
+ * as if they were a single one
+ */
+ bool skip_empty;
+
+ Split(const std::string& str, const std::string& sep, bool skip_empty=false)
+ : str(str), sep(sep), skip_empty(skip_empty) {}
+
+ class const_iterator : public std::iterator<std::input_iterator_tag, std::string>
+ {
+ protected:
+ const Split* split = nullptr;
+ /// Current token
+ std::string cur;
+ /// Position of the first character of the next token
+ size_t end = 0;
+
+ /// Move end past all the consecutive separators that start at its position
+ void skip_separators();
+
+ public:
+ /// Begin iterator
+ const_iterator(const Split& split);
+ /// End iterator
+ const_iterator() {}
+ ~const_iterator();
+
+ const_iterator& operator++();
+ const std::string& operator*() const;
+ const std::string* operator->() const;
+
+ std::string remainder() const;
+
+ bool operator==(const const_iterator& ti) const;
+ bool operator!=(const const_iterator& ti) const;
+ };
+
+ /// Return the begin iterator to split a string on instances of sep
+ const_iterator begin() { return const_iterator(*this); }
+
+ /// Return the end iterator to string split
+ const_iterator end() { return const_iterator(); }
+};
+
+/**
+ * Escape the string so it can safely used as a C string inside double quotes
+ */
+std::string encode_cstring(const std::string& str);
+
+/**
+ * Unescape a C string, stopping at the first double quotes or at the end of
+ * the string.
+ *
+ * lenParsed is set to the number of characters that were pased (which can be
+ * greather than the size of the resulting string in case escapes were found)
+ */
+std::string decode_cstring(const std::string& str, size_t& lenParsed);
+
+/// Urlencode a string
+std::string encode_url(const std::string& str);
+
+/// Decode an urlencoded string
+std::string decode_url(const std::string& str);
+
+/// Encode a string in Base64
+std::string encode_base64(const std::string& str);
+
+/// Decode a string encoded in Base64
+std::string decode_base64(const std::string& str);
+
+}
+}
+#endif
diff --git a/ept/utils/sys.cc b/ept/utils/sys.cc
new file mode 100644
index 0000000..8f6f2ff
--- /dev/null
+++ b/ept/utils/sys.cc
@@ -0,0 +1,786 @@
+#include "sys.h"
+#include "string.h"
+#include <cstddef>
+#include <cstring>
+#include <exception>
+#include <sstream>
+#include <system_error>
+#include <cerrno>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <alloca.h>
+
+namespace {
+
+inline const char* to_cstring(const std::string& s)
+{
+ return s.c_str();
+}
+
+inline const char* to_cstring(const char* s)
+{
+ return s;
+}
+
+}
+
+namespace ept {
+namespace sys {
+
+std::unique_ptr<struct stat> stat(const std::string& pathname)
+{
+ std::unique_ptr<struct stat> res(new struct stat);
+ if (::stat(pathname.c_str(), res.get()) == -1)
+ {
+ if (errno == ENOENT)
+ return std::unique_ptr<struct stat>();
+ else
+ throw std::system_error(errno, std::system_category(), "cannot stat " + pathname);
+ }
+ return res;
+}
+
+void stat(const std::string& pathname, struct stat& st)
+{
+ if (::stat(pathname.c_str(), &st) == -1)
+ throw std::system_error(errno, std::system_category(), "cannot stat " + pathname);
+}
+
+#define common_stat_body(testfunc) \
+ struct stat st; \
+ if (::stat(pathname.c_str(), &st) == -1) { \
+ if (errno == ENOENT) \
+ return false; \
+ else \
+ throw std::system_error(errno, std::system_category(), "cannot stat " + pathname); \
+ } \
+ return testfunc(st.st_mode)
+
+bool isdir(const std::string& pathname)
+{
+ common_stat_body(S_ISDIR);
+}
+
+bool isblk(const std::string& pathname)
+{
+ common_stat_body(S_ISBLK);
+}
+
+bool ischr(const std::string& pathname)
+{
+ common_stat_body(S_ISCHR);
+}
+
+bool isfifo(const std::string& pathname)
+{
+ common_stat_body(S_ISFIFO);
+}
+
+bool islnk(const std::string& pathname)
+{
+ common_stat_body(S_ISLNK);
+}
+
+bool isreg(const std::string& pathname)
+{
+ common_stat_body(S_ISREG);
+}
+
+bool issock(const std::string& pathname)
+{
+ common_stat_body(S_ISSOCK);
+}
+
+#undef common_stat_body
+
+time_t timestamp(const std::string& file)
+{
+ struct stat st;
+ stat(file, st);
+ return st.st_mtime;
+}
+
+time_t timestamp(const std::string& file, time_t def)
+{
+ auto st = sys::stat(file);
+ return st.get() ? st->st_mtime : def;
+}
+
+size_t size(const std::string& file)
+{
+ struct stat st;
+ stat(file, st);
+ return (size_t)st.st_size;
+}
+
+size_t size(const std::string& file, size_t def)
+{
+ auto st = sys::stat(file);
+ return st.get() ? (size_t)st->st_size : def;
+}
+
+ino_t inode(const std::string& file)
+{
+ struct stat st;
+ stat(file, st);
+ return st.st_ino;
+}
+
+ino_t inode(const std::string& file, ino_t def)
+{
+ auto st = sys::stat(file);
+ return st.get() ? st->st_ino : def;
+}
+
+
+bool access(const std::string &s, int m)
+{
+ return ::access(s.c_str(), m) == 0;
+}
+
+bool exists(const std::string& file)
+{
+ return sys::access(file, F_OK);
+}
+
+std::string getcwd()
+{
+#if defined(__GLIBC__)
+ char* cwd = ::get_current_dir_name();
+ if (cwd == NULL)
+ throw std::system_error(errno, std::system_category(), "cannot get the current working directory");
+ const std::string str(cwd);
+ ::free(cwd);
+ return str;
+#else
+ size_t size = pathconf(".", _PC_PATH_MAX);
+ char *buf = (char *)alloca( size );
+ if (::getcwd(buf, size) == NULL)
+ throw std::system_error(errno, std::system_category(), "cannot get the current working directory");
+ return buf;
+#endif
+}
+
+std::string abspath(const std::string& pathname)
+{
+ if (pathname[0] == '/')
+ return str::normpath(pathname);
+ else
+ return str::normpath(str::joinpath(sys::getcwd(), pathname));
+}
+
+
+/*
+ * MMap
+ */
+
+MMap::MMap(void* addr, size_t length)
+ : addr(addr), length(length)
+{
+}
+
+MMap::MMap(MMap&& o)
+ : addr(o.addr), length(o.length)
+{
+ o.addr = MAP_FAILED;
+ o.length = 0;
+}
+
+MMap& MMap::operator=(MMap&& o)
+{
+ if (this == &o) return *this;
+
+ munmap();
+ addr = o.addr;
+ length = o.length;
+ o.addr = MAP_FAILED;
+ o.length = 0;
+ return *this;
+}
+
+MMap::~MMap()
+{
+ if (addr != MAP_FAILED) ::munmap(addr, length);
+}
+
+void MMap::munmap()
+{
+ if (::munmap(addr, length) == -1)
+ throw std::system_error(errno, std::system_category(), "cannot unmap memory");
+ addr = MAP_FAILED;
+}
+
+
+/*
+ * FileDescriptor
+ */
+
+FileDescriptor::FileDescriptor() {}
+FileDescriptor::FileDescriptor(FileDescriptor&& o)
+ : fd(o.fd)
+{
+ o.fd = -1;
+}
+FileDescriptor::FileDescriptor(int fd) : fd(fd) {}
+FileDescriptor::~FileDescriptor() {}
+
+void FileDescriptor::throw_error(const char* desc)
+{
+ throw std::system_error(errno, std::system_category(), desc);
+}
+
+void FileDescriptor::close()
+{
+ if (fd == -1) return;
+ if (::close(fd) == -1)
+ throw_error("cannot close");
+ fd = -1;
+}
+
+void FileDescriptor::fstat(struct stat& st)
+{
+ if (::fstat(fd, &st) == -1)
+ throw_error("cannot stat");
+}
+
+void FileDescriptor::fchmod(mode_t mode)
+{
+ if (::fchmod(fd, mode) == -1)
+ throw_error("cannot fchmod");
+}
+
+size_t FileDescriptor::write(const void* buf, size_t count)
+{
+ ssize_t res = ::write(fd, buf, count);
+ if (res == -1)
+ throw_error("cannot write");
+ return res;
+}
+
+void FileDescriptor::write_all(const void* buf, size_t count)
+{
+ size_t written = 0;
+ while (written < count)
+ written += write((unsigned char*)buf + written, count - written);
+}
+
+MMap FileDescriptor::mmap(size_t length, int prot, int flags, off_t offset)
+{
+ void* res =::mmap(0, length, prot, flags, fd, offset);
+ if (res == MAP_FAILED)
+ throw_error("cannot mmap");
+ return MMap(res, length);
+}
+
+
+/*
+ * NamedFileDescriptor
+ */
+
+NamedFileDescriptor::NamedFileDescriptor(int fd, const std::string& pathname)
+ : FileDescriptor(fd), pathname(pathname)
+{
+}
+
+NamedFileDescriptor::NamedFileDescriptor(NamedFileDescriptor&& o)
+ : FileDescriptor(std::move(o)), pathname(std::move(o.pathname))
+{
+}
+
+NamedFileDescriptor& NamedFileDescriptor::operator=(NamedFileDescriptor&& o)
+{
+ if (this == &o) return *this;
+ fd = o.fd;
+ pathname = std::move(o.pathname);
+ o.fd = -1;
+ return *this;
+}
+
+void NamedFileDescriptor::throw_error(const char* desc)
+{
+ throw std::system_error(errno, std::system_category(), pathname + ": " + desc);
+}
+
+
+/*
+ * Path
+ */
+
+Path::Path(const char* pathname, int flags)
+ : NamedFileDescriptor(-1, pathname)
+{
+ fd = open(pathname, flags | O_PATH);
+ if (fd == -1)
+ throw_error("cannot open path");
+}
+
+Path::Path(const std::string& pathname, int flags)
+ : NamedFileDescriptor(-1, pathname)
+{
+ fd = open(pathname.c_str(), flags | O_PATH);
+ if (fd == -1)
+ throw_error("cannot open path");
+}
+
+Path::Path(Path& parent, const char* pathname, int flags)
+ : NamedFileDescriptor(parent.openat(pathname, flags | O_PATH),
+ str::joinpath(parent.name(), pathname))
+{
+}
+
+Path::~Path()
+{
+ if (fd != -1)
+ ::close(fd);
+}
+
+DIR* Path::fdopendir()
+{
+ int fd1 = ::openat(fd, ".", O_DIRECTORY);
+ if (fd1 == -1)
+ throw_error("cannot open directory");
+
+ DIR* res = ::fdopendir(fd1);
+ if (!res)
+ throw_error("cannot fdopendir");
+
+ return res;
+}
+
+Path::iterator Path::begin()
+{
+ if (fd == -1)
+ return iterator();
+ else
+ return iterator(*this);
+}
+
+Path::iterator Path::end()
+{
+ return iterator();
+}
+
+int Path::openat(const char* pathname, int flags, mode_t mode)
+{
+ int res = ::openat(fd, pathname, flags, mode);
+ if (res == -1)
+ throw_error("cannot openat");
+ return res;
+}
+
+void Path::fstatat(const char* pathname, struct stat& st)
+{
+ if (::fstatat(fd, pathname, &st, 0) == -1)
+ throw_error("cannot fstatat");
+}
+
+void Path::lstatat(const char* pathname, struct stat& st)
+{
+ if (::fstatat(fd, pathname, &st, AT_SYMLINK_NOFOLLOW) == -1)
+ throw_error("cannot fstatat");
+}
+
+void Path::unlinkat(const char* pathname)
+{
+ if (::unlinkat(fd, pathname, 0) == -1)
+ throw_error("cannot unlinkat");
+}
+
+void Path::rmdirat(const char* pathname)
+{
+ if (::unlinkat(fd, pathname, AT_REMOVEDIR) == -1)
+ throw_error("cannot unlinkat");
+}
+
+Path::iterator::iterator()
+{
+}
+
+Path::iterator::iterator(Path& dir)
+ : path(&dir)
+{
+ this->dir = dir.fdopendir();
+
+ long name_max = fpathconf(dir.fd, _PC_NAME_MAX);
+ if (name_max == -1) // Limit not defined, or error: take a guess
+ name_max = 255;
+ size_t len = offsetof(dirent, d_name) + name_max + 1;
+ cur_entry = (struct dirent*)malloc(len);
+ if (cur_entry == NULL)
+ throw std::bad_alloc();
+
+ operator++();
+}
+
+Path::iterator::~iterator()
+{
+ if (cur_entry) free(cur_entry);
+ if (dir) closedir(dir);
+}
+
+bool Path::iterator::operator==(const iterator& i) const
+{
+ if (!dir && !i.dir) return true;
+ if (!dir || !i.dir) return false;
+ return cur_entry->d_ino == i.cur_entry->d_ino;
+}
+bool Path::iterator::operator!=(const iterator& i) const
+{
+ if (!dir && !i.dir) return false;
+ if (!dir || !i.dir) return true;
+ return cur_entry->d_ino != i.cur_entry->d_ino;
+}
+
+void Path::iterator::operator++()
+{
+ struct dirent* result;
+ if (readdir_r(dir, cur_entry, &result) != 0)
+ path->throw_error("cannot readdir_r");
+
+ if (result == nullptr)
+ {
+ // Turn into an end iterator
+ free(cur_entry);
+ cur_entry = nullptr;
+ closedir(dir);
+ dir = nullptr;
+ }
+}
+
+bool Path::iterator::isdir() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_DIR)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ // No d_type, we'll need to stat
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISDIR(st.st_mode);
+}
+
+bool Path::iterator::isblk() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_BLK)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ // No d_type, we'll need to stat
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISBLK(st.st_mode);
+}
+
+bool Path::iterator::ischr() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_CHR)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ // No d_type, we'll need to stat
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISCHR(st.st_mode);
+}
+
+bool Path::iterator::isfifo() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_FIFO)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ // No d_type, we'll need to stat
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISFIFO(st.st_mode);
+}
+
+bool Path::iterator::islnk() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_LNK)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISLNK(st.st_mode);
+}
+
+bool Path::iterator::isreg() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_REG)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISREG(st.st_mode);
+}
+
+bool Path::iterator::issock() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_SOCK)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISSOCK(st.st_mode);
+}
+
+
+void Path::rmtree()
+{
+ for (auto i = begin(); i != end(); ++i)
+ {
+ if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) continue;
+ if (i.isdir())
+ {
+ Path sub(*this, i->d_name);
+ sub.rmtree();
+ }
+ else
+ unlinkat(i->d_name);
+ }
+ // TODO: is there a way to do this using fd instead?
+ rmdir(name());
+}
+
+/*
+ * File
+ */
+
+File::File(const std::string& pathname, int flags, mode_t mode)
+ : NamedFileDescriptor(-1, pathname)
+{
+ fd = open(pathname.c_str(), flags, mode);
+ if (fd == -1)
+ throw std::system_error(errno, std::system_category(), "cannot open file " + pathname);
+}
+
+File::~File()
+{
+ if (fd != -1) ::close(fd);
+}
+
+File File::mkstemp(const std::string& prefix)
+{
+ char* fbuf = (char*)alloca(prefix.size() + 7);
+ memcpy(fbuf, prefix.data(), prefix.size());
+ memcpy(fbuf + prefix.size(), "XXXXXX", 7);
+ int fd = ::mkstemp(fbuf);
+ if (fd < 0)
+ throw std::system_error(errno, std::system_category(), std::string("cannot create temporary file ") + fbuf);
+ return File(fd, fbuf);
+}
+
+std::string read_file(const std::string& file)
+{
+ File in(file, O_RDONLY);
+
+ // Get the file size
+ struct stat st;
+ in.fstat(st);
+
+ // mmap the input file
+ MMap src = in.mmap(st.st_size, PROT_READ, MAP_SHARED);
+
+ return std::string((const char*)src, st.st_size);
+}
+
+void write_file(const std::string& file, const std::string& data, mode_t mode)
+{
+ File out(file, O_WRONLY | O_CREAT, mode);
+ out.write_all(data.data(), data.size());
+ out.close();
+}
+
+void write_file_atomically(const std::string& file, const std::string& data, mode_t mode)
+{
+ File out = File::mkstemp(file);
+
+ // Read the umask
+ mode_t mask = umask(0777);
+ umask(mask);
+
+ // Set the file permissions, honoring umask
+ out.fchmod(mode & ~mask);
+
+ out.write_all(data.data(), data.size());
+ out.close();
+
+ if (rename(out.name().c_str(), file.c_str()) < 0)
+ throw std::system_error(errno, std::system_category(), "cannot rename " + out.name() + " to " + file);
+}
+
+#if 0
+void mkFilePath(const std::string& file)
+{
+ size_t pos = file.rfind('/');
+ if (pos != std::string::npos)
+ mkpath(file.substr(0, pos));
+}
+#endif
+
+bool unlink_ifexists(const std::string& file)
+{
+ if (::unlink(file.c_str()) != 0)
+ {
+ if (errno != ENOENT)
+ throw std::system_error(errno, std::system_category(), "cannot unlink " + file);
+ else
+ return false;
+ }
+ else
+ return true;
+}
+
+bool rename_ifexists(const std::string& src, const std::string& dst)
+{
+ if (::rename(src.c_str(), dst.c_str()) != 0)
+ {
+ if (errno != ENOENT)
+ throw std::system_error(errno, std::system_category(), "cannot rename " + src + " to " + dst);
+ else
+ return false;
+ }
+ else
+ return true;
+}
+
+template<typename String>
+static void impl_mkdir_ifmissing(String pathname, mode_t mode)
+{
+ for (unsigned i = 0; i < 5; ++i)
+ {
+ // If it does not exist, make it
+ if (::mkdir(to_cstring(pathname), mode) != -1)
+ return;
+
+ // throw on all errors except EEXIST. Note that EEXIST "includes the case
+ // where pathname is a symbolic link, dangling or not."
+ if (errno != EEXIST && errno != EISDIR)
+ {
+ std::stringstream msg;
+ msg << "cannot create directory " << pathname;
+ throw std::system_error(errno, std::system_category(), msg.str());
+ }
+
+ // Ensure that, if dir exists, it is a directory
+ std::unique_ptr<struct stat> st = sys::stat(pathname);
+ if (st.get() == NULL)
+ {
+ // Either dir has just been deleted, or we hit a dangling
+ // symlink.
+ //
+ // Retry creating a directory: the more we keep failing, the more
+ // the likelyhood of a dangling symlink increases.
+ //
+ // We could lstat here, but it would add yet another case for a
+ // race condition if the broken symlink gets deleted between the
+ // stat and the lstat.
+ continue;
+ }
+ else if (!S_ISDIR(st->st_mode))
+ {
+ // If it exists but it is not a directory, complain
+ std::stringstream msg;
+ msg << pathname << " exists but is not a directory";
+ throw std::runtime_error(msg.str());
+ }
+ else
+ // If it exists and it is a directory, we're fine
+ return;
+ }
+ std::stringstream msg;
+ msg << pathname << " exists and looks like a dangling symlink";
+ throw std::runtime_error(msg.str());
+}
+
+void mkdir_ifmissing(const char* pathname, mode_t mode)
+{
+ return impl_mkdir_ifmissing(pathname, mode);
+}
+
+void mkdir_ifmissing(const std::string& pathname, mode_t mode)
+{
+ return impl_mkdir_ifmissing(pathname, mode);
+}
+
+void makedirs(const std::string& pathname, mode_t mode)
+{
+ if (pathname == "/" || pathname == ".") return;
+ std::string parent = str::dirname(pathname);
+
+ // First ensure that the upper path exists
+ makedirs(parent, mode);
+
+ // Then create this dir
+ mkdir_ifmissing(pathname, mode);
+}
+
+std::string which(const std::string& name)
+{
+ // argv[0] has an explicit path: ensure it becomes absolute
+ if (name.find('/') != std::string::npos)
+ return sys::abspath(name);
+
+ // argv[0] has no explicit path, look for it in $PATH
+ const char* path = getenv("PATH");
+ if (!path) return name;
+
+ str::Split splitter(path, ":", true);
+ for (const auto& i: splitter)
+ {
+ std::string candidate = str::joinpath(i, name);
+ if (sys::access(candidate, X_OK))
+ return sys::abspath(candidate);
+ }
+
+ return name;
+}
+
+void unlink(const std::string& pathname)
+{
+ if (::unlink(pathname.c_str()) < 0)
+ throw std::system_error(errno, std::system_category(), "cannot unlink " + pathname);
+}
+
+void rmdir(const std::string& pathname)
+{
+ if (::rmdir(pathname.c_str()) < 0)
+ throw std::system_error(errno, std::system_category(), "cannot rmdir " + pathname);
+}
+
+void rmtree(const std::string& pathname)
+{
+ Path path(pathname);
+ path.rmtree();
+}
+
+#if 0
+std::string mkdtemp( std::string tmpl )
+{
+ char *_tmpl = reinterpret_cast< char * >( alloca( tmpl.size() + 1 ) );
+ strcpy( _tmpl, tmpl.c_str() );
+ return ::mkdtemp( _tmpl );
+}
+#endif
+}
+}
diff --git a/ept/utils/sys.h b/ept/utils/sys.h
new file mode 100644
index 0000000..334c983
--- /dev/null
+++ b/ept/utils/sys.h
@@ -0,0 +1,468 @@
+#ifndef EPT_SYS_H
+#define EPT_SYS_H
+
+/**
+ * @author Enrico Zini <enrico@enricozini.org>
+ * @brief Operating system functions
+ *
+ * Copyright (C) 2007--2015 Enrico Zini <enrico@debian.org>
+ */
+
+#include <string>
+//#include <iosfwd>
+#include <memory>
+#include <iterator>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+
+namespace ept {
+namespace sys {
+
+/**
+ * stat() the given file and return the struct stat with the results.
+ * If the file does not exist, return NULL.
+ * Raises exceptions in case of errors.
+ */
+std::unique_ptr<struct stat> stat(const std::string& pathname);
+
+/**
+ * stat() the given file filling in the given structure.
+ * Raises exceptions in case of errors, including if the file does not exist.
+ */
+void stat(const std::string& pathname, struct stat& st);
+
+/**
+ * Returns true if the given pathname is a directory, else false.
+ *
+ * It also returns false if the pathname does not exist.
+ */
+bool isdir(const std::string& pathname);
+
+/// Same as isdir but checks for block devices
+bool isblk(const std::string& pathname);
+
+/// Same as isdir but checks for character devices
+bool ischr(const std::string& pathname);
+
+/// Same as isdir but checks for FIFOs
+bool isfifo(const std::string& pathname);
+
+/// Same as isdir but checks for symbolic links
+bool islnk(const std::string& pathname);
+
+/// Same as isdir but checks for regular files
+bool isreg(const std::string& pathname);
+
+/// Same as isdir but checks for sockets
+bool issock(const std::string& pathname);
+
+/// File mtime
+time_t timestamp(const std::string& file);
+
+/// File mtime (or def if the file does not exist)
+time_t timestamp(const std::string& file, time_t def);
+
+/// File size
+size_t size(const std::string& file);
+
+/// File size (or def if the file does not exist)
+size_t size(const std::string& file, size_t def);
+
+/// File inode number
+ino_t inode(const std::string& file);
+
+/// File inode number (or 0 if the file does not exist)
+ino_t inode(const std::string& file, ino_t def);
+
+/// access() a filename
+bool access(const std::string& s, int m);
+
+/// Same as access(s, F_OK);
+bool exists(const std::string& s);
+
+/// Get the absolute path of the current working directory
+std::string getcwd();
+
+/// Get the absolute path of a file
+std::string abspath(const std::string& pathname);
+
+/**
+ * Wraps a mmapped memory area, unmapping it on destruction.
+ *
+ * MMap objects can be used as normal pointers
+ */
+class MMap
+{
+ void* addr;
+ size_t length;
+
+public:
+ MMap(const MMap&) = delete;
+ MMap(MMap&&);
+ MMap(void* addr, size_t length);
+ ~MMap();
+
+ MMap& operator=(const MMap&) = delete;
+ MMap& operator=(MMap&&);
+
+ size_t size() const { return length; }
+
+ void munmap();
+
+ template<typename T>
+ operator const T*() const { return reinterpret_cast<const T*>(addr); }
+
+ template<typename T>
+ operator T*() const { return reinterpret_cast<T*>(addr); };
+};
+
+/**
+ * Common operations on file descriptors.
+ *
+ * Except when documented otherwise, methods of this class are just thin
+ * wrappers around the libc functions with the same name, that check error
+ * results and throw exceptions if the functions failed.
+ *
+ * Implementing what to do on construction and destruction is left to the
+ * subclassers: at the FileDescriptor level, the destructor does nothing and
+ * leaves the file descriptor open.
+ */
+class FileDescriptor
+{
+protected:
+ int fd = -1;
+
+public:
+ FileDescriptor();
+ FileDescriptor(FileDescriptor&& o);
+ FileDescriptor(int fd);
+ virtual ~FileDescriptor();
+
+ /**
+ * Throw an exception based on errno and the given message.
+ *
+ * This can be overridden by subclasses that may have more information
+ * about the file descriptor, so that they can generate more descriptive
+ * messages.
+ */
+ [[noreturn]] virtual void throw_error(const char* desc);
+
+ void close();
+
+ void fstat(struct stat& st);
+ void fchmod(mode_t mode);
+
+ size_t write(const void* buf, size_t count);
+
+ /**
+ * Write all the data in buf, retrying partial writes
+ */
+ void write_all(const void* buf, size_t count);
+
+ MMap mmap(size_t length, int prot, int flags, off_t offset=0);
+
+ operator int() const { return fd; }
+};
+
+
+/**
+ * File descriptor with a name
+ */
+
+class NamedFileDescriptor : public FileDescriptor
+{
+protected:
+ std::string pathname;
+
+public:
+ NamedFileDescriptor(int fd, const std::string& pathname);
+ NamedFileDescriptor(NamedFileDescriptor&&);
+
+ NamedFileDescriptor& operator=(NamedFileDescriptor&&);
+
+ [[noreturn]] virtual void throw_error(const char* desc);
+
+ /// Return the file pathname
+ const std::string& name() const { return pathname; }
+};
+
+/**
+ * Wrap a path on the file system opened with O_PATH
+ */
+struct Path : public NamedFileDescriptor
+{
+ /**
+ * Iterator for directory entries
+ */
+ struct iterator : public std::iterator<std::input_iterator_tag, struct dirent>
+ {
+ Path* path = nullptr;
+ DIR* dir = nullptr;
+ struct dirent* cur_entry = nullptr;
+
+ // End iterator
+ iterator();
+ // Start iteration on dir
+ iterator(Path& dir);
+ iterator(iterator&) = delete;
+ iterator(iterator&& o)
+ : dir(o.dir), cur_entry(o.cur_entry)
+ {
+ o.dir = nullptr;
+ o.cur_entry = nullptr;
+ }
+ ~iterator();
+ iterator& operator=(iterator&) = delete;
+ iterator& operator=(iterator&&) = delete;
+
+ bool operator==(const iterator& i) const;
+ bool operator!=(const iterator& i) const;
+ struct dirent& operator*() const { return *cur_entry; }
+ struct dirent* operator->() const { return cur_entry; }
+ void operator++();
+
+ /// @return true if we refer to a directory, else false
+ bool isdir() const;
+
+ /// @return true if we refer to a block device, else false
+ bool isblk() const;
+
+ /// @return true if we refer to a character device, else false
+ bool ischr() const;
+
+ /// @return true if we refer to a named pipe (FIFO).
+ bool isfifo() const;
+
+ /// @return true if we refer to a symbolic link.
+ bool islnk() const;
+
+ /// @return true if we refer to a regular file.
+ bool isreg() const;
+
+ /// @return true if we refer to a Unix domain socket.
+ bool issock() const;
+ };
+
+ using NamedFileDescriptor::NamedFileDescriptor;
+
+ /**
+ * Open the given pathname with flags | O_PATH.
+ */
+ Path(const char* pathname, int flags=0);
+ /**
+ * Open the given pathname with flags | O_PATH.
+ */
+ Path(const std::string& pathname, int flags=0);
+ /**
+ * Open the given pathname calling parent.openat, with flags | O_PATH
+ */
+ Path(Path& parent, const char* pathname, int flags=0);
+ Path(const Path&) = delete;
+ Path(Path&&) = default;
+ Path& operator=(const Path&) = delete;
+ Path& operator=(Path&&) = default;
+
+ /**
+ * The destructor closes the file descriptor, but does not check errors on
+ * ::close().
+ *
+ * In normal program flow, it is a good idea to explicitly call
+ * Path::close() in places where it can throw safely.
+ */
+ ~Path();
+
+ DIR* fdopendir();
+
+ /// Begin iterator on all directory entries
+ iterator begin();
+
+ /// End iterator on all directory entries
+ iterator end();
+
+ int openat(const char* pathname, int flags, mode_t mode=0777);
+
+ void fstatat(const char* pathname, struct stat& st);
+
+ /// fstatat with the AT_SYMLINK_NOFOLLOW flag set
+ void lstatat(const char* pathname, struct stat& st);
+
+ void unlinkat(const char* pathname);
+
+ /// unlinkat with the AT_REMOVEDIR flag set
+ void rmdirat(const char* pathname);
+
+ /**
+ * Delete the directory pointed to by this Path, with all its contents.
+ *
+ * The path must point to a directory.
+ */
+ void rmtree();
+};
+
+
+/**
+ * open(2) file descriptors
+ */
+class File : public NamedFileDescriptor
+{
+public:
+ using NamedFileDescriptor::NamedFileDescriptor;
+
+ File(File&&) = default;
+ File(const File&) = delete;
+
+ /// Wrapper around open(2)
+ File(const std::string& pathname, int flags, mode_t mode=0777);
+
+ /**
+ * The destructor closes the file descriptor, but does not check errors on
+ * ::close().
+ *
+ * In normal program flow, it is a good idea to explicitly call
+ * File::close() in places where it can throw safely.
+ */
+ ~File();
+
+ File& operator=(const File&) = delete;
+ File& operator=(File&&) = default;
+
+ static File mkstemp(const std::string& prefix);
+};
+
+/// Read whole file into memory. Throws exceptions on failure.
+std::string read_file(const std::string &file);
+
+/**
+ * Write \a data to \a file, replacing existing contents if it already exists.
+ *
+ * New files are created with the given permission mode, honoring umask.
+ * Permissions of existing files do not change.
+ */
+void write_file(const std::string& file, const std::string& data, mode_t mode=0777);
+
+/**
+ * Write \a data to \a file, replacing existing contents if it already exists.
+ *
+ * Files are created with the given permission mode, honoring umask. If the
+ * file already exists, its mode is ignored.
+ *
+ * Data is written to a temporary file, then moved to its final destination, to
+ * ensure an atomic operation.
+ */
+void write_file_atomically(const std::string& file, const std::string& data, mode_t mode=0777);
+
+#if 0
+// Create a temporary directory based on a template.
+std::string mkdtemp(std::string templ);
+
+/// Ensure that the path to the given file exists, creating it if it does not.
+/// The file itself will not get created.
+void mkFilePath(const std::string& file);
+#endif
+
+/**
+ * Delete a file if it exists. If it does not exist, do nothing.
+ *
+ * @return true if the file was deleted, false if it did not exist
+ */
+bool unlink_ifexists(const std::string& file);
+
+/**
+ * Move \a src to \a dst, without raising exception if \a src does not exist
+ *
+ * @return true if the file was renamed, false if it did not exist
+ */
+bool rename_ifexists(const std::string& src, const std::string& dst);
+
+/// Create the given directory, if it does not already exists.
+/// It will complain if the given pathname already exists but is not a
+/// directory.
+void mkdir_ifmissing(const char* pathname, mode_t mode=0777);
+
+void mkdir_ifmissing(const std::string& pathname, mode_t mode=0777);
+
+/// Create all the component of the given directory, including the directory
+/// itself.
+void makedirs(const std::string& pathname, mode_t=0777);
+
+/**
+ * Compute the absolute path of an executable.
+ *
+ * If \a name is specified as a partial path, it ensures it is made absolute.
+ * If \a name is not specified as a path, it looks for the executable in $PATH
+ * and return its absolute pathname.
+ */
+std::string which(const std::string& name);
+
+/// Delete the file using unlink()
+void unlink(const std::string& pathname);
+
+/// Remove the directory using rmdir(2)
+void rmdir(const std::string& pathname);
+
+/// Delete the directory \a pathname and all its contents.
+void rmtree(const std::string& pathname);
+
+#if 0
+/// Nicely wrap access to directories
+class Directory
+{
+protected:
+ /// Directory pathname
+ std::string m_path;
+
+public:
+ class const_iterator
+ {
+ /// Directory we are iterating
+ const Directory* dir;
+ /// DIR* pointer
+ void* dirp;
+ /// dirent structure used for iterating entries
+ struct dirent* direntbuf;
+
+ public:
+ // Create an end iterator
+ const_iterator();
+ // Create a begin iterator
+ const_iterator(const Directory& dir);
+ // Cleanup properly
+ ~const_iterator();
+
+ /// auto_ptr style copy semantics
+ const_iterator(const const_iterator& i);
+ const_iterator& operator=(const const_iterator& i);
+
+ /// Move to the next directory entry
+ const_iterator& operator++();
+
+ /// @return the current file name
+ std::string operator*() const;
+
+ bool operator==(const const_iterator& iter) const;
+ bool operator!=(const const_iterator& iter) const;
+ };
+
+ Directory(const std::string& path);
+ ~Directory();
+
+ /// Pathname of the directory
+ const std::string& path() const { return m_path; }
+
+ /// Check if the directory exists
+ bool exists() const;
+
+ /// Begin iterator
+ const_iterator begin() const;
+
+ /// End iterator
+ const_iterator end() const;
+};
+
+#endif
+}
+}
+
+#endif
diff --git a/ept/utils/tests-main.cc b/ept/utils/tests-main.cc
new file mode 100644
index 0000000..1aec45a
--- /dev/null
+++ b/ept/utils/tests-main.cc
@@ -0,0 +1,139 @@
+#include "tests.h"
+#include <signal.h>
+#include <cstdlib>
+#include <cstring>
+#include <exception>
+
+void signal_to_exception(int)
+{
+ throw std::runtime_error("killing signal catched");
+}
+
+int main(int argc,const char* argv[])
+{
+ using namespace ept::tests;
+
+ signal(SIGSEGV, signal_to_exception);
+ signal(SIGILL, signal_to_exception);
+
+#if 0
+ if( (argc == 2 && (! strcmp ("help", argv[1]))) || argc > 3 )
+ {
+ std::cout << "TUT example test application." << std::endl;
+ std::cout << "Usage: example [regression] | [list] | [ group] [test]" << std::endl;
+ std::cout << " List all groups: example list" << std::endl;
+ std::cout << " Run all tests: example regression" << std::endl;
+ std::cout << " Run one group: example std::auto_ptr" << std::endl;
+ std::cout << " Run one test: example std::auto_ptr 3" << std::endl;;
+ }
+
+ // std::cout << "\nFAILURE and EXCEPTION in these tests are FAKE ;)\n\n";
+
+ tut::runner.get().set_callback(&visi);
+
+ try
+ {
+ if( argc == 1 || (argc == 2 && std::string(argv[1]) == "regression") )
+ {
+ tut::runner.get().run_tests();
+ }
+ else if( argc == 2 && std::string(argv[1]) == "list" )
+ {
+ std::cout << "registered test groups:" << std::endl;
+ tut::groupnames gl = tut::runner.get().list_groups();
+ tut::groupnames::const_iterator i = gl.begin();
+ tut::groupnames::const_iterator e = gl.end();
+ while( i != e )
+ {
+ std::cout << " " << *i << std::endl;
+ ++i;
+ }
+ }
+ else if( argc == 2 && std::string(argv[1]) != "regression" )
+ {
+ tut::runner.get().run_tests(argv[1]);
+ }
+ else if( argc == 3 )
+ {
+ tut::runner.get().run_test(argv[1],::atoi(argv[2]));
+ }
+ }
+ catch( const std::exception& ex )
+ {
+ std::cerr << "tut raised exception: " << ex.what() << std::endl;
+ }
+#endif
+
+ auto& tests = TestRegistry::get();
+
+ SimpleTestController controller;
+
+ if (const char* whitelist = getenv("TEST_WHITELIST"))
+ controller.whitelist = whitelist;
+
+ if (const char* blacklist = getenv("TEST_BLACKLIST"))
+ controller.blacklist = blacklist;
+
+ auto all_results = tests.run_tests(controller);
+
+ unsigned methods_ok = 0;
+ unsigned methods_failed = 0;
+ unsigned methods_skipped = 0;
+ unsigned test_cases_ok = 0;
+ unsigned test_cases_failed = 0;
+
+ for (const auto& tc_res: all_results)
+ {
+ if (!tc_res.fail_setup.empty())
+ {
+ fprintf(stderr, "%s: %s\n", tc_res.test_case.c_str(), tc_res.fail_setup.c_str());
+ ++test_cases_failed;
+ } else {
+ if (!tc_res.fail_teardown.empty())
+ {
+ fprintf(stderr, "%s: %s\n", tc_res.test_case.c_str(), tc_res.fail_teardown.c_str());
+ ++test_cases_failed;
+ }
+ else
+ ++test_cases_ok;
+
+ for (const auto& tm_res: tc_res.methods)
+ {
+ if (tm_res.skipped)
+ ++methods_skipped;
+ else if (tm_res.is_success())
+ ++methods_ok;
+ else
+ {
+ fprintf(stderr, "\n");
+ if (tm_res.exception_typeid.empty())
+ fprintf(stderr, "%s.%s: %s\n", tm_res.test_case.c_str(), tm_res.test_method.c_str(), tm_res.error_message.c_str());
+ else
+ fprintf(stderr, "%s.%s:[%s] %s\n", tm_res.test_case.c_str(), tm_res.test_method.c_str(), tm_res.exception_typeid.c_str(), tm_res.error_message.c_str());
+ for (const auto& frame : tm_res.error_stack)
+ fprintf(stderr, " %s", frame.format().c_str());
+ ++methods_failed;
+ }
+ }
+ }
+ }
+
+ bool success = true;
+
+ if (test_cases_failed)
+ {
+ success = false;
+ fprintf(stderr, "\n%u/%u test cases had issues initializing or cleaning up\n",
+ test_cases_failed, test_cases_ok + test_cases_failed);
+ }
+
+ if (methods_failed)
+ {
+ success = false;
+ fprintf(stderr, "\n%u/%u tests failed\n", methods_failed, methods_ok + methods_failed);
+ }
+ else
+ fprintf(stderr, "%u tests succeeded\n", methods_ok);
+
+ return success ? 0 : 1;
+}
diff --git a/ept/utils/tests.cc b/ept/utils/tests.cc
new file mode 100644
index 0000000..28ea280
--- /dev/null
+++ b/ept/utils/tests.cc
@@ -0,0 +1,578 @@
+/*
+ * @author Enrico Zini <enrico@enricozini.org>, Peter Rockai (mornfall) <me@mornfall.net>
+ * @brief Utility functions for the unit tests
+ *
+ * Copyright (C) 2006--2007 Peter Rockai (mornfall) <me@mornfall.net>
+ * Copyright (C) 2003--2015 Enrico Zini <enrico@debian.org>
+ */
+
+#include "tests.h"
+#include "string.h"
+#include <fnmatch.h>
+#include <cmath>
+#include <iomanip>
+#include <sys/types.h>
+#include <regex.h>
+
+using namespace std;
+using namespace ept;
+
+const ept::tests::LocationInfo ept_test_location_info;
+
+namespace ept {
+namespace tests {
+
+/*
+ * TestStackFrame
+ */
+
+std::string TestStackFrame::format() const
+{
+ std::stringstream ss;
+ format(ss);
+ return ss.str();
+}
+
+void TestStackFrame::format(std::ostream& out) const
+{
+ out << file << ":" << line << ":" << call;
+ if (!local_info.empty())
+ out << " [" << local_info << "]";
+ out << endl;
+}
+
+
+/*
+ * TestStack
+ */
+
+void TestStack::backtrace(std::ostream& out) const
+{
+ for (const auto& frame: *this)
+ frame.format(out);
+}
+
+std::string TestStack::backtrace() const
+{
+ std::stringstream ss;
+ backtrace(ss);
+ return ss.str();
+}
+
+
+/*
+ * TestFailed
+ */
+
+TestFailed::TestFailed(const std::exception& e)
+ : message(typeid(e).name())
+{
+ message += ": ";
+ message += e.what();
+}
+
+
+#if 0
+std::string Location::fail_msg(const std::string& error) const
+{
+ std::stringstream ss;
+ ss << "test failed at:" << endl;
+ backtrace(ss);
+ ss << file << ":" << line << ":error: " << error << endl;
+ return ss.str();
+}
+
+std::string Location::fail_msg(std::function<void(std::ostream&)> write_error) const
+{
+ std::stringstream ss;
+ ss << "test failed at:" << endl;
+ backtrace(ss);
+ ss << file << ":" << line << ":error: ";
+ write_error(ss);
+ ss << endl;
+ return ss.str();
+}
+#endif
+
+std::ostream& LocationInfo::operator()()
+{
+ str(std::string());
+ clear();
+ return *this;
+}
+
+/*
+ * Assertions
+ */
+
+void assert_startswith(const std::string& actual, const std::string& expected)
+{
+ if (str::startswith(actual, expected)) return;
+ std::stringstream ss;
+ ss << "'" << actual << "' does not start with '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+void assert_endswith(const std::string& actual, const std::string& expected)
+{
+ if (str::endswith(actual, expected)) return;
+ std::stringstream ss;
+ ss << "'" << actual << "' does not end with '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+void assert_contains(const std::string& actual, const std::string& expected)
+{
+ if (actual.find(expected) != std::string::npos) return;
+ std::stringstream ss;
+ ss << "'" << actual << "' does not contain '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+void assert_not_contains(const std::string& actual, const std::string& expected)
+{
+ if (actual.find(expected) == std::string::npos) return;
+ std::stringstream ss;
+ ss << "'" << actual << "' contains '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+namespace {
+
+struct Regexp
+{
+ regex_t compiled;
+
+ Regexp(const char* regex)
+ {
+ if (int err = regcomp(&compiled, regex, REG_EXTENDED | REG_NOSUB))
+ raise_error(err);
+ }
+ ~Regexp()
+ {
+ regfree(&compiled);
+ }
+
+ bool search(const char* s)
+ {
+ return regexec(&compiled, s, 0, nullptr, 0) != REG_NOMATCH;
+ }
+
+ void raise_error(int code)
+ {
+ // Get the size of the error message string
+ size_t size = regerror(code, &compiled, nullptr, 0);
+
+ char* buf = new char[size];
+ regerror(code, &compiled, buf, size);
+ string msg(buf);
+ delete[] buf;
+ throw std::runtime_error(msg);
+ }
+};
+
+}
+
+void assert_re_matches(const std::string& actual, const std::string& expected)
+{
+ Regexp re(expected.c_str());
+ if (re.search(actual.c_str())) return;
+ std::stringstream ss;
+ ss << "'" << actual << "' does not match '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+void assert_not_re_matches(const std::string& actual, const std::string& expected)
+{
+ Regexp re(expected.c_str());
+ if (!re.search(actual.c_str())) return;
+ std::stringstream ss;
+ ss << "'" << actual << "' should not match '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+void assert_true(std::nullptr_t actual)
+{
+ throw TestFailed("actual value nullptr is not true");
+};
+
+void assert_false(std::nullptr_t actual)
+{
+};
+
+
+static void _actual_must_be_set(const char* actual)
+{
+ if (!actual)
+ throw TestFailed("actual value is the null pointer instead of a valid string");
+}
+
+void ActualCString::operator==(const char* expected) const
+{
+ if (expected && _actual)
+ assert_equal<std::string, std::string>(_actual, expected);
+ else if (!expected && !_actual)
+ ;
+ else if (expected)
+ {
+ std::stringstream ss;
+ ss << "actual value is nullptr instead of the expected string \"" << str::encode_cstring(expected) << "\"";
+ throw TestFailed(ss.str());
+ }
+ else
+ {
+ std::stringstream ss;
+ ss << "actual value is the string \"" << str::encode_cstring(_actual) << "\" instead of nullptr";
+ throw TestFailed(ss.str());
+ }
+}
+
+void ActualCString::operator==(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_equal<std::string, std::string>(_actual, expected);
+}
+
+void ActualCString::operator!=(const char* expected) const
+{
+ if (expected && _actual)
+ assert_not_equal<std::string, std::string>(_actual, expected);
+ else if (!expected && !_actual)
+ throw TestFailed("actual and expected values are both nullptr but they should be different");
+}
+
+void ActualCString::operator!=(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_not_equal<std::string, std::string>(_actual, expected);
+}
+
+void ActualCString::operator<(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_less<std::string, std::string>(_actual, expected);
+}
+
+void ActualCString::operator<=(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_less_equal<std::string, std::string>(_actual, expected);
+}
+
+void ActualCString::operator>(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_greater<std::string, std::string>(_actual, expected);
+}
+
+void ActualCString::operator>=(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_greater_equal<std::string, std::string>(_actual, expected);
+}
+
+void ActualCString::matches(const std::string& re) const
+{
+ _actual_must_be_set(_actual);
+ assert_re_matches(_actual, re);
+}
+
+void ActualCString::not_matches(const std::string& re) const
+{
+ _actual_must_be_set(_actual);
+ assert_not_re_matches(_actual, re);
+}
+
+void ActualCString::startswith(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_startswith(_actual, expected);
+}
+
+void ActualCString::endswith(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_endswith(_actual, expected);
+}
+
+void ActualCString::contains(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_contains(_actual, expected);
+}
+
+void ActualCString::not_contains(const std::string& expected) const
+{
+ _actual_must_be_set(_actual);
+ assert_not_contains(_actual, expected);
+}
+
+void ActualStdString::startswith(const std::string& expected) const
+{
+ assert_startswith(_actual, expected);
+}
+
+void ActualStdString::endswith(const std::string& expected) const
+{
+ assert_endswith(_actual, expected);
+}
+
+void ActualStdString::contains(const std::string& expected) const
+{
+ assert_contains(_actual, expected);
+}
+
+void ActualStdString::not_contains(const std::string& expected) const
+{
+ assert_not_contains(_actual, expected);
+}
+
+void ActualStdString::matches(const std::string& re) const
+{
+ assert_re_matches(_actual, re);
+}
+
+void ActualStdString::not_matches(const std::string& re) const
+{
+ assert_not_re_matches(_actual, re);
+}
+
+void ActualDouble::almost_equal(double expected, unsigned places) const
+{
+ if (round((_actual - expected) * exp10(places)) == 0.0)
+ return;
+ std::stringstream ss;
+ ss << std::setprecision(places) << fixed << _actual << " is different than the expected " << expected;
+ throw TestFailed(ss.str());
+}
+
+void ActualDouble::not_almost_equal(double expected, unsigned places) const
+{
+ if (round(_actual - expected * exp10(places)) != 0.0)
+ return;
+ std::stringstream ss;
+ ss << std::setprecision(places) << fixed << _actual << " is the same as the expected " << expected;
+ throw TestFailed(ss.str());
+}
+
+void ActualFunction::throws(const std::string& what_match) const
+{
+ bool thrown = false;
+ try {
+ _actual();
+ } catch (std::exception& e) {
+ thrown = true;
+ wassert(actual(e.what()).matches(what_match));
+ }
+ if (!thrown)
+ throw TestFailed("code did not throw any exception");
+}
+
+#if 0
+void test_assert_file_exists(WIBBLE_TEST_LOCPRM, const std::string& fname)
+{
+ if (not sys::fs::exists(fname))
+ {
+ std::stringstream ss;
+ ss << "file '" << fname << "' does not exists";
+ ept_test_location.fail_test(ss.str());
+ }
+}
+
+void test_assert_not_file_exists(WIBBLE_TEST_LOCPRM, const std::string& fname)
+{
+ if (sys::fs::exists(fname))
+ {
+ std::stringstream ss;
+ ss << "file '" << fname << "' does exists";
+ ept_test_location.fail_test(ss.str());
+ }
+}
+
+#if 0
+struct TestFileExists
+{
+ std::string pathname;
+ bool inverted;
+ TestFileExists(const std::string& pathname, bool inverted=false) : pathname(pathname), inverted(inverted) {}
+ TestFileExists operator!() { return TestFileExists(pathname, !inverted); }
+ void check(EPT_TEST_LOCPRM) const;
+};
+#endif
+
+void TestFileExists::check(WIBBLE_TEST_LOCPRM) const
+{
+ if (!inverted)
+ {
+ if (sys::fs::exists(pathname)) return;
+ std::stringstream ss;
+ ss << "file '" << pathname << "' does not exists";
+ ept_test_location.fail_test(ss.str());
+ } else {
+ if (not sys::fs::exists(pathname)) return;
+ std::stringstream ss;
+ ss << "file '" << pathname << "' exists";
+ ept_test_location.fail_test(ss.str());
+ }
+}
+#endif
+
+TestRegistry& TestRegistry::get()
+{
+ static TestRegistry* instance = 0;
+ if (!instance)
+ instance = new TestRegistry();
+ return *instance;
+}
+
+void TestRegistry::register_test_case(TestCase& test_case)
+{
+ entries.emplace_back(&test_case);
+}
+
+std::vector<TestCaseResult> TestRegistry::run_tests(TestController& controller)
+{
+ std::vector<TestCaseResult> res;
+ for (auto& e: entries)
+ {
+ e->register_tests();
+ // TODO: filter on e.name
+ res.emplace_back(std::move(e->run_tests(controller)));
+ }
+ return res;
+}
+
+TestCaseResult TestCase::run_tests(TestController& controller)
+{
+ TestCaseResult res(name);
+
+ if (!controller.test_case_begin(*this, res))
+ {
+ res.skipped = true;
+ controller.test_case_end(*this, res);
+ return res;
+ }
+
+ try {
+ setup();
+ } catch (std::exception& e) {
+ res.set_setup_failed(e);
+ controller.test_case_end(*this, res);
+ return res;
+ }
+
+ for (auto& m: methods)
+ {
+ // TODO: filter on m.name
+ res.add_test_method(run_test(controller, m));
+ }
+
+ try {
+ teardown();
+ } catch (std::exception& e) {
+ res.set_teardown_failed(e);
+ }
+
+ controller.test_case_end(*this, res);
+ return res;
+}
+
+TestMethodResult TestCase::run_test(TestController& controller, TestMethod& method)
+{
+ TestMethodResult res(name, method.name);
+
+ if (!controller.test_method_begin(method, res))
+ {
+ res.skipped = true;
+ controller.test_method_end(method, res);
+ return res;
+ }
+
+ bool run = true;
+ try {
+ method_setup(res);
+ } catch (std::exception& e) {
+ res.set_setup_exception(e);
+ run = false;
+ }
+
+ if (run)
+ {
+ try {
+ method.test_function();
+ } catch (TestFailed& e) {
+ // Location::fail_test() was called
+ res.set_failed(e);
+ } catch (std::exception& e) {
+ // std::exception was thrown
+ res.set_exception(e);
+ } catch (...) {
+ // An unknown exception was thrown
+ res.set_unknown_exception();
+ }
+ }
+
+ try {
+ method_teardown(res);
+ } catch (std::exception& e) {
+ res.set_teardown_exception(e);
+ }
+
+ controller.test_method_end(method, res);
+ return res;
+}
+
+bool SimpleTestController::test_method_should_run(const std::string& fullname) const
+{
+ if (!whitelist.empty() && fnmatch(whitelist.c_str(), fullname.c_str(), 0) == FNM_NOMATCH)
+ return false;
+
+ if (!blacklist.empty() && fnmatch(blacklist.c_str(), fullname.c_str(), 0) != FNM_NOMATCH)
+ return false;
+
+ return true;
+}
+
+bool SimpleTestController::test_case_begin(const TestCase& test_case, const TestCaseResult& test_case_result)
+{
+ // Skip test case if all its methods should not run
+ bool should_run = false;
+ for (const auto& m : test_case.methods)
+ should_run |= test_method_should_run(test_case.name + "." + m.name);
+ if (!should_run) return false;
+
+ fprintf(stdout, "%s: ", test_case.name.c_str());
+ fflush(stdout);
+ return true;
+}
+
+void SimpleTestController::test_case_end(const TestCase& test_case, const TestCaseResult& test_case_result)
+{
+ if (test_case_result.skipped)
+ ;
+ else if (test_case_result.is_success())
+ fprintf(stdout, "\n");
+ else
+ fprintf(stdout, "\n");
+ fflush(stdout);
+}
+
+bool SimpleTestController::test_method_begin(const TestMethod& test_method, const TestMethodResult& test_method_result)
+{
+ string name = test_method_result.test_case + "." + test_method.name;
+ return test_method_should_run(name);
+}
+
+void SimpleTestController::test_method_end(const TestMethod& test_method, const TestMethodResult& test_method_result)
+{
+ if (test_method_result.skipped)
+ putc('s', stdout);
+ else if (test_method_result.is_success())
+ putc('.', stdout);
+ else
+ putc('x', stdout);
+ fflush(stdout);
+}
+
+}
+}
diff --git a/ept/utils/tests.h b/ept/utils/tests.h
new file mode 100644
index 0000000..3b00a14
--- /dev/null
+++ b/ept/utils/tests.h
@@ -0,0 +1,804 @@
+#ifndef EPT_TESTS_H
+#define EPT_TESTS_H
+
+/**
+ * @author Enrico Zini <enrico@enricozini.org>, Peter Rockai (mornfall) <me@mornfall.net>
+ * @brief Utility functions for the unit tests
+ *
+ * Copyright (C) 2006--2007 Peter Rockai (mornfall) <me@mornfall.net>
+ * Copyright (C) 2003--2013 Enrico Zini <enrico@debian.org>
+ */
+
+#include <string>
+#include <sstream>
+#include <exception>
+#include <functional>
+#include <vector>
+
+namespace ept {
+namespace tests {
+struct LocationInfo;
+}
+}
+
+/*
+ * These global arguments will be shadowed by local variables in functions that
+ * implement tests.
+ *
+ * They are here to act as default root nodes to fulfill method signatures when
+ * tests are called from outside other tests.
+ */
+extern const ept::tests::LocationInfo ept_test_location_info;
+
+namespace ept {
+namespace tests {
+
+/**
+ * Add information to the test backtrace for the tests run in the current
+ * scope.
+ *
+ * Example usage:
+ * \code
+ * test_function(...)
+ * {
+ * EPT_TEST_INFO(info);
+ * for (unsigned i = 0; i < 10; ++i)
+ * {
+ * info() << "Iteration #" << i;
+ * ...
+ * }
+ * }
+ * \endcode
+ */
+struct LocationInfo : public std::stringstream
+{
+ LocationInfo() {}
+
+ /**
+ * Clear the current information and return the output stream to which new
+ * information can be sent
+ */
+ std::ostream& operator()();
+};
+
+/// Information about one stack frame in the test execution stack
+struct TestStackFrame
+{
+ const char* file;
+ int line;
+ const char* call;
+ std::string local_info;
+
+ TestStackFrame(const char* file, int line, const char* call)
+ : file(file), line(line), call(call)
+ {
+ }
+
+ TestStackFrame(const char* file, int line, const char* call, const LocationInfo& local_info)
+ : file(file), line(line), call(call), local_info(local_info.str())
+ {
+ }
+
+ std::string format() const;
+
+ void format(std::ostream& out) const;
+};
+
+struct TestStack : public std::vector<TestStackFrame>
+{
+ using vector::vector;
+
+ /// Return the formatted backtrace for this location
+ std::string backtrace() const;
+
+ /// Write the formatted backtrace for this location to \a out
+ void backtrace(std::ostream& out) const;
+};
+
+/**
+ * Exception raised when a test assertion fails, normally by
+ * Location::fail_test
+ */
+struct TestFailed : public std::exception
+{
+ std::string message;
+ TestStack stack;
+
+ TestFailed(const std::exception& e);
+
+ template<typename ...Args>
+ TestFailed(const std::exception& e, Args&&... args)
+ : TestFailed(e)
+ {
+ add_stack_info(std::forward<Args>(args)...);
+ }
+
+ TestFailed(const std::string& message) : message(message) {}
+
+ template<typename ...Args>
+ TestFailed(const std::string& message, Args&&... args)
+ : TestFailed(message)
+ {
+ add_stack_info(std::forward<Args>(args)...);
+ }
+
+ const char* what() const noexcept override { return message.c_str(); }
+
+ template<typename ...Args>
+ void add_stack_info(Args&&... args) { stack.emplace_back(std::forward<Args>(args)...); }
+};
+
+/**
+ * Use this to declare a local variable with the given name that will be
+ * picked up by tests as extra local info
+ */
+#define EPT_TEST_INFO(name) \
+ ept::tests::LocationInfo ept_test_location_info; \
+ ept::tests::LocationInfo& name = ept_test_location_info
+
+
+/// Test function that ensures that the actual value is true
+template<typename A>
+void assert_true(const A& actual)
+{
+ if (actual) return;
+ std::stringstream ss;
+ ss << "actual value " << actual << " is not true";
+ throw TestFailed(ss.str());
+};
+
+void assert_true(std::nullptr_t actual);
+
+/// Test function that ensures that the actual value is false
+template<typename A>
+void assert_false(const A& actual)
+{
+ if (!actual) return;
+ std::stringstream ss;
+ ss << "actual value " << actual << " is not false";
+ throw TestFailed(ss.str());
+};
+
+void assert_false(std::nullptr_t actual);
+
+/**
+ * Test function that ensures that the actual value is the same as a reference
+ * one
+ */
+template<typename A, typename E>
+void assert_equal(const A& actual, const E& expected)
+{
+ if (actual == expected) return;
+ std::stringstream ss;
+ ss << "value '" << actual << "' is different than the expected '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+/**
+ * Test function that ensures that the actual value is different than a
+ * reference one
+ */
+template<typename A, typename E>
+void assert_not_equal(const A& actual, const E& expected)
+{
+ if (actual != expected) return;
+ std::stringstream ss;
+ ss << "value '" << actual << "' is not different than the expected '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+/// Ensure that the actual value is less than the reference value
+template<typename A, typename E>
+void assert_less(const A& actual, const E& expected)
+{
+ if (actual < expected) return;
+ std::stringstream ss;
+ ss << "value '" << actual << "' is not less than the expected '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+/// Ensure that the actual value is less or equal than the reference value
+template<typename A, typename E>
+void assert_less_equal(const A& actual, const E& expected)
+{
+ if (actual <= expected) return;
+ std::stringstream ss;
+ ss << "value '" << actual << "' is not less than or equals to the expected '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+/// Ensure that the actual value is greater than the reference value
+template<typename A, typename E>
+void assert_greater(const A& actual, const E& expected)
+{
+ if (actual > expected) return;
+ std::stringstream ss;
+ ss << "value '" << actual << "' is not greater than the expected '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+/// Ensure that the actual value is greather or equal than the reference value
+template<typename A, typename E>
+void assert_greater_equal(const A& actual, const E& expected)
+{
+ if (actual >= expected) return;
+ std::stringstream ss;
+ ss << "value '" << actual << "' is not greater than or equals to the expected '" << expected << "'";
+ throw TestFailed(ss.str());
+}
+
+/// Ensure that the string \a actual starts with \a expected
+void assert_startswith(const std::string& actual, const std::string& expected);
+
+/// Ensure that the string \a actual ends with \a expected
+void assert_endswith(const std::string& actual, const std::string& expected);
+
+/// Ensure that the string \a actual contains \a expected
+void assert_contains(const std::string& actual, const std::string& expected);
+
+/// Ensure that the string \a actual does not contain \a expected
+void assert_not_contains(const std::string& actual, const std::string& expected);
+
+/**
+ * Ensure that the string \a actual matches the extended regular expression
+ * \a expected.
+ *
+ * The syntax is that of extended regular expression (see man regex(7) ).
+ */
+void assert_re_matches(const std::string& actual, const std::string& expected);
+
+/**
+ * Ensure that the string \a actual does not match the extended regular
+ * expression \a expected.
+ *
+ * The syntax is that of extended regular expression (see man regex(7) ).
+ */
+void assert_not_re_matches(const std::string& actual, const std::string& expected);
+
+
+template<class A>
+struct Actual
+{
+ A _actual;
+ Actual(const A& actual) : _actual(actual) {}
+ ~Actual() {}
+
+ void istrue() const { assert_true(_actual); }
+ void isfalse() const { assert_false(_actual); }
+ template<typename E> void operator==(const E& expected) const { assert_equal(_actual, expected); }
+ template<typename E> void operator!=(const E& expected) const { assert_not_equal(_actual, expected); }
+ template<typename E> void operator<(const E& expected) const { return assert_less(_actual, expected); }
+ template<typename E> void operator<=(const E& expected) const { return assert_less_equal(_actual, expected); }
+ template<typename E> void operator>(const E& expected) const { return assert_greater(_actual, expected); }
+ template<typename E> void operator>=(const E& expected) const { return assert_greater_equal(_actual, expected); }
+};
+
+struct ActualCString
+{
+ const char* _actual;
+ ActualCString(const char* s) : _actual(s) {}
+
+ void istrue() const { return assert_true(_actual); }
+ void isfalse() const { return assert_false(_actual); }
+ void operator==(const char* expected) const;
+ void operator==(const std::string& expected) const;
+ void operator!=(const char* expected) const;
+ void operator!=(const std::string& expected) const;
+ void operator<(const std::string& expected) const;
+ void operator<=(const std::string& expected) const;
+ void operator>(const std::string& expected) const;
+ void operator>=(const std::string& expected) const;
+ void startswith(const std::string& expected) const;
+ void endswith(const std::string& expected) const;
+ void contains(const std::string& expected) const;
+ void not_contains(const std::string& expected) const;
+ void matches(const std::string& re) const;
+ void not_matches(const std::string& re) const;
+};
+
+struct ActualStdString : public Actual<std::string>
+{
+ ActualStdString(const std::string& s) : Actual<std::string>(s) {}
+
+ void startswith(const std::string& expected) const;
+ void endswith(const std::string& expected) const;
+ void contains(const std::string& expected) const;
+ void not_contains(const std::string& expected) const;
+ void matches(const std::string& re) const;
+ void not_matches(const std::string& re) const;
+};
+
+struct ActualDouble : public Actual<double>
+{
+ using Actual::Actual;
+
+ void almost_equal(double expected, unsigned places) const;
+ void not_almost_equal(double expected, unsigned places) const;
+};
+
+template<typename A>
+inline Actual<A> actual(const A& actual) { return Actual<A>(actual); }
+inline ActualCString actual(const char* actual) { return ActualCString(actual); }
+inline ActualCString actual(char* actual) { return ActualCString(actual); }
+inline ActualStdString actual(const std::string& actual) { return ActualStdString(actual); }
+inline ActualDouble actual(double actual) { return ActualDouble(actual); }
+
+struct ActualFunction : public Actual<std::function<void()>>
+{
+ using Actual::Actual;
+
+ void throws(const std::string& what_match) const;
+};
+
+inline ActualFunction actual_function(std::function<void()> actual) { return ActualFunction(actual); }
+
+
+/**
+ * Run the given command, raising TestFailed with the appropriate backtrace
+ * information if it threw an exception.
+ *
+ * If the command raises TestFailed, it adds the current stack to its stack
+ * information.
+ */
+#define wassert(...) \
+ do { try { \
+ __VA_ARGS__ ; \
+ } catch (TestFailed& e) { \
+ e.add_stack_info(__FILE__, __LINE__, #__VA_ARGS__, ept_test_location_info); \
+ throw; \
+ } catch (std::exception& e) { \
+ throw TestFailed(e, __FILE__, __LINE__, #__VA_ARGS__, ept_test_location_info); \
+ } } while(0)
+
+/// Shortcut to check that a given expression returns true
+#define wassert_true(...) wassert(actual(__VA_ARGS__).istrue())
+
+/// Shortcut to check that a given expression returns false
+#define wassert_false(...) wassert(actual(__VA_ARGS__).isfalse())
+
+/**
+ * Call a function returning its result, and raising TestFailed with the
+ * appropriate backtrace information if it threw an exception.
+ *
+ * If the function raises TestFailed, it adds the current stack to its stack
+ * information.
+ */
+#define wcallchecked(func) \
+ [&]() { try { \
+ return func; \
+ } catch (TestFailed& e) { \
+ e.add_stack_info(__FILE__, __LINE__, #func, ept_test_location_info); \
+ throw; \
+ } catch (std::exception& e) { \
+ throw TestFailed(e, __FILE__, __LINE__, #func, ept_test_location_info); \
+ } }()
+
+
+struct TestCase;
+
+/**
+ * Result of running a test method.
+ */
+struct TestMethodResult
+{
+ /// Name of the test case
+ std::string test_case;
+
+ /// Name of the test method
+ std::string test_method;
+
+ /// If non-empty, the test failed with this error
+ std::string error_message;
+
+ /// Stack frame of where the error happened
+ TestStack error_stack;
+
+ /// If non-empty, the test raised an exception and this is its type ID
+ std::string exception_typeid;
+
+ /// True if the test has been skipped
+ bool skipped = false;
+
+
+ TestMethodResult(const std::string& test_case, const std::string& test_method)
+ : test_case(test_case), test_method(test_method) {}
+
+ void set_failed(TestFailed& e)
+ {
+ error_message = e.what();
+ error_stack = e.stack;
+ if (error_message.empty())
+ error_message = "test failed with an empty error message";
+ }
+
+ void set_exception(std::exception& e)
+ {
+ error_message = e.what();
+ if (error_message.empty())
+ error_message = "test threw an exception with an empty error message";
+ exception_typeid = typeid(e).name();
+ }
+
+ void set_unknown_exception()
+ {
+ error_message = "unknown exception caught";
+ }
+
+ void set_setup_exception(std::exception& e)
+ {
+ error_message = "[setup failed: ";
+ error_message += e.what();
+ error_message += "]";
+ }
+
+ void set_teardown_exception(std::exception& e)
+ {
+ error_message = "[teardown failed: ";
+ error_message += e.what();
+ error_message += "]";
+ }
+
+ bool is_success() const
+ {
+ return error_message.empty();
+ }
+};
+
+/**
+ * Result of running a whole test case
+ */
+struct TestCaseResult
+{
+ /// Name of the test case
+ std::string test_case;
+ /// Outcome of all the methods that have been run
+ std::vector<TestMethodResult> methods;
+ /// Set to a non-empty string if the setup method of the test case failed
+ std::string fail_setup;
+ /// Set to a non-empty string if the teardown method of the test case
+ /// failed
+ std::string fail_teardown;
+ /// Set to true if this test case has been skipped
+ bool skipped = false;
+
+ TestCaseResult(const std::string& test_case) : test_case(test_case) {}
+
+ void set_setup_failed()
+ {
+ fail_setup = "test case setup method threw an unknown exception";
+ }
+
+ void set_setup_failed(std::exception& e)
+ {
+ fail_setup = "test case setup method threw an exception: ";
+ fail_setup += e.what();
+ }
+
+ void set_teardown_failed()
+ {
+ fail_teardown = "test case teardown method threw an unknown exception";
+ }
+
+ void set_teardown_failed(std::exception& e)
+ {
+ fail_teardown = "test case teardown method threw an exception: ";
+ fail_teardown += e.what();
+ }
+
+ void add_test_method(TestMethodResult&& e)
+ {
+ methods.emplace_back(std::move(e));
+ }
+
+ bool is_success() const
+ {
+ if (!fail_setup.empty() || !fail_teardown.empty()) return false;
+ for (const auto& m: methods)
+ if (!m.is_success())
+ return false;
+ return true;
+ }
+};
+
+struct TestCase;
+struct TestCaseResult;
+struct TestMethod;
+struct TestMethodResult;
+
+/**
+ * Abstract interface for the objects that supervise test execution.
+ *
+ * This can be used for printing progress, or to skip test methods or test
+ * cases.
+ */
+struct TestController
+{
+ virtual ~TestController() {}
+
+ /**
+ * Called before running a test case.
+ *
+ * @returns true if the test case should be run, false if it should be skipped
+ */
+ virtual bool test_case_begin(const TestCase& test_case, const TestCaseResult& test_case_result) { return true; }
+
+ /**
+ * Called after running a test case.
+ */
+ virtual void test_case_end(const TestCase& test_case, const TestCaseResult& test_case_result) {}
+
+ /**
+ * Called before running a test method.
+ *
+ * @returns true if the test method should be run, false if it should be skipped
+ */
+ virtual bool test_method_begin(const TestMethod& test_method, const TestMethodResult& test_method_result) { return true; }
+
+ /**
+ * Called after running a test method.
+ */
+ virtual void test_method_end(const TestMethod& test_method, const TestMethodResult& test_method_result) {}
+};
+
+/**
+ * Simple default implementation of TestController.
+ *
+ * It does progress printing to stdout and basic glob-based test method
+ * filtering.
+ */
+struct SimpleTestController : public TestController
+{
+ /// Any method not matching this glob expression will not be run
+ std::string whitelist;
+
+ /// Any method matching this glob expression will not be run
+ std::string blacklist;
+
+ bool test_case_begin(const TestCase& test_case, const TestCaseResult& test_case_result) override;
+ void test_case_end(const TestCase& test_case, const TestCaseResult& test_case_result) override;
+ bool test_method_begin(const TestMethod& test_method, const TestMethodResult& test_method_result) override;
+ void test_method_end(const TestMethod& test_method, const TestMethodResult& test_method_result) override;
+
+ bool test_method_should_run(const std::string& fullname) const;
+};
+
+
+/**
+ * Test registry.
+ *
+ * It collects information about all known test cases and takes care of running
+ * them.
+ */
+struct TestRegistry
+{
+ /// All known test cases
+ std::vector<TestCase*> entries;
+
+ /**
+ * Register a new test case.
+ *
+ * No memory management is done: test_case needs to exist for the whole
+ * lifetime of TestRegistry.
+ */
+ void register_test_case(TestCase& test_case);
+
+ /**
+ * Run all the registered tests using the given controller
+ */
+ std::vector<TestCaseResult> run_tests(TestController& controller);
+
+ /// Get the singleton instance of TestRegistry
+ static TestRegistry& get();
+};
+
+/**
+ * Test method information
+ */
+struct TestMethod
+{
+ /// Name of the test method
+ std::string name;
+
+ /// Main body of the test method
+ std::function<void()> test_function;
+
+ TestMethod(const std::string& name, std::function<void()> test_function)
+ : name(name), test_function(test_function) {}
+};
+
+
+/**
+ * Test case collecting several test methods, and self-registering with the
+ * singleton instance of TestRegistry.
+ */
+struct TestCase
+{
+ /// Name of the test case
+ std::string name;
+
+ /// All registered test methods
+ std::vector<TestMethod> methods;
+
+ TestCase(const std::string& name)
+ : name(name)
+ {
+ TestRegistry::get().register_test_case(*this);
+ }
+ virtual ~TestCase() {}
+
+ /**
+ * This will be called before running the test case, to populate it with
+ * its test methods.
+ *
+ * This needs to be reimplemented with a function that will mostly be a
+ * sequence of calls to add_method().
+ */
+ virtual void register_tests() = 0;
+
+ /**
+ * Set up the test case before it is run.
+ */
+ virtual void setup() {}
+
+ /**
+ * Clean up after the test case is run
+ */
+ virtual void teardown() {}
+
+ /**
+ * Set up before the test method is run
+ */
+ virtual void method_setup(TestMethodResult&) {}
+
+ /**
+ * Clean up after the test method is run
+ */
+ virtual void method_teardown(TestMethodResult&) {}
+
+ /**
+ * Call setup(), run all the tests that have been registered, then
+ * call teardown().
+ *
+ * Exceptions in setup() and teardown() are caught and reported in
+ * TestCaseResult. Test are run using run_test().
+ */
+ virtual TestCaseResult run_tests(TestController& controller);
+
+ /**
+ * Run a test method.
+ *
+ * Call method_setup(), run all the tests that have been registered, then
+ * call method_teardown().
+ *
+ * Exceptions thrown by the test method are caught and reported in
+ * TestMethodResult.
+ *
+ * Exceptions in method_setup() and method_teardown() are caught and
+ * reported in TestMethodResult.
+ */
+ virtual TestMethodResult run_test(TestController& controller, TestMethod& method);
+
+ /**
+ * Register a new test method
+ */
+ template<typename ...Args>
+ void add_method(const std::string& name, std::function<void()> test_function)
+ {
+ methods.emplace_back(name, test_function);
+ }
+
+ /**
+ * Register a new test method
+ */
+ template<typename ...Args>
+ void add_method(const std::string& name, std::function<void()> test_function, Args&&... args)
+ {
+ methods.emplace_back(name, test_function, std::forward<Args>(args)...);
+ }
+
+ /**
+ * Register a new test metheod, with arguments.
+ *
+ * Any extra arguments to the function will be passed to the test method.
+ */
+ template<typename FUNC, typename ...Args>
+ void add_method(const std::string& name, FUNC test_function, Args&&... args)
+ {
+ methods.emplace_back(name, [test_function, args...]() { test_function(args...); });
+ }
+};
+
+
+/**
+ * Base class for test fixtures.
+ *
+ * A fixture will have a constructor and a destructor to do setup/teardown, and
+ * a reset() function to be called inbetween tests.
+ *
+ * Fixtures do not need to descend from Fixture: this implementation is
+ * provided as a default for tests that do not need one, or as a base for
+ * fixtures that do not need reset().
+ */
+struct Fixture
+{
+ virtual ~Fixture() {}
+
+ // Called before each test
+ virtual void test_setup() {}
+
+ // Called after each test
+ virtual void test_teardown() {}
+};
+
+/**
+ * Test case that includes a fixture
+ */
+template<typename FIXTURE>
+struct FixtureTestCase : public TestCase
+{
+ typedef FIXTURE Fixture;
+
+ Fixture* fixture = 0;
+ std::function<Fixture*()> make_fixture;
+
+ template<typename... Args>
+ FixtureTestCase(const std::string& name, Args... args)
+ : TestCase(name)
+ {
+ make_fixture = [=]() { return new Fixture(args...); };
+ }
+
+ void setup() override
+ {
+ TestCase::setup();
+ fixture = make_fixture();
+ }
+
+ void teardown() override
+ {
+ delete fixture;
+ fixture = 0;
+ TestCase::teardown();
+ }
+
+ void method_setup(TestMethodResult& mr) override
+ {
+ TestCase::method_setup(mr);
+ if (fixture) fixture->test_setup();
+ }
+
+ void method_teardown(TestMethodResult& mr) override
+ {
+ if (fixture) fixture->test_teardown();
+ TestCase::method_teardown(mr);
+ }
+
+ /**
+ * Add a method that takes a reference to the fixture as argument.
+ *
+ * Any extra arguments to the function will be passed to the test method
+ * after the fixture.
+ */
+ template<typename FUNC, typename ...Args>
+ void add_method(const std::string& name, FUNC test_function, Args&&... args)
+ {
+ methods.emplace_back(name, [this, test_function, args...] { test_function(*fixture, args...); });
+ }
+};
+
+#if 0
+ struct Test
+ {
+ std::string name;
+ std::function<void()> test_func;
+ };
+
+ /// Add tests to the test case
+ virtual void add_tests() {}
+#endif
+
+
+}
+}
+
+#endif