Skip to content

Commit

Permalink
Add ProblemsGraph ctor tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoinePrv committed Sep 27, 2022
1 parent 58ffce4 commit f373656
Showing 1 changed file with 271 additions and 0 deletions.
271 changes: 271 additions & 0 deletions libmamba/tests/test_satisfiability_error.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
#include <array>
#include <vector>
#include <string>
#include <random>

#include <gtest/gtest.h>
#include <nlohmann/json.hpp>
#include "spdlog/spdlog.h"
#ifdef SPDLOG_FMT_EXTERNAL
#include <fmt/format.h>
#else
#include <spdlog/fmt/bundled/format.h>
#endif

#include "mamba/core/pool.hpp"
#include "mamba/core/solver.hpp"
#include "mamba/core/repo.hpp"
#include "mamba/core/mamba_fs.hpp"
#include "mamba/core/satisfiability_error.hpp"
#include "mamba/core/package_info.hpp"

namespace mamba
{
Expand Down Expand Up @@ -37,4 +54,258 @@ namespace mamba
{
EXPECT_ANY_THROW(DependencyInfo("<foo"));
}

/**
* Simple factory for building a PackageInfo.
*/
auto mkpkg(std::string name, std::string version, std::vector<std::string> dependencies = {})
-> PackageInfo
{
auto pkg = PackageInfo(std::move(name));
pkg.version = std::move(version);
pkg.depends = std::move(dependencies);
pkg.build_string = "buildstring";
return pkg;
}


/**
* Create the repodata.json file containing the package information.
*/
template <typename PkgRange>
auto create_repodata_json(fs::u8path dir, PkgRange const& packages) -> fs::u8path
{
namespace nl = nlohmann;

auto packages_j = nl::json::object();
for (auto const& pkg : packages)
{
auto fname = fmt::format("{}-{}-{}.tar.bz2", pkg.name, pkg.version, pkg.build_string);
packages_j[std::move(fname)] = pkg.json_record();
}
auto repodata_j = nl::json::object();
repodata_j["packages"] = std::move(packages_j);

fs::create_directories(dir / "noarch");
auto repodata_f = dir / "noarch/repodata.json";
std::ofstream(repodata_f, std::ofstream::app) << repodata_j;

return repodata_f;
}

/**
* Generate a string of random characters.
*/
auto random_str(std::size_t n) -> std::string
{
static auto constexpr chars = std::string_view{ "abcdefghijklmnopqrstuvwxyz1234567890" };
auto result = std::string(n, 'a');
auto gen = std::default_random_engine(std::random_device()());
auto choice = std::uniform_int_distribution<std::size_t>(0, chars.size() - 1);
std::generate(result.begin(), result.end(), [&]() { return chars[choice(gen)]; });
return result;
}

/**
* Create a solver and a pool of a conflict.
*
* The underlying packages do not exist, we are onl interested in the conflict.
*/
template <typename PkgRange>
auto create_problem(PkgRange const& packages, std::vector<std::string> const& specs)
{
auto const tmp_prefix = fs::temp_directory_path() / "mamba/tests" / random_str(20);
auto const repodata_f = create_repodata_json(tmp_prefix, packages);

auto pool = std::make_unique<MPool>();
MRepo::create(*pool, "some-name", repodata_f, "some-url");
auto solver = std::make_unique<MSolver>(
*pool, std::vector{ std::pair{ SOLVER_FLAG_ALLOW_DOWNGRADE, 1 } });
solver->add_jobs(specs, SOLVER_INSTALL);

return std::pair{ std::move(solver), std::move(pool) };
}

/**
* Test the test utility function.
*/
TEST(satifiability_error, create_problem)
{
auto [solver, pool] = create_problem(std::array{ mkpkg("foo", "0.1.0", {}) }, { "foo" });
auto const solved = solver->solve();
ASSERT_TRUE(solved);
}

auto create_basic_conflict()
{
return create_problem(
std::array{
mkpkg("A", "0.1.0"),
mkpkg("A", "0.2.0"),
mkpkg("A", "0.3.0"),
},
{ "A=0.4.0" });
}

/**
* Create the PubGrub blog post example.
*
* The example given by Natalie Weizenbaum
* (credits https://nex3.medium.com/pubgrub-2fb6470504f).
*/
auto create_pubgrub()
{
return create_problem(
std::array{
mkpkg("menu", "1.5.0", { "dropdown=2.*" }),
mkpkg("menu", "1.4.0", { "dropdown=2.*" }),
mkpkg("menu", "1.3.0", { "dropdown=2.*" }),
mkpkg("menu", "1.2.0", { "dropdown=2.*" }),
mkpkg("menu", "1.1.0", { "dropdown=2.*" }),
mkpkg("menu", "1.0.0", { "dropdown=1.*" }),
mkpkg("dropdown", "2.3.0", { "icons=2.*" }),
mkpkg("dropdown", "2.2.0", { "icons=2.*" }),
mkpkg("dropdown", "2.1.0", { "icons=2.*" }),
mkpkg("dropdown", "2.0.0", { "icons=2.*" }),
mkpkg("dropdown", "1.8.0", { "icons=1.*", "intl=3.*" }),
mkpkg("icons", "2.0.0"),
mkpkg("icons", "1.0.0"),
mkpkg("intl", "5.0.0"),
mkpkg("intl", "4.0.0"),
mkpkg("intl", "3.0.0"),
},
{ "menu", "icons=1.*", "intl=5.*" });
}

auto create_pubgrub_hard_(bool missing_package)
{
auto packages = std::vector{
mkpkg("menu", "2.1.0", { "dropdown>=2.1", "emoji" }),
mkpkg("menu", "2.0.1", { "dropdown>=2", "emoji" }),
mkpkg("menu", "2.0.0", { "dropdown>=2", "emoji" }),
mkpkg("menu", "1.5.0", { "dropdown=2.*", "emoji" }),
mkpkg("menu", "1.4.0", { "dropdown=2.*", "emoji" }),
mkpkg("menu", "1.3.0", { "dropdown=2.*" }),
mkpkg("menu", "1.2.0", { "dropdown=2.*" }),
mkpkg("menu", "1.1.0", { "dropdown=1.*" }),
mkpkg("menu", "1.0.0", { "dropdown=1.*" }),
mkpkg("emoji", "1.1.0", { "libicons=2.*" }),
mkpkg("emoji", "1.0.0", { "libicons=2.*" }),
mkpkg("dropdown", "2.3.0", { "libicons=2.*" }),
mkpkg("dropdown", "2.2.0", { "libicons=2.*" }),
mkpkg("dropdown", "2.1.0", { "libicons=2.*" }),
mkpkg("dropdown", "2.0.0", { "libicons=2.*" }),
mkpkg("dropdown", "1.8.0", { "libicons=1.*", "intl=3.*" }),
mkpkg("dropdown", "1.7.0", { "libicons=1.*", "intl=3.*" }),
mkpkg("dropdown", "1.6.0", { "libicons=1.*", "intl=3.*" }),
mkpkg("pyicons", "2.0.0", { "libicons=2.*" }),
mkpkg("pyicons", "1.1.0", { "libicons=1.2.*" }),
mkpkg("pyicons", "1.0.0", { "libicons=1.*" }),
mkpkg("pretty", "1.1.0", { "pyicons=1.1.*" }),
mkpkg("pretty", "1.0.1", { "pyicons=1.*" }),
mkpkg("pretty", "1.0.0", { "pyicons=1.*" }),
mkpkg("intl", "5.0.0"),
mkpkg("intl", "4.0.0"),
mkpkg("intl", "3.2.0"),
mkpkg("intl", "3.1.0"),
mkpkg("intl", "3.0.0"),
mkpkg("intl-mod", "1.0.0", { "intl=5.0.*" }),
mkpkg("intl-mod", "1.0.1", { "intl=5.0.*" }),
mkpkg("libicons", "2.1.0"),
mkpkg("libicons", "2.0.1"),
mkpkg("libicons", "2.0.0"),
mkpkg("libicons", "1.2.1"),
mkpkg("libicons", "1.2.0"),
mkpkg("libicons", "1.0.0"),
};

if (missing_package)
{
packages.push_back(mkpkg("dropdown", "2.9.3", { "libnothere>1.0" }));
packages.push_back(mkpkg("dropdown", "2.9.2", { "libicons>10.0", "libnothere>1.0" }));
packages.push_back(mkpkg("dropdown", "2.9.1", { "libicons>10.0", "libnothere>1.0" }));
packages.push_back(mkpkg("dropdown", "2.9.0", { "libicons>10.0" }));
}
return create_problem(packages,
{ "menu", "pyicons=1.*", "intl=5.*", "intl-mod", "pretty>=1.0" });
}

/**
* A harder version of ``create_pubgrub``.
*/
auto create_pubgrub_hard()
{
return create_pubgrub_hard_(false);
}

/**
* The hard version of the alternate PubGrub with missing packages.
*/
auto create_pubgrub_missing()
{
return create_pubgrub_hard_(true);
}

class Problem : public testing::TestWithParam<decltype(&create_basic_conflict)>
{
};

TEST_P(Problem, constructor)
{
auto [solver, pool] = std::invoke(GetParam());
auto const solved = solver->solve();
ASSERT_FALSE(solved);
auto const pb = ProblemsGraph::from_solver(*solver, *pool);
auto const& g = pb.graph();

auto has_problem_type = [](auto const& node) -> bool
{
return std::visit(
[](auto const& n) -> bool
{
using Node = std::remove_const_t<std::remove_reference_t<decltype(n)>>;
if constexpr (!std::is_same_v<Node, ProblemsGraph::RootNode>)
{
return n.problem_type.has_value();
}
return false;
},
node);
};

for (std::size_t id = 0; id < g.number_of_nodes(); ++id)
{
if (g.in_degree(id) == 0)
{
// Only one root node
EXPECT_EQ(id, pb.root_node());
EXPECT_TRUE(std::holds_alternative<ProblemsGraph::RootNode>(g.node(id)));
}
else if (g.out_degree(id) == 0)
{
EXPECT_FALSE(std::holds_alternative<ProblemsGraph::RootNode>(g.node(id)));
// FIXME? Should all leaves have a problem type?
// EXPECT_TRUE(has_problem_type(g.node(id)));
}
else
{
EXPECT_FALSE(std::holds_alternative<ProblemsGraph::RootNode>(g.node(id)));
EXPECT_FALSE(has_problem_type(g.node(id)));
}
}

// All nodes reachable from the root
for (std::size_t node = 0; node < pb.graph().number_of_nodes(); ++node)
{
EXPECT_TRUE(is_reachable(pb.graph(), pb.root_node(), node));
}
}

INSTANTIATE_TEST_SUITE_P(satifiability_error,
Problem,
testing::Values(create_basic_conflict,
create_pubgrub,
create_pubgrub_hard,
create_pubgrub_missing));

}

0 comments on commit f373656

Please sign in to comment.