Add Channel::contains_package (#3121)

* Add CondaURL literals

* Add Channel::contains_package

* Fix test case name

* Bind Channel::contains_package
This commit is contained in:
Antoine Prouvost 2024-01-11 10:10:36 +01:00 committed by GitHub
parent 13d034147f
commit 43e24a864d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 191 additions and 50 deletions

View File

@ -113,6 +113,8 @@ namespace mamba::specs
[[nodiscard]] auto contains_equivalent(const Channel& other) const -> bool;
[[nodiscard]] auto contains_package(const CondaURL& pkg) const -> bool;
private:
CondaURL m_url;

View File

@ -241,6 +241,11 @@ namespace mamba::specs
/** A functional equivalent to ``CondaURL::append_path``. */
auto operator/(const CondaURL& url, std::string_view subpath) -> CondaURL;
auto operator/(CondaURL&& url, std::string_view subpath) -> CondaURL;
namespace conda_url_literals
{
auto operator""_cu(const char* str, std::size_t len) -> CondaURL;
}
}
template <>

View File

@ -121,23 +121,29 @@ namespace mamba::specs
m_display_name = std::move(display_name);
}
namespace
{
auto url_equivalent_with_impl(const CondaURL& lhs, const CondaURL& rhs) -> bool
{
using Decode = typename CondaURL::Decode;
// Not checking users, passwords, and tokens
return
// Schemes
(lhs.scheme() == rhs.scheme())
// Hosts
&& (lhs.host(Decode::no) == rhs.host(Decode::no))
// Different ports are considered different channels
&& (lhs.port() == rhs.port())
// Removing potential trailing '/'
&& (util::rstrip(lhs.path_without_token(Decode::no), '/')
== util::rstrip(rhs.path_without_token(Decode::no), '/'));
}
}
auto Channel::url_equivalent_with(const Channel& other) const -> bool
{
using Decode = typename CondaURL::Decode;
const auto& this_url = url();
const auto& other_url = other.url();
// Not checking users, passwords, and tokens
return
// Schemes
(this_url.scheme() == other_url.scheme())
// Hosts
&& (this_url.host(Decode::no) == other_url.host(Decode::no))
// Different ports are considered different channels
&& (this_url.port() == other_url.port())
// Removing potential trailing '/'
&& (util::rstrip(this_url.path_without_token(Decode::no), '/')
== util::rstrip(other_url.path_without_token(Decode::no), '/'));
return url_equivalent_with_impl(url(), other.url());
}
auto Channel::is_equivalent_to(const Channel& other) const -> bool
@ -147,9 +153,30 @@ namespace mamba::specs
auto Channel::contains_equivalent(const Channel& other) const -> bool
{
if (other.is_package())
{
return contains_package(other.url());
}
return url_equivalent_with(other) && util::set_is_superset_of(platforms(), other.platforms());
}
auto Channel::contains_package(const CondaURL& pkg) const -> bool
{
if (is_package())
{
return url_equivalent_with_impl(url(), pkg);
}
if (!platforms().contains(std::string(pkg.platform_name())))
{
return false;
}
auto pkg_repo = pkg;
pkg_repo.clear_platform();
pkg_repo.clear_package();
return url_equivalent_with_impl(url(), pkg_repo);
}
/****************************************
* Implementation of Channel::resolve *
****************************************/

View File

@ -524,6 +524,14 @@ namespace mamba::specs
url.append_path(subpath);
return std::move(url);
}
namespace conda_url_literals
{
auto operator""_cu(const char* str, std::size_t len) -> CondaURL
{
return CondaURL::parse({ str, len });
}
}
}
auto

View File

