mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Add DatabaseInterface::GetGroupsPath()
It returns the path between the two given groups that maximises user metadata and minimises masterlist metadata involved.
This commit is contained in:
@@ -202,6 +202,7 @@ set (LOOT_API_SRC "${CMAKE_BINARY_DIR}/generated/loot_version.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/crc.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/git_helper.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/version.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/vertex.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/resource.rc")
|
||||
|
||||
set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h"
|
||||
@@ -213,6 +214,7 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/exception/file_access_error.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/exception/git_state_error.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/exception/undefined_group_error.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/enum/edge_type.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/enum/game_type.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/enum/log_level.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/enum/message_type.h"
|
||||
@@ -230,6 +232,7 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/plugin_interface.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/struct/masterlist_info.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/struct/simple_message.h"
|
||||
"${CMAKE_SOURCE_DIR}/include/loot/vertex.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/api_database.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/metadata/condition_evaluator.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/metadata/condition_grammar.h"
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "loot/exception/cyclic_interaction_error.h"
|
||||
#include "loot/metadata/group.h"
|
||||
#include "loot/metadata/message.h"
|
||||
#include "loot/metadata/plugin_metadata.h"
|
||||
@@ -191,7 +192,8 @@ public:
|
||||
* metadata from the masterlist.
|
||||
* @returns An unordered set of Group objects.
|
||||
*/
|
||||
virtual std::unordered_set<Group> GetGroups(bool includeUserMetadata = true) const = 0;
|
||||
virtual std::unordered_set<Group> GetGroups(
|
||||
bool includeUserMetadata = true) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Gets the groups that are defined or extended in the loaded userlist.
|
||||
@@ -207,6 +209,25 @@ public:
|
||||
*/
|
||||
virtual void SetUserGroups(const std::unordered_set<Group>& groups) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the "shortest" path between the two given groups according to
|
||||
* their load after metadata.
|
||||
* @details The "shortest" path is defined as the path that maximises the
|
||||
* amount of user metadata involved while minimising the amount of
|
||||
* masterlist metadata involved. It's not the path involving the
|
||||
* fewest groups.
|
||||
* @param fromGroupName
|
||||
* The name of the source group, that loads earlier.
|
||||
* @param toGroupName
|
||||
* The name of the destination group, that loads later.
|
||||
* @returns A vector of Vertex elements representing the path from the source
|
||||
* group to the destination group, or an empty vector if no path
|
||||
* exists.
|
||||
*/
|
||||
virtual std::vector<Vertex> GetGroupsPath(
|
||||
const std::string& fromGroupName,
|
||||
const std::string& toGroupName) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Set the groups
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2012-2016 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT is free software: you can redistribute
|
||||
it and/or modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
LOOT is distributed in the hope that it will
|
||||
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with LOOT. If not, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LOOT_EDGE_TYPE
|
||||
#define LOOT_EDGE_TYPE
|
||||
|
||||
/**
|
||||
* The namespace used by the LOOT API.
|
||||
*/
|
||||
namespace loot {
|
||||
/**
|
||||
* @brief An enum representing the different possible types of interactions
|
||||
* between plugins or groups.
|
||||
*/
|
||||
enum struct EdgeType : unsigned int {
|
||||
hardcoded,
|
||||
masterFlag,
|
||||
master,
|
||||
masterlistRequirement,
|
||||
userRequirement,
|
||||
masterlistLoadAfter,
|
||||
userLoadAfter,
|
||||
group,
|
||||
overlap,
|
||||
tieBreak,
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -26,60 +26,11 @@
|
||||
#define LOOT_EXCEPTION_CYCLIC_INTERACTION_ERROR
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "loot/vertex.h"
|
||||
|
||||
namespace loot {
|
||||
/**
|
||||
* @brief An enum representing the different possible types of interactions
|
||||
* between plugins or groups.
|
||||
*/
|
||||
enum struct EdgeType : unsigned int {
|
||||
hardcoded,
|
||||
masterFlag,
|
||||
master,
|
||||
masterlistRequirement,
|
||||
userRequirement,
|
||||
masterlistLoadAfter,
|
||||
userLoadAfter,
|
||||
group,
|
||||
overlap,
|
||||
tieBreak,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A class representing a plugin or group vertex in a cyclic interaction
|
||||
* path, and the type of the interaction with the next vertex in the
|
||||
* path.
|
||||
*/
|
||||
class Vertex {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a Vertex with the given name and out edge type.
|
||||
* @param name The name of the plugin or group that this vertex represents.
|
||||
* @param outEdgeType The type of the edge going out from this vertex.
|
||||
*/
|
||||
Vertex(std::string name, EdgeType outEdgeType);
|
||||
|
||||
/**
|
||||
* @brief Get the name of the plugin or group.
|
||||
* @return The name of the plugin or group.
|
||||
*/
|
||||
std::string GetName() const;
|
||||
|
||||
/**
|
||||
* @brief Get the type of the edge going to the next vertex.
|
||||
* @details Each edge goes from the vertex that loads earlier to the vertex
|
||||
* that loads later.
|
||||
* @return The edge type.
|
||||
*/
|
||||
EdgeType GetTypeOfEdgeToNextVertex() const;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
EdgeType outEdgeType_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An exception class thrown if a cyclic interaction is detected when
|
||||
* sorting a load order.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2014-2016 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT is free software: you can redistribute
|
||||
it and/or modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
LOOT is distributed in the hope that it will
|
||||
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with LOOT. If not, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LOOT_VERTEX
|
||||
#define LOOT_VERTEX
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "loot/api_decorator.h"
|
||||
#include "loot/enum/edge_type.h"
|
||||
|
||||
namespace loot {
|
||||
/**
|
||||
* @brief A class representing a plugin or group vertex in a path, and the
|
||||
type of the edge to the next vertex in the path if one exists.
|
||||
*/
|
||||
class Vertex {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a Vertex with the given name and no out edge.
|
||||
* @param name The name of the plugin or group that this vertex represents.
|
||||
*/
|
||||
LOOT_API Vertex(std::string name);
|
||||
|
||||
/**
|
||||
* @brief Construct a Vertex with the given name and out edge type.
|
||||
* @param name The name of the plugin or group that this vertex represents.
|
||||
* @param outEdgeType The type of the edge going out from this vertex.
|
||||
*/
|
||||
LOOT_API Vertex(std::string name, EdgeType outEdgeType);
|
||||
|
||||
/**
|
||||
* @brief Get the name of the plugin or group.
|
||||
* @return The name of the plugin or group.
|
||||
*/
|
||||
LOOT_API std::string GetName() const;
|
||||
|
||||
/**
|
||||
* @brief Get the type of the edge going to the next vertex.
|
||||
* @details Each edge goes from the vertex that loads earlier to the vertex
|
||||
* that loads later.
|
||||
* @return The edge type.
|
||||
*/
|
||||
LOOT_API std::optional<EdgeType> GetTypeOfEdgeToNextVertex() const;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::optional<EdgeType> outEdgeType_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "api/metadata/condition_evaluator.h"
|
||||
#include "api/metadata/yaml/plugin_metadata.h"
|
||||
#include "api/sorting/plugin_sorter.h"
|
||||
#include "api/sorting/group_sort.h"
|
||||
#include "loot/metadata/group.h"
|
||||
#include "loot/exception/file_access_error.h"
|
||||
|
||||
@@ -194,10 +195,20 @@ std::unordered_set<Group> ApiDatabase::GetGroups(bool includeUserMetadata) const
|
||||
std::unordered_set<Group> ApiDatabase::GetUserGroups() const {
|
||||
return userlist_.Groups();
|
||||
}
|
||||
|
||||
void ApiDatabase::SetUserGroups(const std::unordered_set<Group>& groups) {
|
||||
userlist_.SetGroups(groups);
|
||||
}
|
||||
|
||||
|
||||
std::vector<Vertex> ApiDatabase::GetGroupsPath(const std::string& fromGroupName,
|
||||
const std::string& toGroupName) const {
|
||||
auto masterlistGroups = GetGroups(false);
|
||||
auto userGroups = GetUserGroups();
|
||||
|
||||
return loot::GetGroupsPath(masterlistGroups, userGroups, fromGroupName, toGroupName);
|
||||
}
|
||||
|
||||
std::optional<PluginMetadata> ApiDatabase::GetPluginMetadata(const std::string& plugin,
|
||||
bool includeUserMetadata,
|
||||
bool evaluateConditions) const {
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "api/metadata_list.h"
|
||||
#include "loot/database_interface.h"
|
||||
#include "loot/enum/game_type.h"
|
||||
#include "loot/vertex.h"
|
||||
|
||||
namespace loot {
|
||||
struct ApiDatabase : public DatabaseInterface {
|
||||
@@ -72,6 +73,8 @@ struct ApiDatabase : public DatabaseInterface {
|
||||
std::unordered_set<Group> GetGroups(bool includeUserMetadata = true) const;
|
||||
std::unordered_set<Group> GetUserGroups() const;
|
||||
void SetUserGroups(const std::unordered_set<Group>& groups);
|
||||
std::vector<Vertex> GetGroupsPath(const std::string& fromGroupName,
|
||||
const std::string& toGroupName) const;
|
||||
|
||||
std::optional<PluginMetadata> GetPluginMetadata(
|
||||
const std::string& plugin,
|
||||
|
||||
@@ -24,14 +24,6 @@
|
||||
#include "loot/exception/cyclic_interaction_error.h"
|
||||
|
||||
namespace loot {
|
||||
Vertex::Vertex(std::string name, EdgeType outEdgeType) :
|
||||
name_(name),
|
||||
outEdgeType_(outEdgeType) {}
|
||||
|
||||
std::string Vertex::GetName() const { return name_; }
|
||||
|
||||
EdgeType Vertex::GetTypeOfEdgeToNextVertex() const { return outEdgeType_; }
|
||||
|
||||
std::string describe(EdgeType edgeType) {
|
||||
switch (edgeType) {
|
||||
case EdgeType::hardcoded:
|
||||
@@ -63,8 +55,11 @@ std::string describe(EdgeType edgeType) {
|
||||
std::string describeCycle(const std::vector<Vertex>& cycle) {
|
||||
std::string text;
|
||||
for (const auto& vertex : cycle) {
|
||||
text += vertex.GetName() + " --[" +
|
||||
describe(vertex.GetTypeOfEdgeToNextVertex()) + "]-> ";
|
||||
text += vertex.GetName();
|
||||
if (vertex.GetTypeOfEdgeToNextVertex().has_value()) {
|
||||
text += " --[" + describe(vertex.GetTypeOfEdgeToNextVertex().value()) +
|
||||
"]-> ";
|
||||
}
|
||||
}
|
||||
if (!cycle.empty()) {
|
||||
text += cycle[0].GetName();
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "group_sort.h"
|
||||
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/bellman_ford_shortest_paths.hpp>
|
||||
#include <boost/graph/depth_first_search.hpp>
|
||||
#include <boost/graph/graph_traits.hpp>
|
||||
|
||||
@@ -70,6 +71,7 @@ typedef boost::adjacency_list<boost::vecS,
|
||||
GroupGraph;
|
||||
typedef boost::graph_traits<GroupGraph>::vertex_descriptor vertex_t;
|
||||
typedef boost::graph_traits<GroupGraph>::edge_descriptor edge_t;
|
||||
typedef boost::associative_property_map<std::map<edge_t, int>> edge_map_t;
|
||||
|
||||
class AfterGroupsVisitor : public boost::dfs_visitor<> {
|
||||
public:
|
||||
@@ -97,16 +99,10 @@ std::string join(const std::unordered_set<std::string>& set) {
|
||||
return output.substr(0, output.length() - 2);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>>
|
||||
GetTransitiveAfterGroups(const std::unordered_set<Group>& masterlistGroups,
|
||||
const std::unordered_set<Group>& userGroups) {
|
||||
GroupGraph BuildGraph(const std::unordered_set<Group>& masterlistGroups,
|
||||
const std::unordered_set<Group>& userGroups) {
|
||||
GroupGraph graph;
|
||||
|
||||
auto logger = getLogger();
|
||||
if (logger) {
|
||||
logger->info("Sorting groups according to their load after data");
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, vertex_t> groupVertices;
|
||||
for (const auto& group : masterlistGroups) {
|
||||
auto groupSortingData = GroupSortingData(group.GetName());
|
||||
@@ -128,6 +124,7 @@ GetTransitiveAfterGroups(const std::unordered_set<Group>& masterlistGroups,
|
||||
}
|
||||
}
|
||||
|
||||
auto logger = getLogger();
|
||||
for (const vertex_t& vertex :
|
||||
boost::make_iterator_range(boost::vertices(graph))) {
|
||||
auto group = graph[vertex];
|
||||
@@ -164,6 +161,19 @@ GetTransitiveAfterGroups(const std::unordered_set<Group>& masterlistGroups,
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>>
|
||||
GetTransitiveAfterGroups(const std::unordered_set<Group>& masterlistGroups,
|
||||
const std::unordered_set<Group>& userGroups) {
|
||||
GroupGraph graph = BuildGraph(masterlistGroups, userGroups);
|
||||
|
||||
auto logger = getLogger();
|
||||
if (logger) {
|
||||
logger->info("Sorting groups according to their load after data");
|
||||
}
|
||||
|
||||
// Check for cycles.
|
||||
if (logger) {
|
||||
logger->trace("Checking for cycles in the group graph");
|
||||
@@ -194,4 +204,85 @@ GetTransitiveAfterGroups(const std::unordered_set<Group>& masterlistGroups,
|
||||
|
||||
return transitiveAfterGroups;
|
||||
}
|
||||
|
||||
vertex_t GetVertexByName(const GroupGraph& graph, const std::string& name) {
|
||||
for (const auto& vertex :
|
||||
boost::make_iterator_range(boost::vertices(graph))) {
|
||||
if (graph[vertex].GetName() == name) {
|
||||
return vertex;
|
||||
}
|
||||
}
|
||||
|
||||
auto logger = getLogger();
|
||||
if (logger) {
|
||||
logger->error("Can't find group with name \"{}\"", name);
|
||||
}
|
||||
|
||||
throw std::invalid_argument("Can't find group with name \"" + name + "\"");
|
||||
}
|
||||
|
||||
|
||||
std::vector<Vertex> GetGroupsPath(
|
||||
const std::unordered_set<Group>& masterlistGroups,
|
||||
const std::unordered_set<Group>& userGroups,
|
||||
const std::string& fromGroupName,
|
||||
const std::string& toGroupName) {
|
||||
GroupGraph graph = BuildGraph(masterlistGroups, userGroups);
|
||||
|
||||
auto logger = getLogger();
|
||||
|
||||
auto fromVertex = GetVertexByName(graph, fromGroupName);
|
||||
auto toVertex = GetVertexByName(graph, toGroupName);
|
||||
|
||||
std::map<edge_t, int> weightMap;
|
||||
for (const auto& edge : boost::make_iterator_range(boost::edges(graph))) {
|
||||
if (graph[edge] == EdgeType::userLoadAfter) {
|
||||
weightMap[edge] = -1000000; // Magnitude is an arbitrarily large number.
|
||||
} else {
|
||||
weightMap[edge] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<vertex_t> predecessors(boost::num_vertices(graph));
|
||||
std::vector<int> distance(predecessors.size(),
|
||||
(std::numeric_limits<int>::max)());
|
||||
distance[toVertex] = 0;
|
||||
|
||||
bellman_ford_shortest_paths(
|
||||
graph,
|
||||
boost::weight_map(edge_map_t(weightMap))
|
||||
.predecessor_map(boost::make_iterator_property_map(
|
||||
predecessors.begin(), get(boost::vertex_index, graph)))
|
||||
.distance_map(&distance[0])
|
||||
.root_vertex(toVertex));
|
||||
|
||||
std::vector<Vertex> path;
|
||||
vertex_t currentVertex = fromVertex;
|
||||
while (currentVertex != toVertex) {
|
||||
auto nextVertex = predecessors[currentVertex];
|
||||
if (nextVertex == currentVertex) {
|
||||
if (logger) {
|
||||
logger->error(
|
||||
"Unreachable vertex {} encountered while looking for vertex {}",
|
||||
graph[currentVertex].GetName(),
|
||||
graph[toVertex].GetName());
|
||||
}
|
||||
return std::vector<Vertex>();
|
||||
}
|
||||
|
||||
auto pair = boost::edge(nextVertex, currentVertex, graph);
|
||||
if (!pair.second) {
|
||||
throw std::runtime_error("Unexpectedly couldn't find edge between \"" +
|
||||
graph[currentVertex].GetName() + "\" and \"" +
|
||||
graph[nextVertex].GetName() + "\"");
|
||||
}
|
||||
auto vertex = Vertex(graph[currentVertex].GetName(), graph[pair.first]);
|
||||
path.push_back(vertex);
|
||||
|
||||
currentVertex = nextVertex;
|
||||
}
|
||||
path.push_back(Vertex(graph[currentVertex].GetName()));
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,17 @@ std::unordered_map<std::string, std::unordered_set<std::string>>
|
||||
GetTransitiveAfterGroups(const std::unordered_set<Group>& masterlistGroups,
|
||||
const std::unordered_set<Group>& userGroups);
|
||||
|
||||
std::vector<Vertex> GetGroupsPath(
|
||||
const std::unordered_set<Group>& masterlistGroups,
|
||||
const std::unordered_set<Group>& userGroups,
|
||||
const std::string& fromGroupName,
|
||||
const std::string& toGroupName);
|
||||
|
||||
template<typename G>
|
||||
class CycleDetector : public boost::dfs_visitor<> {
|
||||
public:
|
||||
void tree_edge(typename boost::graph_traits<G>::edge_descriptor edge, const G& graph) {
|
||||
void tree_edge(typename boost::graph_traits<G>::edge_descriptor edge,
|
||||
const G& graph) {
|
||||
auto source = boost::source(edge, graph);
|
||||
|
||||
auto vertex = Vertex(graph[source].GetName(), graph[edge]);
|
||||
@@ -64,7 +71,8 @@ public:
|
||||
trail.push_back(vertex);
|
||||
}
|
||||
|
||||
void back_edge(typename boost::graph_traits<G>::edge_descriptor edge, const G& graph) {
|
||||
void back_edge(typename boost::graph_traits<G>::edge_descriptor edge,
|
||||
const G& graph) {
|
||||
auto source = boost::source(edge, graph);
|
||||
auto target = boost::target(edge, graph);
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2018 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT is free software: you can redistribute
|
||||
it and/or modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
LOOT is distributed in the hope that it will
|
||||
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with LOOT. If not, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "loot/vertex.h"
|
||||
|
||||
namespace loot {
|
||||
Vertex::Vertex(std::string name) : name_(name) {}
|
||||
|
||||
Vertex::Vertex(std::string name, EdgeType outEdgeType) :
|
||||
name_(name),
|
||||
outEdgeType_(outEdgeType) {}
|
||||
|
||||
std::string Vertex::GetName() const { return name_; }
|
||||
|
||||
std::optional<EdgeType> Vertex::GetTypeOfEdgeToNextVertex() const {
|
||||
return outEdgeType_;
|
||||
}
|
||||
}
|
||||
@@ -496,6 +496,23 @@ TEST_P(DatabaseInterfaceTest,
|
||||
EXPECT_TRUE(groups.find(Group("group4"))->GetAfterGroups().empty());
|
||||
}
|
||||
|
||||
TEST_P(DatabaseInterfaceTest,
|
||||
getGroupsPathShouldReturnTheShortestPathBetweenTheGivenGroups) {
|
||||
ASSERT_NO_THROW(GenerateMasterlist());
|
||||
ASSERT_NO_THROW(GenerateUserlist());
|
||||
|
||||
ASSERT_NO_THROW(
|
||||
db_->LoadLists(masterlistPath, userlistPath_));
|
||||
|
||||
auto path = db_->GetGroupsPath("group1", "group3");
|
||||
|
||||
ASSERT_EQ(2, path.size());
|
||||
EXPECT_EQ("group1", path[0].GetName());
|
||||
EXPECT_EQ(EdgeType::userLoadAfter, path[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ("group3", path[1].GetName());
|
||||
EXPECT_FALSE(path[1].GetTypeOfEdgeToNextVertex().has_value());
|
||||
}
|
||||
|
||||
TEST_P(DatabaseInterfaceTest,
|
||||
getKnownBashTagsShouldReturnAllBashTagsListedInLoadedMetadata) {
|
||||
ASSERT_NO_THROW(GenerateMasterlist());
|
||||
|
||||
@@ -105,6 +105,80 @@ TEST(GetTransitiveAfterGroups, shouldThrowIfAfterGroupsAreCyclic) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GetGroupsPath, shouldThrowIfTheFromGroupDoesNotExist) {
|
||||
std::unordered_set<Group> groups({Group("a", {"c"}), Group("b", {"a"})});
|
||||
std::unordered_set<Group> userGroups({Group("c", {"b"})});
|
||||
|
||||
EXPECT_THROW(GetGroupsPath(groups, userGroups, "d", "a"),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(GetGroupsPath, shouldThrowIfTheToGroupDoesNotExist) {
|
||||
std::unordered_set<Group> groups({Group("a", {"c"}), Group("b", {"a"})});
|
||||
std::unordered_set<Group> userGroups({Group("c", {"b"})});
|
||||
|
||||
EXPECT_THROW(GetGroupsPath(groups, userGroups, "a", "d"),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(GetGroupsPath,
|
||||
shouldReturnAnEmptyVectorIfThereIsNoPathBetweenTheTwoGroups) {
|
||||
std::unordered_set<Group> groups({Group("a", {}),
|
||||
Group("b", {"a"}),
|
||||
Group("c", {"a"}),
|
||||
Group("d", {"c"}),
|
||||
Group("e", {"b", "d"})});
|
||||
|
||||
auto path = GetGroupsPath(groups, {}, "b", "d");
|
||||
|
||||
EXPECT_TRUE(path.empty());
|
||||
}
|
||||
|
||||
TEST(GetGroupsPath,
|
||||
shouldFindThePathWithTheLeastNumberOfEdgesInAMasterlistOnlyGraph) {
|
||||
std::unordered_set<Group> groups({Group("a", {}),
|
||||
Group("b", {"a"}),
|
||||
Group("c", {"a"}),
|
||||
Group("d", {"c"}),
|
||||
Group("e", {"b", "d"})});
|
||||
|
||||
auto path = GetGroupsPath(groups, {}, "a", "e");
|
||||
|
||||
ASSERT_EQ(3, path.size());
|
||||
EXPECT_EQ("a", path[0].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistLoadAfter,
|
||||
path[0].GetTypeOfEdgeToNextVertex().value());
|
||||
EXPECT_EQ("b", path[1].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistLoadAfter,
|
||||
path[1].GetTypeOfEdgeToNextVertex().value());
|
||||
EXPECT_EQ("e", path[2].GetName());
|
||||
EXPECT_FALSE(path[2].GetTypeOfEdgeToNextVertex().has_value());
|
||||
}
|
||||
|
||||
TEST(GetGroupsPath,
|
||||
shouldFindThePathWithTheLeastNumberOfEdgesThatContainsUserMetadata) {
|
||||
std::unordered_set<Group> groups({Group("a", {}),
|
||||
Group("b", {"a"}),
|
||||
Group("c", {"a"}),
|
||||
Group("e", {"b", "d"})});
|
||||
std::unordered_set<Group> userGroups({Group("d", {"c"})});
|
||||
|
||||
auto path = GetGroupsPath(groups, userGroups, "a", "e");
|
||||
|
||||
ASSERT_EQ(4, path.size());
|
||||
EXPECT_EQ("a", path[0].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistLoadAfter,
|
||||
path[0].GetTypeOfEdgeToNextVertex().value());
|
||||
EXPECT_EQ("c", path[1].GetName());
|
||||
EXPECT_EQ(EdgeType::userLoadAfter,
|
||||
path[1].GetTypeOfEdgeToNextVertex().value());
|
||||
EXPECT_EQ("d", path[2].GetName());
|
||||
EXPECT_EQ(EdgeType::masterlistLoadAfter,
|
||||
path[2].GetTypeOfEdgeToNextVertex().value());
|
||||
EXPECT_EQ("e", path[3].GetName());
|
||||
EXPECT_FALSE(path[3].GetTypeOfEdgeToNextVertex().has_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user