This commit is contained in:
Mars 2024-06-02 06:03:21 -04:00
parent 6e5045f1f4
commit 693fa17d10
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
266 changed files with 60543 additions and 1000 deletions

View file

@ -0,0 +1,190 @@
#include "DocTestExtensions.h"
#include <memory>
#ifndef _WIN32
#include <cstdlib>
#include <unistd.h>
#else
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
// Mingw already defines this, so no need to redefine
#define NOMINMAX
#endif
#include <windows.h>
#include <codecvt>
#include <io.h>
#include <locale>
#include <sys/stat.h>
#endif
namespace quill
{
namespace testing
{
// The ctor redirects the stream to a temporary file.
CapturedStream::CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd))
{
#if defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(_WIN32)
char temp_dir_path[MAX_PATH + 1] = {'\0'}; // NOLINT
char temp_file_path[MAX_PATH + 1] = {'\0'}; // NOLINT
::GetTempPathA(sizeof(temp_dir_path), temp_dir_path);
const UINT success = ::GetTempFileNameA(temp_dir_path, "dtest_redir",
0, // Generate unique file name.
temp_file_path);
if (success == 0)
{
FAIL("Unable to create a temporary file in " << temp_dir_path;);
}
const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE);
if (captured_fd == -1)
{
FAIL("Unable to open_file temporary file " << temp_file_path);
}
filename_ = temp_file_path;
#else
// There's no guarantee that a test has write access to the current
// directory, so we create the temporary file in the /tmp directory
// instead.
// We use /tmp on most systems, and /sdcard on Android.
// That's because Android doesn't have /tmp.
#if defined(__ANDROID__)
char name_template[] = "/data/local/tmp/doctest_captured_stream.XXXXXX";
#else
char name_template[] = "/tmp/captured_stream.XXXXXX";
#endif // __ANDROID__
const int captured_fd = mkstemp(name_template);
if (captured_fd == -1)
{
FAIL("Failed to create tmp file "
<< name_template << " for test; does the test have access to the /tmp directory?");
}
filename_ = name_template;
#endif // _WIN32
fflush(nullptr);
dup2(captured_fd, fd_);
close(captured_fd);
}
CapturedStream::~CapturedStream() { remove(filename_.c_str()); }
std::string CapturedStream::GetCapturedString()
{
if (uncaptured_fd_ != -1)
{
// Restores the original stream.
fflush(nullptr);
dup2(uncaptured_fd_, fd_);
close(uncaptured_fd_);
uncaptured_fd_ = -1;
}
FILE* const file = _fopen(filename_.c_str(), "r");
if (file == nullptr)
{
// FAIL("Failed to open_file tmp file " << filename_ << " for capturing stream.");
}
std::string content = _read_entire_file(file);
_fclose(file);
return content;
}
std::string CapturedStream::_read_entire_file(FILE* file)
{
size_t const file_size = _get_file_size(file);
std::unique_ptr<char[]> buffer{new char[file_size]};
size_t bytes_last_read = 0; // # of bytes read in the last fread()
size_t bytes_read = 0; // # of bytes read so far
fseek(file, 0, SEEK_SET);
do
{
// Keeps reading the file until we cannot read further or the
// pre-determined file size is reached.
bytes_last_read = fread(buffer.get() + bytes_read, 1, file_size - bytes_read, file);
bytes_read += bytes_last_read;
} while (bytes_last_read > 0 && bytes_read < file_size);
std::string content{buffer.get(), bytes_read};
return content;
}
size_t CapturedStream::_get_file_size(FILE* file)
{
fseek(file, 0, SEEK_END);
return static_cast<size_t>(ftell(file));
}
FILE* CapturedStream::_fopen(char const* path, char const* mode)
{
#if defined(_WIN32)
struct wchar_codecvt : public std::codecvt<wchar_t, char, std::mbstate_t>
{
};
std::wstring_convert<wchar_codecvt> converter;
std::wstring wide_path = converter.from_bytes(path);
std::wstring wide_mode = converter.from_bytes(mode);
return _wfopen(wide_path.c_str(), wide_mode.c_str());
#else
return fopen(path, mode);
#endif // _WIN32
}
int CapturedStream::_fclose(FILE* fp) { return fclose(fp); }
// Starts capturing an output stream (stdout/stderr).
void CaptureStream(int fd, const char* stream_name, CapturedStream** stream)
{
if (*stream != nullptr)
{
FAIL("Only one " << stream_name << " capturer can exist at a time.");
}
*stream = new CapturedStream(fd);
}
// Stops capturing the output stream and returns the captured string.
std::string GetCapturedStream(CapturedStream** captured_stream)
{
std::string content = (*captured_stream)->GetCapturedString();
delete *captured_stream;
*captured_stream = nullptr;
return content;
}
#if defined(_MSC_VER) || defined(__BORLANDC__)
// MSVC and C++Builder do not provide a definition of STDERR_FILENO.
const int kStdOutFileno = 1;
const int kStdErrFileno = 2;
#else
const int kStdOutFileno = STDOUT_FILENO;
const int kStdErrFileno = STDERR_FILENO;
#endif // _MSC_VER
void CaptureStdout() { CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); }
void CaptureStderr() { CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); }
std::string GetCapturedStdout() { return GetCapturedStream(&g_captured_stdout); }
std::string GetCapturedStderr() { return GetCapturedStream(&g_captured_stderr); }
} // namespace testing
} // namespace quill