@ -79,44 +79,138 @@ TEST_SUITE("specs::channel")
SUBCASE("Equivalence")
{
for (auto raw_url : {
"https://repo.mamba.pm/"sv,
"https://repo.mamba.pm/t/mytoken/"sv,
"https://user:pass@repo.mamba.pm/conda-forge/win-64/"sv,
"file:///some/folder/"sv,
"ftp://mamba.org/some/folder"sv,
})
SUBCASE("Same platforms")
{
CAPTURE(raw_url);
for (auto raw_url : {
"https://repo.mamba.pm/"sv,
"https://repo.mamba.pm/t/mytoken/"sv,
"https://user:pass@repo.mamba.pm/conda-forge/"sv,
"file:///some/folder/"sv,
"ftp://mamba.org/some/folder"sv,
})
{
CAPTURE(raw_url);
auto url_a = CondaURL::parse(raw_url);
auto url_b = url_a;
url_b.clear_user();
url_b.clear_password();
url_b.clear_token();
auto chan_a = Channel(url_a, "somename", { "linux-64" });
auto chan_b = Channel(url_b, "somename", { "linux-64" });
auto url_a = CondaURL::parse(raw_url);
auto url_b = url_a;
url_b.clear_user();
url_b.clear_password();
url_b.clear_token();
auto chan_a = Channel(url_a, "somename", { "linux-64" });
auto chan_b = Channel(url_b, "somename", { "linux-64" });
// Channel::url_equivalent_with
CHECK(chan_a.url_equivalent_with(chan_a));
CHECK(chan_b.url_equivalent_with(chan_b));
CHECK(chan_a.url_equivalent_with(chan_b));
CHECK(chan_b.url_equivalent_with(chan_a));
// Channel::url_equivalent_with
CHECK(chan_a.url_equivalent_with(chan_a));
CHECK(chan_b.url_equivalent_with(chan_b));
CHECK(chan_a.url_equivalent_with(chan_b));
CHECK(chan_b.url_equivalent_with(chan_a));
// Channel::contains_equivalent
CHECK(chan_a.contains_equivalent(chan_a));
CHECK(chan_b.contains_equivalent(chan_b));
CHECK(chan_a.contains_equivalent(chan_b));
CHECK(chan_b.contains_equivalent(chan_a));
// Channel::contains_equivalent
CHECK(chan_a.contains_equivalent(chan_a));
CHECK(chan_b.contains_equivalent(chan_b));
CHECK(chan_a.contains_equivalent(chan_b));
CHECK(chan_b.contains_equivalent(chan_a));
}
}
chan_a = Channel(chan_a.url(), chan_a.display_name(), { "noarch", "linux-64" });
CHECK(chan_a.contains_equivalent(chan_a));
CHECK(chan_a.contains_equivalent(chan_b));
CHECK_FALSE(chan_b.contains_equivalent(chan_a));
SUBCASE("Platforms superset")
{
for (auto raw_url : {
"https://repo.mamba.pm/"sv,
"https://repo.mamba.pm/t/mytoken/"sv,
"https://user:pass@repo.mamba.pm/conda-forge/"sv,
"file:///some/folder/"sv,
"ftp://mamba.org/some/folder"sv,
})
{
CAPTURE(raw_url);
chan_b = Channel(chan_b.url(), chan_b.display_name(), { "ox-64" });
CHECK_FALSE(chan_a.contains_equivalent(chan_b));
CHECK_FALSE(chan_b.contains_equivalent(chan_a));
auto url_a = CondaURL::parse(raw_url);
auto url_b = url_a;
url_a.clear_user();
url_a.clear_password();
url_a.clear_token();
auto chan_a = Channel(url_a, "somename", { "noarch", "linux-64" });
auto chan_b = Channel(url_b, "somename", { "linux-64" });
CHECK(chan_a.contains_equivalent(chan_a));
CHECK(chan_a.contains_equivalent(chan_b));
CHECK_FALSE(chan_b.contains_equivalent(chan_a));
}
}
SUBCASE("Different platforms")
{
for (auto raw_url : {
"https://repo.mamba.pm/"sv,
"https://repo.mamba.pm/t/mytoken/"sv,
"https://user:pass@repo.mamba.pm/conda-forge/"sv,
"file:///some/folder/"sv,
"ftp://mamba.org/some/folder"sv,
})
{
CAPTURE(raw_url);
auto url_a = CondaURL::parse(raw_url);
auto url_b = url_a;
auto chan_a = Channel(url_a, "somename", { "noarch", "linux-64" });
auto chan_b = Channel(url_b, "somename", { "osx-64" });
CHECK_FALSE(chan_a.contains_equivalent(chan_b));
CHECK_FALSE(chan_b.contains_equivalent(chan_a));
}
}
SUBCASE("Packages")
{
using namespace conda_url_literals;
const auto chan = Channel("https://repo.mamba.pm/"_cu, "conda-forge", { "linux-64" });
CHECK(chan.contains_equivalent(Channel(chan.url() / "linux-64/pkg.conda", "", {})));
CHECK_FALSE(chan.contains_equivalent(Channel(chan.url() / "osx-64/pkg.conda", "", {}))
);
const auto pkg_chan = Channel(chan.url() / "linux-64/foo.tar.bz2", "", {});
CHECK(pkg_chan.contains_equivalent(pkg_chan));
CHECK_FALSE(pkg_chan.contains_equivalent(chan));
CHECK_FALSE(
pkg_chan.contains_equivalent(Channel(chan.url() / "osx-64/pkg.conda", "", {}))
);
}
}
SUBCASE("Contains package")
{
using namespace conda_url_literals;
SUBCASE("https://repo.mamba.pm/")
{
auto chan = Channel("https://repo.mamba.pm/"_cu, "conda-forge", { "linux-64" });
CHECK(chan.contains_package("https://repo.mamba.pm/linux-64/pkg.conda"_cu));
CHECK_FALSE(chan.contains_package("https://repo.mamba.pm/win-64/pkg.conda"_cu));
CHECK_FALSE(chan.contains_package("https://repo.mamba.pm/pkg.conda"_cu));
}
SUBCASE("https://repo.mamba.pm/osx-64/foo.tar.gz")
{
auto chan = Channel("https://repo.mamba.pm/osx-64/foo.tar.bz2"_cu, "", {});
CHECK(chan.contains_package(chan.url()));
CHECK_FALSE(chan.contains_package("https://repo.mamba.pm/win-64/pkg.conda"_cu));
CHECK_FALSE(chan.contains_package("https://repo.mamba.pm/pkg.conda"_cu));
}
SUBCASE("https://user:pass@repo.mamba.pm/conda-forge/win-64/")
{
auto chan = Channel(
"https://user:pass@repo.mamba.pm/conda-forge/"_cu,
"conda-forge",
{ "win-64" }
);
CHECK(chan.contains_package(chan.url() / "win-64/pkg.conda"));
CHECK(chan.contains_package("https://repo.mamba.pm/conda-forge/win-64/pkg.conda"_cu));
CHECK_FALSE(
chan.contains_package("https://repo.mamba.pm/conda-forge/osx-64/pkg.conda"_cu)
);
}
}
}

