feat(shell-surface-pressure-load): step 2 - reject-invalid-pressure-input
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
@@ -313,7 +314,9 @@ class DomainMappingContext::Impl {
|
||||
}
|
||||
|
||||
bool ValidateParameters(const KeywordBlock& block,
|
||||
const std::vector<std::string_view>& allowed) {
|
||||
const std::vector<std::string_view>& allowed,
|
||||
std::string_view unsupported_code =
|
||||
"invalid-keyword-parameter") {
|
||||
std::set<std::string> seen;
|
||||
for (const auto& candidate : block.parameters) {
|
||||
if (!seen.insert(candidate.name).second) {
|
||||
@@ -323,10 +326,10 @@ class DomainMappingContext::Impl {
|
||||
}
|
||||
if (std::find(allowed.begin(), allowed.end(), candidate.name) ==
|
||||
allowed.end()) {
|
||||
return InputFailure(
|
||||
"invalid-keyword-parameter", block.location, block.canonical_name,
|
||||
candidate.name,
|
||||
"The keyword parameter is outside the approved subset.");
|
||||
return InputFailure(std::string{unsupported_code}, block.location,
|
||||
block.canonical_name, candidate.name,
|
||||
"The keyword parameter is outside the approved "
|
||||
"subset.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -437,7 +440,17 @@ class DomainMappingContext::Impl {
|
||||
return InputFailure("invalid-numeric-value", location, keyword, text,
|
||||
"Pressure values require C-locale decimal syntax.");
|
||||
}
|
||||
return ParseDouble(text, value, location, keyword);
|
||||
const char* const begin = text.data();
|
||||
const char* const end = begin + text.size();
|
||||
const char* const numeric_begin = *begin == '+' ? begin + 1U : begin;
|
||||
const auto parsed = std::from_chars(numeric_begin, end, value,
|
||||
std::chars_format::general);
|
||||
if (parsed.ec != std::errc{} || parsed.ptr != end ||
|
||||
!std::isfinite(value)) {
|
||||
return InputFailure("invalid-numeric-value", location, keyword, text,
|
||||
"Pressure values must be finite C-locale values.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseNonlinearStaticDouble(const std::string& text, double& value,
|
||||
@@ -532,6 +545,22 @@ class DomainMappingContext::Impl {
|
||||
});
|
||||
}
|
||||
|
||||
bool IsPressureNameToken(const std::string_view text) const noexcept {
|
||||
if (text.empty() || text.size() > 80U) {
|
||||
return false;
|
||||
}
|
||||
const auto is_initial = [](const char value) {
|
||||
return (value >= 'A' && value <= 'Z') ||
|
||||
(value >= 'a' && value <= 'z') || value == '_';
|
||||
};
|
||||
const auto is_subsequent = [is_initial](const char value) {
|
||||
return is_initial(value) || (value >= '0' && value <= '9') ||
|
||||
value == '-' || value == '_';
|
||||
};
|
||||
return is_initial(text.front()) &&
|
||||
std::all_of(text.begin() + 1, text.end(), is_subsequent);
|
||||
}
|
||||
|
||||
void ParseBlocks() {
|
||||
definition_.source_path = input_.source_path;
|
||||
definition_.source_content_identity = input_.source_content_identity;
|
||||
@@ -800,6 +829,10 @@ class DomainMappingContext::Impl {
|
||||
return;
|
||||
}
|
||||
AddIgnoredWarning(block);
|
||||
} else if (block.canonical_name == "SURFACE") {
|
||||
InputFailure("unsupported-surface-scope", block.location,
|
||||
block.canonical_name, "",
|
||||
"Pressure surfaces are supported only at assembly scope.");
|
||||
} else {
|
||||
pending_section_.reset();
|
||||
beam_section_context_active_ = false;
|
||||
@@ -1319,6 +1352,12 @@ class DomainMappingContext::Impl {
|
||||
"An assembly cannot be nested in an instance.");
|
||||
return;
|
||||
}
|
||||
if (block.canonical_name == "SURFACE") {
|
||||
InputFailure("unsupported-surface-scope", block.location,
|
||||
block.canonical_name, instances_.back().name,
|
||||
"Pressure surfaces are unsupported inside an instance.");
|
||||
return;
|
||||
}
|
||||
InputFailure("unsupported-instance-mesh-semantics", block.location,
|
||||
block.canonical_name, instances_.back().name,
|
||||
"Instance-local mesh definitions are unsupported.");
|
||||
@@ -1334,10 +1373,19 @@ class DomainMappingContext::Impl {
|
||||
return;
|
||||
}
|
||||
const auto* name = RequiredParameterValue(block, name_parameter);
|
||||
const auto* instance = RequiredParameterValue(block, "INSTANCE");
|
||||
if (name == nullptr || instance == nullptr) {
|
||||
if (name == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto* instance_parameter = Parameter(block, "INSTANCE");
|
||||
if (instance_parameter == nullptr || !instance_parameter->value ||
|
||||
instance_parameter->value->empty()) {
|
||||
InputFailure(node_set ? "invalid-keyword-parameter"
|
||||
: "implicit-instance-elset",
|
||||
block.location, block.canonical_name, "INSTANCE",
|
||||
"Assembly element sets require an explicit INSTANCE.");
|
||||
return;
|
||||
}
|
||||
const std::string& instance = *instance_parameter->value;
|
||||
const auto* generate_parameter = Parameter(block, "GENERATE");
|
||||
if (generate_parameter != nullptr && generate_parameter->value) {
|
||||
InputFailure("invalid-keyword-parameter", block.location,
|
||||
@@ -1359,7 +1407,30 @@ class DomainMappingContext::Impl {
|
||||
"element-set namespace.");
|
||||
return;
|
||||
}
|
||||
RawAssemblySet set{node_set, *name, *instance, {}, block.location};
|
||||
if (!node_set && generate_parameter == nullptr) {
|
||||
for (const auto& row : block.data) {
|
||||
for (const auto& field : WithoutTrailingEmpty(row.fields)) {
|
||||
if (field.empty()) {
|
||||
continue;
|
||||
}
|
||||
const bool numeric = !field.empty() &&
|
||||
std::all_of(field.begin(), field.end(),
|
||||
[](const char value) {
|
||||
return value >= '0' &&
|
||||
value <= '9';
|
||||
});
|
||||
if (!numeric) {
|
||||
const std::string code = AsciiCaseInsensitiveEquals(field, *name)
|
||||
? "cyclic-elset"
|
||||
: "nested-elset";
|
||||
InputFailure(code, row.location, block.canonical_name, field,
|
||||
"Assembly ELSET members must be direct labels.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RawAssemblySet set{node_set, *name, instance, {}, block.location};
|
||||
if (!ParseSetMembers(block, generate_parameter != nullptr, set.members)) {
|
||||
return;
|
||||
}
|
||||
@@ -1367,7 +1438,8 @@ class DomainMappingContext::Impl {
|
||||
}
|
||||
|
||||
void ParseElementSurface(const KeywordBlock& block) {
|
||||
if (!ValidateParameters(block, {"TYPE", "NAME"})) {
|
||||
if (!ValidateParameters(block, {"TYPE", "NAME"},
|
||||
"unsupported-parameter")) {
|
||||
return;
|
||||
}
|
||||
const auto* type = RequiredParameterValue(block, "TYPE");
|
||||
@@ -1375,6 +1447,12 @@ class DomainMappingContext::Impl {
|
||||
if (type == nullptr || name == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!IsPressureNameToken(*name)) {
|
||||
InputFailure("invalid-surface-name", block.location,
|
||||
block.canonical_name, *name,
|
||||
"Surface names must use the approved ASCII name syntax.");
|
||||
return;
|
||||
}
|
||||
if (!AsciiCaseInsensitiveEquals(*type, "ELEMENT")) {
|
||||
InputFailure("unsupported-surface-type", block.location,
|
||||
block.canonical_name, *type,
|
||||
@@ -1387,7 +1465,7 @@ class DomainMappingContext::Impl {
|
||||
return;
|
||||
}
|
||||
if (block.data.empty()) {
|
||||
InputFailure("invalid-data-arity", block.location, block.canonical_name,
|
||||
InputFailure("empty-surface", block.location, block.canonical_name,
|
||||
*name, "An element surface requires at least one member.");
|
||||
return;
|
||||
}
|
||||
@@ -1563,7 +1641,8 @@ class DomainMappingContext::Impl {
|
||||
}
|
||||
|
||||
void ParseDsload(const KeywordBlock& block) {
|
||||
if (!ValidateParameters(block, {}) || block.data.empty()) {
|
||||
if (!ValidateParameters(block, {}, "unsupported-dsload-parameter") ||
|
||||
block.data.empty()) {
|
||||
if (!failure_) {
|
||||
InputFailure("invalid-data-arity", block.location, block.canonical_name,
|
||||
"", "DSLOAD requires one or more rows.");
|
||||
@@ -1578,6 +1657,13 @@ class DomainMappingContext::Impl {
|
||||
"", "DSLOAD rows require surface, P, magnitude.");
|
||||
return;
|
||||
}
|
||||
if (!IsPressureNameToken(row.fields[0])) {
|
||||
InputFailure("invalid-surface-name", row.location,
|
||||
block.canonical_name, row.fields[0],
|
||||
"DSLOAD surface names must use the approved ASCII name "
|
||||
"syntax.");
|
||||
return;
|
||||
}
|
||||
if (!AsciiCaseInsensitiveEquals(row.fields[1], "P")) {
|
||||
InputFailure("unsupported-load-type", row.location,
|
||||
block.canonical_name, row.fields[1],
|
||||
@@ -2469,6 +2555,12 @@ class DomainMappingContext::Impl {
|
||||
"A direct surface member must be instance.positive-element-label.");
|
||||
}
|
||||
const std::string instance_name = member.target.substr(0U, dot);
|
||||
if (!IsPressureNameToken(instance_name)) {
|
||||
return InputFailure(
|
||||
"unsupported-surface-target", member.location, "SURFACE",
|
||||
member.target,
|
||||
"A direct surface member must use an identity instance name.");
|
||||
}
|
||||
std::int64_t source_label = 0;
|
||||
if (!ParseInteger(member.target.substr(dot + 1U), source_label,
|
||||
member.location, "SURFACE", true)) {
|
||||
@@ -2479,6 +2571,12 @@ class DomainMappingContext::Impl {
|
||||
return AsciiCaseInsensitiveEquals(value.name, instance_name);
|
||||
});
|
||||
if (raw_instance == instances_.end()) {
|
||||
if (FindPart(instance_name) != nullptr) {
|
||||
return InputFailure(
|
||||
"unsupported-surface-target", member.location, "SURFACE",
|
||||
member.target,
|
||||
"A direct surface member cannot use a part name as an instance.");
|
||||
}
|
||||
return InputFailure("unresolved-reference", member.location, "SURFACE",
|
||||
member.target,
|
||||
"A direct surface member instance must resolve.");
|
||||
@@ -2507,15 +2605,29 @@ class DomainMappingContext::Impl {
|
||||
const RawSurfaceMember& member,
|
||||
std::vector<std::pair<ElementSurfaceFacetDefinition, std::size_t>>*
|
||||
candidates) {
|
||||
if (!IsPressureNameToken(member.target)) {
|
||||
return InputFailure("unsupported-surface-target", member.location,
|
||||
"SURFACE", member.target,
|
||||
"A surface ELSET must use the approved name syntax.");
|
||||
}
|
||||
const auto raw_set = std::find_if(
|
||||
assembly_sets_.begin(), assembly_sets_.end(), [&member](const RawAssemblySet& value) {
|
||||
return !value.is_node_set &&
|
||||
AsciiCaseInsensitiveEquals(value.name, member.target);
|
||||
});
|
||||
if (raw_set == assembly_sets_.end()) {
|
||||
return InputFailure("unresolved-reference", member.location, "SURFACE",
|
||||
member.target,
|
||||
"A surface ELSET must be assembly-scoped and explicit-instance.");
|
||||
const bool part_scoped = std::any_of(
|
||||
parts_.begin(), parts_.end(), [this, &member](const RawPart& part) {
|
||||
return FindSet(part.element_sets, member.target) != nullptr;
|
||||
});
|
||||
if (part_scoped) {
|
||||
return InputFailure(
|
||||
"cross-scope-target", member.location, "SURFACE", member.target,
|
||||
"A surface ELSET must be declared at assembly scope.");
|
||||
}
|
||||
return InputFailure(
|
||||
"missing-reference", member.location, "SURFACE", member.target,
|
||||
"A surface ELSET must be assembly-scoped and explicit-instance.");
|
||||
}
|
||||
const auto raw_instance = std::find_if(
|
||||
instances_.begin(), instances_.end(), [&raw_set](const RawInstance& value) {
|
||||
@@ -2713,7 +2825,7 @@ class DomainMappingContext::Impl {
|
||||
load.surface_name);
|
||||
});
|
||||
if (surface == definition_.element_surfaces.end()) {
|
||||
InputFailure("unresolved-reference", load.data_location, "DSLOAD",
|
||||
InputFailure("missing-reference", load.data_location, "DSLOAD",
|
||||
load.surface_name,
|
||||
"A DSLOAD surface must resolve after surface validation.");
|
||||
return;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user