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
@@ -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");