mirror of
https://github.com/LizardByte/tray.git
synced 2026-11-03 10:37:03 +09:00
Co-authored-by: Thomas Van Laere <4990509+thomvanl@users.noreply.github.com>
203 lines
6.1 KiB
C++
203 lines
6.1 KiB
C++
// standard includes
|
|
#include <array>
|
|
#include <filesystem>
|
|
#include <mutex>
|
|
|
|
// lib includes
|
|
#include <gtest/gtest.h>
|
|
|
|
// test includes
|
|
#include "tests/screenshot_utils.h"
|
|
#include "tests/utils.h"
|
|
|
|
// Undefine the original TEST macro
|
|
#undef TEST
|
|
|
|
// Redefine TEST to use our BaseTest class, to automatically use our BaseTest fixture
|
|
#define TEST(test_case_name, test_name) \
|
|
GTEST_TEST_(test_case_name, test_name, ::BaseTest, ::testing::internal::GetTypeId<::BaseTest>())
|
|
|
|
/**
|
|
* @brief Base class for tests.
|
|
*
|
|
* This class provides a base test fixture for all tests.
|
|
*
|
|
* ``cout``, ``stderr``, and ``stdout`` are redirected to a buffer, and the buffer is printed if the test fails.
|
|
*
|
|
* @todo Retain the color of the original output.
|
|
*/
|
|
class BaseTest: public ::testing::Test {
|
|
protected:
|
|
// https://stackoverflow.com/a/58369622/11214013
|
|
|
|
// we can possibly use some internal googletest functions to capture stdout and stderr, but I have not tested this
|
|
// https://stackoverflow.com/a/33186201/11214013
|
|
|
|
BaseTest() = default;
|
|
|
|
~BaseTest() override = default;
|
|
|
|
void SetUp() override {
|
|
// todo: only run this one time, instead of every time a test is run
|
|
// see: https://stackoverflow.com/questions/2435277/googletest-accessing-the-environment-from-a-test
|
|
// get command line args from the test executable
|
|
testArgs = ::testing::internal::GetArgvs();
|
|
|
|
// then get the directory of the test executable
|
|
// std::string path = ::testing::internal::GetArgvs()[0];
|
|
testBinary = testArgs[0];
|
|
|
|
// get the directory of the test executable
|
|
testBinaryDir = std::filesystem::path(testBinary).parent_path();
|
|
|
|
// If testBinaryDir is empty or `.` then set it to the current directory
|
|
// maybe some better options here: https://stackoverflow.com/questions/875249/how-to-get-current-directory
|
|
if (testBinaryDir.empty() || testBinaryDir.string() == ".") {
|
|
testBinaryDir = std::filesystem::current_path();
|
|
}
|
|
|
|
initializeScreenshotsOnce();
|
|
|
|
sbuf = std::cout.rdbuf(); // save cout buffer (std::cout)
|
|
std::cout.rdbuf(cout_buffer.rdbuf()); // redirect cout to buffer (std::cout)
|
|
}
|
|
|
|
void TearDown() override {
|
|
std::cout.rdbuf(sbuf); // restore cout buffer
|
|
|
|
// get test info
|
|
const ::testing::TestInfo *const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
|
|
|
if (test_info->result()->Failed()) {
|
|
std::cout << std::endl
|
|
<< "Test failed: " << test_info->name() << std::endl
|
|
<< std::endl
|
|
<< "Captured cout:" << std::endl
|
|
<< cout_buffer.str() << std::endl
|
|
<< "Captured stdout:" << std::endl
|
|
<< stdout_buffer.str() << std::endl
|
|
<< "Captured stderr:" << std::endl
|
|
<< stderr_buffer.str() << std::endl;
|
|
}
|
|
|
|
sbuf = nullptr; // clear sbuf
|
|
if (pipe_stdout) {
|
|
pclose(pipe_stdout);
|
|
pipe_stdout = nullptr;
|
|
}
|
|
if (pipe_stderr) {
|
|
pclose(pipe_stderr);
|
|
pipe_stderr = nullptr;
|
|
}
|
|
}
|
|
|
|
// functions and variables
|
|
std::vector<std::string> testArgs; // CLI arguments used
|
|
std::filesystem::path testBinary; // full path of this binary
|
|
std::filesystem::path testBinaryDir; // full directory of this binary
|
|
std::stringstream cout_buffer; // declare cout_buffer
|
|
std::stringstream stdout_buffer; // declare stdout_buffer
|
|
std::stringstream stderr_buffer; // declare stderr_buffer
|
|
std::streambuf *sbuf {nullptr};
|
|
FILE *pipe_stdout {nullptr};
|
|
FILE *pipe_stderr {nullptr};
|
|
bool screenshotsReady {false};
|
|
|
|
void initializeScreenshotsOnce() {
|
|
static std::once_flag screenshotInitFlag;
|
|
std::call_once(screenshotInitFlag, [this]() {
|
|
auto root = testBinaryDir;
|
|
if (!root.empty()) {
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(root / "screenshots", ec);
|
|
}
|
|
screenshot::initialize(root);
|
|
});
|
|
}
|
|
|
|
int exec(const char *cmd) {
|
|
std::array<char, 128> buffer {};
|
|
pipe_stdout = popen((std::string(cmd) + " 2>&1").c_str(), "r");
|
|
pipe_stderr = popen((std::string(cmd) + " 2>&1").c_str(), "r");
|
|
if (!pipe_stdout || !pipe_stderr) {
|
|
throw std::runtime_error("popen() failed!");
|
|
}
|
|
while (fgets(buffer.data(), buffer.size(), pipe_stdout) != nullptr) {
|
|
stdout_buffer << buffer.data();
|
|
}
|
|
while (fgets(buffer.data(), buffer.size(), pipe_stderr) != nullptr) {
|
|
stderr_buffer << buffer.data();
|
|
}
|
|
int returnCode = pclose(pipe_stdout);
|
|
pipe_stdout = nullptr;
|
|
if (returnCode != 0) {
|
|
std::cout << "Error: " << stderr_buffer.str() << std::endl
|
|
<< "Return code: " << returnCode << std::endl;
|
|
}
|
|
return returnCode;
|
|
}
|
|
|
|
bool ensureScreenshotReady() {
|
|
if (screenshotsReady) {
|
|
return true;
|
|
}
|
|
if (std::string reason; !screenshot::is_available(&reason)) {
|
|
screenshotUnavailableReason = reason;
|
|
return false;
|
|
}
|
|
if (const auto root = screenshot::output_root(); root.empty()) {
|
|
screenshotUnavailableReason = "Screenshot output directory not initialized";
|
|
return false;
|
|
}
|
|
screenshotsReady = true;
|
|
return true;
|
|
}
|
|
|
|
bool captureScreenshot(const std::string &name) {
|
|
if (!screenshotsReady) {
|
|
return false;
|
|
}
|
|
bool ok = screenshot::capture(name);
|
|
if (!ok) {
|
|
std::cout << "Failed to capture screenshot: " << name << std::endl;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
std::filesystem::path screenshotsRoot() const {
|
|
return screenshot::output_root();
|
|
}
|
|
|
|
std::string screenshotUnavailableReason;
|
|
};
|
|
|
|
class LinuxTest: public BaseTest {
|
|
protected:
|
|
void SetUp() override {
|
|
#ifndef __linux__
|
|
GTEST_SKIP_("Skipping, this test is for Linux only.");
|
|
#endif
|
|
BaseTest::SetUp();
|
|
}
|
|
};
|
|
|
|
class MacOSTest: public BaseTest {
|
|
protected:
|
|
void SetUp() override {
|
|
#if !defined(__APPLE__) || !defined(__MACH__)
|
|
GTEST_SKIP_("Skipping, this test is for macOS only.");
|
|
#endif
|
|
BaseTest::SetUp();
|
|
}
|
|
};
|
|
|
|
class WindowsTest: public BaseTest {
|
|
protected:
|
|
void SetUp() override { // NOSONAR(cpp:S1185) - contains platform skip logic, not a trivial override
|
|
#ifndef _WIN32
|
|
GTEST_SKIP_("Skipping, this test is for Windows only.");
|
|
#endif
|
|
BaseTest::SetUp();
|
|
}
|
|
};
|