feat(shell-surface-pressure-load): step 2 - reject-invalid-pressure-input

This commit is contained in:
KOKO\Mimi
2026-09-01 13:11:27 +09:00
parent d5fc316ef2
commit c3b671d267
2 changed files with 412 additions and 16 deletions
@@ -1,4 +1,5 @@
#include "fesa/analysis/analysis_model.h"
#include "fesa/core/diagnostic.h"
#include "fesa/io/abaqus/domain_mapper.h"
#include "fesa/io/abaqus/input_reader.h"
@@ -47,6 +48,34 @@ fesa::Result<fesa::Domain> MapText(const std::string& stem,
return fesa::AbaqusDomainMapper{}.Map(parsed.Value());
}
fesa::Result<fesa::ParsedInput> ReadText(const std::string& stem,
const std::string& content) {
const TemporaryInputFile input{stem, content};
return fesa::AbaqusInputReader{}.Read(input.Path());
}
std::string ReplaceOnce(std::string text, const std::string& from,
const std::string& to) {
const std::size_t position = text.find(from);
if (position == std::string::npos) {
throw std::runtime_error{"Expected fixture text was not found."};
}
text.replace(position, from.size(), to);
return text;
}
void ExpectSingleFailure(const fesa::Result<fesa::Domain>& result,
const std::string& code, std::size_t line,
const std::string& keyword) {
ASSERT_FALSE(result.HasValue());
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
const fesa::Diagnostic& diagnostic = result.GetStatus().Diagnostics().front();
EXPECT_EQ(diagnostic.severity, fesa::Severity::kError);
EXPECT_EQ(diagnostic.code, code);
EXPECT_EQ(diagnostic.location.line, line);
EXPECT_EQ(diagnostic.keyword, keyword);
}
std::string ShellDeck(const std::string& assembly_content,
const std::string& dsload_rows) {
return std::string{R"inp(*Part, name=Shell-Part
@@ -302,3 +331,258 @@ TEST(ShellSurfacePressureInput,
EXPECT_EQ(loads[1].magnitude_raw, "-1.25E-3");
EXPECT_DOUBLE_EQ(loads[1].magnitude, -0.00125);
}
TEST(ShellSurfacePressureInput,
RejectsNonAsciiOverlengthOrIllegalContinuationAtStableLocation) {
const auto non_ascii = ReadText("non-ascii", "*Heading\n\xFF\n");
ASSERT_FALSE(non_ascii.HasValue());
ASSERT_EQ(non_ascii.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(non_ascii.GetStatus().Diagnostics().front().code,
"invalid-input-character");
EXPECT_EQ(non_ascii.GetStatus().Diagnostics().front().location.line, 2U);
const auto overlength = ReadText("overlength", std::string(257U, 'x'));
ASSERT_FALSE(overlength.HasValue());
EXPECT_EQ(overlength.GetStatus().Diagnostics().front().code,
"input-line-too-long");
EXPECT_EQ(overlength.GetStatus().Diagnostics().front().location.line, 1U);
const auto continuation = ReadText(
"continuation",
"*Surface, TYPE=ELEMENT, NAME=Pressure-Surface,\n** interruption\n");
ASSERT_FALSE(continuation.HasValue());
EXPECT_EQ(continuation.GetStatus().Diagnostics().front().code,
"malformed-keyword");
EXPECT_EQ(continuation.GetStatus().Diagnostics().front().location.line, 2U);
}
TEST(ShellSurfacePressureInput,
RejectsUnsupportedSurfaceParametersTypesSidesAndTargetKinds) {
ExpectSingleFailure(
MapText("surface-parameter",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface, "
"TRIM=YES\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"unsupported-parameter", 27U, "SURFACE");
ExpectSingleFailure(
MapText("surface-type",
ShellDeck("*SURFACE, TYPE=NODE, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"unsupported-surface-type", 27U, "SURFACE");
ExpectSingleFailure(
MapText("surface-side",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, S1\n",
"Pressure-Surface, P, 1.0\n")),
"unsupported-surface-side", 28U, "SURFACE");
ExpectSingleFailure(
MapText("surface-target-kind",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Shell-Part.10, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"unsupported-surface-target", 28U, "SURFACE");
}
TEST(ShellSurfacePressureInput,
RejectsDsloadParametersLabelsDistributionsAndCardinality) {
const std::string parameter_deck = ReplaceOnce(
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, P, 1.0\n"),
"*Dsload", "*Dsload, AMPLITUDE=Ramp");
ExpectSingleFailure(MapText("dsload-parameter", parameter_deck),
"unsupported-dsload-parameter", 36U, "DSLOAD");
ExpectSingleFailure(
MapText("dsload-label",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, PNU, 1.0\n")),
"unsupported-load-type", 37U, "DSLOAD");
ExpectSingleFailure(
MapText("dsload-cardinality",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, P, 1.0, 2.0\n")),
"invalid-data-arity", 37U, "DSLOAD");
}
TEST(ShellSurfacePressureInput,
RejectsEmptyMissingAmbiguousAndCrossScopeSurfaceTargets) {
ExpectSingleFailure(
MapText("empty-surface",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n",
"Pressure-Surface, P, 1.0\n")),
"empty-surface", 27U, "SURFACE");
ExpectSingleFailure(
MapText("missing-surface",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Missing-Surface, P, 1.0\n")),
"missing-reference", 37U, "DSLOAD");
ExpectSingleFailure(
MapText("cross-scope-elset",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"S4-Set, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"cross-scope-target", 28U, "SURFACE");
ExpectSingleFailure(
MapText("ambiguous-target",
ShellDeck("*ELSET, ELSET=Shared, INSTANCE=Instance-A\n"
"10\n"
"*ELSET, ELSET=Shared, INSTANCE=Instance-B\n"
"10\n"
"*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Shared, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"duplicate-entity", 29U, "ELSET");
}
TEST(ShellSurfacePressureInput,
RejectsImplicitInstanceNestedCyclicAndMalformedElsets) {
ExpectSingleFailure(
MapText("implicit-elset",
ShellDeck("*ELSET, ELSET=Implicit\n"
"10\n"
"*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Implicit, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"implicit-instance-elset", 27U, "ELSET");
ExpectSingleFailure(
MapText("nested-elset",
ShellDeck("*ELSET, ELSET=Inner, INSTANCE=Instance-A\n"
"10\n"
"*ELSET, ELSET=Outer, INSTANCE=Instance-A\n"
"Inner\n"
"*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Outer, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"nested-elset", 30U, "ELSET");
ExpectSingleFailure(
MapText("cyclic-elset",
ShellDeck("*ELSET, ELSET=Loop, INSTANCE=Instance-A\n"
"Loop\n"
"*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Loop, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"cyclic-elset", 28U, "ELSET");
ExpectSingleFailure(
MapText("malformed-elset",
ShellDeck("*ELSET, ELSET=Malformed, INSTANCE=Instance-A\n"
"10, , 20\n"
"*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Malformed, SPOS\n",
"Pressure-Surface, P, 1.0\n")),
"invalid-data-arity", 28U, "ELSET");
}
TEST(ShellSurfacePressureInput,
NormalizesExactSameSideDuplicatesWithOneStableWarning) {
const auto result = MapText(
"same-side-duplicate",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n"
"INSTANCE-A.0010, SPOS\n",
"Pressure-Surface, P, 1.0\n"));
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(OnlySurface(result.Value()).facets.size(), 1U);
ASSERT_EQ(result.Value().Warnings().size(), 1U);
const auto& warning = result.Value().Warnings().front();
EXPECT_EQ(warning.severity, fesa::Severity::kWarning);
EXPECT_EQ(warning.code, "duplicate-surface-member-normalized");
EXPECT_EQ(warning.location.line, 29U);
EXPECT_EQ(warning.keyword, "SURFACE");
EXPECT_EQ(warning.entity_identity, "Pressure-Surface");
}
TEST(ShellSurfacePressureInput, RejectsConflictingSidesForOneElement) {
ExpectSingleFailure(
MapText("conflicting-sides",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n"
"Instance-A.10, SNEG\n",
"Pressure-Surface, P, 1.0\n")),
"conflicting-surface-membership", 29U, "SURFACE");
}
TEST(ShellSurfacePressureInput,
RejectsNonfiniteDExponentAndLocaleDependentMagnitudes) {
ExpectSingleFailure(
MapText("dsload-nan",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, P, NaN\n")),
"invalid-numeric-value", 37U, "DSLOAD");
ExpectSingleFailure(
MapText("dsload-d-exponent",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, P, 1.0D+03\n")),
"invalid-numeric-value", 37U, "DSLOAD");
ExpectSingleFailure(
MapText("dsload-locale",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, P, 1,25\n")),
"invalid-data-arity", 37U, "DSLOAD");
}
TEST(ShellSurfacePressureInput,
PreservesAdditiveRepeatedPressureRowsWithoutDeduplication) {
const auto result = MapText(
"repeated-dsload",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n",
"Pressure-Surface, P, 1.0\n"
"pressure-surface, p, 1.0\n"));
ASSERT_TRUE(result.HasValue());
const auto& loads = result.Value().Steps()[0U].SurfacePressureLoads();
ASSERT_EQ(loads.size(), 2U);
EXPECT_EQ(loads[0].load_ordinal, 0U);
EXPECT_EQ(loads[1].load_ordinal, 1U);
EXPECT_EQ(loads[0].data_ordinal, 0U);
EXPECT_EQ(loads[1].data_ordinal, 1U);
EXPECT_DOUBLE_EQ(loads[0].magnitude, loads[1].magnitude);
}
TEST(ShellSurfacePressureDomain,
MappingFailureCommitsNoPartialSemanticCandidate) {
const auto result = MapText(
"atomic-failure",
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n"
"Instance-A.10, SNEG\n",
"Pressure-Surface, P, 1.0\n"));
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Diagnostics().front().code,
"conflicting-surface-membership");
}
TEST(ShellSurfacePressureInput,
EmitsDiagnosticsInStableSourceOrderAcrossRepeatedRuns) {
const std::string deck =
ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n"
"Instance-A.10, SPOS\n"
"INSTANCE-A.0010, SPOS\n",
"Pressure-Surface, P, 1.0\n");
const auto first = MapText("stable", deck);
const auto second = MapText("stable", deck);
ASSERT_TRUE(first.HasValue());
ASSERT_TRUE(second.HasValue());
ASSERT_EQ(first.Value().Warnings().size(), 1U);
ASSERT_EQ(second.Value().Warnings().size(), 1U);
const auto& left = first.Value().Warnings().front();
const auto& right = second.Value().Warnings().front();
EXPECT_EQ(left.severity, right.severity);
EXPECT_EQ(left.code, right.code);
EXPECT_EQ(left.location.file, right.location.file);
EXPECT_EQ(left.location.line, right.location.line);
EXPECT_EQ(left.keyword, right.keyword);
EXPECT_EQ(left.entity_identity, right.entity_identity);
EXPECT_EQ(left.message, right.message);
}