Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions libmamba/tests/include/mambatests.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
#define LIBMAMBATESTS_HPP

#include <array>
#include <functional>
#include <string_view>
#include <unordered_set>
#include <vector>

#include "mamba/core/context.hpp"
#include "mamba/core/output.hpp"
Expand Down Expand Up @@ -87,6 +90,132 @@ namespace mambatests
void operator()(const mamba::util::environment_map& env);
};

// RAII helper for C++ tests that temporarily override fields on the shared Context
// singleton (see context()). Tests often need to point at a temp prefix, tweak channels,
// or flip feature flags; without restoration, later tests inherit stale state.
//
// Save the value of each touched field on first use and restore it when the guard is
// destroyed. Repeated calls to the same setter only change the live value — the original
// snapshot is always what gets restored.
//
// Use set_* when the test assigns a known value. Use preserve() when the field will be
// changed later by code under test, by direct assignment (ctx.field = …), or by APIs such
// as Configuration::load() — preserve() only snapshots the current value for restoration.
//
// Example:
// auto& ctx = mambatests::context();
// mambatests::ScopedContextChange context_change{ ctx };
// context_change.set_channels({ "conda-forge" }).set_offline(false);
//
// context_change.preserve(&mamba::Context::use_sharded_repodata);
// ctx.use_sharded_repodata = false; // restored to the pre-preserve value at scope end
class ScopedContextChange
{
public:

explicit ScopedContextChange(mamba::Context& ctx)
: m_ctx(ctx)
{
}

~ScopedContextChange()
{
for (auto it = m_restorers.rbegin(); it != m_restorers.rend(); ++it)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (auto it = m_restorers.rbegin(); it != m_restorers.rend(); ++it)
for (auto& restorer : [restorers_reversed](std::ranges::reverse_view(m_restorers)))

{
(*it)();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that if any exception escape this call, abort will be called (because destructors are implicitly noexcept -> exception escaping calls terminate -> no terminate handler leads to abort())

Did you consider calling safe_invoke instead?

}
}

ScopedContextChange& set_target_prefix(const mamba::fs::u8path& prefix)
{
touch(
&mamba::Context::prefix_params,
[&](auto& params) { params.target_prefix = prefix; }
);
return *this;
}

ScopedContextChange& set_root_prefix(const mamba::fs::u8path& prefix)
{
touch(&mamba::Context::prefix_params, [&](auto& params) { params.root_prefix = prefix; });
return *this;
}

ScopedContextChange& set_envs_dirs(std::vector<mamba::fs::u8path> dirs)
{
touch(&mamba::Context::envs_dirs, [&](auto& field) { field = std::move(dirs); });
return *this;
}

ScopedContextChange& set_pkgs_dirs(std::vector<mamba::fs::u8path> dirs)
{
touch(&mamba::Context::pkgs_dirs, [&](auto& field) { field = std::move(dirs); });
return *this;
}

ScopedContextChange& set_prefix_data_interoperability(bool value)
{
touch(&mamba::Context::prefix_data_interoperability, [&](auto& field) { field = value; });
return *this;
}

ScopedContextChange& set_channels(std::vector<std::string> channels)
{
touch(&mamba::Context::channels, [&](auto& field) { field = std::move(channels); });
return *this;
}

ScopedContextChange& set_use_sharded_repodata(bool value)
{
touch(&mamba::Context::use_sharded_repodata, [&](auto& field) { field = value; });
return *this;
}

ScopedContextChange& set_offline(bool value)
{
touch(&mamba::Context::offline, [&](auto& field) { field = value; });
return *this;
}

ScopedContextChange& set_platform(std::string platform)
{
touch(&mamba::Context::platform, [&](auto& field) { field = std::move(platform); });
return *this;
}

// Snapshot member for restoration without assigning a new value. The member pointer
// syntax (e.g. &mamba::Context::platform) selects which Context field to guard.
template <typename T>
ScopedContextChange& preserve(T mamba::Context::* member)
{
touch(member, [](auto&) {});
return *this;
}

ScopedContextChange(const ScopedContextChange&) = delete;
ScopedContextChange& operator=(const ScopedContextChange&) = delete;
ScopedContextChange(ScopedContextChange&&) = delete;
ScopedContextChange& operator=(ScopedContextChange&&) = delete;

private:

template <typename T, typename F>
void touch(T mamba::Context::* member, F&& mutator)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void touch(T mamba::Context::* member, F&& mutator)
void touch(T& member, F&& mutator)

Then capture the reference to member, with the field pointer being &member.

{
auto& field = m_ctx.*member;
if (m_saved_fields.insert(static_cast<const void*>(&field)).second)
{
m_restorers.push_back([&field, initial = field]() mutable
{ field = std::move(initial); });
}
mutator(field);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mutator(field);
std::invoke(std::forward<F>(mutator), field);

}

mamba::Context& m_ctx;
std::vector<std::function<void()>> m_restorers;
std::unordered_set<const void*> m_saved_fields;
};

