#include #include #include #include #include #include #include #include #include #include #include "fesa/app/fesa_application.h" #include "mitc4_reference_comparison.h" #ifndef FESA_TEST_SOURCE_DIR #error FESA_TEST_SOURCE_DIR must identify the repository root. #endif #ifndef FESA_TEST_BINARY_DIR #error FESA_TEST_BINARY_DIR must identify the CMake binary root. #endif namespace { constexpr const char* kInternalFormulation = "FESA-MITC4"; constexpr const char* kIntegrationRule = "2x2x2-gauss; mitc4-edge-midpoint-shear"; constexpr std::size_t kNodeCount = 49U; constexpr std::size_t kComponentCount = 6U; std::string ReadBytes(const std::filesystem::path& path) { std::ifstream stream{path, std::ios::binary}; if (!stream) { throw std::runtime_error{"Unable to read declared reference artifact."}; } return {std::istreambuf_iterator{stream}, std::istreambuf_iterator{}}; } struct ArtifactSnapshot { std::string bytes; std::filesystem::file_time_type last_write_time; }; ArtifactSnapshot Snapshot(const std::filesystem::path& path) { return {ReadBytes(path), std::filesystem::last_write_time(path)}; } void ExpectUnchanged(const std::filesystem::path& path, const ArtifactSnapshot& before) { EXPECT_EQ(ReadBytes(path), before.bytes) << path.string(); EXPECT_EQ(std::filesystem::last_write_time(path), before.last_write_time) << path.string(); } struct CaseEvidence { fesa::test::Mitc4ComparisonReport report; std::filesystem::path comparison_json; }; CaseEvidence RunCase(const std::string& case_id, const std::string& source_element_type, const std::filesystem::path& reference_directory, const std::filesystem::path& input, const std::filesystem::path& csv, const std::string& output_name) { const auto input_before = Snapshot(input); const auto csv_before = Snapshot(csv); const std::filesystem::path output_directory = std::filesystem::path{FESA_TEST_BINARY_DIR} / "reference" / output_name; std::error_code error; std::filesystem::remove_all(output_directory, error); error.clear(); if (!std::filesystem::create_directories(output_directory, error) || error) { throw std::runtime_error{"Unable to create MITC4 evidence directory."}; } const auto results = output_directory / "results.h5"; const auto comparison = output_directory / "comparison.json"; fesa::FesaApplication application; EXPECT_EQ(application.Run({input.string(), "--output", results.string()}), 0); EXPECT_TRUE(std::filesystem::is_regular_file(results)); auto comparison_result = fesa::test::Mitc4ReferenceComparison::Compare( {case_id, source_element_type, input, csv, results}); if (!comparison_result.HasValue()) { std::string diagnostics; for (const auto& diagnostic : comparison_result.GetStatus().Diagnostics()) { diagnostics += "\n" + diagnostic.code + ": " + diagnostic.message; } ADD_FAILURE() << "MITC4 comparison precheck failed for " << case_id << diagnostics; return {{}, comparison}; } EXPECT_TRUE(fesa::test::Mitc4ReferenceComparison::WriteDeterministicJson( comparison_result.Value(), comparison) .IsOk()); EXPECT_TRUE(std::filesystem::is_regular_file(comparison)); std::vector generated; for (const auto& entry : std::filesystem::directory_iterator{output_directory}) { generated.push_back(entry.path().filename().string()); } std::sort(generated.begin(), generated.end()); EXPECT_EQ(generated, (std::vector{"comparison.json", "results.h5"})); EXPECT_TRUE(std::filesystem::is_directory(reference_directory)); ExpectUnchanged(input, input_before); ExpectUnchanged(csv, csv_before); return {std::move(comparison_result.Value()), comparison}; } void ExpectCommonMetadata(const fesa::test::Mitc4ComparisonReport& report, const std::string& case_id, const std::string& source_element_type) { EXPECT_EQ(report.case_id, case_id); EXPECT_EQ(report.source_element_type, source_element_type); EXPECT_EQ(report.internal_formulation, kInternalFormulation); EXPECT_EQ(report.integration_rule, kIntegrationRule); } void ExpectComparisonCoverage(const fesa::test::Mitc4ComparisonReport& report) { ASSERT_EQ(report.rows.size(), kNodeCount * kComponentCount); ASSERT_EQ(report.metrics.size(), 2U); ASSERT_EQ(report.vector_metrics.size(), kNodeCount); EXPECT_TRUE(report.passed); const std::size_t blocking_rows = static_cast(std::count_if( report.rows.begin(), report.rows.end(), [](const fesa::test::Mitc4RowDecision& row) { return row.blocking; })); const std::size_t rotation_rows = report.rows.size() - blocking_rows; EXPECT_EQ(blocking_rows, kNodeCount * 3U); EXPECT_EQ(rotation_rows, kNodeCount * 3U); EXPECT_TRUE(std::all_of(report.rows.begin(), report.rows.end(), [](const fesa::test::Mitc4RowDecision& row) { return !row.blocking || row.within_tolerance; })); const auto rotation_metric = std::find_if(report.metrics.begin(), report.metrics.end(), [](const fesa::test::Mitc4ComponentMetrics& metric) { return !metric.blocking; }); ASSERT_NE(rotation_metric, report.metrics.end()); const std::size_t row_warning_count = static_cast( std::count_if(report.rows.begin(), report.rows.end(), [](const fesa::test::Mitc4RowDecision& row) { return !row.blocking && !row.within_tolerance; })); ASSERT_EQ(report.warnings.size(), row_warning_count + (rotation_metric->rms_passed ? 0U : 1U)); std::size_t warning_index = 0U; for (std::size_t row_index = 0U; row_index < report.rows.size(); ++row_index) { const auto& row = report.rows[row_index]; if (!row.blocking && !row.within_tolerance) { EXPECT_EQ(report.warnings[warning_index].code, "rotation-reference-exceedance"); EXPECT_EQ(report.warnings[warning_index].row, row_index); ++warning_index; } } if (!rotation_metric->rms_passed) { EXPECT_EQ(report.warnings[warning_index].code, "rotation-reference-rms-exceedance"); EXPECT_EQ(report.warnings[warning_index].row, rotation_metric->worst_row); } } // MITC4-E2E-S4-001 TEST(Mitc4S4Reference, PreservesS4AndWritesCommonMitc4Metadata) { const std::filesystem::path root{FESA_TEST_SOURCE_DIR}; const auto directory = root / "reference" / "shell"; const auto evidence = RunCase("shell-s4", "S4", directory, directory / "shell.inp", directory / "shell displacements.csv", "mitc4-shell-s4-metadata"); ExpectCommonMetadata(evidence.report, "shell-s4", "S4"); } // MITC4-E2E-S4-002 TEST(Mitc4S4Reference, PassesBlockingUAndReportsEveryUrRow) { const std::filesystem::path root{FESA_TEST_SOURCE_DIR}; const auto directory = root / "reference" / "shell"; const auto evidence = RunCase( "shell-s4", "S4", directory, directory / "shell.inp", directory / "shell displacements.csv", "mitc4-shell-s4-comparison"); ExpectCommonMetadata(evidence.report, "shell-s4", "S4"); ExpectComparisonCoverage(evidence.report); } } // namespace