#include #include #include #include #include #include #include #include #include #include #include #include namespace { class TemporaryDeck final { public: TemporaryDeck(std::string_view name, std::string_view contents) : path_{std::filesystem::path{testing::TempDir()} / name} { std::ofstream output{path_, std::ios::binary}; output.write( contents.data(), static_cast(contents.size())); if (!output) { throw std::runtime_error{"Failed to write temporary Abaqus deck."}; } } ~TemporaryDeck() { std::error_code error; std::filesystem::remove(path_, error); } TemporaryDeck(const TemporaryDeck&) = delete; TemporaryDeck& operator=(const TemporaryDeck&) = delete; [[nodiscard]] const std::filesystem::path& path() const noexcept { return path_; } private: std::filesystem::path path_; }; fesa::SetResolutionResult parse_and_resolve( const TemporaryDeck& input) { const fesa::ParseDeckResult parsed = fesa::parse_deck(input.path()); if (!parsed.deck.has_value()) { throw std::runtime_error{"Test deck did not parse."}; } return fesa::resolve_sets(*parsed.deck); } const fesa::ResolvedSet& find_set( const fesa::SetResolutionResult& result, const fesa::ResolvedSetScope scope, const std::string_view scope_name, const fesa::ResolvedSetKind kind, const std::string_view set_name) { const auto found = std::ranges::find_if( result.sets, [=](const fesa::ResolvedSet& set) { return set.scope == scope && set.scope_name == scope_name && set.kind == kind && set.set_name == set_name; }); if (found == result.sets.end()) { throw std::runtime_error{"Expected resolved set was not found."}; } return *found; } const fesa::Diagnostic& find_diagnostic( const fesa::SetResolutionResult& result, const std::string_view code, const std::size_t line) { const auto found = std::ranges::find_if( result.diagnostics, [=](const fesa::Diagnostic& diagnostic) { return diagnostic.code == code && diagnostic.source.has_value() && diagnostic.source->line == line; }); if (found == result.diagnostics.end()) { throw std::runtime_error{"Expected set diagnostic was not found."}; } return *found; } TEST( SetResolution, CanonicalizesExplicitGenerateNestedForwardDuplicateAndEmptySets) { const TemporaryDeck input{ "fesa-set-resolution-valid.inp", "*PART, NAME=BeamPart\n" "*NODE\n" "1, 0, 0, 0\n" "2, 1, 0, 0\n" "3, 2, 0, 0\n" "*ELEMENT, TYPE=B31, ELSET=ImplicitElements\n" "10, 1, 2\n" "20, 2, 3\n" "*NSET, NSET=AllNodes\n" "GeneratedNodes, 3, 1, 3\n" "*NSET, NSET=GeneratedNodes, GENERATE\n" "1, 3, 1\n" "*NSET, NSET=EmptyNodes\n" "*ELSET, ELSET=AllElements\n" "GeneratedElements, ImplicitElements, 20\n" "*ELSET, ELSET=GeneratedElements, GENERATE\n" "10, 20, 10\n" "*END PART\n"}; const fesa::SetResolutionResult result = parse_and_resolve(input); ASSERT_TRUE(result.diagnostics.empty()); EXPECT_EQ( find_set( result, fesa::ResolvedSetScope::part, "BeamPart", fesa::ResolvedSetKind::node, "AllNodes") .sorted_unique_labels, (std::vector{1, 2, 3})); EXPECT_TRUE( find_set( result, fesa::ResolvedSetScope::part, "BeamPart", fesa::ResolvedSetKind::node, "EmptyNodes") .sorted_unique_labels.empty()); EXPECT_EQ( find_set( result, fesa::ResolvedSetScope::part, "BeamPart", fesa::ResolvedSetKind::element, "AllElements") .sorted_unique_labels, (std::vector{10, 20})); } TEST(PartSet, KeepsNodeAndElementSetNamespacesSeparate) { const TemporaryDeck input{ "fesa-part-set-kind-collision.inp", "*PART, NAME=BeamPart\n" "*NODE\n" "1, 0, 0, 0\n" "2, 1, 0, 0\n" "*ELEMENT, TYPE=B31\n" "1, 1, 2\n" "*NSET, NSET=Shared\n" "1\n" "*ELSET, ELSET=Shared\n" "1\n" "*END PART\n"}; const fesa::SetResolutionResult result = parse_and_resolve(input); ASSERT_TRUE(result.diagnostics.empty()); EXPECT_EQ( find_set( result, fesa::ResolvedSetScope::part, "BeamPart", fesa::ResolvedSetKind::node, "Shared") .sorted_unique_labels, (std::vector{1})); EXPECT_EQ( find_set( result, fesa::ResolvedSetScope::part, "BeamPart", fesa::ResolvedSetKind::element, "Shared") .sorted_unique_labels, (std::vector{1})); } TEST(AssemblySet, KeepsPartScopeSeparateAndLiftsActivePartLabels) { const TemporaryDeck input{ "fesa-assembly-set-scope-collision.inp", "*PART, NAME=BeamPart\n" "*NODE\n" "1, 0, 0, 0\n" "2, 1, 0, 0\n" "*NSET, NSET=Shared\n" "1\n" "*END PART\n" "*ASSEMBLY, NAME=RootAssembly\n" "*INSTANCE, NAME=Beam-1, PART=BeamPart\n" "*END INSTANCE\n" "*NSET, NSET=Shared, INSTANCE=Beam-1\n" "2, 2\n" "*END ASSEMBLY\n"}; const fesa::SetResolutionResult result = parse_and_resolve(input); ASSERT_TRUE(result.diagnostics.empty()); ASSERT_EQ(result.sets.size(), 2U); EXPECT_EQ( find_set( result, fesa::ResolvedSetScope::part, "BeamPart", fesa::ResolvedSetKind::node, "Shared") .sorted_unique_labels, (std::vector{1})); EXPECT_EQ( find_set( result, fesa::ResolvedSetScope::assembly, "RootAssembly", fesa::ResolvedSetKind::node, "Shared") .sorted_unique_labels, (std::vector{2})); EXPECT_TRUE(std::ranges::is_sorted( result.sets, [](const fesa::ResolvedSet& left, const fesa::ResolvedSet& right) { return std::tuple{ left.scope, left.scope_name, left.kind, left.set_name} < std::tuple{ right.scope, right.scope_name, right.kind, right.set_name}; })); } TEST(SetResolution, ReportsCycleAtTheClosingReferenceSource) { const TemporaryDeck input{ "fesa-set-resolution-cycle.inp", "*NSET, NSET=First\n" "Second\n" "*NSET, NSET=Second\n" "First\n"}; const fesa::SetResolutionResult result = parse_and_resolve(input); EXPECT_TRUE(result.sets.empty()); const fesa::Diagnostic& diagnostic = find_diagnostic(result, "abaqus.semantic.set_cycle", 4U); EXPECT_EQ(diagnostic.stage, fesa::DiagnosticStage::semantic); EXPECT_EQ(diagnostic.source->file, input.path()); } TEST(SetResolution, ReportsUnknownSetAndEntityAtTheirMemberRows) { const TemporaryDeck input{ "fesa-set-resolution-missing.inp", "*NODE\n" "1, 0, 0, 0\n" "*NSET, NSET=MissingEntity\n" "2\n" "*NSET, NSET=MissingSet\n" "Unknown\n"}; const fesa::SetResolutionResult result = parse_and_resolve(input); EXPECT_TRUE(result.sets.empty()); EXPECT_EQ(result.diagnostics.size(), 2U); EXPECT_EQ( find_diagnostic( result, "abaqus.semantic.missing_set_member", 4U) .stage, fesa::DiagnosticStage::semantic); EXPECT_EQ( find_diagnostic( result, "abaqus.semantic.missing_set_member", 6U) .stage, fesa::DiagnosticStage::semantic); } TEST(SetResolution, ReportsInvalidGenerateRangesInInputOrder) { const TemporaryDeck input{ "fesa-set-resolution-generate-invalid.inp", "*NSET, NSET=Reversed, GENERATE\n" "3, 1, 1\n" "*NSET, NSET=NotDivisible, GENERATE\n" "1, 4, 2\n" "*ELSET, ELSET=WrongFieldCount, GENERATE\n" "1, 2\n"}; const fesa::SetResolutionResult result = parse_and_resolve(input); EXPECT_TRUE(result.sets.empty()); ASSERT_EQ(result.diagnostics.size(), 3U); for (std::size_t index = 0; index < result.diagnostics.size(); ++index) { EXPECT_EQ( result.diagnostics[index].code, "abaqus.semantic.invalid_generate"); ASSERT_TRUE(result.diagnostics[index].source.has_value()); } EXPECT_EQ(result.diagnostics[0].source->line, 2U); EXPECT_EQ(result.diagnostics[1].source->line, 4U); EXPECT_EQ(result.diagnostics[2].source->line, 6U); } TEST(AssemblySet, RejectsAnInstanceOtherThanTheSingleActiveInstance) { const TemporaryDeck input{ "fesa-assembly-set-wrong-instance.inp", "*PART, NAME=BeamPart\n" "*NODE\n" "1, 0, 0, 0\n" "*END PART\n" "*ASSEMBLY, NAME=RootAssembly\n" "*INSTANCE, NAME=Beam-1, PART=BeamPart\n" "*END INSTANCE\n" "*NSET, NSET=Fixed, INSTANCE=Other\n" "1\n" "*END ASSEMBLY\n"}; const fesa::SetResolutionResult result = parse_and_resolve(input); EXPECT_TRUE(result.sets.empty()); EXPECT_EQ( find_diagnostic(result, "abaqus.semantic.wrong_instance", 8U) .stage, fesa::DiagnosticStage::semantic); } } // namespace