/******************************************
* Implementation of EnvironmentCleaner *
******************************************/
Expand Down
50 changes: 9 additions & 41 deletions libmamba/tests/src/core/test_channel_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,45 +161,13 @@ TEST_CASE("load_channels", "[mamba::api][channel_loader]")
{
// Use test singletons so Console/progress bar are initialized (avoids SIGABRT)
Context& ctx = mambatests::context();

// Save and restore context so we don't affect other tests (e.g. test_configuration
// expects default ssl_verify / config state)
struct ContextGuard
{
Context& ctx;
std::vector<std::string> channels;
std::map<std::string, std::vector<std::string>> mirrored_channels;
std::vector<fs::u8path> pkgs_dirs;
bool offline;
std::string ssl_verify;
std::string channel_alias;
std::map<std::string, std::string> proxy_servers;

explicit ContextGuard(Context& c)
: ctx(c)
, channels(c.channels)
, mirrored_channels(c.mirrored_channels)
, pkgs_dirs(c.pkgs_dirs)
, offline(c.offline)
, ssl_verify(c.remote_fetch_params.ssl_verify)
, channel_alias(c.channel_alias)
, proxy_servers(c.remote_fetch_params.proxy_servers)
{
}

~ContextGuard()
{
ctx.channels = std::move(channels);
ctx.mirrored_channels = std::move(mirrored_channels);
ctx.pkgs_dirs = std::move(pkgs_dirs);
ctx.offline = offline;
ctx.remote_fetch_params.ssl_verify = std::move(ssl_verify);
ctx.channel_alias = std::move(channel_alias);
ctx.remote_fetch_params.proxy_servers = std::move(proxy_servers);
}
};

ContextGuard guard(ctx);
mambatests::ScopedContextChange context_change{ ctx };
context_change.preserve(&mamba::Context::channels)
.preserve(&mamba::Context::mirrored_channels)
.preserve(&mamba::Context::pkgs_dirs)
.preserve(&mamba::Context::offline)
.preserve(&mamba::Context::remote_fetch_params)
.preserve(&mamba::Context::channel_alias);

ctx.channels = {};
ctx.mirrored_channels = {};
Expand All @@ -220,8 +188,8 @@ TEST_CASE("load_channels", "[mamba::api][channel_loader]")
TEST_CASE("load_channels with root_packages", "[mamba::core][mamba::api::channel_loader]")
{
auto& ctx = mambatests::context();
ctx.channels = { "conda-forge" };
ctx.use_sharded_repodata = true;
mambatests::ScopedContextChange context_change{ ctx };
context_change.set_channels({ "conda-forge" }).set_use_sharded_repodata(true);

auto channel_context = ChannelContext::make_conda_compatible(ctx);
solver::libsolv::Database db{ channel_context.params() };
Expand Down
5 changes: 2 additions & 3 deletions libmamba/tests/src/core/test_channels_hook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ namespace mamba::testing

ChannelsHookFixture()
{
m_channel_alias_bu = ctx.channel_alias;
m_context_change.preserve(&mamba::Context::channel_alias).preserve(&mamba::Context::channels);
}

~ChannelsHookFixture()
{
config.reset_configurables();
ctx.channel_alias = m_channel_alias_bu;
mamba::util::unset_env("CONDA_CHANNELS");
}

Expand Down Expand Up @@ -104,12 +103,12 @@ namespace mamba::testing

mamba::Context& ctx = mambatests::context();
mamba::Configuration config{ ctx };
mambatests::ScopedContextChange m_context_change{ ctx };

private:

std::unique_ptr<TemporaryFile> tempfile_ptr = std::make_unique<TemporaryFile>("mambarc", ".yaml");
std::unique_ptr<TemporaryFile> envfile_ptr = std::make_unique<TemporaryFile>("env", ".yaml");
std::string m_channel_alias_bu;
};
} // namespace mamba::testing

Expand Down
20 changes: 6 additions & 14 deletions libmamba/tests/src/core/test_configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,13 @@ namespace mamba

