|
| 1 | +// Copyright (c) 2026, QuantStack and Mamba Contributors |
| 2 | +// |
| 3 | +// Distributed under the terms of the BSD 3-Clause License. |
| 4 | +// |
| 5 | +// The full license is in the file LICENSE, distributed with this software. |
| 6 | + |
| 7 | +#include <fstream> |
| 8 | + |
| 9 | +#include <catch2/catch_all.hpp> |
| 10 | + |
| 11 | +#include "mamba/core/fsutil.hpp" |
| 12 | +#include "mamba/core/util.hpp" |
| 13 | +#include "mamba/core/util_os.hpp" |
| 14 | +#include "mamba/specs/package_info.hpp" |
| 15 | + |
| 16 | +#include "core/link.hpp" |
| 17 | +#include "core/transaction_context.hpp" |
| 18 | + |
| 19 | +#include "mambatests.hpp" |
| 20 | + |
| 21 | +namespace mamba |
| 22 | +{ |
| 23 | + namespace |
| 24 | + { |
| 25 | + TEST_CASE("run_scripts_in_transaction") |
| 26 | + { |
| 27 | + // Ensure Console is initialized to avoid singleton access errors |
| 28 | + (void) mambatests::context(); |
| 29 | + |
| 30 | + const auto tmp_dir = TemporaryDirectory(); |
| 31 | + const fs::u8path prefix = tmp_dir.path() / "prefix"; |
| 32 | + const fs::u8path cache_dir = tmp_dir.path() / "cache"; |
| 33 | + |
| 34 | + specs::PackageInfo pkg("test_pkg"); |
| 35 | + pkg.version = "1.0"; |
| 36 | + pkg.build_string = "0"; |
| 37 | + |
| 38 | + const fs::u8path pkg_source = cache_dir / pkg.str(); |
| 39 | + const fs::u8path bin_dir_relative = get_bin_directory_short_path(); |
| 40 | + |
| 41 | + fs::create_directories(prefix / "conda-meta"); |
| 42 | + fs::create_directories(pkg_source / bin_dir_relative); |
| 43 | + fs::create_directories(prefix / bin_dir_relative); |
| 44 | + |
| 45 | + // Provide a dummy conda binary so wrap_call activation wrapper |
| 46 | + // succeeds in the test environment |
| 47 | + if (util::on_win) |
| 48 | + { |
| 49 | + fs::create_directories(prefix / "condabin"); |
| 50 | + auto conda_bat_path = prefix / "condabin" |
| 51 | + / (get_self_exe_path().stem().string() + ".bat"); |
| 52 | + auto out = open_ofstream(conda_bat_path); |
| 53 | + out << "@exit /b 0\n"; |
| 54 | + } |
| 55 | + else |
| 56 | + { |
| 57 | + fs::create_directories(prefix / "bin"); |
| 58 | + auto conda_path = prefix / "bin" / "conda"; |
| 59 | + auto out = open_ofstream(conda_path); |
| 60 | + out << "#!/bin/sh\n"; |
| 61 | + out << "if [ \"$1\" = \"shell.posix\" ] && [ \"$2\" = \"hook\" ]; then\n"; |
| 62 | + out << " echo 'conda() { :; }'\n"; // Define conda as a no-op function |
| 63 | + out << "fi\n"; |
| 64 | + make_executable(conda_path); |
| 65 | + } |
| 66 | + |
| 67 | + const auto make_tx_context = [&] |
| 68 | + { |
| 69 | + TransactionParams tx_params{ |
| 70 | + .is_mamba_exe = false, |
| 71 | + .json_output = false, |
| 72 | + .verbosity = 0, |
| 73 | + .shortcuts = false, |
| 74 | + .envs_dirs = {}, |
| 75 | + .platform = "linux-64", |
| 76 | + .prefix_params = |
| 77 | + PrefixParams{ |
| 78 | + .target_prefix = prefix, |
| 79 | + .root_prefix = prefix, |
| 80 | + .conda_prefix = prefix, |
| 81 | + .relocate_prefix = prefix, |
| 82 | + }, |
| 83 | + .link_params = {}, |
| 84 | + .threads_params = {}, |
| 85 | + }; |
| 86 | + |
| 87 | + return TransactionContext( |
| 88 | + tx_params, |
| 89 | + { "3.14.4", "3.14.4" }, |
| 90 | + "lib/python3.14/site-packages", |
| 91 | + {} |
| 92 | + ); |
| 93 | + }; |
| 94 | + |
| 95 | + std::string script_ext = util::on_win ? ".bat" : ".sh"; |
| 96 | + std::string pre_link_name = ".test_pkg-pre-link" + script_ext; |
| 97 | + std::string post_link_name = ".test_pkg-post-link" + script_ext; |
| 98 | + std::string pre_unlink_name = ".test_pkg-pre-unlink" + script_ext; |
| 99 | + std::string post_unlink_name = ".test_pkg-post-unlink" + script_ext; |
| 100 | + |
| 101 | + auto create_script = [](const fs::u8path& p, const fs::u8path& marker_path) |
| 102 | + { |
| 103 | + std::ofstream out = open_ofstream(p); |
| 104 | + if (util::on_win) |
| 105 | + { |
| 106 | + out << "@echo off\n"; |
| 107 | + out << "echo done > \"" << marker_path.string() << "\"\n"; |
| 108 | + } |
| 109 | + else |
| 110 | + { |
| 111 | + out << "#!/bin/sh\n"; |
| 112 | + out << "echo done > \"" << marker_path.string() << "\"\n"; |
| 113 | + } |
| 114 | + }; |
| 115 | + |
| 116 | + fs::u8path pre_link_marker = tmp_dir.path() / "pre_link_done.txt"; |
| 117 | + fs::u8path post_link_marker = tmp_dir.path() / "post_link_done.txt"; |
| 118 | + fs::u8path pre_unlink_marker = tmp_dir.path() / "pre_unlink_done.txt"; |
| 119 | + fs::u8path post_unlink_marker = tmp_dir.path() / "post_unlink_done.txt"; |
| 120 | + |
| 121 | + create_script(pkg_source / bin_dir_relative / pre_link_name, pre_link_marker); |
| 122 | + create_script(prefix / bin_dir_relative / post_link_name, post_link_marker); |
| 123 | + create_script(prefix / bin_dir_relative / pre_unlink_name, pre_unlink_marker); |
| 124 | + create_script(prefix / bin_dir_relative / post_unlink_name, post_unlink_marker); |
| 125 | + |
| 126 | + // Prepare metadata for unlinking |
| 127 | + { |
| 128 | + auto out = open_ofstream(prefix / "conda-meta" / (pkg.str() + ".json")); |
| 129 | + out << R"({ |
| 130 | + "name": "test_pkg", |
| 131 | + "version": "1.0", |
| 132 | + "build_string": "0", |
| 133 | + "paths_data": { "paths": [], "paths_version": 1 } |
| 134 | + })"; |
| 135 | + } |
| 136 | + // Prepare paths.json for linking |
| 137 | + { |
| 138 | + fs::create_directories(pkg_source / "info"); |
| 139 | + auto out = open_ofstream(pkg_source / "info" / "paths.json"); |
| 140 | + out << R"({ "paths": [], "paths_version": 1 })"; |
| 141 | + } |
| 142 | + // Prepare repodata_record.json for linking |
| 143 | + { |
| 144 | + auto out = open_ofstream(pkg_source / "info" / "repodata_record.json"); |
| 145 | + out << R"({ "noarch": null })"; |
| 146 | + } |
| 147 | + |
| 148 | + SECTION("linking executes pre-link and post-link") |
| 149 | + { |
| 150 | + auto tx_context = make_tx_context(); |
| 151 | + LinkPackage link_pkg(pkg, cache_dir, &tx_context); |
| 152 | + |
| 153 | + REQUIRE(link_pkg.execute()); |
| 154 | + |
| 155 | + REQUIRE(fs::exists(pre_link_marker)); |
| 156 | + REQUIRE(fs::exists(post_link_marker)); |
| 157 | + } |
| 158 | + |
| 159 | + SECTION("unlinking executes pre-unlink and post-unlink") |
| 160 | + { |
| 161 | + auto tx_context = make_tx_context(); |
| 162 | + UnlinkPackage unlink_pkg(pkg, cache_dir, &tx_context); |
| 163 | + |
| 164 | + REQUIRE(unlink_pkg.execute()); |
| 165 | + |
| 166 | + REQUIRE(fs::exists(pre_unlink_marker)); |
| 167 | + // post-unlink script should not be executed as deprecated |
| 168 | + REQUIRE(!fs::exists(post_unlink_marker)); |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | +} // namespace mamba |
0 commit comments