191 lines
4.8 KiB
C++
191 lines
4.8 KiB
C++
#include <psemek/test/test.hpp>
|
|
#include <psemek/util/pretty_print.hpp>
|
|
#include <psemek/log/log.hpp>
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <chrono>
|
|
|
|
namespace psemek::test
|
|
{
|
|
|
|
static std::map<std::string, void(*)(context &)> tests;
|
|
|
|
static std::string normalize(std::string name)
|
|
{
|
|
std::replace(name.begin(), name.end(), '_', '/');
|
|
|
|
for (std::size_t i = 0;;)
|
|
{
|
|
i = name.find("//", i);
|
|
if (i == std::string::npos)
|
|
break;
|
|
name.replace(i, 2, "_");
|
|
++i;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
void add_test_case(char const * name, void(*f)(context &))
|
|
{
|
|
std::string pname = normalize(name);
|
|
|
|
if (tests.count(pname))
|
|
{
|
|
std::cerr << "Test " << pname << " already registered" << std::endl;
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
|
|
tests[pname] = f;
|
|
}
|
|
|
|
struct sink
|
|
: log::sink
|
|
{
|
|
std::vector<std::pair<log::level, std::string>> messages;
|
|
|
|
void put_message(log::message const & msg) override
|
|
{
|
|
messages.push_back({msg.level, std::string(msg.message)});
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
int main(int argc, char ** argv)
|
|
{
|
|
std::set<std::string> tests;
|
|
if (argc == 1)
|
|
{
|
|
for (auto const & p : psemek::test::tests)
|
|
tests.insert(p.first);
|
|
}
|
|
else
|
|
{
|
|
for (int a = 1; a < argc; ++a)
|
|
{
|
|
std::string pattern = std::string(argv[a]);
|
|
if (pattern.empty())
|
|
{
|
|
std::cerr << "Empty test case pattern in argument list" << std::endl;
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
if (pattern.back() != '/')
|
|
pattern += "/";
|
|
for (auto const & p : psemek::test::tests)
|
|
{
|
|
if (p.first == argv[a] || p.first.starts_with(pattern))
|
|
tests.insert(p.first);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::size_t test_count = tests.size();
|
|
|
|
std::size_t const test_index_len = std::ceil(std::log10(test_count + 1));
|
|
|
|
std::string const indent = std::string(2 * test_index_len + 6, ' ');
|
|
|
|
std::size_t max_name_length = 0;
|
|
for (auto const & name : tests)
|
|
max_name_length = std::max(max_name_length, name.size());
|
|
|
|
std::size_t success = 0;
|
|
|
|
using clock = std::chrono::high_resolution_clock;
|
|
|
|
auto all_start = clock::now();
|
|
|
|
std::unique_ptr<psemek::log::sink> sink = std::make_unique<psemek::test::sink>();
|
|
auto sink_ptr = static_cast<psemek::test::sink *>(sink.get());
|
|
|
|
psemek::log::register_thread("test");
|
|
|
|
std::size_t i = 0;
|
|
for (auto const & name : tests)
|
|
{
|
|
++i;
|
|
|
|
std::cout
|
|
<< '[' << std::setfill(' ') << std::setw(test_index_len) << std::right << i << '/' << test_count << "] "
|
|
<< std::left << std::setfill('.') << std::setw(max_name_length + 5) << name;
|
|
|
|
psemek::test::context ctx;
|
|
|
|
psemek::log::add_sink(std::move(sink));
|
|
|
|
bool failure = true;
|
|
|
|
auto start = clock::now();
|
|
try
|
|
{
|
|
psemek::test::tests[name](ctx);
|
|
auto end = clock::now();
|
|
std::cout << "ok " << psemek::util::pretty(end - start, std::chrono::milliseconds{1});
|
|
|
|
auto filter = [](auto const & m){ return m.first >= psemek::log::level::info; };
|
|
if (std::count_if(sink_ptr->messages.begin(), sink_ptr->messages.end(), filter) == 1)
|
|
{
|
|
std::cout << " " << std::find_if(sink_ptr->messages.begin(), sink_ptr->messages.end(), filter)->second;
|
|
sink_ptr->messages.clear();
|
|
}
|
|
std::cout << std::endl;
|
|
failure = false;
|
|
++success;
|
|
}
|
|
catch (psemek::test::failure const & e)
|
|
{
|
|
auto end = clock::now();
|
|
std::cout << "failure " << psemek::util::pretty(end - start, std::chrono::milliseconds{1}) << std::endl;
|
|
std::cout << indent << "Reason: " << e.message() << std::endl;
|
|
std::cout << indent << "Location: " << e.location() << std::endl;
|
|
}
|
|
catch (std::exception const & e)
|
|
{
|
|
auto end = clock::now();
|
|
std::cout << "failure: " << e.what() << " " << psemek::util::pretty(end - start, std::chrono::milliseconds{1}) << std::endl;
|
|
}
|
|
catch (...)
|
|
{
|
|
auto end = clock::now();
|
|
std::cout << "failure: (unknown exception) " << psemek::util::pretty(end - start, std::chrono::milliseconds{1}) << std::endl;
|
|
}
|
|
|
|
sink = psemek::log::remove_sink(sink_ptr);
|
|
|
|
for (auto const & msg : sink_ptr->messages)
|
|
{
|
|
if (failure || msg.first >= psemek::log::level::info)
|
|
std::cout << indent << msg.second << std::endl;
|
|
}
|
|
sink_ptr->messages.clear();
|
|
|
|
std::size_t max_profile_name_len = 0;
|
|
for (auto const & p : ctx.profile)
|
|
max_profile_name_len = std::max(max_profile_name_len, p.name.size());
|
|
|
|
for (auto const & p : ctx.profile)
|
|
{
|
|
std::cout << indent
|
|
<< std::setfill(' ') << std::setw(max_profile_name_len + 5) << p.name
|
|
<< psemek::util::pretty(p.duration, std::chrono::milliseconds{1})
|
|
<< (p.ended_with_exception ? " (exception)" : "")
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
auto all_end = clock::now();
|
|
|
|
std::cout << std::endl;
|
|
std::cout << (success == test_count ? "Success: " : "Failure: ") << success << '/' << test_count << " passed ("
|
|
<< psemek::util::pretty(all_end - all_start, std::chrono::milliseconds{1}) << ")" << std::endl;
|
|
|
|
if (success <= test_count)
|
|
return EXIT_FAILURE;
|
|
}
|