fix(input): enforce strict Abaqus subset contract

This commit is contained in:
KOKO\Mimi
2026-08-01 03:48:29 +09:00
parent 9269847c83
commit af886f3f90
25 changed files with 722 additions and 34 deletions
+16
View File
@@ -26,6 +26,7 @@ heading_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_defa
preprint_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
restart_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
output_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
enum_case_insensitive_valid valid valid/case_insensitive_enums.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
node_duplicate_invalid invalid invalid/node_duplicate.inp semantic abaqus.semantic.duplicate_node_label 3 - - - - - - - - - - -
element_type_invalid invalid invalid/element_wrong_type.inp semantic abaqus.semantic.unsupported_element 4 - - - - - - - - - - -
part_unclosed_invalid invalid invalid/part_unclosed.inp syntax abaqus.syntax.unclosed_part 1 - - - - - - - - - - -
@@ -52,3 +53,18 @@ preprint_parameter_invalid invalid invalid/preprint_invalid_parameter.inp syntax
restart_scope_invalid invalid invalid/restart_outside_step.inp syntax abaqus.syntax.invalid_restart_scope 1 - - - - - - - - - - -
output_parameter_invalid invalid invalid/output_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 3 - - - - - - - - - - -
unknown_keyword_invalid invalid invalid/unknown_include.inp syntax abaqus.unsupported_keyword 1 - - - - - - - - - - -
node_parameter_invalid invalid invalid/node_unsupported_parameter.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
node_scope_invalid invalid invalid/node_wrong_scope.inp syntax abaqus.syntax.invalid_node_scope 2 - - - - - - - - - - -
material_scope_invalid invalid invalid/material_wrong_scope.inp syntax abaqus.syntax.invalid_material_scope 2 - - - - - - - - - - -
generate_form_invalid invalid invalid/generate_valued.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
flat_set_instance_invalid invalid invalid/flat_set_instance.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
global_set_hierarchy_invalid invalid invalid/global_set_hierarchical.inp semantic abaqus.semantic.mixed_mesh_organization 1 - - - - - - - - - - -
duplicate_part_invalid invalid invalid/duplicate_part.inp semantic abaqus.semantic.duplicate_part 3 - - - - - - - - - - -
node_surplus_field_invalid invalid invalid/node_surplus_field.inp semantic abaqus.semantic.invalid_node_data 2 - - - - - - - - - - -
node_empty_invalid invalid invalid/node_empty.inp semantic abaqus.semantic.invalid_node_data 1 - - - - - - - - - - -
element_surplus_field_invalid invalid invalid/element_surplus_field.inp semantic abaqus.semantic.invalid_element_data 2 - - - - - - - - - - -
element_empty_invalid invalid invalid/element_empty.inp semantic abaqus.semantic.invalid_element_data 1 - - - - - - - - - - -
hierarchical_part_target_invalid invalid invalid/hierarchical_part_set_target.inp semantic abaqus.semantic.missing_node_target 25 - - - - - - - - - - -
material_data_invalid invalid invalid/material_data.inp syntax abaqus.syntax.data_without_keyword 2 - - - - - - - - - - -
node_coordinate_invalid invalid invalid/node_invalid_coordinate.inp semantic abaqus.semantic.invalid_number 2 - - - - - - - - - - -
element_missing_node_invalid invalid invalid/element_missing_node.inp semantic abaqus.semantic.missing_node 4 - - - - - - - - - - -
1 # case_id outcome fixture expected_stage expected_code expected_line node_count element_count node_set_count element_set_count prescribed_dof_count nodal_load_count shear_source shear_area_y shear_area_z checked_node_set checked_element_set
26 preprint_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
27 restart_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
28 output_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
29 enum_case_insensitive_valid valid valid/case_insensitive_enums.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
30 node_duplicate_invalid invalid invalid/node_duplicate.inp semantic abaqus.semantic.duplicate_node_label 3 - - - - - - - - - - -
31 element_type_invalid invalid invalid/element_wrong_type.inp semantic abaqus.semantic.unsupported_element 4 - - - - - - - - - - -
32 part_unclosed_invalid invalid invalid/part_unclosed.inp syntax abaqus.syntax.unclosed_part 1 - - - - - - - - - - -
53 restart_scope_invalid invalid invalid/restart_outside_step.inp syntax abaqus.syntax.invalid_restart_scope 1 - - - - - - - - - - -
54 output_parameter_invalid invalid invalid/output_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 3 - - - - - - - - - - -
55 unknown_keyword_invalid invalid invalid/unknown_include.inp syntax abaqus.unsupported_keyword 1 - - - - - - - - - - -
56 node_parameter_invalid invalid invalid/node_unsupported_parameter.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
57 node_scope_invalid invalid invalid/node_wrong_scope.inp syntax abaqus.syntax.invalid_node_scope 2 - - - - - - - - - - -
58 material_scope_invalid invalid invalid/material_wrong_scope.inp syntax abaqus.syntax.invalid_material_scope 2 - - - - - - - - - - -
59 generate_form_invalid invalid invalid/generate_valued.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
60 flat_set_instance_invalid invalid invalid/flat_set_instance.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
61 global_set_hierarchy_invalid invalid invalid/global_set_hierarchical.inp semantic abaqus.semantic.mixed_mesh_organization 1 - - - - - - - - - - -
62 duplicate_part_invalid invalid invalid/duplicate_part.inp semantic abaqus.semantic.duplicate_part 3 - - - - - - - - - - -
63 node_surplus_field_invalid invalid invalid/node_surplus_field.inp semantic abaqus.semantic.invalid_node_data 2 - - - - - - - - - - -
64 node_empty_invalid invalid invalid/node_empty.inp semantic abaqus.semantic.invalid_node_data 1 - - - - - - - - - - -
65 element_surplus_field_invalid invalid invalid/element_surplus_field.inp semantic abaqus.semantic.invalid_element_data 2 - - - - - - - - - - -
66 element_empty_invalid invalid invalid/element_empty.inp semantic abaqus.semantic.invalid_element_data 1 - - - - - - - - - - -
67 hierarchical_part_target_invalid invalid invalid/hierarchical_part_set_target.inp semantic abaqus.semantic.missing_node_target 25 - - - - - - - - - - -
68 material_data_invalid invalid invalid/material_data.inp syntax abaqus.syntax.data_without_keyword 2 - - - - - - - - - - -
69 node_coordinate_invalid invalid invalid/node_invalid_coordinate.inp semantic abaqus.semantic.invalid_number 2 - - - - - - - - - - -
70 element_missing_node_invalid invalid invalid/element_missing_node.inp semantic abaqus.semantic.missing_node 4 - - - - - - - - - - -
+8
View File
@@ -0,0 +1,8 @@
*PART, NAME=BeamPart
*END PART
*PART, NAME=BeamPart
*END PART
*ASSEMBLY, NAME=RootAssembly
*INSTANCE, NAME=Beam-1, PART=BeamPart
*END INSTANCE
*END ASSEMBLY
+4
View File
@@ -0,0 +1,4 @@
*ELEMENT, TYPE=B31
*STEP
*STATIC
*END STEP
+13
View File
@@ -0,0 +1,13 @@
*NODE
1, 0.0, 0.0, 0.0
*ELEMENT, TYPE=B31, ELSET=Beam
1, 1, 2
*MATERIAL, NAME=Steel
*ELASTIC
210000.0, 0.3
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*STEP
*STATIC
*END STEP
@@ -0,0 +1,5 @@
*ELEMENT, TYPE=B31
1, 1, 2, 3
*STEP
*STATIC
*END STEP
+2
View File
@@ -0,0 +1,2 @@
*NSET, NSET=Fixed, INSTANCE=Beam-1
1
+2
View File
@@ -0,0 +1,2 @@
*NSET, NSET=Generated, GENERATE=YES
1, 2, 1
@@ -0,0 +1,7 @@
*NSET, NSET=Ignored
*PART, NAME=BeamPart
*END PART
*ASSEMBLY, NAME=RootAssembly
*INSTANCE, NAME=Beam-1, PART=BeamPart
*END INSTANCE
*END ASSEMBLY
@@ -0,0 +1,26 @@
*PART, NAME=BeamPart
*NODE
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
*ELEMENT, TYPE=B31, ELSET=Beam
1, 1, 2
*NSET, NSET=Fixed
1
*ELSET, ELSET=Beam
1
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*END PART
*ASSEMBLY, NAME=RootAssembly
*INSTANCE, NAME=Beam-1, PART=BeamPart
*END INSTANCE
*END ASSEMBLY
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*STEP
*STATIC
*BOUNDARY
Fixed, 1, 6
*END STEP
+2
View File
@@ -0,0 +1,2 @@
*MATERIAL, NAME=Steel
unexpected data
@@ -0,0 +1,3 @@
*PART, NAME=BeamPart
*MATERIAL, NAME=Steel
*END PART
+4
View File
@@ -0,0 +1,4 @@
*NODE
*STEP
*STATIC
*END STEP
@@ -0,0 +1,5 @@
*NODE
1, invalid, 0.0, 0.0
*STEP
*STATIC
*END STEP
+5
View File
@@ -0,0 +1,5 @@
*NODE
1, 0.0, 0.0, 0.0, 9.0
*STEP
*STATIC
*END STEP
@@ -0,0 +1,2 @@
*NODE, EXTRA=1
1, 0.0, 0.0, 0.0
+4
View File
@@ -0,0 +1,4 @@
*ASSEMBLY, NAME=RootAssembly
*NODE
1, 0.0, 0.0, 0.0
*END ASSEMBLY
+1 -1
View File
@@ -4,7 +4,7 @@
1, 0.0, 0.0, 0.0
** Comments and blank lines do not terminate the current data record.
2, 1.0, 0.0, 0.0,
2, 1.0, 0.0, 0.0
*eLeMeNt, TYPE=B31, elset=Beam
1, 1, 2
*NSET, NSET=Fixed
+22
View File
@@ -0,0 +1,22 @@
*NODE
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
*ELEMENT, TYPE=b31, ELSET=Beam
1, 1, 2
*NSET, NSET=Fixed
1
*ELSET, ELSET=Beam
1
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*BEAM GENERAL SECTION, SECTION=general, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*BOUNDARY
Fixed, 1, 6
*STEP, NAME=Load
*STATIC
*CLOAD
2, 2, -1.0
*END STEP
@@ -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"));
@@ -130,6 +130,37 @@ TEST(ActiveInput, RejectsMixedFlatAndHierarchicalMeshAtTheFlatRecord) {
EXPECT_EQ(diagnostic.source->file, fixture_path("invalid/mixed_mesh.inp"));
}
TEST(ActiveInput, RejectsGlobalSetsInHierarchicalInput) {
const TemporaryDeck input{
"fesa-global-set-with-hierarchy.inp",
"*NSET, NSET=Ignored\n"
"*PART, NAME=BeamPart\n"
"*END PART\n"
"*ASSEMBLY, NAME=RootAssembly\n"
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
"*END INSTANCE\n"
"*END ASSEMBLY\n"};
expect_failure(
parse(input.path()), "abaqus.semantic.mixed_mesh_organization", 1U);
}
TEST(ActiveInput, RejectsDuplicatePartNamesAtTheSecondDefinition) {
const TemporaryDeck input{
"fesa-duplicate-parts.inp",
"*PART, NAME=BeamPart\n"
"*END PART\n"
"*PART, NAME=BeamPart\n"
"*END PART\n"
"*ASSEMBLY, NAME=RootAssembly\n"
"*INSTANCE, NAME=Beam-1, PART=BeamPart\n"
"*END INSTANCE\n"
"*END ASSEMBLY\n"};
expect_failure(
parse(input.path()), "abaqus.semantic.duplicate_part", 3U);
}
TEST(ActiveInput, RejectsHierarchicalInputWithoutAnAssembly) {
const TemporaryDeck input{
"fesa-missing-assembly.inp",
+209 -1
View File
@@ -74,7 +74,7 @@ TEST(AbaqusParser, ParsesCaseInsensitiveKeywordsCommentsAndCommaFields) {
EXPECT_EQ(nodes.data[0], (std::vector<std::string>{
"1", "0.0", "0.0", "0.0"}));
EXPECT_EQ(nodes.data[1], (std::vector<std::string>{
"2", "1.0", "0.0", "0.0", ""}));
"2", "1.0", "0.0", "0.0"}));
EXPECT_EQ(nodes.source.file, path);
EXPECT_EQ(nodes.source.line, 3U);
EXPECT_EQ(nodes.source.column, 1U);
@@ -121,6 +121,214 @@ TEST(AbaqusParser, RejectsUnsupportedKeywordsInsteadOfIgnoringThem) {
EXPECT_EQ(result.diagnostics[0].source->line, 2U);
}
TEST(AbaqusParser, RejectsUnsupportedParametersOnSupportedKeywords) {
struct Case final {
std::string_view name;
std::string_view contents;
std::size_t line;
};
const Case cases[]{
{"node", "*NODE, EXTRA=1\n1, 0.0, 0.0, 0.0\n", 1U},
{"element", "*ELEMENT, TYPE=B31, EXTRA=1\n1, 1, 2\n", 1U},
{"part", "*PART, NAME=P, EXTRA=1\n*END PART\n", 1U},
{"end-part", "*PART, NAME=P\n*END PART, EXTRA=1\n", 2U},
{
"assembly",
"*ASSEMBLY, NAME=A, EXTRA=1\n*END ASSEMBLY\n",
1U,
},
{
"end-assembly",
"*ASSEMBLY, NAME=A\n*END ASSEMBLY, EXTRA=1\n",
2U,
},
{
"instance",
"*ASSEMBLY, NAME=A\n"
"*INSTANCE, NAME=I, PART=P, EXTRA=1\n"
"*END INSTANCE\n"
"*END ASSEMBLY\n",
2U,
},
{
"end-instance",
"*ASSEMBLY, NAME=A\n"
"*INSTANCE, NAME=I, PART=P\n"
"*END INSTANCE, EXTRA=1\n"
"*END ASSEMBLY\n",
3U,
},
{"nset", "*NSET, NSET=S, EXTRA=1\n1\n", 1U},
{"elset", "*ELSET, ELSET=S, EXTRA=1\n1\n", 1U},
{"material", "*MATERIAL, NAME=M, EXTRA=1\n", 1U},
{
"elastic",
"*MATERIAL, NAME=M\n*ELASTIC, EXTRA=1\n1.0, 0.3\n",
2U,
},
{
"beam-general-section",
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=S, "
"MATERIAL=M, EXTRA=1\n"
"1.0, 1.0, 0.0, 1.0, 1.0\n"
"0.0, 1.0, 0.0\n",
1U,
},
{
"transverse-shear-stiffness",
"*TRANSVERSE SHEAR STIFFNESS, EXTRA=1\n1.0, 1.0, 0.0\n",
1U,
},
};
for (const auto& test_case : cases) {
const TemporaryDeck input{
"fesa-parser-unsupported-parameter-" +
std::string{test_case.name} + ".inp",
test_case.contents,
};
const auto result = fesa::parse_deck(input.path());
SCOPED_TRACE(test_case.name);
EXPECT_FALSE(result.deck.has_value());
ASSERT_EQ(result.diagnostics.size(), 1U);
EXPECT_EQ(
result.diagnostics[0].code,
"abaqus.syntax.unsupported_parameter");
ASSERT_TRUE(result.diagnostics[0].source.has_value());
EXPECT_EQ(result.diagnostics[0].source->line, test_case.line);
}
}
TEST(AbaqusParser, RejectsInvalidParameterFormsAndSetScopeOptions) {
struct Case final {
std::string_view name;
std::string_view contents;
std::string_view code;
};
const Case cases[]{
{
"generate-valued",
"*NSET, NSET=S, GENERATE=YES\n1, 2, 1\n",
"abaqus.syntax.unsupported_parameter",
},
{
"generate-empty-valued",
"*NSET, NSET=S, GENERATE=\n1, 2, 1\n",
"abaqus.syntax.unsupported_parameter",
},
{
"empty-element-set",
"*ELEMENT, TYPE=B31, ELSET=\n1, 1, 2\n",
"abaqus.syntax.invalid_parameter",
},
{
"instance-on-flat-set",
"*NSET, NSET=S, INSTANCE=I\n1\n",
"abaqus.syntax.unsupported_parameter",
},
{
"instance-on-part-set",
"*PART, NAME=P\n"
"*NSET, NSET=S, INSTANCE=I\n"
"1\n"
"*END PART\n",
"abaqus.syntax.unsupported_parameter",
},
};
for (const auto& test_case : cases) {
const TemporaryDeck input{
"fesa-parser-parameter-form-" + std::string{test_case.name} +
".inp",
test_case.contents,
};
const auto result = fesa::parse_deck(input.path());
SCOPED_TRACE(test_case.name);
EXPECT_FALSE(result.deck.has_value());
ASSERT_EQ(result.diagnostics.size(), 1U);
EXPECT_EQ(result.diagnostics[0].code, test_case.code);
ASSERT_TRUE(result.diagnostics[0].source.has_value());
EXPECT_EQ(result.diagnostics[0].source->line, 1U +
(test_case.name == "instance-on-part-set" ? 1U : 0U));
}
}
TEST(AbaqusParser, RejectsModelKeywordsOutsideTheirDocumentedScopes) {
struct Case final {
std::string_view name;
std::string_view contents;
std::string_view code;
std::size_t line;
};
const Case cases[]{
{
"node-in-assembly",
"*ASSEMBLY, NAME=A\n"
"*NODE\n"
"1, 0.0, 0.0, 0.0\n"
"*END ASSEMBLY\n",
"abaqus.syntax.invalid_node_scope",
2U,
},
{
"material-in-part",
"*PART, NAME=P\n"
"*MATERIAL, NAME=M\n"
"*END PART\n",
"abaqus.syntax.invalid_material_scope",
2U,
},
{
"section-in-assembly",
"*ASSEMBLY, NAME=A\n"
"*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=S, MATERIAL=M\n"
"1.0, 1.0, 0.0, 1.0, 1.0\n"
"0.0, 1.0, 0.0\n"
"*END ASSEMBLY\n",
"abaqus.syntax.invalid_section_scope",
2U,
},
};
for (const auto& test_case : cases) {
const TemporaryDeck input{
"fesa-parser-invalid-scope-" + std::string{test_case.name} +
".inp",
test_case.contents,
};
const auto result = fesa::parse_deck(input.path());
SCOPED_TRACE(test_case.name);
EXPECT_FALSE(result.deck.has_value());
ASSERT_EQ(result.diagnostics.size(), 1U);
EXPECT_EQ(result.diagnostics[0].code, test_case.code);
ASSERT_TRUE(result.diagnostics[0].source.has_value());
EXPECT_EQ(result.diagnostics[0].source->line, test_case.line);
}
}
TEST(AbaqusParser, RejectsDataRowsOnMaterial) {
const TemporaryDeck input{
"fesa-parser-material-data.inp",
"*MATERIAL, NAME=Steel\n"
"unexpected data\n"};
const auto result = fesa::parse_deck(input.path());
EXPECT_FALSE(result.deck.has_value());
ASSERT_EQ(result.diagnostics.size(), 1U);
EXPECT_EQ(
result.diagnostics[0].code,
"abaqus.syntax.data_without_keyword");
ASSERT_TRUE(result.diagnostics[0].source.has_value());
EXPECT_EQ(result.diagnostics[0].source->line, 2U);
}
TEST(ScopedDeck, PreservesPartAssemblyAndInstanceScopes) {
const auto path =
fixture_path("minimal_part_instance_cantilever.inp");