//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2019 James D. Mitchell
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#define CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS
#define CATCH_CONFIG_FAST_COMPILE
#define CATCH_CONFIG_MAIN
#include <algorithm>
// for transform, find_if
#include <cctype>
// for tolower
#include <chrono>
// for nanoseconds
#include <cmath>
// for pow
#include <cstdlib>
// for exit, size_t
#include <iomanip>
// for operator<<, setw
#include <iostream>
// for string, operator<<, basic_ostream
#include <locale>
// for locale
#include <string>
// for char_traits, allocator, hash
#include <unordered_map>
// for unordered_map, __hash_map_iterator
#include <vector>
// for vector
#include "catch.hpp" // for Colour, Colour::Code::BrightRed, CATCH_REGISTER...
#include "libsemigroups/config.hpp" // for LIBSEMIGROUPS_BACKWARD_ENABLED
#include "libsemigroups/string.hpp" // for to_string, unicode_string_length
#include "libsemigroups/timer.hpp" // for Timer
#ifdef LIBSEMIGROUPS_BACKWARD_ENABLED
#if defined(__CYGWIN__) ||
defined(__CYGWIN32__)
#undef LIBSEMIGROUPS_BACKWARD_ENABLED
#else
#include "backward-cpp/backward.hpp" // for StackTrace, TraceResolver, Reso...
#endif
#endif
namespace {
#ifdef LIBSEMIGROUPS_BACKWARD_ENABLED
struct InstallSIGINTHandler {
InstallSIGINTHandler() {
signal(SIGINT, signalHandler);
}
static void signalHandler(
int signum) {
using namespace backward;
StackTrace st;
st.load_here(32);
Printer p;
p.print(st);
exit(signum);
}
} InstallSIGINTHandler_;
#endif
}
// namespace
struct LibsemigroupsLineInfo {
explicit LibsemigroupsLineInfo(
Catch::TestCaseInfo
const& testInfo)
: _file(testInfo.lineInfo.file),
_line(libsemigroups::detail::to_string(testInfo.lineInfo.line)) {}
std::string _file;
std::string _line;
};
struct LibsemigroupsListener :
Catch::TestEventListenerBase {
using TestEventListenerBase::TestEventListenerBase;
// inherit constructor
void testCaseStarting(
Catch::TestCaseInfo
const& testInfo) override {
_first_call =
true;
_one = testInfo.name;
_two =
" . . .";
_three =
"";
_pad = 0;
_depth = 0;
static std::unordered_map<std::string, LibsemigroupsLineInfo> map;
auto pos = testInfo.name.find(
":");
if (pos == std::string::npos) {
// Just run the test, and ignore the registration of the id
return;
}
auto exp_tag
= std::string(testInfo.name.cbegin(),
testInfo.name.cbegin() + testInfo.name.find(
":"));
if (!find_tag(testInfo, exp_tag)) {
// Just run the test, and ignore the registration of the id
return;
}
auto it = map.find(exp_tag);
if (it != map.end()) {
{
Catch::Colour colourGuard(
Catch::Colour::BrightRed);
Catch::cerr() <<
"Duplicate test case: [" << exp_tag <<
"]!\n"
<<
" first seen at: " << (*it).second._file <<
":"
<< (*it).second._line <<
"\n"
<<
" redefined at: " << testInfo.lineInfo.file <<
":"
<< testInfo.lineInfo.line <<
"\n";
}
std::
exit(1);
}
else {
map.emplace(exp_tag, LibsemigroupsLineInfo(testInfo));
}
check_category(testInfo);
}
void sectionEnded(
Catch::SectionStats
const& sectionStats) override {
if (_one != sectionStats.sectionInfo.name) {
_two =
" . . .\n";
if (_three.empty()) {
_three =
" -- with " + sectionStats.sectionInfo.name;
}
else {
_three +=
" and " + sectionStats.sectionInfo.name;
}
_pad = 79 - _three.size();
}
else {
size_t t =
static_cast<size_t>(sectionStats.durationInSeconds
* std::pow(10, 9));
if (_first_call) {
if (_one.size() + _two.size() > 72) {
_one = _one.substr(0, 66 - _two.size()) +
" [...]";
}
Catch::Colour colourGuard(
Catch::Colour::BrightWhite);
std::cout << _one << _two;
_first_call =
false;
}
if (_three.empty()) {
_pad = 79 - (_one.size() + _two.size());
}
auto ts
= libsemigroups::detail::Timer::string(std::chrono::nanoseconds(t));
_pad -= libsemigroups::detail::unicode_string_length(ts);
std::cout << _three << std::setw(_pad) <<
"";
{
Catch::Colour colourGuard(
Catch::Colour::BrightWhite);
std::cout << ts <<
"\n";
}
_three.clear();
}
}
// The way the total time below is displayed is unattractive, so decided to
// leave this out for the time being.
// void testRunEnded(Catch::TestRunStats const&) override {
// std::cout << libsemigroups::detail::Timer::string(
// std::chrono::nanoseconds(_total_time))
// << std::endl;
//}
bool find_tag(
Catch::TestCaseInfo
const& testInfo, std::string tag) {
std::transform(tag.begin(), tag.end(), tag.begin(), ::tolower);
return std::find_if(testInfo.tags.cbegin(),
testInfo.tags.cend(),
[&tag](std::string t) ->
bool {
std::transform(
t.begin(), t.end(), t.begin(), ::tolower);
return t == tag;
})
!= testInfo.tags.cend();
}
void check_category(
Catch::TestCaseInfo
const& testInfo) {
if (!(find_tag(testInfo,
"quick") || find_tag(testInfo,
"standard")
|| find_tag(testInfo,
"extreme") || find_tag(testInfo,
"fail"))) {
{
Catch::Colour colourGuard(
Catch::Colour::BrightRed);
Catch::cerr()
<<
"Missing category tag: [quick|standard|extreme|fail]!\n"
<<
" in test case at " << testInfo.lineInfo.file <<
":"
<< testInfo.lineInfo.line <<
"\n";
}
std::
exit(1);
}
}
bool _first_call;
size_t _total_time = 0;
std::string _one;
std::string _two;
std::string _three;
size_t _pad;
size_t _depth;
};
CATCH_REGISTER_LISTENER(LibsemigroupsListener)