#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" #include #include #include #include #include #include #include #include namespace { class TemporaryInputFile { public: TemporaryInputFile(const std::string& stem, const std::string& content) : path_{std::filesystem::temp_directory_path() / ("fesa-shell-surface-pressure-" + stem + ".inp")} { std::ofstream stream{path_, std::ios::binary | std::ios::trunc}; stream.write(content.data(), static_cast(content.size())); if (!stream) { throw std::runtime_error{"Unable to create pressure input fixture."}; } } ~TemporaryInputFile() { std::error_code error; std::filesystem::remove(path_, error); } const std::filesystem::path& Path() const noexcept { return path_; } private: std::filesystem::path path_; }; fesa::Result MapText(const std::string& stem, const std::string& content) { const TemporaryInputFile input{stem, content}; const auto parsed = fesa::AbaqusInputReader{}.Read(input.Path()); if (!parsed.HasValue()) { return fesa::Result::Failure(parsed.GetStatus()); } return fesa::AbaqusDomainMapper{}.Map(parsed.Value()); } fesa::Result 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& 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 *Node 1, 0., 0., 0. 2, 1., 0., 0. 3, 1., 1., 0. 4, 0., 1., 0. 5, 2., 0., 0. 6, 2., 1., 0. *Element, type=S4 0010, 1, 2, 3, 4 *Element, type=S4R 0020, 2, 5, 6, 3 *Elset, elset=S4-Set 0010 *Elset, elset=S4R-Set 0020 *Shell Section, elset=S4-Set, material=Steel 0.1 *Shell Section, elset=S4R-Set, material=Steel 0.1 *End Part *Assembly, name=Assembly *Instance, name=Instance-B, part=Shell-Part *End Instance *Instance, name=Instance-A, part=Shell-Part *End Instance )inp"} + assembly_content + R"inp(*End Assembly *Material, name=Steel *Elastic 210000., 0.3 *Step, name=Pressure-Step, nlgeom=NO *Static 0.1, 1., 0.01, 1. *Dsload )inp" + dsload_rows + "*End Step\n"; } const fesa::ElementSurfaceDefinition& OnlySurface(const fesa::Domain& domain) { EXPECT_EQ(domain.ElementSurfaces().size(), 1U); return domain.ElementSurfaces().front(); } } // namespace TEST(ShellSurfacePressureInput, MapsDirectInstanceFacetsForS4AndS4rCaseInsensitively) { auto result = MapText( "direct", ShellDeck("*SuRfAcE, NAME=Direct-Surface, TYPE=ELEMENT\n" "instance-a.0010, spos\n" "INSTANCE-A.0020, sneg\n", "direct-surface, P, 1.0\n")); ASSERT_TRUE(result.HasValue()); const auto& surface = OnlySurface(result.Value()); EXPECT_EQ(surface.surface_name_raw, "Direct-Surface"); EXPECT_EQ(surface.surface_name_canonical, "DIRECT-SURFACE"); ASSERT_EQ(surface.facets.size(), 2U); EXPECT_EQ(surface.facets[0].instance_name_raw, "Instance-A"); EXPECT_EQ(surface.facets[0].source_element_label, 10); EXPECT_EQ(surface.facets[0].source_element_label_raw, "0010"); EXPECT_EQ(surface.facets[0].source_element_type, "S4"); EXPECT_EQ(surface.facets[0].side, fesa::ShellSurfaceSide::kSpos); EXPECT_EQ(surface.facets[1].source_element_type, "S4R"); EXPECT_EQ(surface.facets[1].side, fesa::ShellSurfaceSide::kSneg); } TEST(ShellSurfacePressureInput, MapsExplicitInstanceElsetAndResultNeutralInternalParameter) { auto result = MapText( "elset", ShellDeck("*ELSET, ELSET=Flat-Members, INTERNAL, INSTANCE=Instance-A\n" "10, 20\n" "*SURFACE, TYPE=ELEMENT, NAME=Elset-Surface\n" "flat-members, SPOS\n", "Elset-Surface, P, 5.0\n")); ASSERT_TRUE(result.HasValue()); const auto& surface = OnlySurface(result.Value()); ASSERT_EQ(surface.facets.size(), 2U); for (const auto& facet : surface.facets) { EXPECT_EQ(facet.member_kind, fesa::SurfaceMemberKind::kElset); EXPECT_EQ(facet.elset_name_raw, "flat-members"); EXPECT_EQ(facet.elset_name_canonical, "FLAT-MEMBERS"); EXPECT_EQ(facet.instance_name_raw, "Instance-A"); EXPECT_EQ(facet.side, fesa::ShellSurfaceSide::kSpos); } } TEST(ShellSurfacePressureInput, MapsMixedDirectAndElsetMembersAcrossTwoExplicitInstancesWithStableDataMemberAndElsetOrder) { auto result = MapText( "mixed", ShellDeck("*ELSET, ELSET=Flat-B, INSTANCE=instance-b\n" "20\n" "*ELSET, ELSET=Flat-A, INSTANCE=INSTANCE-A\n" "10\n" "*SURFACE, TYPE=ELEMENT, NAME=Mixed-Surface\n" "instance-a.20, SNEG\n" "flat-b, SPOS\n" "INSTANCE-B.10, sneg\n" "FLAT-A, spos\n", "Mixed-Surface, P, 3.0\n")); ASSERT_TRUE(result.HasValue()); const auto& surface = OnlySurface(result.Value()); ASSERT_EQ(surface.facets.size(), 4U); EXPECT_EQ(surface.facets[0].instance_name_raw, "Instance-B"); EXPECT_EQ(surface.facets[0].source_element_label, 10); EXPECT_EQ(surface.facets[0].side, fesa::ShellSurfaceSide::kSneg); EXPECT_EQ(surface.facets[0].first_member_ordinal, 2U); EXPECT_EQ(surface.facets[1].instance_name_raw, "Instance-B"); EXPECT_EQ(surface.facets[1].source_element_label, 20); EXPECT_EQ(surface.facets[1].member_kind, fesa::SurfaceMemberKind::kElset); EXPECT_EQ(surface.facets[1].first_member_ordinal, 1U); EXPECT_EQ(surface.facets[2].instance_name_raw, "Instance-A"); EXPECT_EQ(surface.facets[2].source_element_label, 10); EXPECT_EQ(surface.facets[2].member_kind, fesa::SurfaceMemberKind::kElset); EXPECT_EQ(surface.facets[2].first_member_ordinal, 3U); EXPECT_EQ(surface.facets[3].instance_name_raw, "Instance-A"); EXPECT_EQ(surface.facets[3].source_element_label, 20); EXPECT_EQ(surface.facets[3].member_kind, fesa::SurfaceMemberKind::kDirect); EXPECT_EQ(surface.facets[3].first_member_ordinal, 0U); for (const auto& facet : surface.facets) { EXPECT_EQ(facet.part_name_raw, "Shell-Part"); EXPECT_EQ(facet.part_name_canonical, "SHELL-PART"); EXPECT_EQ(facet.instance_name_canonical, facet.instance_name_raw == "Instance-A" ? "INSTANCE-A" : "INSTANCE-B"); } } TEST(ShellSurfacePressureInput, PreservesSposSnegMagnitudeTextLocationsAndSourceOrder) { auto result = MapText( "provenance", ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n" "Instance-A.10, SPOS\n" "Instance-A.20, SNEG\n", "pressure-surface, p, -0.0\n" "PRESSURE-SURFACE, P, +2.50e+03\n")); ASSERT_TRUE(result.HasValue()); const auto& surface = OnlySurface(result.Value()); ASSERT_EQ(surface.facets.size(), 2U); EXPECT_EQ(surface.facets[0].facet_ordinal, 0U); EXPECT_EQ(surface.facets[0].side, fesa::ShellSurfaceSide::kSpos); EXPECT_EQ(surface.facets[1].facet_ordinal, 1U); EXPECT_EQ(surface.facets[1].side, fesa::ShellSurfaceSide::kSneg); const auto& step = result.Value().Steps()[0U]; ASSERT_EQ(step.SurfacePressureLoads().size(), 2U); const auto& first = step.SurfacePressureLoads()[0U]; const auto& second = step.SurfacePressureLoads()[1U]; EXPECT_EQ(first.step_name_raw, "Pressure-Step"); EXPECT_EQ(first.step_name_canonical, "PRESSURE-STEP"); EXPECT_EQ(first.surface_name_raw, "pressure-surface"); EXPECT_EQ(first.surface_name_canonical, "PRESSURE-SURFACE"); EXPECT_EQ(first.load_type, "P"); EXPECT_DOUBLE_EQ(first.magnitude, 0.0); EXPECT_EQ(first.magnitude_raw, "-0.0"); EXPECT_EQ(first.data_ordinal, 0U); EXPECT_EQ(first.load_ordinal, 0U); EXPECT_LT(first.keyword_location.line, first.data_location.line); EXPECT_EQ(second.magnitude_raw, "+2.50e+03"); EXPECT_DOUBLE_EQ(second.magnitude, 2500.0); EXPECT_EQ(second.data_ordinal, 1U); EXPECT_EQ(second.load_ordinal, 1U); } TEST(ShellSurfacePressureDomain, OwnsCanonicalFacetsAndStepOwnedAdditivePressureRecords) { fesa::ModelDefinition definition{}; definition.element_surfaces = { {"Surface", "SURFACE", {{0U, 0U, "Part", "PART", "Instance", "INSTANCE", 10, "0010", "S4", fesa::ShellSurfaceSide::kSpos, {"owned.inp", 3U}, 0U, fesa::SurfaceMemberKind::kDirect, "", ""}}, {"owned.inp", 2U}, 0U}}; definition.steps = {{"Step", {}, {}, {{"Step", "STEP", 0U, "Surface", "SURFACE", "P", 1.0, "1.0", {"owned.inp", 7U}, {"owned.inp", 8U}, 0U, 0U}}, fesa::StaticProcedureKind::kLinearSmallDisplacement, false, 0.1, 1.0, 0.01, 1.0, {"owned.inp", 6U}}}; auto result = fesa::Domain::Create(definition); ASSERT_TRUE(result.HasValue()); definition.element_surfaces[0].facets[0].source_element_label = 99; definition.steps[0].surface_pressure_loads[0].magnitude = 99.0; const fesa::Domain& domain = result.Value(); static_assert(std::is_same_v< decltype(std::declval().ElementSurfaces()), const std::vector&>); static_assert(std::is_same_v< decltype(std::declval() .SurfacePressureLoads()), const std::vector&>); EXPECT_EQ(domain.ElementSurfaces()[0].facets[0].source_element_label, 10); EXPECT_DOUBLE_EQ(domain.Steps()[0U].SurfacePressureLoads()[0].magnitude, 1.0); } TEST(ShellSurfacePressureDomain, AnalysisModelBorrowsActivePressureWithoutCopyingDomain) { auto mapped = MapText( "analysis-model", ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n" "Instance-A.10, SPOS\n", "Pressure-Surface, P, 1.0\n")); ASSERT_TRUE(mapped.HasValue()); auto model = fesa::AnalysisModel::Create(mapped.Value()); ASSERT_TRUE(model.HasValue()); EXPECT_EQ(&model.Value().ActiveSurfacePressureLoads(), &mapped.Value().Steps()[0U].SurfacePressureLoads()); EXPECT_EQ(model.Value().ActiveSurfacePressureLoads().size(), 1U); } TEST(ShellSurfacePressureInput, AcceptsWhitespaceSignedZeroAndFiniteScientificNotation) { auto result = MapText( "numbers", ShellDeck("*SURFACE, TYPE=ELEMENT, NAME=Pressure-Surface\n" "Instance-A.10, SPOS\n", " pressure-surface , p , +0 \n" "Pressure-Surface, P, -1.25E-3\n")); ASSERT_TRUE(result.HasValue()); const auto& loads = result.Value().Steps()[0U].SurfacePressureLoads(); ASSERT_EQ(loads.size(), 2U); EXPECT_EQ(loads[0].magnitude_raw, "+0"); EXPECT_DOUBLE_EQ(loads[0].magnitude, 0.0); 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); }