Files
FESA/tests/unit/validation/reference_csv_test.cpp
T

236 lines
8.1 KiB
C++

#include <gtest/gtest.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <vector>
#include <fesa/validation/reference_csv.hpp>
namespace {
class TemporaryCsv final {
public:
TemporaryCsv(std::string_view name, std::string_view contents)
: path_{std::filesystem::path{testing::TempDir()} / name} {
std::ofstream output{path_, std::ios::binary};
output.write(
contents.data(), static_cast<std::streamsize>(contents.size()));
if (!output) {
throw std::runtime_error{"Failed to write temporary reference CSV."};
}
}
~TemporaryCsv() {
std::error_code error;
std::filesystem::remove(path_, error);
}
TemporaryCsv(const TemporaryCsv&) = delete;
TemporaryCsv& operator=(const TemporaryCsv&) = delete;
[[nodiscard]] const std::filesystem::path& path() const noexcept {
return path_;
}
private:
std::filesystem::path path_;
};
std::filesystem::path fixture_path(const std::string_view name) {
return std::filesystem::path{FESA_TEST_SOURCE_DIR} / "fixtures" /
"reference" / name;
}
std::filesystem::path supplied_reference_path(const std::string_view name) {
return std::filesystem::path{FESA_TEST_SOURCE_DIR}.parent_path() /
"reference" / "cantilever beam" / name;
}
bool has_diagnostic(
const std::vector<fesa::Diagnostic>& diagnostics,
const std::string_view code) {
return std::ranges::any_of(
diagnostics,
[code](const fesa::Diagnostic& diagnostic) {
return diagnostic.code == code;
});
}
TEST(ReferenceCsv, ReadsSuppliedDisplacementsWithWhitespaceHeader) {
const auto result = fesa::read_reference_csv(
fesa::ReferenceQuantity::displacement,
supplied_reference_path("cantilever beam displacements.csv"),
"Part-1-1");
ASSERT_TRUE(result.diagnostics.empty());
ASSERT_EQ(result.rows.size(), 11U);
EXPECT_EQ(
result.rows.front().quantity,
fesa::ReferenceQuantity::displacement);
EXPECT_EQ(result.rows.front().position.instance_name, "Part-1-1");
EXPECT_EQ(result.rows.front().position.entity_label, 1);
EXPECT_FALSE(result.rows.front().position.end_node_label.has_value());
EXPECT_EQ(
result.rows.front().values,
(std::vector<double>{0.0, 0.0, -1.0e-32, 0.0, 1.0e-31, 0.0}));
EXPECT_EQ(result.rows.back().position.entity_label, 11);
EXPECT_EQ(result.rows.back().values[2], -1.92e-4);
}
TEST(ReferenceCsv, ReadsSuppliedReactionsWithWhitespaceHeader) {
const auto result = fesa::read_reference_csv(
fesa::ReferenceQuantity::reaction,
supplied_reference_path("cantilever beam reactions.csv"),
"Part-1-1");
ASSERT_TRUE(result.diagnostics.empty());
ASSERT_EQ(result.rows.size(), 11U);
EXPECT_EQ(result.rows.front().position.instance_name, "Part-1-1");
EXPECT_EQ(result.rows.front().position.entity_label, 1);
EXPECT_FALSE(result.rows.front().position.end_node_label.has_value());
EXPECT_EQ(
result.rows.front().values,
(std::vector<double>{0.0, 0.0, 1.0e4, 0.0, -1.0e5, 0.0}));
}
TEST(InternalForceCsv, MapsAllSixComponentsAndElementEndPosition) {
const auto result = fesa::read_reference_csv(
fesa::ReferenceQuantity::internal_force,
fixture_path("internalforces.csv"),
"unused-request-name");
ASSERT_TRUE(result.diagnostics.empty());
ASSERT_EQ(result.rows.size(), 2U);
EXPECT_EQ(
result.rows.front().quantity,
fesa::ReferenceQuantity::internal_force);
EXPECT_EQ(result.rows.front().position.instance_name, "Part-1-1");
EXPECT_EQ(result.rows.front().position.entity_label, 501);
ASSERT_TRUE(result.rows.front().position.end_node_label.has_value());
EXPECT_EQ(*result.rows.front().position.end_node_label, 101);
EXPECT_EQ(
result.rows.front().values,
(std::vector<double>{1.25, -2.5, 3.75, -4.0, 5.5, -6.25}));
}
TEST(StressCsv, FillsOmittedInstanceAndReadsCentroidStress) {
const auto result = fesa::read_reference_csv(
fesa::ReferenceQuantity::centroid_stress,
fixture_path("stresses.csv"),
"Part-1-1");
ASSERT_TRUE(result.diagnostics.empty());
ASSERT_EQ(result.rows.size(), 2U);
EXPECT_EQ(result.rows.front().position.instance_name, "Part-1-1");
EXPECT_EQ(result.rows.front().position.entity_label, 501);
ASSERT_TRUE(result.rows.front().position.end_node_label.has_value());
EXPECT_EQ(*result.rows.front().position.end_node_label, 101);
EXPECT_EQ(result.rows.front().values, (std::vector<double>{42.5}));
EXPECT_EQ(result.rows.back().values, (std::vector<double>{-17.25}));
}
TEST(ReferenceCsv, AcceptsUtf8BomOnlyAtTheStartAndTrimsValues) {
const TemporaryCsv input{
"fesa-reference-bom.csv",
"\xEF\xBB\xBF Part Instance Name , Node Label , U-U1 , U-U2 , "
"U-U3 , UR-UR1 , UR-UR2 , UR-UR3\n"
" Beam-1 , 7 , 1 , 2 , 3 , 4 , 5 , 6 \n"};
const auto result = fesa::read_reference_csv(
fesa::ReferenceQuantity::displacement, input.path(), "unused");
ASSERT_TRUE(result.diagnostics.empty());
ASSERT_EQ(result.rows.size(), 1U);
EXPECT_EQ(result.rows[0].position.instance_name, "Beam-1");
EXPECT_EQ(result.rows[0].position.entity_label, 7);
EXPECT_EQ(
result.rows[0].values,
(std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
}
TEST(ReferenceCsv, DiagnosesMissingRequiredColumn) {
const TemporaryCsv input{
"fesa-reference-missing-column.csv",
"Node Label,U-U1,U-U2,U-U3,UR-UR1,UR-UR2,Unexpected\n"
"1,0,0,0,0,0,0\n"};
const auto result = fesa::read_reference_csv(
fesa::ReferenceQuantity::displacement, input.path(), "Part-1-1");
EXPECT_TRUE(result.rows.empty());
ASSERT_TRUE(has_diagnostic(
result.diagnostics, "validation.reference_csv_missing_column"));
ASSERT_TRUE(result.diagnostics.front().source.has_value());
EXPECT_EQ(result.diagnostics.front().source->line, 1U);
}
TEST(ReferenceCsv, DiagnosesDuplicateResultPosition) {
const TemporaryCsv input{
"fesa-reference-duplicate.csv",
"Element Label,Node Label,Sxx\n"
"8,2,10\n"
"8,2,11\n"};
const auto result = fesa::read_reference_csv(
fesa::ReferenceQuantity::centroid_stress,
input.path(),
"Part-1-1");
EXPECT_TRUE(result.rows.empty());
ASSERT_TRUE(has_diagnostic(
result.diagnostics, "validation.reference_csv_duplicate_row"));
ASSERT_TRUE(result.diagnostics.front().source.has_value());
EXPECT_EQ(result.diagnostics.front().source->line, 3U);
}
TEST(ReferenceCsv, DiagnosesInvalidAndNonfiniteNumbers) {
const TemporaryCsv invalid{
"fesa-reference-invalid-number.csv",
"Node Label,RF-RF1,RF-RF2,RF-RF3,RM-RM1,RM-RM2,RM-RM3\n"
"1,0,0,not-a-number,0,0,0\n"};
const TemporaryCsv nonfinite{
"fesa-reference-nonfinite-number.csv",
"Element Label,Node Label,Sxx\n"
"8,2,nan\n"};
const auto invalid_result = fesa::read_reference_csv(
fesa::ReferenceQuantity::reaction,
invalid.path(),
"Part-1-1");
const auto nonfinite_result = fesa::read_reference_csv(
fesa::ReferenceQuantity::centroid_stress,
nonfinite.path(),
"Part-1-1");
EXPECT_TRUE(invalid_result.rows.empty());
EXPECT_TRUE(nonfinite_result.rows.empty());
EXPECT_TRUE(has_diagnostic(
invalid_result.diagnostics,
"validation.reference_csv_invalid_number"));
EXPECT_TRUE(has_diagnostic(
nonfinite_result.diagnostics,
"validation.reference_csv_invalid_number"));
}
TEST(ReferenceCsv, RejectsUtf8BomOutsideTheFileStart) {
const TemporaryCsv input{
"fesa-reference-misplaced-bom.csv",
"Node Label,\xEF\xBB\xBF U-U1,U-U2,U-U3,UR-UR1,UR-UR2,UR-UR3\n"
"1,0,0,0,0,0,0\n"};
const auto result = fesa::read_reference_csv(
fesa::ReferenceQuantity::displacement, input.path(), "Part-1-1");
EXPECT_TRUE(result.rows.empty());
EXPECT_TRUE(has_diagnostic(
result.diagnostics,
"validation.reference_csv_invalid_encoding"));
}
} // namespace