#include #include #include #include #include #include #include #define NOMINMAX #include #include "../../unit/io/hdf5/solid_output_test_fixture.h" #include "fesa/app/fesa_application.h" namespace { std::map> NumericSnapshot( const std::filesystem::path& path, unsigned p) { std::vector paths{"/model/nodal_constraint_mask"}; if (p == 2) paths.insert( paths.end(), {"/steps/Step-1/eigen/modes", "/steps/Step-1/eigen/frequencies", "/steps/Step-1/eigen/vectors", "/steps/Step-1/eigen/verification/pairs", "/steps/Step-1/eigen/verification/eta_gram", "/steps/Step-1/eigen/verification/max_eta_orth"}); else { if (p != 0) paths.push_back("/steps/Step-1/accepted_frames"); const unsigned count = p == 0 ? 1 : p == 1 ? 2 : 3; for (unsigned i = 0; i < count; ++i) { const auto root = "/steps/Step-1/frames/" + std::to_string(i); for (const auto* field : {"nodal/displacement", "nodal/reaction", "nodal/residual", "solid/stress", "solid/strain", "global/strain_energy"}) paths.push_back(root + "/" + field); if (p >= 3) for (const auto* field : {"nodal/velocity", "nodal/acceleration", "nodal/algorithmic_acceleration", "global/kinetic_energy", "global/external_work", "global/energy_balance_error", "global/linear_momentum", "global/angular_momentum"}) paths.push_back(root + "/" + field); } } const hid_t file = H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); if (file < 0) throw std::runtime_error("CLI result file missing"); std::map> result; for (const auto& dataset_path : paths) { const hid_t dataset = H5Dopen2(file, dataset_path.c_str(), H5P_DEFAULT); if (dataset < 0) { H5Fclose(file); throw std::runtime_error("CLI mandatory dataset missing"); } const hid_t type = H5Dget_type(dataset), space = H5Dget_space(dataset); const auto count = H5Sget_simple_extent_npoints(space); std::vector bytes(static_cast(count) * H5Tget_size(type)); const herr_t read = count == 0 ? 0 : H5Dread(dataset, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, bytes.data()); H5Sclose(space); H5Tclose(type); H5Dclose(dataset); if (read < 0) { H5Fclose(file); throw std::runtime_error("CLI mandatory dataset read failed"); } result.emplace(dataset_path, std::move(bytes)); } H5Fclose(file); return result; } TEST(SolidApplication, AllFiveActualProceduresWriteOfficialSolidSchema) { for (unsigned p = 0; p < 5; ++p) { SCOPED_TRACE(p); solid_output_test::Files files(p); ASSERT_EQ(fesa::FesaApplication{}.Run({files.input.string()}), 0); const hid_t f = H5Fopen(files.output.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); ASSERT_GE(f, 0); const hid_t g = H5Gopen2(f, "/metadata", H5P_DEFAULT); ASSERT_GE(g, 0); const hid_t a = H5Aopen(g, "analysis_procedure", H5P_DEFAULT); ASSERT_GE(a, 0); const hid_t t = H5Aget_type(a); char* value = nullptr; ASSERT_GE(H5Aread(a, t, &value), 0); EXPECT_STREQ(value, solid_output_test::Procedure(p)); H5free_memory(value); H5Tclose(t); H5Aclose(a); H5Gclose(g); H5Fclose(f); } } TEST(SolidApplication, NativeExecutableRunsAllFiveAndKeepsWarningsAndTypedLogs) { std::vector module(32768); const DWORD length = GetModuleFileNameW(nullptr, module.data(), static_cast(module.size())); ASSERT_GT(length, 0u); ASSERT_LT(length, module.size()); const auto executable = std::filesystem::path(std::wstring(module.data(), length)) .parent_path() .parent_path() / "src/fesa/fesa.exe"; ASSERT_TRUE(std::filesystem::exists(executable)); for (unsigned p = 0; p < 5; ++p) { SCOPED_TRACE(p); solid_output_test::Files files(p); ASSERT_EQ(fesa::FesaApplication{}.Run({files.input.string()}), 0); const auto expected = NumericSnapshot(files.output, p); std::wstring command = L"\"" + executable.wstring() + L"\" \"" + files.input.wstring() + L"\""; STARTUPINFOW startup{}; startup.cb = sizeof(startup); PROCESS_INFORMATION process{}; ASSERT_TRUE(CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup, &process)); const DWORD wait = WaitForSingleObject(process.hProcess, 60000); if (wait != WAIT_OBJECT_0) TerminateProcess(process.hProcess, 99); DWORD exit = 99; EXPECT_TRUE(GetExitCodeProcess(process.hProcess, &exit)); CloseHandle(process.hThread); CloseHandle(process.hProcess); ASSERT_EQ(wait, WAIT_OBJECT_0); ASSERT_EQ(exit, 0u); EXPECT_GT(H5Fis_hdf5(files.output.string().c_str()), 0); EXPECT_EQ(NumericSnapshot(files.output, p), expected); std::ifstream stream(files.log, std::ios::binary); std::string log{std::istreambuf_iterator(stream), {}}; EXPECT_EQ(log.find("correction_rotation"), std::string::npos); EXPECT_EQ(log.find("residual_moment"), std::string::npos); if (p == 1) EXPECT_NE(log.find("schema=solid-static-log-v1"), std::string::npos); if (p >= 3) { EXPECT_NE(log.find("schema=solid-hht-log-v1"), std::string::npos); EXPECT_NE(log.find("h2_physical_da_over_L="), std::string::npos); EXPECT_NE(log.find("h2_algorithmic_dA_over_L="), std::string::npos); EXPECT_NE(log.find("normalized-high-low-physical-state;RN64-observables"), std::string::npos); } const hid_t file = H5Fopen(files.output.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); ASSERT_GE(file, 0); const hid_t diagnostics = H5Dopen2(file, "/diagnostics", H5P_DEFAULT); ASSERT_GE(diagnostics, 0); const hid_t space = H5Dget_space(diagnostics); hsize_t rows = 0; H5Sget_simple_extent_dims(space, &rows, nullptr); EXPECT_GT(rows, 0u); H5Sclose(space); H5Dclose(diagnostics); H5Fclose(file); } } TEST(SolidApplication, SuccessfulWarningsRetainSourceLocationAndTarget) { solid_output_test::Files files(0); testing::internal::CaptureStderr(); const int exit = fesa::FesaApplication{}.Run({files.input.string()}); const auto diagnostics = testing::internal::GetCapturedStderr(); ASSERT_EQ(exit, 0); EXPECT_NE(diagnostics.find("code=inactive-solid-rotation-boundary"), std::string::npos); EXPECT_NE(diagnostics.find("entity_identity=Fixed"), std::string::npos); EXPECT_NE(diagnostics.find("line=30"), std::string::npos); EXPECT_NE(diagnostics.find(files.input.generic_u8string()), std::string::npos); } } // namespace