Current File : /home/inlingua/miniconda3/lib/python3.12/site-packages/conda/testing/solver_helpers.py |
# Copyright (C) 2012 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
"""Helpers for testing the solver."""
from __future__ import annotations
import collections
import functools
import json
import pathlib
import time
from tempfile import TemporaryDirectory
import pytest
from ..base.context import context
from ..core.solve import Solver
from ..exceptions import (
PackagesNotFoundError,
ResolvePackageNotFound,
UnsatisfiableError,
)
from ..models.channel import Channel
from ..models.match_spec import MatchSpec
from ..models.records import PackageRecord
from . import helpers
@functools.cache
def index_packages(num):
"""Get the index data of the ``helpers.get_index_r_*`` helpers."""
# XXX: get_index_r_X should probably be refactored to avoid loading the environment like this.
get_index = getattr(helpers, f"get_index_r_{num}")
index, _ = get_index(context.subdir)
return list(index.values())
def package_string(record):
return f"{record.channel.name}::{record.name}-{record.version}-{record.build}"
def package_string_set(packages):
"""Transforms package container in package string set."""
return {package_string(record) for record in packages}
def package_dict(packages):
"""Transforms package container into a dictionary."""
return {record.name: record for record in packages}
class SimpleEnvironment:
"""Helper environment object."""
REPO_DATA_KEYS = (
"build",
"build_number",
"depends",
"license",
"md5",
"name",
"sha256",
"size",
"subdir",
"timestamp",
"version",
"track_features",
"features",
)
def __init__(self, path, solver_class, subdirs=context.subdirs):
self._path = pathlib.Path(path)
self._prefix_path = self._path / "prefix"
self._channels_path = self._path / "channels"
self._solver_class = solver_class
self.subdirs = subdirs
self.installed_packages = []
# if repo_packages is a list, the packages will be put in a `test` channel
# if it is a dictionary, it the keys are the channel name and the value
# the channel packages
self.repo_packages: list[str] | dict[str, list[str]] = []
def solver(self, add, remove):
"""Writes ``repo_packages`` to the disk and creates a solver instance."""
channels = []
self._write_installed_packages()
for channel_name, packages in self._channel_packages.items():
self._write_repo_packages(channel_name, packages)
channel = Channel(str(self._channels_path / channel_name))
channels.append(channel)
return self._solver_class(
prefix=self._prefix_path,
subdirs=self.subdirs,
channels=channels,
specs_to_add=add,
specs_to_remove=remove,
)
def solver_transaction(self, add=(), remove=(), as_specs=False):
packages = self.solver(add=add, remove=remove).solve_final_state()
if as_specs:
return packages
return package_string_set(packages)
def install(self, *specs, as_specs=False):
return self.solver_transaction(add=specs, as_specs=as_specs)
def remove(self, *specs, as_specs=False):
return self.solver_transaction(remove=specs, as_specs=as_specs)
@property
def _channel_packages(self):
"""Helper that unfolds the ``repo_packages`` into a dictionary."""
if isinstance(self.repo_packages, dict):
return self.repo_packages
return {"test": self.repo_packages}
def _package_data(self, record):
"""Turn record into data, to be written in the JSON environment/repo files."""
data = {
key: value
for key, value in vars(record).items()
if key in self.REPO_DATA_KEYS
}
if "subdir" not in data:
data["subdir"] = context.subdir
return data
def _write_installed_packages(self):
if not self.installed_packages:
return
conda_meta = self._prefix_path / "conda-meta"
conda_meta.mkdir(exist_ok=True, parents=True)
# write record files
for record in self.installed_packages:
record_path = (
conda_meta / f"{record.name}-{record.version}-{record.build}.json"
)
record_data = self._package_data(record)
record_data["channel"] = record.channel.name
record_path.write_text(json.dumps(record_data))
# write history file
history_path = conda_meta / "history"
history_path.write_text(
"\n".join(
(
"==> 2000-01-01 00:00:00 <==",
*map(package_string, self.installed_packages),
)
)
)
def _write_repo_packages(self, channel_name, packages):
"""Write packages to the channel path."""
# build package data
package_data = collections.defaultdict(dict)
for record in packages:
package_data[record.subdir][record.fn] = self._package_data(record)
# write repodata
assert set(self.subdirs).issuperset(set(package_data.keys()))
for subdir in self.subdirs:
subdir_path = self._channels_path / channel_name / subdir
subdir_path.mkdir(parents=True, exist_ok=True)
subdir_path.joinpath("repodata.json").write_text(
json.dumps(
{
"info": {
"subdir": subdir,
},
"packages": package_data.get(subdir, {}),
}
)
)
def empty_prefix():
return TemporaryDirectory(prefix="conda-test-repo-")
@pytest.fixture()
def temp_simple_env(solver_class=Solver) -> SimpleEnvironment:
with empty_prefix() as prefix:
yield SimpleEnvironment(prefix, solver_class)
class SolverTests:
"""Tests for :py:class:`conda.core.solve.Solver` implementations."""
@property
def solver_class(self) -> type[Solver]:
"""Class under test."""
raise NotImplementedError
@property
def tests_to_skip(self):
return {} # skip reason -> list of tests to skip
@pytest.fixture(autouse=True)
def skip_tests(self, request):
for reason, skip_list in self.tests_to_skip.items():
if request.node.name in skip_list:
pytest.skip(reason)
@pytest.fixture()
def env(self):
with TemporaryDirectory(prefix="conda-test-repo-") as tmpdir:
self.env = SimpleEnvironment(tmpdir, self.solver_class)
yield self.env
self.env = None
def find_package_in_list(self, packages, **kwargs):
for record in packages:
if all(getattr(record, key) == value for key, value in kwargs.items()):
return record
def find_package(self, **kwargs):
if isinstance(self.env.repo_packages, dict):
if "channel" not in kwargs:
raise ValueError(
"Repo has multiple channels, the `channel` argument must be specified"
)
packages = self.env.repo_packages[kwargs["channel"]]
else:
packages = self.env.repo_packages
return self.find_package_in_list(packages, **kwargs)
def assert_unsatisfiable(self, exc_info, entries):
"""Helper to assert that a :py:class:`conda.exceptions.UnsatisfiableError`
instance as a the specified set of unsatisfiable specifications.
"""
assert issubclass(exc_info.type, UnsatisfiableError)
if exc_info.type is UnsatisfiableError:
assert (
sorted(
tuple(map(str, entries)) for entries in exc_info.value.unsatisfiable
)
== entries
)
def test_empty(self, env):
env.repo_packages = index_packages(1)
assert env.install() == set()
def test_iopro_mkl(self, env):
env.repo_packages = index_packages(1)
assert env.install("iopro 1.4*", "python 2.7*", "numpy 1.7*") == {
"test::iopro-1.4.3-np17py27_p0",
"test::numpy-1.7.1-py27_0",
"test::openssl-1.0.1c-0",
"test::python-2.7.5-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::unixodbc-2.3.1-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py27_1",
"test::pip-1.3.1-py27_1",
}
def test_iopro_nomkl(self, env):
env.repo_packages = index_packages(1)
assert env.install(
"iopro 1.4*", "python 2.7*", "numpy 1.7*", MatchSpec(track_features="mkl")
) == {
"test::iopro-1.4.3-np17py27_p0",
"test::mkl-rt-11.0-p0",
"test::numpy-1.7.1-py27_p0",
"test::openssl-1.0.1c-0",
"test::python-2.7.5-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::unixodbc-2.3.1-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py27_1",
"test::pip-1.3.1-py27_1",
}
def test_mkl(self, env):
env.repo_packages = index_packages(1)
assert env.install("mkl") == env.install(
"mkl 11*", MatchSpec(track_features="mkl")
)
def test_accelerate(self, env):
env.repo_packages = index_packages(1)
assert env.install("accelerate") == env.install(
"accelerate", MatchSpec(track_features="mkl")
)
def test_scipy_mkl(self, env):
env.repo_packages = index_packages(1)
records = env.install(
"scipy",
"python 2.7*",
"numpy 1.7*",
MatchSpec(track_features="mkl"),
as_specs=True,
)
for record in records:
if record.name in ("numpy", "scipy"):
assert "mkl" in record.features
assert "test::numpy-1.7.1-py27_p0" in package_string_set(records)
assert "test::scipy-0.12.0-np17py27_p0" in package_string_set(records)
def test_anaconda_nomkl(self, env):
env.repo_packages = index_packages(1)
records = env.install("anaconda 1.5.0", "python 2.7*", "numpy 1.7*")
assert len(records) == 107
assert "test::scipy-0.12.0-np17py27_0" in records
def test_pseudo_boolean(self, env):
env.repo_packages = index_packages(1)
# The latest version of iopro, 1.5.0, was not built against numpy 1.5
assert env.install("iopro", "python 2.7*", "numpy 1.5*") == {
"test::iopro-1.4.3-np15py27_p0",
"test::numpy-1.5.1-py27_4",
"test::openssl-1.0.1c-0",
"test::python-2.7.5-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::unixodbc-2.3.1-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py27_1",
"test::pip-1.3.1-py27_1",
}
assert env.install(
"iopro", "python 2.7*", "numpy 1.5*", MatchSpec(track_features="mkl")
) == {
"test::iopro-1.4.3-np15py27_p0",
"test::mkl-rt-11.0-p0",
"test::numpy-1.5.1-py27_p4",
"test::openssl-1.0.1c-0",
"test::python-2.7.5-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::unixodbc-2.3.1-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py27_1",
"test::pip-1.3.1-py27_1",
}
def test_unsat_from_r1(self, env):
env.repo_packages = index_packages(1)
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("numpy 1.5*", "scipy 0.12.0b1")
self.assert_unsatisfiable(
exc_info,
[
("numpy=1.5",),
("scipy==0.12.0b1", "numpy[version='1.6.*|1.7.*']"),
],
)
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("numpy 1.5*", "python 3*")
self.assert_unsatisfiable(
exc_info,
[
("numpy=1.5", "nose", "python=3.3"),
("numpy=1.5", "python[version='2.6.*|2.7.*']"),
("python=3",),
],
)
with pytest.raises((ResolvePackageNotFound, PackagesNotFoundError)) as exc_info:
env.install("numpy 1.5*", "numpy 1.6*")
if exc_info.type is ResolvePackageNotFound:
assert sorted(map(str, exc_info.value.bad_deps)) == [
"numpy[version='1.5.*,1.6.*']",
]
def test_unsat_simple(self, env):
env.repo_packages = [
helpers.record(name="a", depends=["c >=1,<2"]),
helpers.record(name="b", depends=["c >=2,<3"]),
helpers.record(name="c", version="1.0"),
helpers.record(name="c", version="2.0"),
]
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("a", "b")
self.assert_unsatisfiable(
exc_info,
[
("a", "c[version='>=1,<2']"),
("b", "c[version='>=2,<3']"),
],
)
def test_get_dists(self, env):
env.repo_packages = index_packages(1)
records = env.install("anaconda 1.4.0")
assert "test::anaconda-1.4.0-np17py33_0" in records
assert "test::freetype-2.4.10-0" in records
def test_unsat_shortest_chain_1(self, env):
env.repo_packages = [
helpers.record(name="a", depends=["d", "c <1.3.0"]),
helpers.record(name="b", depends=["c"]),
helpers.record(
name="c",
version="1.3.6",
),
helpers.record(
name="c",
version="1.2.8",
),
helpers.record(name="d", depends=["c >=0.8.0"]),
]
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("c=1.3.6", "a", "b")
self.assert_unsatisfiable(
exc_info,
[
("a", "c[version='<1.3.0']"),
("a", "d", "c[version='>=0.8.0']"),
("b", "c"),
("c=1.3.6",),
],
)
def test_unsat_shortest_chain_2(self, env):
env.repo_packages = [
helpers.record(name="a", depends=["d", "c >=0.8.0"]),
helpers.record(name="b", depends=["c"]),
helpers.record(
name="c",
version="1.3.6",
),
helpers.record(
name="c",
version="1.2.8",
),
helpers.record(name="d", depends=["c <1.3.0"]),
]
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("c=1.3.6", "a", "b")
self.assert_unsatisfiable(
exc_info,
[
("a", "c[version='>=0.8.0']"),
("a", "d", "c[version='<1.3.0']"),
("b", "c"),
("c=1.3.6",),
],
)
def test_unsat_shortest_chain_3(self, env):
env.repo_packages = [
helpers.record(name="a", depends=["f", "e"]),
helpers.record(name="b", depends=["c"]),
helpers.record(
name="c",
version="1.3.6",
),
helpers.record(
name="c",
version="1.2.8",
),
helpers.record(name="d", depends=["c >=0.8.0"]),
helpers.record(name="e", depends=["c <1.3.0"]),
helpers.record(name="f", depends=["d"]),
]
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("c=1.3.6", "a", "b")
self.assert_unsatisfiable(
exc_info,
[
("a", "e", "c[version='<1.3.0']"),
("b", "c"),
("c=1.3.6",),
],
)
def test_unsat_shortest_chain_4(self, env):
env.repo_packages = [
helpers.record(name="a", depends=["py =3.7.1"]),
helpers.record(name="py_req_1"),
helpers.record(name="py_req_2"),
helpers.record(
name="py", version="3.7.1", depends=["py_req_1", "py_req_2"]
),
helpers.record(
name="py", version="3.6.1", depends=["py_req_1", "py_req_2"]
),
]
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("a", "py=3.6.1")
self.assert_unsatisfiable(
exc_info,
[
("a", "py=3.7.1"),
("py=3.6.1",),
],
)
def test_unsat_chain(self, env):
# a -> b -> c=1.x -> d=1.x
# e -> c=2.x -> d=2.x
env.repo_packages = [
helpers.record(name="a", depends=["b"]),
helpers.record(name="b", depends=["c >=1,<2"]),
helpers.record(name="c", version="1.0", depends=["d >=1,<2"]),
helpers.record(name="d", version="1.0"),
helpers.record(name="e", depends=["c >=2,<3"]),
helpers.record(name="c", version="2.0", depends=["d >=2,<3"]),
helpers.record(name="d", version="2.0"),
]
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("a", "e")
self.assert_unsatisfiable(
exc_info,
[
("a", "b", "c[version='>=1,<2']"),
("e", "c[version='>=2,<3']"),
],
)
def test_unsat_any_two_not_three(self, env):
# can install any two of a, b and c but not all three
env.repo_packages = [
helpers.record(name="a", version="1.0", depends=["d >=1,<2"]),
helpers.record(name="a", version="2.0", depends=["d >=2,<3"]),
helpers.record(name="b", version="1.0", depends=["d >=1,<2"]),
helpers.record(name="b", version="2.0", depends=["d >=3,<4"]),
helpers.record(name="c", version="1.0", depends=["d >=2,<3"]),
helpers.record(name="c", version="2.0", depends=["d >=3,<4"]),
helpers.record(name="d", version="1.0"),
helpers.record(name="d", version="2.0"),
helpers.record(name="d", version="3.0"),
]
# a and b can be installed
installed = env.install("a", "b", as_specs=True)
assert any(k.name == "a" and k.version == "1.0" for k in installed)
assert any(k.name == "b" and k.version == "1.0" for k in installed)
# a and c can be installed
installed = env.install("a", "c", as_specs=True)
assert any(k.name == "a" and k.version == "2.0" for k in installed)
assert any(k.name == "c" and k.version == "1.0" for k in installed)
# b and c can be installed
installed = env.install("b", "c", as_specs=True)
assert any(k.name == "b" and k.version == "2.0" for k in installed)
assert any(k.name == "c" and k.version == "2.0" for k in installed)
# a, b and c cannot be installed
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("a", "b", "c")
self.assert_unsatisfiable(
exc_info,
[
("a", "d[version='>=1,<2|>=2,<3']"),
("b", "d[version='>=1,<2|>=3,<4']"),
("c", "d[version='>=2,<3|>=3,<4']"),
],
)
def test_unsat_expand_single(self, env):
env.repo_packages = [
helpers.record(name="a", depends=["b", "c"]),
helpers.record(name="b", depends=["d >=1,<2"]),
helpers.record(name="c", depends=["d >=2,<3"]),
helpers.record(name="d", version="1.0"),
helpers.record(name="d", version="2.0"),
]
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("a")
self.assert_unsatisfiable(
exc_info,
[
("b", "d[version='>=1,<2']"),
("c", "d[version='>=2,<3']"),
],
)
def test_unsat_missing_dep(self, env):
env.repo_packages = [
helpers.record(name="a", depends=["b", "c"]),
helpers.record(name="b", depends=["c >=2,<3"]),
helpers.record(name="c", version="1.0"),
]
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("a", "b")
self.assert_unsatisfiable(
exc_info,
[
("a", "b"),
("b",),
],
)
def test_nonexistent(self, env):
with pytest.raises((ResolvePackageNotFound, PackagesNotFoundError)):
env.install("notarealpackage 2.0*")
with pytest.raises((ResolvePackageNotFound, PackagesNotFoundError)):
env.install("numpy 1.5")
def test_timestamps_and_deps(self, env):
env.repo_packages = index_packages(1) + [
helpers.record(
name="mypackage",
version="1.0",
build="hash12_0",
timestamp=1,
depends=["libpng 1.2.*"],
),
helpers.record(
name="mypackage",
version="1.0",
build="hash15_0",
timestamp=0,
depends=["libpng 1.5.*"],
),
]
# libpng 1.2
records_12 = env.install("libpng 1.2.*", "mypackage")
assert "test::libpng-1.2.50-0" in records_12
assert "test::mypackage-1.0-hash12_0" in records_12
# libpng 1.5
records_15 = env.install("libpng 1.5.*", "mypackage")
assert "test::libpng-1.5.13-1" in records_15
assert "test::mypackage-1.0-hash15_0" in records_15
# this is testing that previously installed reqs are not disrupted
# by newer timestamps. regression test of sorts for
# https://github.com/conda/conda/issues/6271
assert (
env.install("mypackage", *env.install("libpng 1.2.*", as_specs=True))
== records_12
)
assert (
env.install("mypackage", *env.install("libpng 1.5.*", as_specs=True))
== records_15
)
# unspecified python version should maximize libpng (v1.5),
# even though it has a lower timestamp
assert env.install("mypackage") == records_15
def test_nonexistent_deps(self, env):
env.repo_packages = index_packages(1) + [
helpers.record(
name="mypackage",
version="1.0",
depends=["nose", "python 3.3*", "notarealpackage 2.0*"],
),
helpers.record(
name="mypackage",
version="1.1",
depends=["nose", "python 3.3*"],
),
helpers.record(
name="anotherpackage",
version="1.0",
depends=["nose", "mypackage 1.1"],
),
helpers.record(
name="anotherpackage",
version="2.0",
depends=["nose", "mypackage"],
),
]
# XXX: missing find_matches and reduced_index
assert env.install("mypackage") == {
"test::mypackage-1.1-0",
"test::nose-1.3.0-py33_0",
"test::openssl-1.0.1c-0",
"test::python-3.3.2-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py33_1",
"test::pip-1.3.1-py33_1",
}
assert env.install("anotherpackage 1.0") == {
"test::anotherpackage-1.0-0",
"test::mypackage-1.1-0",
"test::nose-1.3.0-py33_0",
"test::openssl-1.0.1c-0",
"test::python-3.3.2-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py33_1",
"test::pip-1.3.1-py33_1",
}
assert env.install("anotherpackage") == {
"test::anotherpackage-2.0-0",
"test::mypackage-1.1-0",
"test::nose-1.3.0-py33_0",
"test::openssl-1.0.1c-0",
"test::python-3.3.2-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py33_1",
"test::pip-1.3.1-py33_1",
}
# Add 1s to make sure the new repodata.jsons have different mod times
time.sleep(1)
# This time, the latest version is messed up
env.repo_packages = index_packages(1) + [
helpers.record(
name="mypackage",
version="1.0",
depends=["nose", "python 3.3*"],
),
helpers.record(
name="mypackage",
version="1.1",
depends=["nose", "python 3.3*", "notarealpackage 2.0*"],
),
helpers.record(
name="anotherpackage",
version="1.0",
depends=["nose", "mypackage 1.0"],
),
helpers.record(
name="anotherpackage",
version="2.0",
depends=["nose", "mypackage"],
),
]
# XXX: missing find_matches and reduced_index
assert env.install("mypackage") == {
"test::mypackage-1.0-0",
"test::nose-1.3.0-py33_0",
"test::openssl-1.0.1c-0",
"test::python-3.3.2-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py33_1",
"test::pip-1.3.1-py33_1",
}
# TODO: We need UnsatisfiableError here because mamba does not
# have more granular exceptions yet.
with pytest.raises((ResolvePackageNotFound, UnsatisfiableError)):
env.install("mypackage 1.1")
assert env.install("anotherpackage 1.0") == {
"test::anotherpackage-1.0-0",
"test::mypackage-1.0-0",
"test::nose-1.3.0-py33_0",
"test::openssl-1.0.1c-0",
"test::python-3.3.2-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py33_1",
"test::pip-1.3.1-py33_1",
}
# If recursive checking is working correctly, this will give
# anotherpackage 2.0, not anotherpackage 1.0
assert env.install("anotherpackage") == {
"test::anotherpackage-2.0-0",
"test::mypackage-1.0-0",
"test::nose-1.3.0-py33_0",
"test::openssl-1.0.1c-0",
"test::python-3.3.2-0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
"test::distribute-0.6.36-py33_1",
"test::pip-1.3.1-py33_1",
}
def test_install_package_with_feature(self, env):
env.repo_packages = index_packages(1) + [
helpers.record(
name="mypackage",
version="1.0",
depends=["python 3.3*"],
features="feature",
),
helpers.record(
name="feature",
version="1.0",
depends=["python 3.3*"],
track_features="feature",
),
]
# should not raise
env.install("mypackage", "feature 1.0")
def test_unintentional_feature_downgrade(self, env):
# See https://github.com/conda/conda/issues/6765
# With the bug in place, this bad build of scipy
# will be selected for install instead of a later
# build of scipy 0.11.0.
good_rec_match = MatchSpec("channel-1::scipy==0.11.0=np17py33_3")
good_rec = next(
prec for prec in index_packages(1) if good_rec_match.match(prec)
)
bad_deps = tuple(d for d in good_rec.depends if not d.startswith("numpy"))
bad_rec = PackageRecord.from_objects(
good_rec,
channel="test",
build=good_rec.build.replace("_3", "_x0"),
build_number=0,
depends=bad_deps,
fn=good_rec.fn.replace("_3", "_x0"),
url=good_rec.url.replace("_3", "_x0"),
)
env.repo_packages = index_packages(1) + [bad_rec]
records = env.install("scipy 0.11.0")
assert "test::scipy-0.11.0-np17py33_x0" not in records
assert "test::scipy-0.11.0-np17py33_3" in records
def test_circular_dependencies(self, env):
env.repo_packages = index_packages(1) + [
helpers.record(
name="package1",
depends=["package2"],
),
helpers.record(
name="package2",
depends=["package1"],
),
]
assert (
env.install("package1", "package2")
== env.install("package1")
== env.install("package2")
)
def test_irrational_version(self, env):
env.repo_packages = index_packages(1)
assert env.install("pytz 2012d", "python 3*") == {
"test::distribute-0.6.36-py33_1",
"test::openssl-1.0.1c-0",
"test::pip-1.3.1-py33_1",
"test::python-3.3.2-0",
"test::pytz-2012d-py33_0",
"test::readline-6.2-0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
}
def test_no_features(self, env):
env.repo_packages = index_packages(1)
assert env.install("python 2.6*", "numpy 1.6*", "scipy 0.11*") == {
"test::distribute-0.6.36-py26_1",
"test::numpy-1.6.2-py26_4",
"test::openssl-1.0.1c-0",
"test::pip-1.3.1-py26_1",
"test::python-2.6.8-6",
"test::readline-6.2-0",
"test::scipy-0.11.0-np16py26_3",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
}
assert env.install(
"python 2.6*", "numpy 1.6*", "scipy 0.11*", MatchSpec(track_features="mkl")
) == {
"test::distribute-0.6.36-py26_1",
"test::mkl-rt-11.0-p0",
"test::numpy-1.6.2-py26_p4",
"test::openssl-1.0.1c-0",
"test::pip-1.3.1-py26_1",
"test::python-2.6.8-6",
"test::readline-6.2-0",
"test::scipy-0.11.0-np16py26_p3",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
}
env.repo_packages += [
helpers.record(
name="pandas",
version="0.12.0",
build="np16py27_0",
depends=[
"dateutil",
"numpy 1.6*",
"python 2.7*",
"pytz",
],
),
helpers.record(
name="numpy",
version="1.6.2",
build="py27_p5",
build_number=0,
depends=[
"mkl-rt 11.0",
"python 2.7",
],
features="mkl",
),
]
assert env.install("pandas 0.12.0 np16py27_0", "python 2.7*") == {
"test::dateutil-2.1-py27_1",
"test::distribute-0.6.36-py27_1",
"test::numpy-1.6.2-py27_4",
"test::openssl-1.0.1c-0",
"test::pandas-0.12.0-np16py27_0",
"test::pip-1.3.1-py27_1",
"test::python-2.7.5-0",
"test::pytz-2013b-py27_0",
"test::readline-6.2-0",
"test::six-1.3.0-py27_0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
}
assert env.install(
"pandas 0.12.0 np16py27_0", "python 2.7*", MatchSpec(track_features="mkl")
) == {
"test::dateutil-2.1-py27_1",
"test::distribute-0.6.36-py27_1",
"test::mkl-rt-11.0-p0",
"test::numpy-1.6.2-py27_p4",
"test::openssl-1.0.1c-0",
"test::pandas-0.12.0-np16py27_0",
"test::pip-1.3.1-py27_1",
"test::python-2.7.5-0",
"test::pytz-2013b-py27_0",
"test::readline-6.2-0",
"test::six-1.3.0-py27_0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
}
@pytest.mark.xfail(reason="CONDA_CHANNEL_PRIORITY does not seem to have any effect")
def test_channel_priority_1(self, monkeypatch, env):
# XXX: Test is skipped because CONDA_CHANNEL_PRIORITY does not seems to
# have any effect. I have also tried conda.common.io.env_var like
# the other tests but no luck.
env.repo_packages = {}
env.repo_packages["channel-A"] = []
env.repo_packages["channel-1"] = index_packages(1)
pandas_0 = self.find_package(
channel="channel-1",
name="pandas",
version="0.10.1",
build="np17py27_0",
)
env.repo_packages["channel-A"].append(pandas_0)
# channel-1 has pandas np17py27_1, channel-A only has np17py27_0
# when priority is set, it channel-A should take precedence and
# np17py27_0 be installed, otherwise np17py27_1 should be installed as
# it has a higher build version
monkeypatch.setenv("CONDA_CHANNEL_PRIORITY", "True")
assert "channel-A::pandas-0.11.0-np16py27_0" in env.install(
"pandas", "python 2.7*", "numpy 1.6*"
)
monkeypatch.setenv("CONDA_CHANNEL_PRIORITY", "False")
assert "channel-1::pandas-0.11.0-np16py27_1" in env.install(
"pandas", "python 2.7*", "numpy 1.6*"
)
# now lets revert the channels
env.repo_packages = dict(reversed(env.repo_packages.items()))
monkeypatch.setenv("CONDA_CHANNEL_PRIORITY", "True")
assert "channel-1::pandas-0.11.0-np16py27_1" in env.install(
"pandas", "python 2.7*", "numpy 1.6*"
)
@pytest.mark.xfail(reason="CONDA_CHANNEL_PRIORITY does not seem to have any effect")
def test_unsat_channel_priority(self, monkeypatch, env):
# XXX: Test is skipped because CONDA_CHANNEL_PRIORITY does not seems to
# have any effect. I have also tried conda.common.io.env_var like
# the other tests but no luck.
env.repo_packages = {}
# higher priority
env.repo_packages["channel-1"] = [
helpers.record(
name="a",
version="1.0",
depends=["c"],
),
helpers.record(
name="b",
version="1.0",
depends=["c >=2,<3"],
),
helpers.record(
name="c",
version="1.0",
),
]
# lower priority, missing c 2.0
env.repo_packages["channel-2"] = [
helpers.record(
name="a",
version="2.0",
depends=["c"],
),
helpers.record(
name="b",
version="2.0",
depends=["c >=2,<3"],
),
helpers.record(
name="c",
version="1.0",
),
helpers.record(
name="c",
version="2.0",
),
]
monkeypatch.setenv("CONDA_CHANNEL_PRIORITY", "True")
records = env.install("a", "b", as_specs=True)
# channel-1 a and b packages (1.0) installed
assert any(k.name == "a" and k.version == "1.0" for k in records)
assert any(k.name == "b" and k.version == "1.0" for k in records)
monkeypatch.setenv("CONDA_CHANNEL_PRIORITY", "False")
records = env.install("a", "b", as_specs=True)
# no channel priority, largest version of a and b (2.0) installed
assert any(k.name == "a" and k.version == "2.0" for k in records)
assert any(k.name == "b" and k.version == "2.0" for k in records)
monkeypatch.setenv("CONDA_CHANNEL_PRIORITY", "True")
with pytest.raises(UnsatisfiableError) as exc_info:
env.install("a", "b")
self.assert_unsatisfiable(exc_info, [("b", "c[version='>=2,<3']")])
@pytest.mark.xfail(
reason="There is some weird global state making "
"this test fail when the whole test suite is run"
)
def test_remove(self, env):
env.repo_packages = index_packages(1)
records = env.install("pandas", "python 2.7*", as_specs=True)
assert package_string_set(records) == {
"test::dateutil-2.1-py27_1",
"test::distribute-0.6.36-py27_1",
"test::numpy-1.7.1-py27_0",
"test::openssl-1.0.1c-0",
"test::pandas-0.11.0-np17py27_1",
"test::pip-1.3.1-py27_1",
"test::python-2.7.5-0",
"test::pytz-2013b-py27_0",
"test::readline-6.2-0",
"test::scipy-0.12.0-np17py27_0",
"test::six-1.3.0-py27_0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
}
env.installed_packages = records
assert env.remove("pandas") == {
"test::dateutil-2.1-py27_1",
"test::distribute-0.6.36-py27_1",
"test::numpy-1.7.1-py27_0",
"test::openssl-1.0.1c-0",
"test::pip-1.3.1-py27_1",
"test::python-2.7.5-0",
"test::pytz-2013b-py27_0",
"test::readline-6.2-0",
"test::scipy-0.12.0-np17py27_0",
"test::six-1.3.0-py27_0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
}
assert env.remove("numpy") == {
"test::dateutil-2.1-py27_1",
"test::distribute-0.6.36-py27_1",
"test::openssl-1.0.1c-0",
"test::pip-1.3.1-py27_1",
"test::python-2.7.5-0",
"test::pytz-2013b-py27_0",
"test::readline-6.2-0",
"test::six-1.3.0-py27_0",
"test::sqlite-3.7.13-0",
"test::system-5.8-1",
"test::tk-8.5.13-0",
"test::zlib-1.2.7-0",
}
def test_surplus_features_1(self, env):
env.repo_packages += [
helpers.record(
name="feature",
track_features="feature",
),
helpers.record(
name="package1",
features="feature",
),
helpers.record(
name="package2",
version="1.0",
features="feature",
depends=["package1"],
),
helpers.record(
name="package2",
version="2.0",
features="feature",
),
]
assert env.install("package2", "feature") == {
"test::package2-2.0-0",
"test::feature-1.0-0",
}
def test_surplus_features_2(self, env):
env.repo_packages += [
helpers.record(
name="feature",
track_features="feature",
),
helpers.record(
name="package1",
features="feature",
),
helpers.record(
name="package2",
version="1.0",
build_number=0,
features="feature",
depends=["package1"],
),
helpers.record(
name="package2",
version="1.0",
build_number=1,
features="feature",
),
]
assert env.install("package2", "feature") == {
"test::package2-1.0-0",
"test::feature-1.0-0",
}
def test_get_reduced_index_broadening_with_unsatisfiable_early_dep(self, env):
# Test that spec broadening reduction doesn't kill valid solutions
# In other words, the order of packages in the index should not affect the
# overall result of the reduced index.
# see discussion at https://github.com/conda/conda/pull/8117#discussion_r249249815
env.repo_packages += [
helpers.record(
name="a",
version="1.0",
# not satisfiable. This record should come first, so that its c==2
# constraint tries to mess up the inclusion of the c record below,
# which should be included as part of b's deps, but which is
# broader than this dep.
depends=["b", "c==2"],
),
helpers.record(
name="a",
version="2.0",
depends=["b"],
),
helpers.record(
name="b",
depends=["c"],
),
helpers.record(
name="c",
),
]
assert env.install("a") == {
"test::a-2.0-0",
"test::b-1.0-0",
"test::c-1.0-0",
}
def test_get_reduced_index_broadening_preferred_solution(self, env):
# test that order of index reduction does not eliminate what should be a preferred solution
# https://github.com/conda/conda/pull/8117#discussion_r249216068
env.repo_packages += [
helpers.record(
name="top",
version="1.0",
# this is the first processed record, and imposes a broadening constraint on bottom
# if things are overly restricted, we'll end up with bottom 1.5 in our solution
# instead of the preferred (latest) 2.5
depends=["middle", "bottom==1.5"],
),
helpers.record(
name="top",
version="2.0",
depends=["middle"],
),
helpers.record(
name="middle",
depends=["bottom"],
),
helpers.record(
name="bottom",
version="1.5",
),
helpers.record(
name="bottom",
version="2.5",
),
]
for record in env.install("top", as_specs=True):
if record.name == "top":
assert (
record.version == "2.0"
), f"top version should be 2.0, but is {record.version}"
elif record.name == "bottom":
assert (
record.version == "2.5"
), f"bottom version should be 2.5, but is {record.version}"
def test_arch_preferred_over_noarch_when_otherwise_equal(self, env):
env.repo_packages += [
helpers.record(
name="package1",
subdir="noarch",
),
helpers.record(
name="package1",
),
]
records = env.install("package1", as_specs=True)
assert len(records) == 1
assert records[0].subdir == context.subdir
def test_noarch_preferred_over_arch_when_version_greater(self, env):
env.repo_packages += [
helpers.record(
name="package1",
version="2.0",
subdir="noarch",
),
helpers.record(
name="package1",
version="1.0",
),
]
records = env.install("package1", as_specs=True)
assert len(records) == 1
assert records[0].subdir == "noarch"
def test_noarch_preferred_over_arch_when_version_greater_dep(self, env):
env.repo_packages += [
helpers.record(
name="package1",
version="1.0",
),
helpers.record(
name="package1",
version="2.0",
subdir="noarch",
),
helpers.record(
name="package2",
depends=["package1"],
),
]
records = env.install("package2", as_specs=True)
package1 = self.find_package_in_list(records, name="package1")
assert package1.subdir == "noarch"
def test_noarch_preferred_over_arch_when_build_greater(self, env):
env.repo_packages += [
helpers.record(
name="package1",
build_number=0,
),
helpers.record(
name="package1",
build_number=1,
subdir="noarch",
),
]
records = env.install("package1", as_specs=True)
assert len(records) == 1
assert records[0].subdir == "noarch"
def test_noarch_preferred_over_arch_when_build_greater_dep(self, env):
env.repo_packages += [
helpers.record(
name="package1",
build_number=0,
),
helpers.record(
name="package1",
build_number=1,
subdir="noarch",
),
helpers.record(
name="package2",
depends=["package1"],
),
]
records = env.install("package2", as_specs=True)
package1 = self.find_package_in_list(records, name="package1")
assert package1.subdir == "noarch"