Configuration()
{
m_channel_alias_bu = ctx.channel_alias;
m_ssl_verify = ctx.remote_fetch_params.ssl_verify;
m_proxy_servers = ctx.remote_fetch_params.proxy_servers;
m_context_change.preserve(&mamba::Context::channel_alias)
.preserve(&mamba::Context::remote_fetch_params);
}

~Configuration()
{
config.reset_configurables();
ctx.channel_alias = m_channel_alias_bu;
ctx.remote_fetch_params.ssl_verify = m_ssl_verify;
ctx.remote_fetch_params.proxy_servers = m_proxy_servers;
}

protected:
Expand Down Expand Up @@ -131,16 +127,10 @@ namespace mamba

mamba::Context& ctx = mambatests::context();
mamba::Configuration config{ ctx };
mambatests::ScopedContextChange m_context_change{ ctx };

private:

// Variables to restore the original Context state and avoid
// side effect across the tests. A better solution would be to
// save and restore the whole context (that requires refactoring
// of the Context class)
std::string m_channel_alias_bu;
std::string m_ssl_verify;
std::map<std::string, std::string> m_proxy_servers;
mambatests::EnvironmentCleaner restore = { mambatests::CleanMambaEnv() };
};

Expand Down Expand Up @@ -874,6 +864,9 @@ namespace mamba

TEST_CASE_METHOD(Configuration, "platform")
{
mambatests::ScopedContextChange context_change{ ctx };
context_change.preserve(&mamba::Context::platform);

REQUIRE(ctx.platform == ctx.host_platform);

std::string rc = "platform: mylinux-128";
Expand Down Expand Up @@ -903,7 +896,6 @@ namespace mamba
);

config.at("platform").clear_values();
ctx.platform = ctx.host_platform;
}

#define TEST_BOOL_CONFIGURABLE(NAME, CTX) \
Expand Down
5 changes: 3 additions & 2 deletions libmamba/tests/src/core/test_cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,9 @@ namespace mamba
if constexpr (util::on_mac || util::on_linux)
{
auto& ctx = mambatests::context();
ctx.prefix_params.root_prefix = "/home/user/micromamba/";
ctx.envs_dirs = { ctx.prefix_params.root_prefix / "envs" };
mambatests::ScopedContextChange context_change{ ctx };
context_change.set_root_prefix("/home/user/micromamba/")
.set_envs_dirs({ fs::u8path("/home/user/micromamba/envs") });
fs::u8path prefix = "/home/user/micromamba/envs/testprefix";

auto& pp = ctx.prefix_params;
Expand Down
7 changes: 3 additions & 4 deletions libmamba/tests/src/core/test_env_lockfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,15 +412,16 @@ namespace mamba
// so we only have this test for yaml/conda env-lock-files.

auto& ctx = mambatests::context();
mambatests::ScopedContextChange context_change{ ctx };
context_change.set_platform("linux-64");

const fs::u8path lockfile_path{ mambatests::test_data_dir
/ "env_lockfile/good_multiple_categories-lock.yaml" };
auto channel_context = ChannelContext::make_conda_compatible(mambatests::context());
solver::libsolv::Database db{ channel_context.params() };
add_logger_to_database(db);
mamba::MultiPackageCache pkg_cache({ "/tmp/" }, ctx.validation_params);

ctx.platform = "linux-64";

auto check_categories =
[&](std::vector<std::string> categories, size_t num_conda, size_t num_pip)
{
Expand Down Expand Up @@ -450,8 +451,6 @@ namespace mamba
check_categories({ "main", "dev" }, 31, 6);
check_categories({ "dev" }, 28, 1);
check_categories({ "nonesuch" }, 0, 0);

ctx.platform = ctx.host_platform;
}

}
Expand Down
8 changes: 6 additions & 2 deletions libmamba/tests/src/core/test_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ namespace mamba
{
TEST_CASE("no_progress_bars")
{
mambatests::context().graphics_params.no_progress_bars = true;
auto& ctx = mambatests::context();
mambatests::ScopedContextChange context_change{ ctx };
context_change.preserve(&mamba::Context::graphics_params);

ctx.graphics_params.no_progress_bars = true;
auto proxy = Console::instance().add_progress_bar("conda-forge");
REQUIRE_FALSE(proxy.defined());
REQUIRE_FALSE(proxy);

mambatests::context().graphics_params.no_progress_bars = false;
ctx.graphics_params.no_progress_bars = false;
proxy = Console::instance().add_progress_bar("conda-forge");
REQUIRE(proxy.defined());
REQUIRE(proxy);
Expand Down
Loading
Loading