mirror of https://github.com/mamba-org/mamba.git
Add MatchSpec::contains_except_channel" (#3231)
* Add MatchSpec::contains_except_channel * Add MatchSpec::contains_except_channel overload * Bind MatchSpec::contains_except_channel * Add documentation for MatchSpec::contains
This commit is contained in:
parent
ce840bbf8c
commit
7125518164
|
@ -431,3 +431,24 @@ with an example
|
|||
``python==3.7`` (strong equality).
|
||||
This is intuitively different from how we write ``python=3.7``, which we must write with
|
||||
attributes as ``python[version="=3.7"]``.
|
||||
|
||||
The method
|
||||
:cpp:func:`MatchSpec.contains_except_channel <mamba::specs::MatchSpec::contains_except_channel>`
|
||||
can be used to check if a package is contained (matched) by the current |MatchSpec|.
|
||||
The somewhat verbose name serve to indicate that the channel is ignored in this function.
|
||||
As mentionned in the :ref:`Channel section<libmamba_usage_channel>` resolving and matching channels
|
||||
is a delicate operation.
|
||||
In addition, the channel is a part that describe the **provenance** of a package and not is content
|
||||
so various application ay want to handle it in different ways.
|
||||
The :cpp:func:`MatchSpec.channel <mamba::specs::MatchSpec::channel>` attribute can be used to
|
||||
reason about the possible channel contained in the |MatchSpec|.
|
||||
|
||||
.. code:: python
|
||||
|
||||
import libmambapy.specs as specs
|
||||
|
||||
ms = specs.MatchSpec.parse("conda-forge::py*[build_number='>4']")
|
||||
|
||||
assert ms.contains(name="python", build_number=5)
|
||||
assert not ms.contains(name="numpy", build_number=8)
|
||||
assert ms.channel.location == "conda-forge"
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
|
||||
namespace mamba::specs
|
||||
{
|
||||
class PackageInfo;
|
||||
|
||||
class MatchSpec
|
||||
{
|
||||
public:
|
||||
|
@ -105,6 +107,25 @@ namespace mamba::specs
|
|||
|
||||
[[nodiscard]] auto is_simple() const -> bool;
|
||||
|
||||
/**
|
||||
* Check if the MatchSpec matches the given package.
|
||||
*
|
||||
* The check exclude anything related to the channel, du to the difficulties in
|
||||
* comparing unresolved channels and the fact that this check can be also be done once
|
||||
* at a repository level when the user knows how packages are organised.
|
||||
*
|
||||
* This function is written as a generic template, to acomodate various uses: the fact
|
||||
* that the attributes may not always be in the correct format in the package, and that
|
||||
* their parsing may be cached.
|
||||
*/
|
||||
template <typename Pkg>
|
||||
[[nodiscard]] auto contains_except_channel(const Pkg& pkg) const -> bool;
|
||||
|
||||
/**
|
||||
* Convenience wrapper making necessary convertions.
|
||||
*/
|
||||
[[nodiscard]] auto contains_except_channel(const PackageInfo& pkg) const -> bool;
|
||||
|
||||
private:
|
||||
|
||||
struct ExtraMembers
|
||||
|
@ -156,4 +177,43 @@ struct fmt::formatter<::mamba::specs::MatchSpec>
|
|||
|
||||
auto format(const ::mamba::specs::MatchSpec& spec, format_context& ctx) -> decltype(ctx.out());
|
||||
};
|
||||
|
||||
/*********************************
|
||||
* Implementation of MatchSpec *
|
||||
*********************************/
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
template <typename Pkg>
|
||||
auto MatchSpec::contains_except_channel(const Pkg& pkg) const -> bool
|
||||
{
|
||||
if ( //
|
||||
!name().contains(std::invoke(&Pkg::name, pkg)) //
|
||||
|| !version().contains(std::invoke(&Pkg::version, pkg)) //
|
||||
|| !build_string().contains(std::invoke(&Pkg::build_string, pkg)) //
|
||||
|| !build_number().contains(std::invoke(&Pkg::build_number, pkg)) //
|
||||
|| (!md5().empty() && (md5() != std::invoke(&Pkg::md5, pkg))) //
|
||||
|| (!sha256().empty() && (sha256() != std::invoke(&Pkg::sha256, pkg))) //
|
||||
|| (!license().empty() && (license() != std::invoke(&Pkg::license, pkg))) //
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto& plats = platforms();
|
||||
plats.has_value() && !plats->get().contains(std::invoke(&Pkg::platform, pkg)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto& tfeats = track_features();
|
||||
tfeats.has_value()
|
||||
&& !util::set_is_subset_of(tfeats->get(), std::invoke(&Pkg::track_features, pkg)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "mamba/specs/archive.hpp"
|
||||
#include "mamba/specs/match_spec.hpp"
|
||||
#include "mamba/specs/package_info.hpp"
|
||||
#include "mamba/util/parsers.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
|
@ -944,6 +945,39 @@ namespace mamba::specs
|
|||
&& m_build_number.is_explicitly_free();
|
||||
}
|
||||
|
||||
auto MatchSpec::contains_except_channel(const PackageInfo& pkg) const -> bool
|
||||
{
|
||||
struct Pkg
|
||||
{
|
||||
std::string_view name;
|
||||
Version version; // Converted
|
||||
std::string_view build_string;
|
||||
std::size_t build_number;
|
||||
std::string_view md5;
|
||||
std::string_view sha256;
|
||||
std::string_view license;
|
||||
std::reference_wrapper<const std::string> platform;
|
||||
string_set track_features; // Converted
|
||||
};
|
||||
|
||||
auto maybe_ver = Version::parse(pkg.version.empty() ? "0" : pkg.version);
|
||||
if (!maybe_ver)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return contains_except_channel(Pkg{
|
||||
/* .name= */ pkg.name,
|
||||
/* .version= */ std::move(maybe_ver).value(),
|
||||
/* .build_string= */ pkg.build_string,
|
||||
/* .build_number= */ pkg.build_number,
|
||||
/* .md5= */ pkg.md5,
|
||||
/* .sha256= */ pkg.sha256,
|
||||
/* .license= */ pkg.license,
|
||||
/* .platform= */ pkg.platform,
|
||||
/* .track_features= */ string_set(pkg.track_features.cbegin(), pkg.track_features.cend()),
|
||||
});
|
||||
}
|
||||
|
||||
auto MatchSpec::extra() -> ExtraMembers&
|
||||
{
|
||||
if (!m_extra.has_value())
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <doctest/doctest.h>
|
||||
|
||||
#include "mamba/specs/match_spec.hpp"
|
||||
#include "mamba/specs/package_info.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
using namespace mamba;
|
||||
|
@ -496,4 +497,229 @@ TEST_SUITE("specs::match_spec")
|
|||
CHECK_FALSE(ms.is_simple());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("MatchSpec::contains")
|
||||
{
|
||||
// Note that tests for individual ``contains`` functions (``VersionSpec::contains``,
|
||||
// ``BuildNumber::contains``, ``GlobSpec::contains``...) are tested in their respective
|
||||
// test files.
|
||||
|
||||
using namespace specs::match_spec_literals;
|
||||
using namespace specs::version_literals;
|
||||
|
||||
struct Pkg
|
||||
{
|
||||
std::string name = {};
|
||||
specs::Version version = {};
|
||||
std::string build_string = {};
|
||||
std::size_t build_number = {};
|
||||
std::string md5 = {};
|
||||
std::string sha256 = {};
|
||||
std::string license = {};
|
||||
DynamicPlatform platform = {};
|
||||
MatchSpec::string_set track_features = {};
|
||||
};
|
||||
|
||||
SUBCASE("python")
|
||||
{
|
||||
const auto ms = "python"_ms;
|
||||
CHECK(ms.contains_except_channel(Pkg{ "python" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "pypy" }));
|
||||
|
||||
CHECK(ms.contains_except_channel(PackageInfo{ "python" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(PackageInfo{ "pypy" }));
|
||||
}
|
||||
|
||||
SUBCASE("py*")
|
||||
{
|
||||
const auto ms = "py*"_ms;
|
||||
CHECK(ms.contains_except_channel(Pkg{ "python" }));
|
||||
CHECK(ms.contains_except_channel(Pkg{ "pypy" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "rust" }));
|
||||
|
||||
CHECK(ms.contains_except_channel(PackageInfo{ "python" }));
|
||||
CHECK(ms.contains_except_channel(PackageInfo{ "pypy" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(PackageInfo{ "rust" }));
|
||||
}
|
||||
|
||||
SUBCASE("py*>=3.7")
|
||||
{
|
||||
const auto ms = "py*>=3.7"_ms;
|
||||
CHECK(ms.contains_except_channel(Pkg{ "python", "3.7"_v }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "pypy", "3.6"_v }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "rust", "3.7"_v }));
|
||||
|
||||
CHECK(ms.contains_except_channel(PackageInfo{ "python", "3.7", "bld", 0 }));
|
||||
CHECK_FALSE(ms.contains_except_channel(PackageInfo{ "pypy", "3.6", "bld", 0 }));
|
||||
CHECK_FALSE(ms.contains_except_channel(PackageInfo{ "rust", "3.7", "bld", 0 }));
|
||||
}
|
||||
|
||||
SUBCASE("py*>=3.7=*cpython")
|
||||
{
|
||||
const auto ms = "py*>=3.7=*cpython"_ms;
|
||||
CHECK(ms.contains_except_channel(Pkg{ "python", "3.7"_v, "37_cpython" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "pypy", "3.6"_v, "cpython" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "pypy", "3.8"_v, "pypy" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "rust", "3.7"_v, "cpyhton" }));
|
||||
}
|
||||
|
||||
SUBCASE("py*[version='>=3.7', build=*cpython]")
|
||||
{
|
||||
const auto ms = "py*[version='>=3.7', build=*cpython]"_ms;
|
||||
CHECK(ms.contains_except_channel(Pkg{ "python", "3.7"_v, "37_cpython" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "pypy", "3.6"_v, "cpython" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "pypy", "3.8"_v, "pypy" }));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{ "rust", "3.7"_v, "cpyhton" }));
|
||||
}
|
||||
|
||||
SUBCASE("pkg[build_number='>3']")
|
||||
{
|
||||
const auto ms = "pkg[build_number='>3']"_ms;
|
||||
auto pkg = Pkg{ "pkg" };
|
||||
pkg.build_number = 4;
|
||||
CHECK(ms.contains_except_channel(pkg));
|
||||
pkg.build_number = 2;
|
||||
CHECK_FALSE(ms.contains_except_channel(pkg));
|
||||
}
|
||||
|
||||
SUBCASE("pkg[md5=helloiamnotreallymd5haha]")
|
||||
{
|
||||
const auto ms = "pkg[md5=helloiamnotreallymd5haha]"_ms;
|
||||
|
||||
auto pkg = Pkg{ "pkg" };
|
||||
pkg.md5 = "helloiamnotreallymd5haha";
|
||||
CHECK(ms.contains_except_channel(pkg));
|
||||
|
||||
for (auto md5 : { "helloiamnotreallymd5hahaevillaugh", "hello", "" })
|
||||
{
|
||||
CAPTURE(std::string_view(md5));
|
||||
pkg.md5 = md5;
|
||||
CHECK_FALSE(ms.contains_except_channel(pkg));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("pkg[sha256=helloiamnotreallysha256hihi]")
|
||||
{
|
||||
const auto ms = "pkg[sha256=helloiamnotreallysha256hihi]"_ms;
|
||||
|
||||
auto pkg = Pkg{ "pkg" };
|
||||
pkg.sha256 = "helloiamnotreallysha256hihi";
|
||||
CHECK(ms.contains_except_channel(pkg));
|
||||
|
||||
for (auto sha256 : { "helloiamnotreallysha256hihicutelaugh", "hello", "" })
|
||||
{
|
||||
CAPTURE(std::string_view(sha256));
|
||||
pkg.sha256 = sha256;
|
||||
CHECK_FALSE(ms.contains_except_channel(pkg));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("pkg[license=helloiamnotreallylicensehoho]")
|
||||
{
|
||||
const auto ms = "pkg[license=helloiamnotreallylicensehoho]"_ms;
|
||||
|
||||
auto pkg = Pkg{ "pkg" };
|
||||
pkg.license = "helloiamnotreallylicensehoho";
|
||||
CHECK(ms.contains_except_channel(pkg));
|
||||
|
||||
for (auto license : { "helloiamnotreallylicensehohodadlaugh", "hello", "" })
|
||||
{
|
||||
CAPTURE(std::string_view(license));
|
||||
pkg.license = license;
|
||||
CHECK_FALSE(ms.contains_except_channel(pkg));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("pkg[subdir='linux-64,linux-64-512']")
|
||||
{
|
||||
const auto ms = "pkg[subdir='linux-64,linux-64-512']"_ms;
|
||||
|
||||
auto pkg = Pkg{ "pkg" };
|
||||
|
||||
for (auto plat : { "linux-64", "linux-64-512" })
|
||||
{
|
||||
CAPTURE(std::string_view(plat));
|
||||
pkg.platform = plat;
|
||||
CHECK(ms.contains_except_channel(pkg));
|
||||
}
|
||||
|
||||
for (auto plat : { "linux", "linux-512", "", "linux-64,linux-64-512" })
|
||||
{
|
||||
CAPTURE(std::string_view(plat));
|
||||
pkg.platform = plat;
|
||||
CHECK_FALSE(ms.contains_except_channel(pkg));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("pkg[track_features='mkl,openssl']")
|
||||
{
|
||||
using string_set = typename MatchSpec::string_set;
|
||||
|
||||
const auto ms = "pkg[track_features='mkl,openssl']"_ms;
|
||||
|
||||
auto pkg = Pkg{ "pkg" };
|
||||
|
||||
for (auto tfeats : { string_set{ "openssl", "mkl" } })
|
||||
{
|
||||
pkg.track_features = tfeats;
|
||||
CHECK(ms.contains_except_channel(pkg));
|
||||
}
|
||||
|
||||
for (auto tfeats : { string_set{ "openssl" }, string_set{ "mkl" }, string_set{} })
|
||||
{
|
||||
pkg.track_features = tfeats;
|
||||
CHECK_FALSE(ms.contains_except_channel(pkg));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Complex")
|
||||
{
|
||||
const auto ms = "py*>=3.7=bld[build_number='<=2', md5=lemd5, track_features='mkl,openssl']"_ms;
|
||||
|
||||
CHECK(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "python",
|
||||
/* .version= */ "3.8.0"_v,
|
||||
/* .build_string= */ "bld",
|
||||
/* .build_number= */ 2,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "MIT",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{ "openssl", "mkl" },
|
||||
}));
|
||||
CHECK(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "python",
|
||||
/* .version= */ "3.12.0"_v,
|
||||
/* .build_string= */ "bld",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{ "openssl", "mkl" },
|
||||
}));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "python",
|
||||
/* .version= */ "3.3.0"_v, // Not matching
|
||||
/* .build_string= */ "bld",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "lemd5",
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{ "openssl", "mkl" },
|
||||
}));
|
||||
CHECK_FALSE(ms.contains_except_channel(Pkg{
|
||||
/* .name= */ "python",
|
||||
/* .version= */ "3.12.0"_v,
|
||||
/* .build_string= */ "bld",
|
||||
/* .build_number= */ 0,
|
||||
/* .md5= */ "wrong", // Not matching
|
||||
/* .sha256= */ "somesha256",
|
||||
/* .license= */ "GPL",
|
||||
/* .platform= */ "linux-64",
|
||||
/* .track_features =*/{ "openssl", "mkl" },
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -306,9 +306,9 @@ namespace mambapy
|
|||
py::arg("credentials") = CondaURL::Credentials::Hide
|
||||
);
|
||||
|
||||
auto py_channel_spec = py::class_<UnresolvedChannel>(m, "UnresolvedChannel");
|
||||
auto py_unresolved_channel = py::class_<UnresolvedChannel>(m, "UnresolvedChannel");
|
||||
|
||||
py::enum_<UnresolvedChannel::Type>(py_channel_spec, "Type")
|
||||
py::enum_<UnresolvedChannel::Type>(py_unresolved_channel, "Type")
|
||||
.value("URL", UnresolvedChannel::Type::URL)
|
||||
.value("PackageURL", UnresolvedChannel::Type::PackageURL)
|
||||
.value("Path", UnresolvedChannel::Type::Path)
|
||||
|
@ -318,7 +318,7 @@ namespace mambapy
|
|||
.def(py::init(&enum_from_str<UnresolvedChannel::Type>));
|
||||
py::implicitly_convertible<py::str, UnresolvedChannel::Type>();
|
||||
|
||||
py_channel_spec //
|
||||
py_unresolved_channel //
|
||||
.def_static("parse", UnresolvedChannel::parse)
|
||||
.def(
|
||||
py::init<std::string, UnresolvedChannel::platform_set, UnresolvedChannel::Type>(),
|
||||
|
@ -621,21 +621,21 @@ namespace mambapy
|
|||
pkg.version = std::move(version);
|
||||
pkg.build_string = std::move(build_string);
|
||||
pkg.build_number = std::move(build_number);
|
||||
pkg.channel = channel;
|
||||
pkg.package_url = package_url;
|
||||
pkg.platform = platform;
|
||||
pkg.filename = filename;
|
||||
pkg.license = license;
|
||||
pkg.md5 = md5;
|
||||
pkg.sha256 = sha256;
|
||||
pkg.signatures = signatures;
|
||||
pkg.track_features = track_features;
|
||||
pkg.dependencies = depends;
|
||||
pkg.constrains = constrains;
|
||||
pkg.defaulted_keys = defaulted_keys;
|
||||
pkg.noarch = noarch;
|
||||
pkg.size = size;
|
||||
pkg.timestamp = timestamp;
|
||||
pkg.channel = std::move(channel);
|
||||
pkg.package_url = std::move(package_url);
|
||||
pkg.platform = std::move(platform);
|
||||
pkg.filename = std::move(filename);
|
||||
pkg.license = std::move(license);
|
||||
pkg.md5 = std::move(md5);
|
||||
pkg.sha256 = std::move(sha256);
|
||||
pkg.signatures = std::move(signatures);
|
||||
pkg.track_features = std::move(track_features);
|
||||
pkg.dependencies = std::move(depends);
|
||||
pkg.constrains = std::move(constrains);
|
||||
pkg.defaulted_keys = std::move(defaulted_keys);
|
||||
pkg.noarch = std::move(noarch);
|
||||
pkg.size = std::move(size);
|
||||
pkg.timestamp = std::move(timestamp);
|
||||
return pkg;
|
||||
}
|
||||
),
|
||||
|
@ -752,6 +752,59 @@ namespace mambapy
|
|||
.def_property("features", &MatchSpec::features, &MatchSpec::set_features)
|
||||
.def_property("track_features", &MatchSpec::track_features, &MatchSpec::set_track_features)
|
||||
.def_property("optional", &MatchSpec::optional, &MatchSpec::set_optional)
|
||||
.def(
|
||||
"contains_except_channel",
|
||||
[](const MatchSpec& ms, const PackageInfo& pkg)
|
||||
{ return ms.contains_except_channel(pkg); }
|
||||
)
|
||||
.def(
|
||||
"contains_except_channel",
|
||||
[](const MatchSpec& ms,
|
||||
std::string_view name,
|
||||
const Version& version,
|
||||
std::string_view build_string,
|
||||
std::size_t build_number,
|
||||
std::string_view md5,
|
||||
std::string_view sha256,
|
||||
std::string_view license,
|
||||
std::string& platform,
|
||||
MatchSpec::string_set track_features)
|
||||
{
|
||||
struct Pkg
|
||||
{
|
||||
std::string_view name;
|
||||
std::reference_wrapper<const Version> version;
|
||||
std::string_view build_string;
|
||||
std::size_t build_number;
|
||||
std::string_view md5;
|
||||
std::string_view sha256;
|
||||
std::string_view license;
|
||||
std::reference_wrapper<const std::string> platform;
|
||||
const MatchSpec::string_set track_features;
|
||||
};
|
||||
|
||||
return ms.contains_except_channel(Pkg{
|
||||
/* .name= */ name,
|
||||
/* .version= */ version,
|
||||
/* .build_string= */ build_string,
|
||||
/* .build_number= */ build_number,
|
||||
/* .md5= */ md5,
|
||||
/* .sha256= */ sha256,
|
||||
/* .license= */ license,
|
||||
/* .platform= */ platform,
|
||||
/* .track_features= */ std::move(track_features),
|
||||
});
|
||||
},
|
||||
py::arg("name") = "",
|
||||
py::arg("version") = Version(),
|
||||
py::arg("build_string") = "",
|
||||
py::arg("build_number") = 0,
|
||||
py::arg("md5") = "",
|
||||
py::arg("sha256") = "",
|
||||
py::arg("license") = "",
|
||||
py::arg("platform") = "",
|
||||
py::arg("track_features") = MatchSpec::string_set{}
|
||||
)
|
||||
.def("is_file", &MatchSpec::is_file)
|
||||
.def("is_simple", &MatchSpec::is_simple)
|
||||
.def("conda_build_form", &MatchSpec::conda_build_form)
|
||||
|
|
|
@ -871,6 +871,20 @@ def test_MatchSpec():
|
|||
assert other is not ms
|
||||
|
||||
|
||||
def test_MatchSpec_contains():
|
||||
MatchSpec = libmambapy.specs.MatchSpec
|
||||
PackageInfo = libmambapy.specs.PackageInfo
|
||||
|
||||
ms = MatchSpec.parse("conda-forge::py*[build_number='>4']")
|
||||
|
||||
assert ms.contains_except_channel(name="python", build_number=5, build_string="bld")
|
||||
assert not ms.contains_except_channel(name="python", build_number=2)
|
||||
assert not ms.contains_except_channel(name="rust", build_number=4)
|
||||
|
||||
assert ms.contains_except_channel(PackageInfo(name="python", build_number=5))
|
||||
assert not ms.contains_except_channel(PackageInfo(name="python"))
|
||||
|
||||
|
||||
def test_MatchSpec_V2Migrator():
|
||||
"""Explicit migration help added from v1 to v2."""
|
||||
import libmambapy
|
||||
|
|
Loading…
Reference in New Issue