View File

@ -12,7 +12,7 @@
using namespace mamba;
using namespace mamba::specs;
TEST_SUITE("specs::channel_spec")
TEST_SUITE("specs::unresolved_channel")
{
using PlatformSet = typename util::flat_set<std::string>;
using Type = typename UnresolvedChannel::Type;

View File

@ -489,6 +489,7 @@ namespace mambapy
.def("url_equivalent_with", &Channel::url_equivalent_with)
.def("is_equivalent_to", &Channel::is_equivalent_to)
.def("contains_equivalent", &Channel::contains_equivalent)
.def("contains_package", &Channel::contains_package)
.def(py::self == py::self)
.def(py::self != py::self)
.def("__hash__", &hash<Channel>)

View File

@ -474,8 +474,10 @@ def test_Channel():
assert hash(chan) != 0
# Weak comparison
other.url = chan.url
other.platforms = chan.platforms | {"human-67"}
chan = Channel(url=url_1, platforms=platforms_1, display_name=display_name_1)
other = Channel(
url=url_1, platforms=(chan.platforms | {"human-67"}), display_name=display_name_1
)
assert chan.url_equivalent_with(chan)
assert chan.url_equivalent_with(other)
assert other.url_equivalent_with(chan)
@ -485,6 +487,8 @@ def test_Channel():
assert chan.contains_equivalent(chan)
assert other.contains_equivalent(chan)
assert not chan.contains_equivalent(other)
assert chan.contains_package(chan.url / "noarch/pkg.conda")
assert not chan.contains_package(chan.url / "win-64/pkg.conda")
def test_Channel_resolve():