#include "fesa/io/hdf5/eigen_hdf5_results_writer.h" #include #include #include #include #include #include #include #include #include #include #include #include "fesa/analysis/linear_frequency_procedure.h" #include "fesa/model/domain.h" #include "io/hdf5/eigen_hdf5_self_check.h" namespace { fesa::ModelDefinition Definition(const std::filesystem::path& source) { fesa::ModelDefinition definition{}; definition.source_path = source; definition.source_content_identity = "fnv1a64:0123456789abcdef"; definition.nodes = {{{"I", 1, "1"}, {-1.0, -1.0, 0.0}, {source, 1U}}, {{"I", 2, "2"}, {1.0, -1.0, 0.0}, {source, 2U}}, {{"I", 3, "3"}, {1.0, 1.0, 0.0}, {source, 3U}}, {{"I", 4, "4"}, {-1.0, 1.0, 0.0}, {source, 4U}}}; definition.materials = {{"Steel", 12.0, 0.0, {source, 5U}}}; definition.shell_sections = {{"S", 1.0, 0U, {source, 6U}}}; definition.shell_elements = {{{"I", 1, "1"}, fesa::ShellSourceElementType::kS4, {0U, 1U, 2U, 3U}, 0U, 0U, {source, 7U}}}; for (std::size_t node = 0U; node < 4U; ++node) { definition.shell_node_initial_frames.push_back( {static_cast(node), {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}}); } definition.densities = {{0U, 1.0, {source, 8U}}}; definition.frequency_step = fesa::FrequencyStepDefinition{"Step-2", "STEP-2", {source, 9U}, {source, 10U}, {source, 11U}, 1, false, false, false, false, false, false, fesa::FrequencyEigensolver::kLanczos, fesa::FrequencyNormalization::kMass, {}}; return definition; } TEST(EigenHdf5ResultsWriter, WritesModalSchemaAndRejectsTamperedState) { const auto path = std::filesystem::temp_directory_path() / "fesa-eigen-writer-test.h5"; std::error_code ignored; std::filesystem::remove(path, ignored); const auto source = path.parent_path() / "fesa-eigen-writer-test.inp"; auto domain_result = fesa::Domain::Create(Definition(source)); ASSERT_TRUE(domain_result.HasValue()); fesa::Domain domain{std::move(domain_result.Value())}; fesa::EigenHdf5ResultsWriter writer; auto execution = fesa::LinearFrequencyProcedureExecutor{}.Execute(domain); ASSERT_TRUE(execution.HasValue()); fesa::EigenAnalysisState state = std::move(execution.Value()); ASSERT_TRUE(writer.Write(path, domain, state, {}).IsOk()); const hid_t file = H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); ASSERT_GE(file, 0); EXPECT_GT(H5Lexists(file, "/steps/Step-1/eigen/modes", H5P_DEFAULT), 0); const hid_t controls = H5Dopen2(file, "/steps/Step-1/eigen/backend/base_fpm", H5P_DEFAULT); ASSERT_GE(controls, 0); std::array stored_controls{}; ASSERT_GE(H5Dread(controls, H5T_NATIVE_INT64, H5S_ALL, H5S_ALL, H5P_DEFAULT, stored_controls.data()), 0); EXPECT_EQ(stored_controls[5U], 0); EXPECT_GE(H5Dclose(controls), 0); EXPECT_GE(H5Fclose(file), 0); std::ifstream original_file{path, std::ios::binary}; const std::string original{std::istreambuf_iterator{original_file}, {}}; original_file.close(); const std::vector> mutations{ [](auto& candidate) { candidate.vectors(0U, 0U) = std::numeric_limits::quiet_NaN(); }, [](auto& candidate) { candidate.eigenpair_checks[0].rho_b = 1e-5; }, [](auto& candidate) { candidate.eigenpair_checks[0].eigenvalue += 1.0; }, [](auto& candidate) { candidate.modes[0].cluster_id = 7U; }, [](auto& candidate) { candidate.constraint_mask[0] = 1U; }, [](auto& candidate) { candidate.nodal_reduction[0].rcond_inf = 1e-15; }, [](auto& candidate) { candidate.backend.base_fpm[5U] = 1; }, [](auto& candidate) { candidate.eta_gram = 1e-5; }}; for (const auto& mutate : mutations) { auto candidate = state; mutate(candidate); EXPECT_FALSE(writer.Write(path, domain, candidate, {}).IsOk()); } std::ifstream retained_file{path, std::ios::binary}; const std::string retained{std::istreambuf_iterator{retained_file}, {}}; EXPECT_TRUE(retained == original); retained_file.close(); ASSERT_TRUE( fesa::hdf5_internal::SelfCheckEigenFile(path, domain, state, {}).IsOk()); const hid_t corrupt_file = H5Fopen(path.string().c_str(), H5F_ACC_RDWR, H5P_DEFAULT); ASSERT_GE(corrupt_file, 0); const hid_t nodes = H5Dopen2(corrupt_file, "/model/nodes", H5P_DEFAULT); ASSERT_GE(nodes, 0); const hid_t id_type = H5Tcreate(H5T_COMPOUND, sizeof(std::uint64_t)); ASSERT_GE(H5Tinsert(id_type, "internal_node_id", 0U, H5T_NATIVE_UINT64), 0); const std::array duplicate_ids{0U, 0U, 2U, 3U}; ASSERT_GE(H5Dwrite(nodes, id_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, duplicate_ids.data()), 0); EXPECT_GE(H5Tclose(id_type), 0); EXPECT_GE(H5Dclose(nodes), 0); EXPECT_GE(H5Fclose(corrupt_file), 0); EXPECT_FALSE( fesa::hdf5_internal::SelfCheckEigenFile(path, domain, state, {}).IsOk()); std::filesystem::remove(path, ignored); } } // namespace