fix(input): enforce strict Abaqus subset contract
This commit is contained in:
@@ -173,6 +173,128 @@ TEST(DeckToDomain, NormalizesFlatAndSingleInstanceDecksEquivalently) {
|
||||
fesa::ShearPropertySource::phase1_default);
|
||||
}
|
||||
|
||||
TEST(DeckToDomain, RequiresExactNodeAndB31DataWidthsAndNonemptyRecords) {
|
||||
struct Case final {
|
||||
std::string_view name;
|
||||
std::string_view mesh;
|
||||
std::string_view code;
|
||||
};
|
||||
const Case cases[]{
|
||||
{
|
||||
"node-surplus-field",
|
||||
"*NODE\n1, 0.0, 0.0, 0.0, 9.0\n"
|
||||
"2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n1, 1, 2\n",
|
||||
"abaqus.semantic.invalid_node_data",
|
||||
},
|
||||
{
|
||||
"node-empty",
|
||||
"*NODE\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n1, 1, 2\n",
|
||||
"abaqus.semantic.invalid_node_data",
|
||||
},
|
||||
{
|
||||
"element-surplus-field",
|
||||
"*NODE\n1, 0.0, 0.0, 0.0\n2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n1, 1, 2, 3\n",
|
||||
"abaqus.semantic.invalid_element_data",
|
||||
},
|
||||
{
|
||||
"element-empty",
|
||||
"*NODE\n1, 0.0, 0.0, 0.0\n2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n",
|
||||
"abaqus.semantic.invalid_element_data",
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& test_case : cases) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-exact-mesh-data-" + std::string{test_case.name} + ".inp",
|
||||
std::string{test_case.mesh} +
|
||||
"*ELSET, ELSET=Beam\n1\n"
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"*ELASTIC\n210000.0, 0.3\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, "
|
||||
"MATERIAL=Steel\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n0.0, 1.0, 0.0\n"
|
||||
"*STEP\n*STATIC\n*END STEP\n",
|
||||
};
|
||||
|
||||
const auto result = parse_and_map(input.path());
|
||||
|
||||
SCOPED_TRACE(test_case.name);
|
||||
EXPECT_FALSE(result.domain.has_value());
|
||||
EXPECT_TRUE(has_diagnostic(result, test_case.code));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DeckToDomain, ReportsMeshDataErrorsAtTheOffendingRows) {
|
||||
const TemporaryDeck invalid_coordinate{
|
||||
"fesa-node-coordinate-source.inp",
|
||||
"*NODE\n"
|
||||
"1, invalid, 0.0, 0.0\n"
|
||||
"*STEP\n"
|
||||
"*STATIC\n"
|
||||
"*END STEP\n"};
|
||||
const TemporaryDeck missing_node{
|
||||
"fesa-element-node-source.inp",
|
||||
"*NODE\n"
|
||||
"1, 0.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n"
|
||||
"1, 1, 2\n"
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"*ELASTIC\n"
|
||||
"210000.0, 0.3\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||
"0.0, 1.0, 0.0\n"
|
||||
"*STEP\n"
|
||||
"*STATIC\n"
|
||||
"*END STEP\n"};
|
||||
|
||||
const auto coordinate_result = parse_and_map(invalid_coordinate.path());
|
||||
const auto missing_node_result = parse_and_map(missing_node.path());
|
||||
|
||||
const fesa::Diagnostic* coordinate =
|
||||
find_diagnostic(coordinate_result, "abaqus.semantic.invalid_number");
|
||||
ASSERT_NE(coordinate, nullptr);
|
||||
ASSERT_TRUE(coordinate->source.has_value());
|
||||
EXPECT_EQ(coordinate->source->line, 2U);
|
||||
|
||||
const fesa::Diagnostic* node =
|
||||
find_diagnostic(missing_node_result, "abaqus.semantic.missing_node");
|
||||
ASSERT_NE(node, nullptr);
|
||||
ASSERT_TRUE(node->source.has_value());
|
||||
EXPECT_EQ(node->source->line, 4U);
|
||||
}
|
||||
|
||||
TEST(DeckToDomain, AcceptsCaseInsensitiveElementAndSectionValues) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-case-insensitive-enums.inp",
|
||||
"*NODE\n"
|
||||
"1, 0.0, 0.0, 0.0\n"
|
||||
"2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=b31, ELSET=Beam\n"
|
||||
"1, 1, 2\n"
|
||||
"*ELSET, ELSET=Beam\n"
|
||||
"1\n"
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"*ELASTIC\n"
|
||||
"210000.0, 0.3\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=general, ELSET=Beam, MATERIAL=Steel\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||
"0.0, 1.0, 0.0\n"
|
||||
"*STEP\n"
|
||||
"*STATIC\n"
|
||||
"*END STEP\n"};
|
||||
|
||||
const auto result = parse_and_map(input.path());
|
||||
|
||||
ASSERT_TRUE(result.domain.has_value());
|
||||
EXPECT_TRUE(result.diagnostics.empty());
|
||||
EXPECT_EQ(result.domain->beam_elements().size(), 1U);
|
||||
}
|
||||
|
||||
TEST(ActiveInstance, ExcludesPartsNotReferencedByTheInstance) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-active-instance.inp",
|
||||
@@ -406,6 +528,42 @@ TEST(Boundary, CanonicalizesIdenticalGlobalAndStepPrescriptions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Boundary, HierarchicalTargetsRequireAssemblySets) {
|
||||
const TemporaryDeck input{
|
||||
"fesa-hierarchical-part-set-target.inp",
|
||||
"*PART, NAME=BeamPart\n"
|
||||
"*NODE\n"
|
||||
"1, 0.0, 0.0, 0.0\n"
|
||||
"2, 1.0, 0.0, 0.0\n"
|
||||
"*ELEMENT, TYPE=B31, ELSET=Beam\n"
|
||||
"1, 1, 2\n"
|
||||
"*NSET, NSET=Fixed\n"
|
||||
"1\n"
|
||||
"*ELSET, ELSET=Beam\n"
|
||||
"1\n"
|
||||
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel\n"
|
||||
"1.0, 1.0, 0.0, 1.0, 1.0\n"
|
||||
"0.0, 1.0, 0.0\n"
|
||||
"*END PART\n"
|
||||
"*ASSEMBLY, NAME=RootAssembly\n"
|
||||
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
|
||||
"*END INSTANCE\n"
|
||||
"*END ASSEMBLY\n"
|
||||
"*MATERIAL, NAME=Steel\n"
|
||||
"*ELASTIC\n"
|
||||
"210000.0, 0.3\n"
|
||||
"*STEP\n"
|
||||
"*STATIC\n"
|
||||
"*BOUNDARY\n"
|
||||
"Fixed, 1, 6\n"
|
||||
"*END STEP\n"};
|
||||
|
||||
const auto result = parse_and_map(input.path());
|
||||
|
||||
EXPECT_FALSE(result.domain.has_value());
|
||||
EXPECT_TRUE(has_diagnostic(result, "abaqus.semantic.missing_node_target"));
|
||||
}
|
||||
|
||||
TEST(Boundary, ReportsConflictAtTheConflictingDataRow) {
|
||||
const auto result =
|
||||
parse_and_map(fixture_path("invalid/boundary_conflict.inp"));
|
||||
|
||||
Reference in New Issue
Block a user