fix: Handle `MatchSpec` with brackets when parsing environments' history (#3490)

* fix: Adapt `parse_comment_line` to handle more `MatchSpec`

Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>

* test: Add non-regression python test

Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>

* test: Add non-regression C++ test

Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>

* Apply review comments

Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>

Co-authored-by: Johan Mabille <johan.mabille@gmail.com>

* test: Skip test on Windows

Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>

---------

Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>
Co-authored-by: Johan Mabille <johan.mabille@gmail.com>
This commit is contained in:
Julien Jerphanion 2024-10-02 17:21:57 +02:00 committed by GitHub
parent d162759107
commit b7367417f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 0 deletions

View File

@ -126,12 +126,50 @@ namespace mamba
{
needle[0] = value[idx_start];
idx_end = value.find_first_of(needle.c_str(), idx_search);
// Capturing `MatchSpecs` without internal quotes (e.g `libcurl`)
if (idx_end != std::string::npos && value[idx_end - 1] != '\\')
{
pkg_specs.push_back(value.substr(idx_start + 1, idx_end - 1 - idx_start));
idx_start = value.find_first_of("\'\"", idx_end + 1);
idx_search = idx_start + 1;
}
// Capturing `MatchSpecs` with metadata (e.g `libcurl[version=\">=7.86,<8.10\"]`)
else if (idx_end != std::string::npos && value[idx_end - 1] == '\\')
{
// Find if "[" is present in between idx_search and idx_end
auto idx_bracket = value.find_first_of("[", idx_search);
// If "[" is present, then find the closing bracket
if (idx_bracket != std::string::npos && idx_bracket < idx_end)
{
auto idx_closing_bracket = value.find_first_of("]", idx_bracket);
if (idx_closing_bracket != std::string::npos)
{
auto start_string = idx_start + 1;
auto end_string = idx_closing_bracket + 1;
auto len_matchspec = end_string - start_string;
// Quotes are excluded (e.g. `libcurl[version=\">=7.86,<8.10\"]` is
// extracted from `"libcurl[version=\">=7.86,<8.10\"]"`)
auto match_spec = value.substr(start_string, len_matchspec);
// Remove the backslash from the MatchSpec
match_spec.erase(
std::remove(match_spec.begin(), match_spec.end(), '\\'),
match_spec.end()
);
pkg_specs.push_back(std::move(match_spec));
idx_start = value.find_first_of("\'\"", end_string + 1);
idx_search = idx_start + 1;
}
}
// If "[" is not present, then there's a problem with the MatchSpec
else if (idx_bracket == std::string::npos || idx_bracket > idx_end)
{
throw std::runtime_error("Parsing of history file failed at: " + value);
}
}
else
{
idx_search = idx_end;

View File

@ -0,0 +1,44 @@
==> 2024-10-02 12:29:11 <==
# cmd: micromamba create -n repro2 pandas[version=">=0.25.2,<3"]
# conda version: 3.8.0
+conda-forge::_libgcc_mutex-0.1-conda_forge
+conda-forge::python_abi-3.12-5_cp312
+conda-forge::ca-certificates-2024.8.30-hbcca054_0
+conda-forge::ld_impl_linux-64-2.43-h712a8e2_1
+conda-forge::libgomp-14.1.0-h77fa898_1
+conda-forge::_openmp_mutex-4.5-2_gnu
+conda-forge::libgcc-14.1.0-h77fa898_1
+conda-forge::libgfortran5-14.1.0-hc5f4f2c_1
+conda-forge::libgcc-ng-14.1.0-h69a702a_1
+conda-forge::openssl-3.3.2-hb9d3cd8_0
+conda-forge::libexpat-2.6.3-h5888daf_0
+conda-forge::libstdcxx-14.1.0-hc0a3c3a_1
+conda-forge::libgfortran-14.1.0-h69a702a_1
+conda-forge::libffi-3.4.2-h7f98852_5
+conda-forge::libxcrypt-4.4.36-hd590300_1
+conda-forge::bzip2-1.0.8-h4bc722e_7
+conda-forge::ncurses-6.5-he02047a_1
+conda-forge::libzlib-1.3.1-h4ab18f5_1
+conda-forge::xz-5.2.6-h166bdaf_0
+conda-forge::libuuid-2.38.1-h0b41bf4_0
+conda-forge::libnsl-2.0.1-hd590300_0
+conda-forge::libgfortran-ng-14.1.0-h69a702a_1
+conda-forge::readline-8.2-h8228510_1
+conda-forge::tk-8.6.13-noxft_h4845f30_101
+conda-forge::libsqlite-3.46.1-hadc24fc_0
+conda-forge::libopenblas-0.3.27-pthreads_hac2b453_1
+conda-forge::libblas-3.9.0-24_linux64_openblas
+conda-forge::libcblas-3.9.0-24_linux64_openblas
+conda-forge::liblapack-3.9.0-24_linux64_openblas
+conda-forge::tzdata-2024a-h8827d51_1
+conda-forge::python-3.12.6-hc5c86c4_2_cpython
+conda-forge::wheel-0.44.0-pyhd8ed1ab_0
+conda-forge::setuptools-75.1.0-pyhd8ed1ab_0
+conda-forge::pip-24.2-pyh8b19718_1
+conda-forge::six-1.16.0-pyh6c4a22f_0
+conda-forge::pytz-2024.1-pyhd8ed1ab_0
+conda-forge::python-tzdata-2024.2-pyhd8ed1ab_0
+conda-forge::python-dateutil-2.9.0-pyhd8ed1ab_0
+conda-forge::numpy-2.1.1-py312h58c1407_0
+conda-forge::pandas-2.2.3-py312hf9745cd_1
# update specs: ["pandas[version=\">=0.25.2,<3\"]"]

View File

@ -93,6 +93,18 @@ namespace mamba
REQUIRE_EQ(updated_history_buffer.str(), check_buffer.str());
}
TEST_CASE("parse_metadata")
{
auto channel_context = ChannelContext::make_conda_compatible(mambatests::context());
History history_instance(
mambatests::test_data_dir / "history/parse_metadata",
channel_context
);
// Must not throw
std::vector<History::UserRequest> user_reqs = history_instance.get_user_requests();
}
#ifndef _WIN32
TEST_CASE("parse_segfault")
{

View File

@ -1328,3 +1328,18 @@ def test_create_package_with_non_url_char(tmp_home, tmp_root_prefix):
res = helpers.create("-n", "myenv", "-c", "conda-forge", "x264>=1!0", "--json")
assert any(pkg["name"] == "x264" for pkg in res["actions"]["LINK"])
@pytest.mark.timeout(20)
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
@pytest.mark.skipif(
platform.system() == "Windows", reason="This test fails on Windows for unknown reasons"
)
def test_parsable_env_history_with_metadata(tmp_home, tmp_root_prefix, tmp_path):
env_prefix = tmp_path / "env-micromamba-list"
res = helpers.create("-p", env_prefix, 'pandas[version=">=0.25.2,<3"]', "--json")
assert res["success"]
# Must not hang
helpers.umamba_list("-p", env_prefix, "--json")