View file

@ -0,0 +1,97 @@
#pragma once
#include "doctest/doctest.h"
#include "quill/core/Attributes.h"
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
#define REQUIRE_STREQ(str1, str2) \
do \
{ \
if (strcmp(str1, str2) != 0) \
{ \
MESSAGE("Expected equality of these values: \n\t str1: " \
<< std::string_view{str1} << "\n\t str2: " << std::string_view{str2}); \
REQUIRE_UNARY(false); \
}; \
} while (0)
#define REQUIRE_WSTREQ(str1, str2) \
do \
{ \
if (wcscmp(str1, str2) != 0) \
{ \
MESSAGE("Expected equality of these values: \n\t str1: " << str1 << "\n\t str2: " << str2); \
REQUIRE_UNARY(false); \
}; \
} while (0)
#define REQUIRE_STRNEQ(str1, str2) \
do \
{ \
if (strcmp(str1, str2) == 0) \
{ \
MESSAGE("Expected non-equality of these values: \n\t str1: " << str1 << "\n\t str2: " << str2); \
REQUIRE_UNARY(false); \
}; \
} while (0)
namespace quill
{
namespace testing
{
/**
* Object that captures an output stream (stdout/stderr).
*/
class CapturedStream
{
public:
// The ctor redirects the stream to a temporary file.
explicit CapturedStream(int fd);
~CapturedStream();
std::string GetCapturedString();
private:
static std::string _read_entire_file(FILE* file);
static size_t _get_file_size(FILE* file);
static FILE* _fopen(char const* path, char const* mode);
static int _fclose(FILE* fp);
private:
int fd_; // A stream to capture.
int uncaptured_fd_;
std::string filename_; // Name of the temporary file holding the stderr output.
};
QUILL_MAYBE_UNUSED static CapturedStream* g_captured_stderr = nullptr;
QUILL_MAYBE_UNUSED static CapturedStream* g_captured_stdout = nullptr;
// Starts capturing an output stream (stdout/stderr).
void CaptureStream(int fd, const char* stream_name, CapturedStream** stream);
// Stops capturing the output stream and returns the captured string.
std::string GetCapturedStream(CapturedStream** captured_stream);
// Starts capturing stdout.
void CaptureStdout();
// Starts capturing stderr.
void CaptureStderr();
// Stops capturing stdout and returns the captured string.
std::string GetCapturedStdout();
// Stops capturing stderr and returns the captured string.
std::string GetCapturedStderr();
} // namespace testing
} // namespace quill

View file

@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"

View file

