Files
MapLibreParticleLab/mapbox-gl-native/expression-test/expression_test_logger.cpp
2020-12-16 17:26:47 +01:00

178 lines
6.5 KiB
C++

#include "expression_test_logger.hpp"
#include "expression_test_runner.hpp"
#include "filesystem.hpp"
#include <mbgl/util/io.hpp>
#include <mbgl/util/string.hpp>
#include <sstream>
using namespace mbgl;
using namespace std::literals;
namespace {
const char* resultsStyle = R"HTML(
<style>
body { font: 18px/1.2 -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif; padding: 10px; }
h1 { font-size: 32px; margin-bottom: 0; }
button { vertical-align: middle; }
h2 { font-size: 24px; font-weight: normal; margin: 10px 0 10px; line-height: 1; }
.test { border-bottom: 1px dotted #bbb; padding-bottom: 5px; }
.tests { border-top: 1px dotted #bbb; margin-top: 10px; }
.test p, .test pre { margin: 0 0 10px; }
.test pre { font-size: 14px; }
.label { color: white; font-size: 18px; padding: 2px 6px 3px; border-radius: 3px; margin-right: 3px; vertical-align: bottom; display: inline-block; }
.hide { display: none; }
.test.failed > h2 > span { background: red; }
.test.passed > h2 > span { background: green; }
.test.ignored > h2 > span { background: grey; }
.test.errored > h2 > span { background: #ff006f; }
.test.ignored.passed > h2 > span { background: #E8A408; }
</style>
)HTML";
const char* resultsHeaderButtons = R"HTML(
<button id='toggle-sequence'>Toggle test sequence</button>
<button id='toggle-passed'>Toggle passed tests</button>
<button id='toggle-ignored'>Toggle ignored tests</button>
</h1>
)HTML";
const char* resultsScript = R"HTML(
<script>
document.getElementById('toggle-passed').addEventListener('click', function (e) {
for (const row of document.querySelectorAll('.test.passed')) {
row.classList.toggle('hide');
}
});
document.getElementById('toggle-ignored').addEventListener('click', function (e) {
for (const row of document.querySelectorAll('.test.ignored')) {
row.classList.toggle('hide');
}
});
document.getElementById('toggle-sequence').addEventListener('click', function (e) {
document.getElementById('test-sequence').classList.toggle('hide');
});
</script>
)HTML";
std::string createResultItem(const TestRunOutput& result, const std::string& status, bool shouldHide) {
std::ostringstream html;
html << "<div class=\"test " << status << (shouldHide ? " hide" : "") << "\">\n";
html << R"(<h2><span class="label">)" << status << "</span> " << result.id << "</h2>\n";
html << "<p><pre>"s << result.expression << "</pre></p>\n"s;
if (result.passed) {
html << "<strong>Serialized:</strong><p><pre>"s << result.serialized << "</pre></p>\n"s;
} else {
html << "<p><strong>Difference:</strong><pre>" << result.text << "</pre></p>\n";
}
html << "</div>\n";
return html.str();
}
std::string createResultPage(const TestStats& stats, bool shuffle, uint32_t seed) {
const std::size_t unsuccessfulCount = stats.errored.size() + stats.failed.size();
const bool unsuccessful = unsuccessfulCount > 0;
std::ostringstream resultsPage;
// Style
resultsPage << resultsStyle;
// Header with buttons
if (unsuccessful) {
resultsPage << R"HTML(<h1 style="color: red;">)HTML";
resultsPage << util::toString(unsuccessfulCount) << " tests failed.";
} else {
resultsPage << R"HTML(<h1 style="color: green;">)HTML";
resultsPage << "All tests passed!";
}
resultsPage << resultsHeaderButtons;
// Test sequence
{
resultsPage << "<div id='test-sequence' class='hide'>\n";
// Failed tests
if (unsuccessful) {
resultsPage << "<p><strong>Failed tests:</strong>";
for (const auto& failed : stats.failed) {
resultsPage << failed.id << " ";
}
resultsPage << "<p><strong>Errored tests:</strong>";
for (const auto& errored : stats.errored) {
resultsPage << errored.id << " ";
}
resultsPage << "</p>\n";
}
// Test sequence
resultsPage << "<p><strong>Test sequence: </strong>";
for (const auto& id : stats.ids) {
resultsPage << id << " ";
}
resultsPage << "</p>\n";
// Shuffle
if (shuffle) {
resultsPage << "<p><strong>Shuffle seed</strong>: " << util::toString(seed) << "</p>\n";
}
resultsPage << "</div>\n";
}
// Script
resultsPage << resultsScript;
// Tests
resultsPage << "<div class=\"tests\">\n";
const auto appendResult = [&] (const auto& results, const std::string& status, bool hide = false) {
for (const auto& result : results) {
resultsPage << createResultItem(result, status, hide);
}
};
appendResult(stats.passed, "passed"s, unsuccessful);
appendResult(stats.failed, "failed"s);
appendResult(stats.errored, "errored"s);
appendResult(stats.ignorePassed, "ignored passed"s, unsuccessful);
appendResult(stats.ignoreFailed, "ignored"s, true);
resultsPage << "</div>\n";
return resultsPage.str();
}
} // namespace
void printStats(const TestStats& stats) {
const std::size_t count = stats.testCount();
if (std::size_t passedTests = stats.passed.size()) {
printf(ANSI_COLOR_GREEN "%zu passed (%.1lf%%)" ANSI_COLOR_RESET "\n", passedTests, 100.0 * passedTests / count);
}
if (std::size_t ignorePassedTests = stats.ignorePassed.size()) {
printf(ANSI_COLOR_YELLOW "%zu passed but were ignored (%.1lf%%)" ANSI_COLOR_RESET "\n", ignorePassedTests, 100.0 * ignorePassedTests / count);
}
if (std::size_t ignoreFailedTests = stats.ignoreFailed.size()) {
printf(ANSI_COLOR_LIGHT_GRAY "%zu ignored (%.1lf%%)" ANSI_COLOR_RESET "\n", ignoreFailedTests, 100.0 * ignoreFailedTests / count);
}
if (std::size_t failedTests = stats.failed.size()) {
printf(ANSI_COLOR_RED "%zu failed (%.1lf%%)" ANSI_COLOR_RESET "\n", failedTests, 100.0 * failedTests / count);
}
if (std::size_t erroredTests = stats.errored.size()) {
printf(ANSI_COLOR_RED "%zu errored (%.1lf%%)" ANSI_COLOR_RESET "\n", erroredTests, 100.0 * erroredTests / count);
}
}
void writeHTMLResults(const TestStats& stats, const std::string& rootPath, bool shuffle, uint32_t seed) {
filesystem::path path = filesystem::path(rootPath) / "index.html"s;
try {
util::write_file(path.string(), createResultPage(stats, shuffle, seed));
printf("Results at: %s\n", path.string().c_str());
} catch (std::exception&) {
printf(ANSI_COLOR_RED "* ERROR can't write result page %s" ANSI_COLOR_RESET "\n", path.string().c_str());
}
}