:py:mod:`fixtures` ================== .. py:module:: conda.testing.fixtures .. autoapi-nested-parse:: Collection of pytest fixtures used in conda tests. Classes ------- .. autoapisummary:: conda.testing.fixtures.CondaCLIFixture conda.testing.fixtures.PipCLIFixture conda.testing.fixtures.PathFactoryFixture conda.testing.fixtures.TmpEnvFixture conda.testing.fixtures.TmpChannelFixture conda.testing.fixtures.HttpTestServerFixture Functions --------- .. autoapisummary:: conda.testing.fixtures.suppress_resource_warning conda.testing.fixtures.tmpdir conda.testing.fixtures.clear_subdir_cache conda.testing.fixtures.reset_conda_context conda.testing.fixtures.temp_package_cache conda.testing.fixtures.parametrized_solver_fixture conda.testing.fixtures.solver_classic conda.testing.fixtures.solver_libmamba conda.testing.fixtures._solver_helper conda.testing.fixtures.conda_cli conda.testing.fixtures.session_conda_cli conda.testing.fixtures.pip_cli conda.testing.fixtures.path_factory conda.testing.fixtures.tmp_env conda.testing.fixtures.empty_env conda.testing.fixtures.session_tmp_env conda.testing.fixtures.tmp_channel conda.testing.fixtures.context_aware_monkeypatch conda.testing.fixtures.tmp_pkgs_dir conda.testing.fixtures.tmp_envs_dir conda.testing.fixtures.PYTHONPATH conda.testing.fixtures.context_testdata conda.testing.fixtures.http_test_server Attributes ---------- .. autoapisummary:: conda.testing.fixtures.TEST_CONDARC conda.testing.fixtures.Solver .. py:data:: TEST_CONDARC :value: '' .. py:function:: suppress_resource_warning() Suppress `Unclosed Socket Warning` It seems urllib3 keeps a socket open to avoid costly recreation costs. xref: https://github.com/kennethreitz/requests/issues/1882 .. py:function:: tmpdir(tmpdir, request) .. py:function:: clear_subdir_cache() .. py:function:: reset_conda_context() Resets the context object after each test function is run. .. py:function:: temp_package_cache(tmp_path_factory, monkeypatch: pytest.MonkeyPatch) -> collections.abc.Iterator[pathlib.Path] Used to isolate package or index cache from other tests. .. py:function:: parametrized_solver_fixture(request: pytest.FixtureRequest) -> collections.abc.Iterable[Literal['libmamba', 'classic']] A parameterized fixture that sets the solver backend to (1) libmamba and (2) classic for each test. It's using autouse=True, so only import it in modules that actually need it. Note that skips and xfails need to be done _inside_ the test body. Decorators can't be used because they are evaluated before the fixture has done its work! So, instead of: @pytest.mark.skipif(context.solver == "libmamba", reason="...") def test_foo(): ... Do: def test_foo(): if context.solver == "libmamba": pytest.skip("...") ... .. py:function:: solver_classic(request: pytest.FixtureRequest) -> collections.abc.Iterable[Literal['classic']] .. py:function:: solver_libmamba(request: pytest.FixtureRequest) -> collections.abc.Iterable[Literal['libmamba']] .. py:data:: Solver .. py:function:: _solver_helper(request: pytest.FixtureRequest, solver: Solver) -> collections.abc.Iterable[Solver] .. py:class:: CondaCLIFixture .. py:attribute:: capsys :type: pytest.CaptureFixture | None .. py:method:: __call__(*argv: conda.common.path.PathType, raises: type[Exception] | tuple[type[Exception], Ellipsis]) -> tuple[str, str, pytest.ExceptionInfo] __call__(*argv: conda.common.path.PathType) -> tuple[str, str, int] Test conda CLI. Mimic what is done in `conda.cli.main.main`. `conda ...` == `conda_cli(...)` :param argv: Arguments to parse. :param raises: Expected exception to intercept. If provided, the raised exception will be returned instead of exit code (see pytest.raises and pytest.ExceptionInfo). :return: Command results (stdout, stderr, exit code or pytest.ExceptionInfo). .. py:method:: _cast_args(argv: tuple[conda.common.path.PathType, Ellipsis]) -> collections.abc.Iterable[str] :staticmethod: Cast args to string. .. py:function:: conda_cli(capsys: pytest.CaptureFixture) -> collections.abc.Iterator[CondaCLIFixture] A function scoped fixture returning CondaCLIFixture instance. Use this for any commands that are local to the current test (e.g., creating a conda environment only used in the test). .. py:function:: session_conda_cli() -> collections.abc.Iterator[CondaCLIFixture] A session scoped fixture returning CondaCLIFixture instance. Use this for any commands that are global to the test session (e.g., creating a conda environment shared across tests, `conda info`, etc.). .. py:class:: PipCLIFixture Fixture for calling pip in specific conda environments. .. py:method:: __call__(*argv: conda.common.path.PathType, prefix: conda.common.path.PathType, raises: type[Exception] | tuple[type[Exception], Ellipsis]) -> tuple[str, str, pytest.ExceptionInfo] __call__(*argv: conda.common.path.PathType, prefix: conda.common.path.PathType) -> tuple[str, str, int] Test pip CLI in a specific conda environment. `pip ...` in environment == `pip_cli(..., prefix=env_path)` :param argv: Arguments to pass to pip. :param prefix: Path to the conda environment containing pip. :param raises: Expected exception to intercept. If provided, the raised exception will be returned instead of exit code (see pytest.raises and pytest.ExceptionInfo). :return: Command results (stdout, stderr, exit code or pytest.ExceptionInfo). .. py:function:: pip_cli() -> collections.abc.Iterator[PipCLIFixture] A function scoped fixture returning PipCLIFixture instance. Use this for calling pip commands in specific conda environments during tests. Uses `python -m pip` for reliable cross-platform execution. .. rubric:: Example def test_pip_install(tmp_env, pip_cli): with tmp_env("python=3.10", "pip") as prefix: stdout, stderr, code = pip_cli("install", "requests", prefix=prefix) assert code == 0 .. py:class:: PathFactoryFixture .. py:attribute:: tmp_path :type: pathlib.Path .. py:method:: __call__(name: str | None = None, *, prefix: str | None = None, infix: str | None = None, suffix: str | None = None) -> pathlib.Path Unique, non-existent path factory. Extends pytest's `tmp_path` fixture with a new unique, non-existent path for usage in cases where we need a temporary path that doesn't exist yet. Default behavior (no arguments): ``path_factory()`` → ``tmp_path/ab12cd34ef56`` (12-char UUID) Two modes of operation (mutually exclusive): 1. Name mode: Pass a complete path name. ``path_factory("myfile.txt")`` → ``tmp_path/myfile.txt`` 2. Parts mode: Pass prefix/infix/suffix; unspecified parts get UUID defaults. ``path_factory(infix="!")`` → ``tmp_path/ab12!ef56`` ``path_factory(suffix=".yml")`` → ``tmp_path/ab12cd34.yml`` :param name: Complete path name (mutually exclusive with prefix/infix/suffix) :param prefix: Prefix for generated name (mutually exclusive with name param) :param infix: Infix for generated name (mutually exclusive with name param) :param suffix: Suffix for generated name (mutually exclusive with name param) :return: A new unique path .. py:function:: path_factory(tmp_path: pathlib.Path) -> collections.abc.Iterator[PathFactoryFixture] A function scoped fixture returning PathFactoryFixture instance. Use this to generate any number of temporary paths for the test that are unique and do not exist yet. .. py:class:: TmpEnvFixture .. py:attribute:: path_factory :type: PathFactoryFixture | pytest.TempPathFactory .. py:attribute:: conda_cli :type: CondaCLIFixture .. py:method:: get_path(name: str | None = None, prefix: str | None = None, infix: str | None = None, suffix: str | None = None) -> pathlib.Path .. py:method:: __call__(*args: str, prefix: str | os.PathLike | None = None, name: str | None = None, path_prefix: str | None = None, path_infix: str | None = None, path_suffix: str | None = None, shallow: bool | None = None) -> collections.abc.Iterator[pathlib.Path] Generate a conda environment with the provided packages. Path customization (mutually exclusive options): 1. Auto-generated path (default): Unique path in tmp_path. ``tmp_env()`` → ``tmp_path/ab12cd34ef56`` (12-char UUID) 2. Custom prefix: Specify exact location. ``tmp_env(prefix="/path/to/env")`` → ``/path/to/env`` 3. Name mode: Specify env name directly. ``tmp_env(name="my-test-env")`` → ``tmp_path/my-test-env`` 4. Parts mode: Customize path name generation (useful for special char testing). ``tmp_env(path_infix="!")`` → ``tmp_path/ab12!ef56`` :param args: Arguments to pass to conda create (e.g., packages, flags) :param prefix: Exact prefix path (mutually exclusive with name/path_* params) :param name: Env name (mutually exclusive with prefix/path_* params) :param path_prefix: Prefix for path name (mutually exclusive with prefix/name params) :param path_infix: Infix for path name (mutually exclusive with prefix/name params) :param path_suffix: Suffix for path name (mutually exclusive with prefix/name params) :param shallow: If True, create env on disk only without conda create :return: The conda environment's prefix .. py:function:: tmp_env(path_factory: PathFactoryFixture, conda_cli: CondaCLIFixture) -> collections.abc.Iterator[TmpEnvFixture] A function scoped fixture returning TmpEnvFixture instance. Use this when creating a conda environment that is local to the current test. .. py:function:: empty_env(tmp_env: TmpEnvFixture) -> pathlib.Path A function scoped fixture returning an empty environment. Use this when creating a conda environment that is empty. .. py:function:: session_tmp_env(tmp_path_factory: pytest.TempPathFactory, session_conda_cli: CondaCLIFixture) -> collections.abc.Iterator[TmpEnvFixture] A session scoped fixture returning TmpEnvFixture instance. Use this when creating a conda environment that is shared across tests. .. py:class:: TmpChannelFixture .. py:attribute:: path_factory :type: PathFactoryFixture .. py:attribute:: conda_cli :type: CondaCLIFixture .. py:method:: __call__(*specs: str) -> collections.abc.Iterator[tuple[pathlib.Path, str]] .. py:function:: tmp_channel(path_factory: PathFactoryFixture, conda_cli: CondaCLIFixture) -> collections.abc.Iterator[TmpChannelFixture] A function scoped fixture returning TmpChannelFixture instance. .. py:function:: context_aware_monkeypatch(monkeypatch: pytest.MonkeyPatch) -> pytest.MonkeyPatch A monkeypatch fixture that resets context after each test. .. py:function:: tmp_pkgs_dir(path_factory: PathFactoryFixture, mocker: pytest_mock.MockerFixture) -> collections.abc.Iterator[pathlib.Path] A function scoped fixture returning a temporary package cache directory. .. py:function:: tmp_envs_dir(path_factory: PathFactoryFixture, mocker: pytest_mock.MockerFixture) -> collections.abc.Iterator[pathlib.Path] A function scoped fixture returning a temporary environment directory. .. py:function:: PYTHONPATH() We need to set this so Python loads the dev version of 'conda', usually taken from `conda/` in the root of the cloned repo. This root is usually the working directory when we run `pytest`. Otherwise, it will import the one installed in the base environment, which might have not been overwritten with `pip install -e . --no-deps`. This doesn't happen in other tests because they run with the equivalent of `python -m conda`. However, some tests directly run `conda (shell function) which calls `conda` (Python entry point). When a script is called this way, it bypasses the automatic "working directory is first on sys.path" behavior you find in `python -m` style calls. See https://docs.python.org/3/library/sys_path_init.html for details. .. py:function:: context_testdata() -> None .. py:class:: HttpTestServerFixture Fixture providing HTTP test server for serving local files. .. py:attribute:: server :type: http.server.ThreadingHTTPServer .. py:attribute:: host :type: str .. py:attribute:: port :type: int .. py:attribute:: url :type: str .. py:attribute:: directory :type: pathlib.Path .. py:method:: __post_init__() Log server startup for debugging. .. py:method:: get_url(path: str = '') -> str Get full URL for a given path on the server. :param path: Relative path on the server (e.g., "subdir/package.tar.bz2") :return: Full URL .. py:function:: http_test_server(request: pytest.FixtureRequest, path_factory: PathFactoryFixture) -> collections.abc.Iterator[HttpTestServerFixture] Function-scoped HTTP test server for serving local files. This fixture starts an HTTP server on a random port and serves files from a directory. The server supports both IPv4 and IPv6. Usage without parametrize (dynamic content): def test_dynamic(http_test_server): # Server uses temporary directory automatically (http_test_server.directory / "file.txt").write_text("content") url = http_test_server.get_url("file.txt") response = requests.get(url) assert response.status_code == 200 Usage with parametrize (pre-existing directory): @pytest.mark.parametrize("http_test_server", ["tests/data/mock-channel"], indirect=True) def test_existing(http_test_server): url = http_test_server.get_url("file.txt") response = requests.get(url) assert response.status_code == 200 Use ``None`` in parametrize to mix pre-existing directories with dynamic content: @pytest.mark.parametrize("http_test_server", ["tests/data", None], indirect=True) :param request: pytest fixture request object :param path_factory: path_factory fixture for creating unique temp directories :return: HttpTestServerFixture with server, host, port, url, and directory attributes :raises ValueError: If parametrized directory is invalid