Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,61 @@
#pragma once
#include "testing/testing.hpp"
#include "testing/testregister.hpp"
#include "base/timer.hpp"
#include <iostream>
namespace base
{
class BenchmarkNTimes
{
public:
BenchmarkNTimes(int repeatCount, double maxSecondsToSucceed)
: m_repeatCount(repeatCount)
, m_maxSecondsToSucceed(maxSecondsToSucceed)
, m_iteration(0)
{}
~BenchmarkNTimes()
{
double const secondsElapsed = m_timer.ElapsedSeconds();
TEST_GREATER(m_repeatCount, 0, ());
TEST_LESS_OR_EQUAL(secondsElapsed, m_maxSecondsToSucceed, (m_repeatCount));
std::cout << secondsElapsed << "s total";
if (secondsElapsed > 0)
{
std::cout << ", " << static_cast<int>(m_repeatCount / secondsElapsed) << "/s, ";
/*
if (secondsElapsed / m_repeatCount * 1000 >= 10)
std::cout << static_cast<int>(secondsElapsed / m_repeatCount * 1000) << "ms each";
else */
if (secondsElapsed / m_repeatCount * 1000000 >= 10)
std::cout << static_cast<int>(secondsElapsed / m_repeatCount * 1000000) << "us each";
else
std::cout << static_cast<int>(secondsElapsed / m_repeatCount * 1000000000) << "ns each";
}
std::cout << " ...";
}
int Iteration() const { return m_iteration; }
bool ContinueIterating() const { return m_iteration < m_repeatCount; }
void NextIteration() { ++m_iteration; }
private:
int const m_repeatCount;
double const m_maxSecondsToSucceed;
int m_iteration;
Timer m_timer;
};
} // namespace base
#define BENCHMARK_TEST(name) \
void Benchmark_##name(); \
TestRegister g_BenchmarkRegister_##name("Benchmark::" #name, __FILE__, &Benchmark_##name); \
void Benchmark_##name()
#define BENCHMARK_N_TIMES(times, maxTimeToSucceed) \
for (::base::BenchmarkNTimes benchmark(times, maxTimeToSucceed); benchmark.ContinueIterating(); \
benchmark.NextIteration())

255
libs/testing/testing.hpp Normal file
View file

