diff --git a/cpp/src/api/exception/exception.cpp b/cpp/src/api/exception/exception.cpp index e932f90c..9890e051 100644 --- a/cpp/src/api/exception/exception.cpp +++ b/cpp/src/api/exception/exception.cpp @@ -82,23 +82,25 @@ EdgeType toEdgeType(std::string_view edgeTypeDisplay) { } std::vector parseCyclicError(std::string_view what) { - const auto suffix = what.substr(0, CYCLIC_ERROR_PREFIX.size()); + constexpr std::string_view SEPARATOR = " > "; + const auto suffix = what.substr(CYCLIC_ERROR_PREFIX.size()); std::vector vertices; size_t pos = 0; while (pos < suffix.size()) { - const auto sepPos = suffix.find("--", pos); - const auto escapedName = suffix.substr(pos, sepPos); - const auto name = replace(replace(escapedName, "\\-", "-"), "\\\\", "\\"); + const auto sepPos = suffix.find(SEPARATOR, pos); + const auto escapedName = suffix.substr(pos, sepPos - pos); + const auto name = replace(replace(escapedName, "\\>", ">"), "\\\\", "\\"); - if (sepPos != std::string::npos) { - const auto secondSepPos = suffix.find("--", sepPos + 2); + const auto secondSepPos = suffix.find(SEPARATOR, sepPos + SEPARATOR.size()); + if (secondSepPos != std::string::npos) { const auto escapedEdgeName = - suffix.substr(sepPos + 2, secondSepPos - (sepPos + 2)); + suffix.substr(sepPos + SEPARATOR.size(), + secondSepPos - (sepPos + SEPARATOR.size())); vertices.push_back(Vertex(name, toEdgeType(escapedEdgeName))); - pos = secondSepPos + 2; + pos = secondSepPos + SEPARATOR.size(); } else { vertices.push_back(Vertex(name)); pos = suffix.size(); diff --git a/cpp/src/error.rs b/cpp/src/error.rs index ccc5992b..3ce9a107 100644 --- a/cpp/src/error.rs +++ b/cpp/src/error.rs @@ -27,9 +27,9 @@ impl std::fmt::Display for VerboseError { Self::CyclicInteractionError(cycle) => { write!(f, "CyclicInteractionError: ")?; for vertex in cycle { - let name = vertex.name().replace('\\', "\\\\").replace('-', "\\-"); + let name = vertex.name().replace('\\', "\\\\").replace('>', "\\>"); match vertex.out_edge_type() { - Some(e) => write!(f, "{name}--{e}--")?, + Some(e) => write!(f, "{name} > {e} > ")?, None => write!(f, "{name}")?, } } diff --git a/cpp/src/tests/api/interface/database_interface_test.h b/cpp/src/tests/api/interface/database_interface_test.h index 22a1d950..7f41930e 100644 --- a/cpp/src/tests/api/interface/database_interface_test.h +++ b/cpp/src/tests/api/interface/database_interface_test.h @@ -409,6 +409,31 @@ TEST_P(DatabaseInterfaceTest, EXPECT_FALSE(path[1].GetTypeOfEdgeToNextVertex().has_value()); } +TEST_P(DatabaseInterfaceTest, + getGroupsPathShouldThrowIfThereIsACycleBetweenGroups) { + auto& db = handle_->GetDatabase(); + + auto group1Name = "backslash \\\\ group \\"; + auto group2Name = "greater than > group"; + Group group1(group1Name, {group2Name}); + Group group2(group2Name, {group1Name}); + + db.SetUserGroups({group1, group2}); + + try { + handle_->GetDatabase().GetGroupsPath(group1Name, group2Name); + FAIL(); + } catch (CyclicInteractionError& e) { + const auto cycle = e.GetCycle(); + ASSERT_EQ(2, cycle.size()); + ASSERT_EQ(2, cycle.size()); + EXPECT_EQ(group1Name, cycle[0].GetName()); + EXPECT_EQ(EdgeType::userLoadAfter, cycle[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(group2Name, cycle[1].GetName()); + EXPECT_EQ(EdgeType::userLoadAfter, cycle[1].GetTypeOfEdgeToNextVertex()); + } +} + TEST_P(DatabaseInterfaceTest, getKnownBashTagsShouldReturnAllBashTagsListedInLoadedMetadata) { ASSERT_NO_THROW(GenerateMasterlist()); diff --git a/cpp/src/tests/api/interface/game_interface_test.h b/cpp/src/tests/api/interface/game_interface_test.h index 9682deda..c8d9b176 100644 --- a/cpp/src/tests/api/interface/game_interface_test.h +++ b/cpp/src/tests/api/interface/game_interface_test.h @@ -469,6 +469,32 @@ TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfAGivenPluginIsNotLoaded) { EXPECT_THROW(handle_->SortPlugins(plugins), PluginNotLoadedError); } +TEST_P(GameInterfaceTest, sortPluginsShouldThrowIfACyclicInteractionOccurs) { + std::vector plugins{blankEsp, blankDifferentEsp}; + handle_->LoadPlugins({blankEsp, blankDifferentEsp}, false); + + auto& db = handle_->GetDatabase(); + PluginMetadata blankEspMetadata(blankEsp); + blankEspMetadata.SetLoadAfterFiles({File(blankDifferentEsp)}); + db.SetPluginUserMetadata(blankEspMetadata); + + PluginMetadata blankDifferentEspMetadata(blankDifferentEsp); + blankDifferentEspMetadata.SetLoadAfterFiles({File(blankEsp)}); + db.SetPluginUserMetadata(blankDifferentEspMetadata); + + try { + handle_->SortPlugins(plugins); + FAIL(); + } catch (CyclicInteractionError& e) { + const auto cycle = e.GetCycle(); + ASSERT_EQ(2, cycle.size()); + EXPECT_EQ(blankDifferentEsp, cycle[0].GetName()); + EXPECT_EQ(EdgeType::userLoadAfter, cycle[0].GetTypeOfEdgeToNextVertex()); + EXPECT_EQ(blankEsp, cycle[1].GetName()); + EXPECT_EQ(EdgeType::userLoadAfter, cycle[1].GetTypeOfEdgeToNextVertex()); + } +} + TEST_P(GameInterfaceTest, clearLoadedPluginsShouldClearThePluginsCache) { handle_->LoadPlugins({std::filesystem::u8path(blankEsm)}, true); const auto pointer = handle_->GetPlugin(blankEsm);