feat(field-support): added field support system, mid migration

currently the barotope and the pressure force operator are migrated to the new support system
This commit is contained in:
2026-08-23 10:13:53 -04:00
parent dc912fd15e
commit 0f3ca8050b
137 changed files with 29975 additions and 16389 deletions

View File

@@ -11,6 +11,8 @@
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
#include <CLI/CLI.hpp>
@@ -65,15 +67,13 @@ std::string ansiToHtml(const std::string &text) {
while (i < len) {
// Look for ANSI CSI sequence '\033[' or '\x1b['
if ((htmlEscaped[i] == '\033' || htmlEscaped[i] == '\x1b') &&
i + 1 < len && htmlEscaped[i + 1] == '[') {
if ((htmlEscaped[i] == '\033' || htmlEscaped[i] == '\x1b') && i + 1 < len && htmlEscaped[i + 1] == '[') {
size_t seqStart = i + 2;
size_t seqEnd = htmlEscaped.find('m', seqStart);
if (seqEnd != std::string::npos) {
std::string codeStr =
htmlEscaped.substr(seqStart, seqEnd - seqStart);
i = seqEnd + 1;
std::string codeStr = htmlEscaped.substr(seqStart, seqEnd - seqStart);
i = seqEnd + 1;
std::istringstream codeStream(codeStr);
std::string codeVal;
@@ -229,10 +229,28 @@ class CheckReporter : public Catch::StreamingReporterBase {
std::vector<std::string> m_currentFailures;
std::vector<std::string> m_currentInfos;
std::unordered_set<unsigned int> m_currentInfoSequences;
std::vector<TestCaseData> m_testRunData;
void captureInfoMessages(Catch::AssertionStats const &assertionStats) {
for (auto const &message : assertionStats.infoMessages) {
if (m_currentInfoSequences.insert(message.sequence).second) {
m_currentInfos.push_back(message.message);
}
}
}
public:
using StreamingReporterBase::StreamingReporterBase;
explicit CheckReporter(Catch::ReporterConfig &&config) : Catch::StreamingReporterBase(std::move(config)) {
// INFO messages are delivered through assertionEnded. Request passing
// assertions as well so HTML logging does not depend on Catch2's -s
// flag.
m_preferences.shouldReportAllAssertions = true;
// This reporter does not use assertionStarting events. Disabling them
// preserves Catch2's successful-assertion fast path where possible.
m_preferences.shouldReportAllAssertionStarts = false;
}
static std::string getDescription() {
return "Console reporter with wrapping, tags, and collapsible HTML "
@@ -245,28 +263,26 @@ public:
std::cout << '\n';
std::cout << std::left << std::setw(85) << "Test Case Name"
<< "Status " << std::right << std::setw(8) << "Passed"
<< std::setw(8) << "Failed" << '\n';
<< "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << '\n';
std::cout << std::string(121, '-') << '\n';
}
void assertionEnded(Catch::AssertionStats const &assertionStats) override {
StreamingReporterBase::assertionEnded(assertionStats);
// Capture INFO messages regardless of pass/fail status
for (auto const &msg : assertionStats.infoMessages) {
m_currentInfos.push_back(msg.message);
}
// Capture every INFO message encountered by either a passing or failing
// assertion. Message sequence IDs prevent a scoped INFO from being
// repeated once for every assertion that occurs while it remains
// active.
captureInfoMessages(assertionStats);
if (!assertionStats.assertionResult.isOk()) {
auto const &result = assertionStats.assertionResult;
std::ostringstream oss;
oss << " \033[31m-> FAILED:\033[0m "
<< result.getSourceInfo().file << ":"
<< result.getSourceInfo().line << '\n';
oss << " " << result.getTestMacroName() << "( "
<< result.getExpression() << " )\n";
oss << " \033[31m-> FAILED:\033[0m " << result.getSourceInfo().file << ":" << result.getSourceInfo().line
<< '\n';
oss << " " << result.getTestMacroName() << "( " << result.getExpression() << " )\n";
if (result.hasExpandedExpression()) {
oss << " with expansion:\n"
@@ -290,10 +306,8 @@ public:
std::string name = stats.testInfo->name;
auto wrappedName = wrapText(name, 83);
std::cout << std::left << std::setw(85) << wrappedName[0] << mark
<< " " << std::right << std::setw(8)
<< stats.totals.assertions.passed << std::setw(8)
<< stats.totals.assertions.failed << '\n';
std::cout << std::left << std::setw(85) << wrappedName[0] << mark << " " << std::right << std::setw(8)
<< stats.totals.assertions.passed << std::setw(8) << stats.totals.assertions.failed << '\n';
for (size_t i = 1; i < wrappedName.size(); ++i) {
std::cout << " \033[90m↳ \033[0m" // Dim indent arrow
@@ -317,12 +331,13 @@ public:
}
m_testRunData.push_back(
{name, tagsStr, passed, stats.totals.assertions.passed,
stats.totals.assertions.failed, m_currentFailures, m_currentInfos}
{name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, m_currentFailures,
m_currentInfos}
);
m_currentFailures.clear();
m_currentInfos.clear();
m_currentInfoSequences.clear();
}
void testRunEnded(Catch::TestRunStats const &_testRunStats) override {
@@ -334,27 +349,17 @@ public:
auto const &as = _testRunStats.totals.assertions;
std::string tc_passed_str =
tc.passed > 0
? "\033[32m" + std::to_string(tc.passed) + " passed\033[0m"
: "0 passed";
tc.passed > 0 ? "\033[32m" + std::to_string(tc.passed) + " passed\033[0m" : "0 passed";
std::string tc_failed_str =
tc.failed > 0
? "\033[31m" + std::to_string(tc.failed) + " failed\033[0m"
: "0 failed";
tc.failed > 0 ? "\033[31m" + std::to_string(tc.failed) + " failed\033[0m" : "0 failed";
std::string as_passed_str =
as.passed > 0
? "\033[32m" + std::to_string(as.passed) + " passed\033[0m"
: "0 passed";
as.passed > 0 ? "\033[32m" + std::to_string(as.passed) + " passed\033[0m" : "0 passed";
std::string as_failed_str =
as.failed > 0
? "\033[31m" + std::to_string(as.failed) + " failed\033[0m"
: "0 failed";
as.failed > 0 ? "\033[31m" + std::to_string(as.failed) + " failed\033[0m" : "0 failed";
std::cout << "Test Cases: " << tc_passed_str << ", " << tc_failed_str
<< ", " << tc.total() << " total\n";
std::cout << "Assertions: " << as_passed_str << ", " << as_failed_str
<< ", " << as.total() << " total\n\n";
std::cout << "Test Cases: " << tc_passed_str << ", " << tc_failed_str << ", " << tc.total() << " total\n";
std::cout << "Assertions: " << as_passed_str << ", " << as_failed_str << ", " << as.total() << " total\n\n";
generateHtmlReport(_testRunStats);
}
@@ -365,68 +370,66 @@ private:
if (!html)
return;
html
<< "<!DOCTYPE html>\n<html lang='en'>\n<head>\n"
<< "<meta charset='UTF-8'>\n"
<< "<meta name='viewport' content='width=device-width, "
"initial-scale=1.0'>\n"
<< "<title>Test Run Summary</title>\n"
<< "<style>\n"
<< "body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe "
"UI', "
"Roboto, Helvetica, Arial, sans-serif; "
"background: #f4f6f8; color: #333; margin: 0; padding: 2rem; }\n"
<< "h1 { color: #2c3e50; border-bottom: 2px solid #e0e0e0; "
"padding-bottom: 0.5rem; }\n"
<< ".summary-cards { display: flex; gap: 1rem; margin-bottom: "
"2rem; }\n"
<< ".card { background: white; padding: 1rem 1.5rem; "
"border-radius: "
"8px; box-shadow: 0 2px 4px "
"rgba(0,0,0,0.05); flex: 1; }\n"
<< ".card h3 { margin-top: 0; font-size: 0.9rem; color: #7f8c8d; "
"text-transform: uppercase; }\n"
<< ".card p { font-size: 1.5rem; font-weight: bold; margin: 0; }\n"
<< ".text-green { color: #27ae60; }\n"
<< ".text-red { color: #e74c3c; }\n"
<< ".test-item { background: white; border-radius: 8px; padding: "
"1rem; "
"margin-bottom: 1rem; box-shadow: 0 2px "
"4px rgba(0,0,0,0.05); border-left: 5px solid #bdc3c7; }\n"
<< ".test-item.passed { border-left-color: #27ae60; }\n"
<< ".test-item.failed { border-left-color: #e74c3c; }\n"
<< ".test-header { display: flex; justify-content: space-between; "
"align-items: flex-start; }\n"
<< ".test-name { font-size: 1.1rem; font-weight: 600; margin: 0 0 "
"0.5rem 0; word-break: break-word; }\n"
<< ".tags { font-size: 0.8rem; color: #2980b9; background: "
"#ebf5fb; "
"padding: 2px 6px; border-radius: 4px; "
"display: inline-block; margin-top: 4px; }\n"
<< ".stats { font-size: 0.9rem; color: #7f8c8d; }\n"
<< "details { margin-top: 0.8rem; background: #f8f9fa; border: 1px "
"solid #e9ecef; border-radius: 6px; "
"padding: 0.5rem 0.8rem; }\n"
<< "summary { cursor: pointer; font-weight: 600; color: #34495e; "
"user-select: none; font-size: 0.9rem; }\n"
<< "summary:hover { color: #2980b9; }\n"
<< "pre { background: #1e293b; color: #f8fafc; padding: 1rem; "
"border-radius: 4px; overflow-x: auto; "
"font-size: 0.85rem; line-height: 1.4; margin-top: 0.5rem; }\n"
<< "pre.info-block { background: #0f172a; border-left: 4px solid "
"#0284c7; }\n"
<< "</style>\n</head>\n<body>\n";
html << "<!DOCTYPE html>\n<html lang='en'>\n<head>\n"
<< "<meta charset='UTF-8'>\n"
<< "<meta name='viewport' content='width=device-width, "
"initial-scale=1.0'>\n"
<< "<title>Test Run Summary</title>\n"
<< "<style>\n"
<< "body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe "
"UI', "
"Roboto, Helvetica, Arial, sans-serif; "
"background: #f4f6f8; color: #333; margin: 0; padding: 2rem; }\n"
<< "h1 { color: #2c3e50; border-bottom: 2px solid #e0e0e0; "
"padding-bottom: 0.5rem; }\n"
<< ".summary-cards { display: flex; gap: 1rem; margin-bottom: "
"2rem; }\n"
<< ".card { background: white; padding: 1rem 1.5rem; "
"border-radius: "
"8px; box-shadow: 0 2px 4px "
"rgba(0,0,0,0.05); flex: 1; }\n"
<< ".card h3 { margin-top: 0; font-size: 0.9rem; color: #7f8c8d; "
"text-transform: uppercase; }\n"
<< ".card p { font-size: 1.5rem; font-weight: bold; margin: 0; }\n"
<< ".text-green { color: #27ae60; }\n"
<< ".text-red { color: #e74c3c; }\n"
<< ".test-item { background: white; border-radius: 8px; padding: "
"1rem; "
"margin-bottom: 1rem; box-shadow: 0 2px "
"4px rgba(0,0,0,0.05); border-left: 5px solid #bdc3c7; }\n"
<< ".test-item.passed { border-left-color: #27ae60; }\n"
<< ".test-item.failed { border-left-color: #e74c3c; }\n"
<< ".test-header { display: flex; justify-content: space-between; "
"align-items: flex-start; }\n"
<< ".test-name { font-size: 1.1rem; font-weight: 600; margin: 0 0 "
"0.5rem 0; word-break: break-word; }\n"
<< ".tags { font-size: 0.8rem; color: #2980b9; background: "
"#ebf5fb; "
"padding: 2px 6px; border-radius: 4px; "
"display: inline-block; margin-top: 4px; }\n"
<< ".stats { font-size: 0.9rem; color: #7f8c8d; }\n"
<< "details { margin-top: 0.8rem; background: #f8f9fa; border: 1px "
"solid #e9ecef; border-radius: 6px; "
"padding: 0.5rem 0.8rem; }\n"
<< "summary { cursor: pointer; font-weight: 600; color: #34495e; "
"user-select: none; font-size: 0.9rem; }\n"
<< "summary:hover { color: #2980b9; }\n"
<< "pre { background: #1e293b; color: #f8fafc; padding: 1rem; "
"border-radius: 4px; overflow-x: auto; "
"font-size: 0.85rem; line-height: 1.4; margin-top: 0.5rem; }\n"
<< "pre.info-block { background: #0f172a; border-left: 4px solid "
"#0284c7; }\n"
<< "</style>\n</head>\n<body>\n";
html << "<h1>Test Run Summary</h1>\n";
// Summary Cards
html << "<div class='summary-cards'>\n";
html << "<div class='card'><h3>Total Cases</h3><p>"
<< stats.totals.testCases.total() << "</p></div>\n";
html << "<div class='card'><h3>Cases Passed</h3><p class='text-green'>"
<< stats.totals.testCases.passed << "</p></div>\n";
html << "<div class='card'><h3>Cases Failed</h3><p class='text-red'>"
<< stats.totals.testCases.failed << "</p></div>\n";
html << "<div class='card'><h3>Total Cases</h3><p>" << stats.totals.testCases.total() << "</p></div>\n";
html << "<div class='card'><h3>Cases Passed</h3><p class='text-green'>" << stats.totals.testCases.passed
<< "</p></div>\n";
html << "<div class='card'><h3>Cases Failed</h3><p class='text-red'>" << stats.totals.testCases.failed
<< "</p></div>\n";
html << "</div>\n";
for (const auto &test : m_testRunData) {
@@ -434,26 +437,21 @@ private:
html << "<div class='test-item " << statusClass << "'>\n";
html << " <div class='test-header'>\n";
html << " <div>\n";
html << " <h3 class='test-name'>" << escapeHtml(test.name)
<< "</h3>\n";
html << " <h3 class='test-name'>" << escapeHtml(test.name) << "</h3>\n";
if (!test.tags.empty()) {
html << " <div class='tags'>" << escapeHtml(test.tags)
<< "</div>\n";
html << " <div class='tags'>" << escapeHtml(test.tags) << "</div>\n";
}
html << " </div>\n";
html << " <div class='stats'>\n";
html << " <span class='text-green'>&#10003; "
<< test.assertionsPassed << "</span> | ";
html << " <span class='text-red'>&#10007; "
<< test.assertionsFailed << "</span>\n";
html << " <span class='text-green'>&#10003; " << test.assertionsPassed << "</span> | ";
html << " <span class='text-red'>&#10007; " << test.assertionsFailed << "</span>\n";
html << " </div>\n";
html << " </div>\n";
// Collapsible INFO Messages section with ANSI color rendering
if (!test.infoMessages.empty()) {
html << " <details>\n";
html << " <summary>Info Logs (" << test.infoMessages.size()
<< ")</summary>\n";
html << " <summary>Info Logs (" << test.infoMessages.size() << ")</summary>\n";
html << " <pre class='info-block'>";
for (const auto &info : test.infoMessages) {
html << "[INFO] " << ansiToHtml(info) << "\n";
@@ -465,8 +463,8 @@ private:
// Collapsible Failures section with ANSI color rendering
if (!test.failureMessages.empty()) {
html << " <details open>\n";
html << " <summary class='text-red'>Failure Details ("
<< test.failureMessages.size() << ")</summary>\n";
html << " <summary class='text-red'>Failure Details (" << test.failureMessages.size()
<< ")</summary>\n";
html << " <pre>";
for (const auto &msg : test.failureMessages) {
html << ansiToHtml(msg) << "\n";
@@ -542,14 +540,11 @@ int main(
}
const auto is_reporter_option = [](const std::string &argument) {
return argument == "-r" || argument == "--reporter" ||
argument.starts_with("-r=") ||
return argument == "-r" || argument == "--reporter" || argument.starts_with("-r=") ||
argument.starts_with("--reporter=");
};
if (const bool has_reporter =
std::ranges::any_of(catch_arguments, is_reporter_option);
!has_reporter) {
if (const bool has_reporter = std::ranges::any_of(catch_arguments, is_reporter_option); !has_reporter) {
catch_arguments.emplace_back("--reporter");
catch_arguments.emplace_back("check");
}
@@ -563,9 +558,7 @@ int main(
Catch::Session session;
if (const int catch_parse_result = session.applyCommandLine(
static_cast<int>(catch_argv.size()), catch_argv.data()
);
if (const int catch_parse_result = session.applyCommandLine(static_cast<int>(catch_argv.size()), catch_argv.data());
catch_parse_result != 0) {
return catch_parse_result;
}
@@ -577,8 +570,7 @@ int main(
const int hdiv_max_q1d = mfem::DeviceDofQuadLimits::Get().HDIV_MAX_Q1D;
std::cout << "H(div) maximum Q1D = " << hdiv_max_q1d << '\n';
std::cout << "Approximate maximum safe integration order = "
<< 2 * hdiv_max_q1d - 1 << '\n';
std::cout << "Approximate maximum safe integration order = " << 2 * hdiv_max_q1d - 1 << '\n';
mean_field::utils::Args test_args = cfg.main();
@@ -597,4 +589,4 @@ int main(
test_utils::set_args(std::move(test_args));
return session.run();
}
}