@ -0,0 +1,155 @@
#include "TestUtilities.h"
#include <algorithm>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <random>
#include <sstream>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
namespace quill
{
namespace testing
{
// Convert the given file to a vector
std::vector<std::string> file_contents(fs::path const& filename)
{
std::ifstream out_file(filename.string());
std::vector<std::string> lines;
for (std::string current_line; getline(out_file, current_line);)
{
lines.push_back(current_line);
}
return lines;
}
// Convert the given file to a vector
std::vector<std::wstring> wfile_contents(fs::path const& filename)
{
std::wifstream out_file(filename.string());
std::vector<std::wstring> lines;
for (std::wstring current_line; getline(out_file, current_line);)
{
lines.push_back(current_line);
}
return lines;
}
// Search a vector for the given string
bool file_contains(std::vector<std::string> const& file_vector, std::string const& search_string)
{
auto const search =
std::find_if(file_vector.cbegin(), file_vector.cend(), [&search_string](std::string const& elem)
{ return elem.find(search_string) != std::string::npos; });
bool const success = search != file_vector.cend();
if (!success)
{
// We failed to find and we will log for diagnostic reasons
std::cout << "Failed to find '" << search_string << "' in:\n";
for (auto const& line : file_vector)
{
std::cout << "'" << line << "'\n";
}
}
return success;
}
void create_file(fs::path const& filename, std::string const& text)
{
if (std::ofstream file(filename); file.is_open())
{
if (!text.empty())
{
file << text;
}
file.close();
}
}
void remove_file(fs::path const& filename)
{
std::error_code ec;
fs::remove(filename, ec);
}
std::vector<std::string> gen_random_strings(size_t n, int min_len, int max_len)
{
// Generate random strings
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> dist_chars(32, 126);
// length of strings
std::uniform_int_distribution<int> dist_len(min_len, max_len);
// Generate a vector of random strings of dist_len
std::vector<std::string> random_strings_vec;
random_strings_vec.reserve(n);
std::string result;
for (size_t i = 0; i < n; ++i)
{
std::generate_n(std::back_inserter(result), dist_len(mt),
[&] { return static_cast<char>(dist_chars(mt)); });
random_strings_vec.emplace_back(std::move(result));
}
return random_strings_vec;
}
uint64_t parse_timestamp(std::string const& timestamp_str)
{
std::tm tm = {};
std::istringstream ss(timestamp_str);
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); // Extract date and time
// Extract fractional seconds
size_t pos = timestamp_str.find('.');
std::string fractional_seconds_str = timestamp_str.substr(pos + 1, 9);
uint64_t fractional_seconds = std::stoull(fractional_seconds_str);
auto time_point = std::chrono::system_clock::from_time_t(std::mktime(&tm));
auto nanoseconds_since_epoch =
std::chrono::duration_cast<std::chrono::nanoseconds>(time_point.time_since_epoch()).count();
return static_cast<uint64_t>(nanoseconds_since_epoch) + fractional_seconds;
}
bool is_timestamp_ordered(std::vector<std::string> const& file_contents)
{
uint64_t previous_timestamp = 0;
bool first = true;
for (auto const& line : file_contents)
{
uint64_t current_timestamp = parse_timestamp(line);
if (!first)
{
if (current_timestamp < previous_timestamp)
{
return false;
}
}
previous_timestamp = current_timestamp;
first = false;
}
return true;
}
} // namespace testing
} // namespace quill

View file

@ -0,0 +1,30 @@
#pragma once
#include "quill/core/Common.h"
#include "quill/core/Filesystem.h"
#include "DocTestExtensions.h"
#include <cstring>
#include <string>
#include <vector>
namespace quill
{
namespace testing
{
// Convert the given file to a vector
std::vector<std::string> file_contents(fs::path const& filename);
std::vector<std::wstring> wfile_contents(fs::path const& filename);
// Search a vector for the given string
bool file_contains(std::vector<std::string> const& file_vector, std::string const& search_string);
void create_file(fs::path const& filename, std::string const& text = std::string{});
void remove_file(fs::path const& filename);
std::vector<std::string> gen_random_strings(size_t n, int min_len, int max_len);
uint64_t parse_timestamp(std::string const& timestamp_str);
bool is_timestamp_ordered(std::vector<std::string> const& file_contents);
} // namespace testing
} // namespace quill