# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from enum import Enum
from typing import Literal, TypeAlias
from conda.core.solve import UpdateModifier, DepsModifier
from conda.models.enums import PackageType
from conda.models.match_spec import MatchSpec
from msgspec import Struct, field
[docs]
class TestChannel(Enum):
"""Enumeration of test channel identifiers available in the test fixture data.
These names correspond to the channel directories bundled with the test suite.
"""
CHANNEL_1 = "channel-1"
CHANNEL_2 = "channel-2"
CHANNEL_4 = "channel-4"
CHANNEL_6 = "channel-6"
CHANNEL_7 = "channel-7"
CHANNEL_8 = "channel-8"
CHANNEL_9 = "channel-9"
CHANNEL_10 = "channel-10"
CHANNEL_11 = "channel-11"
CHANNEL_12 = "channel-12"
CHANNEL_13 = "channel-13"
CHANNEL_14 = "channel-14"
CHANNEL_FREEZE = "channel-freeze"
CHANNEL_EMPTY = "channel-empty"
CONDA_FORMAT_REPO = "conda_format_repo"
TEST = "test"
def __str__(self):
return self.value
[docs]
class TestSubdir(Enum):
"""Enumeration of platform subdirectories available in the test fixture data."""
NOARCH = "noarch"
LINUX_64 = "linux-64"
CONDA_TEST = "conda-test"
def __str__(self):
return self.value
[docs]
class ChannelPriority(Enum):
"""Enumeration of channel priority modes, mirroring conda's ``ChannelPriority`` setting.
Controls whether packages from higher-priority channels are preferred over
those from lower-priority channels when both satisfy a requirement.
"""
STRICT = "strict"
FLEXIBLE = "flexible"
DISABLED = "disabled"
def __str__(self):
return self.value
[docs]
class PrefixRecord(
Struct,
tag_field="record_type",
tag="prefix",
frozen=True,
forbid_unknown_fields=True,
kw_only=True,
):
"""Represents a package record in the solution inspected by
``determine_constricting_specs()``. The ``record_type`` discriminator field
is always ``"prefix"`` and is set automatically.
"""
package_type: PackageType | None = None
"""The conda package type. Optional; defaults to ``None``.
Valid values mirror ``conda.models.enums.PackageType``, e.g.
``"noarch_generic"``, ``"noarch_python"``, ``"virtual_system"``, etc."""
name: str
"""The package name (e.g. ``"numpy"``)."""
version: str
"""The package version string (e.g. ``"1.24.3"``)."""
channel: str
"""The channel the package was installed from (e.g. ``"conda-forge"``)."""
subdir: str
"""The platform subdirectory the package belongs to (e.g. ``"linux-64"``)."""
fn: str
"""The filename of the package archive
(e.g. ``"numpy-1.24.3-py311h0000000_0.conda"``)."""
build: str = "0"
"""The build string (e.g. ``"py311h0000000_0"``). Defaults to ``"0"``."""
build_number: int = 0
"""The build number. Defaults to ``0``."""
paths_data: list[str] | None = None
"""List of relative paths recorded in the package's path data. Optional."""
files: list[str] | None = None
"""List of files installed by the package. Optional."""
depends: list[str] = []
"""List of run-dependency match specs (e.g. ``["python >=3.11", "numpy"]``).
Defaults to an empty list."""
constrains: list[str] = []
"""List of match specs describing optional runtime constraints (`run_constrained`)."""
[docs]
class TestOutput(
Struct,
frozen=True,
forbid_unknown_fields=True,
):
"""Expected output for a ``kind: solve`` test case.
Describes the expected final state returned by a successful solve.
"""
final_state: str | list[str] | None = None
"""The expected package distribution string(s) returned by the solve.
``None`` only asserts that the solve succeeds without checking its result."""
[docs]
class DiffTestOutput(
Struct,
frozen=True,
forbid_unknown_fields=True,
):
"""Expected output for a ``kind: solve_for_diff`` test case.
Describes the expected package-level diff (unlinks and links) produced by
the solver rather than the full final environment state.
"""
unlink_precs: str | list[str] | None = None
"""The package distribution string(s) expected to be unlinked from the
prefix. ``None`` means no unlinks are expected."""
link_precs: str | list[str] | None = None
"""The package distribution string(s) expected to be linked into the
prefix. ``None`` means no links are expected."""
[docs]
class UnsatisfiableTestError(
Struct,
tag_field="exception",
tag="UnsatisfiableError",
frozen=True,
forbid_unknown_fields=True,
):
"""Expected error for a test where the solver should raise ``UnsatisfiableError``.
The ``exception`` discriminator field is always ``"UnsatisfiableError"``
and is set automatically.
"""
entries: str | list[str | list[str]]
"""The conflicting dependency chain(s) that make the environment
unsatisfiable. Each entry may be a single string or a list of strings
representing one conflict path. May also be given as a single string
instead of a list when there is only one entry."""
# message fragments may be either a single string, a list that applies to
# every solver, or a mapping keyed by solver name. A mapping needs a key for
# each solver the entry runs on. Use an empty value for a solver with
# nothing to check. A missing key is an error.
message_excludes: str | list[str] | dict[str, str | list[str]] = []
"""Substring(s) that must NOT appear in the raised exception's message.
May be a single string, a list, or a mapping keyed by solver name.
Defaults to an empty list (no exclusion checks)."""
message_includes: str | list[str] | dict[str, str | list[str]] = []
"""Substring(s) that must appear in the raised exception's message.
May be a single string, a list, or a mapping keyed by solver name.
Defaults to an empty list (no inclusion checks)."""
[docs]
class PackagesNotFoundTestError(
Struct,
tag_field="exception",
tag="PackagesNotFoundError",
frozen=True,
forbid_unknown_fields=True,
):
"""Expected error for a test where the solver should raise ``PackagesNotFoundError``.
The ``exception`` discriminator field is always ``"PackagesNotFoundError"``
and is set automatically.
"""
entries: str | list[str | list[str]]
"""The package spec(s) that could not be found in any configured channel.
Each entry is a string or list of strings. May also be given as a
single string instead of a list when there is only one entry."""
[docs]
class ResolvePackageNotFoundTestError(
Struct,
tag_field="exception",
tag="ResolvePackageNotFound",
frozen=True,
forbid_unknown_fields=True,
):
"""Expected error for a test where the solver should raise ``ResolvePackageNotFound``.
The ``exception`` discriminator field is always ``"ResolvePackageNotFound"``
and is set automatically.
"""
entries: str | list[str | list[str]]
"""The package spec(s) that could not be resolved. Each entry is a string
(or list of strings) describing the missing package. May also be given
as a single string instead of a list when there is only one entry."""
[docs]
class SpecsConfigurationConflictTestError(
Struct,
tag_field="exception",
tag="SpecsConfigurationConflictError",
frozen=True,
forbid_unknown_fields=True,
):
"""Expected error for a test where the solver should raise ``SpecsConfigurationConflictError``.
This error occurs when explicitly requested specs conflict with pinned
package constraints. The ``exception`` discriminator field is always
``"SpecsConfigurationConflictError"`` and is set automatically.
"""
requested_specs: str | list[str]
"""The explicitly requested spec(s) that conflict with pinned constraints.
May be a single string or a list of strings."""
pinned_specs: str | list[str]
"""The pinned spec(s) that conflict with the requested specs. May be a
single string or a list of strings."""
TestError: TypeAlias = (
UnsatisfiableTestError
| ResolvePackageNotFoundTestError
| PackagesNotFoundTestError
| SpecsConfigurationConflictTestError
)
[docs]
class Provenance(
Struct,
frozen=True,
forbid_unknown_fields=True,
):
"""Records the origin of a test case — where it came from in the conda source tree.
This information is used to trace each test back to its upstream source
commit and to generate links to the original test in the conda repository.
"""
node_id: str
"""The pytest node ID of the original test in the upstream conda test suite
(e.g. ``"tests/test_solve.py::TestSolveUserStories::test_install_numpy"``)."""
commit: str
"""The full Git commit SHA of the upstream conda commit the test was
ported from (e.g. ``"03329e0f4a627c9b9aa92ef34f7f93b9aa83e438"``)."""
url: str
"""The URL to the specific source file in the upstream conda repository at
the recorded commit, linking directly to the test's location on GitHub."""
[docs]
class SolveTestSpec(
Struct,
tag_field="kind",
tag="solve",
frozen=True,
forbid_unknown_fields=True,
):
"""Test spec for a standard solve operation (``kind: solve``).
Asserts that the solver produces a specific final environment state
given the inputs. The ``kind`` discriminator field is always ``"solve"``
and is set automatically.
"""
name: str
"""A human-readable name for the test (e.g. ``"test_install_numpy"``)."""
id: str
"""A stable unique identifier for this test case. Used to look up the
test by ID independently of its name or position in the file."""
provenance: Provenance
"""Provenance information linking this test back to its upstream source."""
input: TestInput
"""The solver inputs for this test case."""
output: TestOutput
"""The expected output (final environment state) after the solve."""
description: str | None = None
"""An optional human-readable description of what this test exercises.
Defaults to ``None``."""
test_function: str = "test_solve"
"""The name of the base-test method to invoke for this spec.
Defaults to ``"test_solve"``."""
solvers: str | list[str] | None = None
"""Restrict this test to a specific solver backend or list of backends
(e.g. ``"classic"`` or ``["classic", "libmamba"]``). ``None`` means
the test runs against all registered solver backends."""
xfail_solvers: str | list[str] | None = None
"""Solver backend(s) for which this test is expected to fail (xfail) rather
than error. May be a single string or a list. ``None`` means no solver is
expected to fail."""
xfail_reason: str | None = None
"""A human-readable explanation of why the solver(s) in ``xfail_solvers``
are expected to fail. ``None`` if not applicable."""
[docs]
class SolveForDiffTestSpec(
Struct,
tag_field="kind",
tag="solve_for_diff",
frozen=True,
forbid_unknown_fields=True,
):
"""Test spec for a solve-for-diff operation (``kind: solve_for_diff``).
Asserts that the solver produces specific unlink/link package sets rather
than a full final environment state. The ``kind`` discriminator field is
always ``"solve_for_diff"`` and is set automatically.
"""
name: str
"""A human-readable name for the test."""
id: str
"""A stable unique identifier for this test case."""
provenance: Provenance
"""Provenance information linking this test back to its upstream source."""
input: TestInput
"""The solver inputs for this test case."""
output: DiffTestOutput
"""The expected diff output (packages to unlink and link)."""
description: str | None = None
"""An optional human-readable description of what this test exercises.
Defaults to ``None``."""
test_function: str = "test_solve_for_diff"
"""The name of the base-test method to invoke for this spec.
Defaults to ``"test_solve_for_diff"``."""
solvers: str | list[str] | None = None
"""Restrict this test to a specific solver backend or list of backends.
``None`` means the test runs against all registered solver backends."""
xfail_solvers: str | list[str] | None = None
"""Solver backend(s) for which this test is expected to fail (xfail) rather
than error. May be a single string or a list. ``None`` means no solver is
expected to fail."""
xfail_reason: str | None = None
"""A human-readable explanation of why the solver(s) in ``xfail_solvers``
are expected to fail. ``None`` if not applicable."""
[docs]
class Constriction(
Struct,
frozen=True,
forbid_unknown_fields=True,
):
"""A single constricting package relationship identified by the solver.
Represents one entry in the output of a ``determine_constricting_specs``
solve, describing which installed package is blocking a requested upgrade
or installation.
"""
package: str
"""The name of the installed package that is imposing the constriction
(e.g. ``"scipy"``)."""
constricting_match_spec: str
"""The match spec from ``package``'s dependencies that is blocking the
requested operation (e.g. ``"numpy >=1.22,<1.24"``)."""
[docs]
class DeterminingConstrictingSpecsTestOutput(
Struct,
frozen=True,
forbid_unknown_fields=True,
):
"""Expected output for a ``kind: determine_constricting_specs`` test case.
Describes the set of constricting package relationships the solver is
expected to identify.
"""
constrictions: list[Constriction] | None = None
"""The list of constrictions the solver is expected to report. Each entry
is a :class:`Constriction` describing one blocking dependency. ``None``
means the solver is expected to report no constrictions."""
def constrictions_as_list(self):
return (
None
if self.constrictions is None
else [
(c.package, MatchSpec(c.constricting_match_spec))
for c in self.constrictions
]
)
[docs]
class DetermineConstrictingSpecsTestSpec(
Struct,
tag_field="kind",
tag="determine_constricting_specs",
frozen=True,
forbid_unknown_fields=True,
):
"""Test spec for a constricting-specs determination (``kind: determine_constricting_specs``).
Asserts that the solver correctly identifies which installed packages are
blocking a requested operation. The ``kind`` discriminator field is always
``"determine_constricting_specs"`` and is set automatically.
"""
name: str
"""A human-readable name for the test."""
id: str
"""A stable unique identifier for this test case."""
provenance: Provenance
"""Provenance information linking this test back to its upstream source."""
input: TestInput
"""The solver inputs for this test case."""
output: DeterminingConstrictingSpecsTestOutput
"""The expected output listing the constricting package relationships."""
description: str | None = None
"""An optional human-readable description of what this test exercises.
Defaults to ``None``."""
test_function: str = "test_determine_constricting_specs"
"""The name of the base-test method to invoke for this spec.
Defaults to ``"test_determine_constricting_specs"``."""
solvers: str | list[str] | None = None
"""Restrict this test to a specific solver backend or list of backends.
``None`` means the test runs against all registered solver backends."""
xfail_solvers: str | list[str] | None = None
"""Solver backend(s) for which this test is expected to fail (xfail) rather
than error. May be a single string or a list. ``None`` means no solver is
expected to fail."""
xfail_reason: str | None = None
"""A human-readable explanation of why the solver(s) in ``xfail_solvers``
are expected to fail. ``None`` if not applicable."""
[docs]
class UnsatisfiableTestSpec(
Struct,
tag_field="kind",
tag="unsatisfiable",
frozen=True,
forbid_unknown_fields=True,
):
"""Test spec for a solve that is expected to fail (``kind: unsatisfiable``).
Asserts that the solver raises a specific exception given the inputs.
The ``kind`` discriminator field is always ``"unsatisfiable"`` and is
set automatically.
"""
name: str
"""A human-readable name for the test."""
id: str
"""A stable unique identifier for this test case."""
provenance: Provenance
"""Provenance information linking this test back to its upstream source."""
input: TestInput
"""The solver inputs for this test case."""
error: TestError
"""The expected error the solver should raise. Must be one of
:class:`UnsatisfiableTestError`,
:class:`ResolvePackageNotFoundTestError`,
:class:`PackagesNotFoundTestError`, or
:class:`SpecsConfigurationConflictTestError`, discriminated by the
``exception`` field."""
description: str | None = None
"""An optional human-readable description of what this test exercises.
Defaults to ``None``."""
operation: Literal["solve_final_state", "solve_for_diff"] = "solve_final_state"
"""Which solve operation is expected to raise the error: computing the
final environment state or computing the unlink/link diff. Defaults to
``"solve_final_state"``."""
test_function: str = "test_unsatisfiable"
"""The name of the base-test method to invoke for this spec.
Defaults to ``"test_unsatisfiable"``."""
solvers: str | list[str] | None = None
"""Restrict this test to a specific solver backend or list of backends.
``None`` means the test runs against all registered solver backends."""
xfail_solvers: str | list[str] | None = None
"""Solver backend(s) for which this test is expected to fail (xfail) rather
than error. May be a single string or a list. ``None`` means no solver is
expected to fail."""
xfail_reason: str | None = None
"""A human-readable explanation of why the solver(s) in ``xfail_solvers``
are expected to fail. ``None`` if not applicable."""
TestSpec: TypeAlias = (
SolveTestSpec
| SolveForDiffTestSpec
| DetermineConstrictingSpecsTestSpec
| UnsatisfiableTestSpec
)
[docs]
class TestModule(Struct):
"""The top-level container for a conda solver test file.
A test file (typically a ``.yaml`` file) deserializes into a
``TestModule``, which holds an ordered list of test specs. Each spec
is one of the four supported test kinds, discriminated by the ``kind``
field.
"""
tests: list[TestSpec]
"""The list of test specs contained in this module. Each element is one
of :class:`SolveTestSpec`, :class:`SolveForDiffTestSpec`,
:class:`DetermineConstrictingSpecsTestSpec`, or
:class:`UnsatisfiableTestSpec`, selected by the ``kind`` field."""