156 lines
6.7 KiB
C++
156 lines
6.7 KiB
C++
#define NOMINMAX
|
|
#include "fesa/io/hdf5/eigen_hdf5_results_writer.h"
|
|
|
|
#include <Windows.h>
|
|
#include <gtest/gtest.h>
|
|
#include <hdf5.h>
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#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<fesa::EntityIndex>(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<std::int64_t, 128> 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<char>{original_file}, {}};
|
|
original_file.close();
|
|
const std::vector<std::function<void(fesa::EigenAnalysisState&)>> mutations{
|
|
[](auto& candidate) {
|
|
candidate.vectors(0U, 0U) = std::numeric_limits<double>::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<char>{retained_file}, {}};
|
|
EXPECT_TRUE(retained == original);
|
|
retained_file.close();
|
|
// Denying delete sharing forces the actual atomic replacement to fail after
|
|
// writing and validating a fresh candidate, while retaining a valid final.
|
|
const HANDLE lock = CreateFileW(path.c_str(), GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
ASSERT_NE(lock, INVALID_HANDLE_VALUE);
|
|
const auto locked_write = writer.Write(path, domain, state, {});
|
|
EXPECT_NE(CloseHandle(lock), 0);
|
|
ASSERT_FALSE(locked_write.IsOk());
|
|
ASSERT_FALSE(locked_write.Diagnostics().empty());
|
|
EXPECT_EQ(locked_write.Diagnostics()[0].code, "hdf5-finalization-failure");
|
|
std::ifstream locked_final{path, std::ios::binary};
|
|
const std::string after_failure{std::istreambuf_iterator<char>{locked_final},
|
|
{}};
|
|
EXPECT_EQ(after_failure, original);
|
|
locked_final.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<std::uint64_t, 4> 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
|