@ -0,0 +1,255 @@
#pragma once
#include "testing/testregister.hpp"
#include "base/exception.hpp"
#include "base/logging.hpp"
#include "base/math.hpp"
#include "base/src_point.hpp"
#include <string>
// TestRegister is static to avoid this warning:
// No previous extern declaration for non-static variable 'g_testRegister_TESTNAME'
// note: declare 'static' if the variable is not intended to be used outside of this translation unit
#define UNIT_TEST(name) \
void UnitTest_##name(); \
static TestRegister g_testRegister_##name(#name, __FILE__, &UnitTest_##name); \
void UnitTest_##name()
#define UNIT_CLASS_TEST(CLASS, NAME) \
struct UnitClass_##CLASS##_##NAME : public CLASS \
{ \
public: \
void NAME(); \
}; \
UNIT_TEST(CLASS##_##NAME) \
{ \
UnitClass_##CLASS##_##NAME instance; \
instance.NAME(); \
} \
void UnitClass_##CLASS##_##NAME::NAME()
DECLARE_EXCEPTION(TestFailureException, RootException);
namespace base
{
[[noreturn]] inline void OnTestFailed(SrcPoint const & srcPoint, std::string const & msg)
{
LOG(LINFO, ("FAILED"));
LOG(LINFO, (::DebugPrint(srcPoint.FileName()) + ":" + ::DebugPrint(srcPoint.Line()), msg));
MYTHROW(TestFailureException, (srcPoint.FileName(), srcPoint.Line(), msg));
}
} // namespace base
namespace testing
{
void RunEventLoop();
void StopEventLoop();
void Wait();
void Notify();
} // namespace testing
// This struct contains parsed command line options. It may contain pointers to argc contents.
struct CommandLineOptions
{
CommandLineOptions() = default;
char const * m_filterRegExp = nullptr;
char const * m_suppressRegExp = nullptr;
char const * m_dataPath = nullptr;
char const * m_resourcePath = nullptr;
bool m_help = false;
bool m_listTests = false;
};
CommandLineOptions const & GetTestingOptions();
#define TEST(X, msg) \
do \
{ \
if (X) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), ::base::Message("TEST(" #X ")", ::base::Message msg)); \
} \
} \
while (0)
#define TEST_EQUAL(X, Y, msg) \
do \
{ \
if ((X) == (Y)) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), \
::base::Message("TEST(" #X " == " #Y ")", ::base::Message(X, Y), ::base::Message msg)); \
} \
} \
while (0)
#define TEST_NOT_EQUAL(X, Y, msg) \
do \
{ \
if ((X) != (Y)) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), \
::base::Message("TEST(" #X " != " #Y ")", ::base::Message(X, Y), ::base::Message msg)); \
} \
} \
while (0)
#define TEST_LESS(X, Y, msg) \
do \
{ \
if ((X) < (Y)) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), \
::base::Message("TEST(" #X " < " #Y ")", ::base::Message(X, Y), ::base::Message msg)); \
} \
} \
while (0)
#define TEST_LESS_OR_EQUAL(X, Y, msg) \
do \
{ \
if ((X) <= (Y)) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), \
::base::Message("TEST(" #X " <= " #Y ")", ::base::Message(X, Y), ::base::Message msg)); \
} \
} \
while (0)
#define TEST_GREATER(X, Y, msg) \
do \
{ \
if ((X) > (Y)) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), \
::base::Message("TEST(" #X " > " #Y ")", ::base::Message(X, Y), ::base::Message msg)); \
} \
} \
while (0)
#define TEST_GREATER_OR_EQUAL(X, Y, msg) \
do \
{ \
if ((X) >= (Y)) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), \
::base::Message("TEST(" #X " >= " #Y ")", ::base::Message(X, Y), ::base::Message msg)); \
} \
} \
while (0)
#define TEST_ALMOST_EQUAL_ULPS(X, Y, msg) \
do \
{ \
if (AlmostEqualULPs(X, Y)) \
{} \
else \
{ \
::base::OnTestFailed( \
SRC(), ::base::Message("TEST(AlmostEqualULPs(" #X ", " #Y ")", ::base::Message(X, Y), ::base::Message msg)); \
} \
} \
while (0)
#define TEST_NOT_ALMOST_EQUAL_ULPS(X, Y, msg) \
do \
{ \
if (!AlmostEqualULPs(X, Y)) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), ::base::Message("TEST(!AlmostEqualULPs(" #X ", " #Y ")", ::base::Message(X, Y), \
::base::Message msg)); \
} \
} \
while (0)
#define TEST_ALMOST_EQUAL_ABS(X, Y, eps, msg) \
do \
{ \
if (AlmostEqualAbs(X, Y, eps)) \
{} \
else \
{ \
::base::OnTestFailed(SRC(), ::base::Message("TEST(!AlmostEqualAbs(" #X ", " #Y ", " #eps ")", \
::base::Message(X, Y, eps), ::base::Message msg)); \
} \
} \
while (0)
// TODO(AlexZ): Add more cool macroses (or switch all unit tests to gtest).
#define TEST_THROW(X, exception, msg) \
do \
{ \
bool expected_exception = false; \
try \
{ \
X; \
} \
catch (std::exception const &) \
{ \
expected_exception = true; \
} \
catch (...) \
{ \
::base::OnTestFailed(SRC(), ::base::Message("Unexpected exception at TEST(" #X ")", ::base::Message msg)); \
} \
if (!expected_exception) \
::base::OnTestFailed(SRC(), ::base::Message("Expected exception " #exception " was not thrown in TEST(" #X ")", \
::base::Message msg)); \
} \
while (0)
#define TEST_NO_THROW(X, msg) \
do \
{ \
try \
{ \
X; \
} \
catch (RootException const & ex) \
{ \
::base::OnTestFailed(SRC(), ::base::Message("Unexpected exception at TEST(" #X ")", ::base::Message(ex.Msg()), \
::base::Message msg)); \
} \
catch (...) \
{ \
::base::OnTestFailed(SRC(), ::base::Message("Unexpected exception at TEST(" #X ")", ::base::Message msg)); \
} \
} \
while (0)
#define TEST_ANY_THROW(X, msg) \
do \
{ \
bool was_exception = false; \
try \
{ \
X; \
} \
catch (...) \
{ \
was_exception = true; \
} \
if (!was_exception) \
::base::OnTestFailed(SRC(), ::base::Message("No exceptions were thrown in TEST(" #X ")", ::base::Message msg)); \
} \
while (0)

View file

@ -0,0 +1,292 @@
#include "testing/testing.hpp"
#include "testing/testregister.hpp"
#include "base/logging.hpp"
#include "base/timer.hpp"
#include "base/waiter.hpp"
#include "std/target_os.hpp"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <regex>
#include <string>
#include <vector>
#ifdef WITH_GL_MOCK
#include "base/scope_guard.hpp"
#include "drape/drape_tests/gl_mock_functions.hpp"
#endif
#ifdef OMIM_OS_IPHONE
#include <CoreFoundation/CoreFoundation.h>
#endif
#ifndef OMIM_UNIT_TEST_DISABLE_PLATFORM_INIT
#include "platform/platform.hpp"
#endif
#if defined(OMIM_UNIT_TEST_WITH_QT_EVENT_LOOP) && !defined(OMIM_OS_IPHONE)
#include <QtCore/Qt>
#ifdef OMIM_OS_MAC // on Mac OS X native run loop works only for QApplication :(
#include <QtWidgets/QApplication>
#define QAPP QApplication
#else
#include <QtCore/QCoreApplication>
#define QAPP QCoreApplication
#endif
#endif
namespace testing
{
using namespace std;
base::Waiter g_waiter;
void RunEventLoop()
{
#if defined(OMIM_OS_IPHONE)
CFRunLoopRun();
#elif defined(QAPP)
QAPP::exec();
#endif
}
void StopEventLoop()
{
#if defined(OMIM_OS_IPHONE)
CFRunLoopStop(CFRunLoopGetMain());
#elif defined(QAPP)
QAPP::exit();
#endif
}
void Wait()
{
g_waiter.Wait();
g_waiter.Reset();
}
void Notify()
{
g_waiter.Notify();
}
bool g_lastTestOK = true;
CommandLineOptions g_testingOptions;
int const kOptionFieldWidth = 32;
char const kFilterOption[] = "--filter=";
char const kSuppressOption[] = "--suppress=";
char const kHelpOption[] = "--help";
char const kDataPathOptions[] = "--data_path=";
char const kResourcePathOptions[] = "--user_resource_path=";
char const kListAllTestsOption[] = "--list_tests";
enum Status
{
STATUS_SUCCESS = 0,
STATUS_FAILED = 1,
STATUS_BROKEN_FRAMEWORK = 5,
};
void DisplayOption(ostream & os, char const * option, char const * description)
{
os << " " << setw(kOptionFieldWidth) << left << option << " " << description << '\n';
}
void DisplayOption(ostream & os, char const * option, char const * value, char const * description)
{
os << " " << setw(kOptionFieldWidth) << left << (string(option) + value) << " " << description << '\n';
}
void Usage(char const * name)
{
cerr << "USAGE: " << name << " [options]\n\n";
cerr << "OPTIONS:\n";
DisplayOption(cerr, kFilterOption, "<ECMA Regexp>", "Run tests with names corresponding to regexp.");
DisplayOption(cerr, kSuppressOption, "<ECMA Regexp>", "Do not run tests with names corresponding to regexp.");
DisplayOption(cerr, kDataPathOptions, "<Path>", "Path to data files.");
DisplayOption(cerr, kResourcePathOptions, "<Path>", "Path to resources, styles and classificators.");
DisplayOption(cerr, kListAllTestsOption, "List all the tests in the test suite and exit.");
DisplayOption(cerr, kHelpOption, "Print this help message and exit.");
}
void ParseOptions(int argc, char * argv[], CommandLineOptions & options)
{
for (int i = 1; i < argc; ++i)
{
std::string_view const arg = argv[i];
if (arg.starts_with(kFilterOption))
options.m_filterRegExp = argv[i] + sizeof(kFilterOption) - 1;
if (arg.starts_with(kSuppressOption))
options.m_suppressRegExp = argv[i] + sizeof(kSuppressOption) - 1;
if (arg.starts_with(kDataPathOptions))
options.m_dataPath = argv[i] + sizeof(kDataPathOptions) - 1;
if (arg.starts_with(kResourcePathOptions))
options.m_resourcePath = argv[i] + sizeof(kResourcePathOptions) - 1;
if (arg == kHelpOption)
options.m_help = true;
if (arg == kListAllTestsOption)
options.m_listTests = true;
}
#ifndef OMIM_UNIT_TEST_DISABLE_PLATFORM_INIT
// Setting stored paths from testingmain.cpp
Platform & pl = GetPlatform();
if (options.m_dataPath)
pl.SetWritableDirForTests(options.m_dataPath);
if (options.m_resourcePath)
{
pl.SetResourceDir(options.m_resourcePath);
pl.SetSettingsDir(options.m_resourcePath);
}
#endif
}
CommandLineOptions const & GetTestingOptions()
{
return g_testingOptions;
}
int main(int argc, char * argv[])
{
#if defined(OMIM_UNIT_TEST_WITH_QT_EVENT_LOOP) && !defined(OMIM_OS_IPHONE)
QAPP theApp(argc, argv);
UNUSED_VALUE(theApp);
#else
UNUSED_VALUE(argc);
UNUSED_VALUE(argv);
#endif
base::ScopedLogLevelChanger const infoLogLevel(LINFO);
#if defined(OMIM_OS_DESKTOP) || defined(OMIM_OS_IPHONE)
base::SetLogMessageFn(base::LogMessageTests);
#endif
#ifdef WITH_GL_MOCK
emul::GLMockFunctions::Init(&argc, argv);
SCOPE_GUARD(GLMockScope, bind(&emul::GLMockFunctions::Teardown));
#endif
vector<string> testnames;
vector<bool> testResults;
int numFailedTests = 0;
ParseOptions(argc, argv, g_testingOptions);
if (g_testingOptions.m_help)
{
Usage(argv[0]);
return STATUS_SUCCESS;
}
regex filterRegExp;
if (g_testingOptions.m_filterRegExp)
filterRegExp.assign(g_testingOptions.m_filterRegExp);
regex suppressRegExp;
if (g_testingOptions.m_suppressRegExp)
suppressRegExp.assign(g_testingOptions.m_suppressRegExp);
for (TestRegister * test = TestRegister::FirstRegister(); test; test = test->m_next)
{
string filename(test->m_filename);
string testname(test->m_testname);
// Retrieve fine file name.
auto const lastSlash = filename.find_last_of("\\/");
if (lastSlash != string::npos)
filename.erase(0, lastSlash + 1);
testnames.push_back(filename + "::" + testname);
testResults.push_back(true);
}
if (GetTestingOptions().m_listTests)
{
for (auto const & name : testnames)
cout << name << '\n';
return 0;
}
int testIndex = 0;
for (TestRegister * test = TestRegister::FirstRegister(); test; ++testIndex, test = test->m_next)
{
auto const & testname = testnames[testIndex];
if (g_testingOptions.m_filterRegExp && !regex_search(testname.begin(), testname.end(), filterRegExp))
continue;
if (g_testingOptions.m_suppressRegExp && regex_search(testname.begin(), testname.end(), suppressRegExp))
continue;
LOG(LINFO, ("Running", testname));
if (!g_lastTestOK)
{
// Somewhere else global variables have been reset.
LOG(LERROR, ("\n\nSOMETHING IS REALLY WRONG IN THE UNIT TEST FRAMEWORK!!!"));
return STATUS_BROKEN_FRAMEWORK;
}
base::HighResTimer timer(true);
try
{
// Run the test.
test->m_fn();
#ifdef WITH_GL_MOCK
emul::GLMockFunctions::ValidateAndClear();
#endif
if (g_lastTestOK)
{
LOG(LINFO, ("OK"));
}
else
{
testResults[testIndex] = false;
++numFailedTests;
}
}
catch (TestFailureException const &)
{
testResults[testIndex] = false;
++numFailedTests;
}
catch (exception const & ex)
{
LOG(LERROR, ("FAILED", "<<<Exception thrown [", ex.what(), "].>>>"));
testResults[testIndex] = false;
++numFailedTests;
}
catch (...)
{
LOG(LERROR, ("FAILED<<<Unknown exception thrown.>>>"));
testResults[testIndex] = false;
++numFailedTests;
}
g_lastTestOK = true;
uint64_t const elapsed = timer.ElapsedNanoseconds();
LOG(LINFO, ("Test took", elapsed / 1000000, "ms\n"));
}
if (numFailedTests != 0)
{
LOG(LINFO, (numFailedTests, " tests failed:"));
for (size_t i = 0; i < testnames.size(); ++i)
if (!testResults[i])
LOG(LINFO, (testnames[i]));
LOG(LINFO, ("Some tests FAILED."));
return STATUS_FAILED;
}
LOG(LINFO, ("All tests passed."));
return STATUS_SUCCESS;
}
} // namespace testing
int main(int argc, char * argv[])
{
return ::testing::main(argc, argv);
}

View file

@ -0,0 +1,45 @@
#pragma once
#include <functional>
#include <utility>
class TestRegister
{
public:
TestRegister(char const * testname, char const * filename, std::function<void()> && fnTest)
: m_testname(testname)
, m_filename(filename)
, m_fn(std::move(fnTest))
, m_next(nullptr)
{
if (FirstRegister() == nullptr)
{
FirstRegister() = this;
}
else
{
TestRegister * last = FirstRegister();
while (last->m_next != nullptr)
last = last->m_next;
last->m_next = this;
}
}
static TestRegister *& FirstRegister()
{
static TestRegister * test = nullptr;
return test;
}
// Test name.
char const * m_testname;
// File name.
char const * m_filename;
// Function to run test.
std::function<void()> m_fn;
// Next test in chain.
TestRegister * m_next;
};