Testing Guide#
This guide covers everything you need to know about testing conda-pypi, including how to run tests, write new tests, and use the test infrastructure.
Running Tests#
conda-pypi uses pytest for testing. All test commands should be run through pixi to ensure the correct environment is used.
Basic Test Execution#
Run the full test suite with Python 3.14:
pixi run -e test-py314 test
Testing with Different Python Versions#
The project supports Python 3.10 through 3.14. Test your changes across all supported versions:
# Python 3.10
pixi run -e test-py310 test
# Python 3.11
pixi run -e test-py311 test
# Python 3.12
pixi run -e test-py312 test
# Python 3.13
pixi run -e test-py313 test
# Python 3.14
pixi run -e test-py314 test
Running Specific Tests#
You can run specific test files or test functions:
# Run a specific test file
pixi run -e test-py314 test tests/test_build.py
# Run a specific test function
pixi run -e test-py314 test tests/test_build.py::test_indexable
# Run tests matching a pattern
pixi run -e test-py314 test -k "test_conda"
Test Markers#
Tests are organized using pytest markers:
# Run only benchmark tests
pixi run benchmark
# Skip benchmark tests (default behavior)
pixi run -e test-py314 test -m "not benchmark"
Verbose Output#
For more detailed test output:
# Show print statements
pixi run -e test-py314 test -s
# Verbose pytest output
pixi run -e test-py314 test -v
# Even more verbose
pixi run -e test-py314 test -vv
Known upstream failures#
Conda 26.7.2 drops wheel records when a local monolithic channel is used alongside a sharded channel (conda#16676). The two affected installation tests expect only the specific failures caused by this bug. Unexpected passes and unrelated failures still fail the suite.
The tests check whether the installed conda preserves wheel records, so builds containing the upstream fix run the normal assertions. A backport to 26.7.x is proposed. These markers do not repair wheel-channel installation in conda 26.7.2.
Running Benchmarks#
Performance benchmarks use pytest-benchmark and are tracked in Bencher. Run them locally with the Python 3.12 environment used in CI:
pixi run --locked -e test-py312 benchmark
To save the results in the JSON format uploaded by CI:
pixi run --locked -e test-py312 benchmark --benchmark-json benchmark_results.json
Benchmarks are marked with @pytest.mark.benchmark and are excluded from the regular test suite by default.
The Test workflow measures benchmarks on Ubuntu 24.04. Each case uses fixed local wheel fixtures, one warmup round, and five measured rounds with fresh prefixes and output directories. Environment creation happens outside the measured work. Quiet mode disables progress-animation threads. When changing the workload or fixture data, use a new benchmark name so historical timings remain comparable.
For pull requests, the workflow runs the base and head production code sequentially on the same runner with the same PR-head tests, fixtures, and resolved dependencies. This roughly doubles benchmark execution time. The reporter requires both runs to contain the same benchmark names and compares them using a fresh baseline branch for that workflow run, without changing the base branch’s history. The initial wheel-build alert tolerance is a 25% latency increase, following Bencher’s relative benchmarking example. Tune this tolerance as measurements accumulate. An incompatible baseline or differing benchmark names produces a neutral comparison check, with the head measurements retained as an artifact.
Conversion benchmarks remain informational because repeated measurements of unchanged code showed substantial variation. Their tests append _bencher_ignore to the benchmark name for Bencher’s native alert suppression. Bencher removes the suffix when storing the results. The Bencher Report (conda-pypi wheel builds) check covers wheel-build alerts. Enable conversion alerts once control runs demonstrate stable measurements.
The separate Track Benchmarks workflow uses the shared conda/actions/bencher action to upload results. Testbeds combine the producer’s Ubuntu version, architecture, Python major/minor version, and CPU model. These names separate environments but do not guarantee identical hardware or load. The benchmark-results-v3 artifact records the results, producing event, runner image version, benchmark harness revision, dependency versions, and a best-effort Bencher noise diagnostic. Noise measurements are diagnostic only and do not adjust timings or decide whether a run passes.
Base branch uploads build a history for each testbed. Historical regression detection uses a t-test with a 0.99 probability setting and at least 10 historical measurements, up to 64. The 0.99 setting is not a 1% slowdown tolerance. A green historical check means no alert was raised, which can also happen before enough history exists.
Maintainers enable uploads by creating the conda-pypi project in Bencher and setting the BENCHER_API_KEY repository secret to its project API key. Local runs do not require Bencher credentials. Shared runner load, cache state, and dependency changes can still affect timings. Repeat unexpected results before treating them as regressions.
Writing Tests#
Test Organization#
Tests are organized in the tests/ directory:
tests/
├── conftest.py # Shared fixtures and configuration
├── cli/ # CLI-specific tests
├── pypi_local_index/ # Local package index data
├── conda_local_channel/ # Local conda channel data
└── test_*.py # Test modules
Test Structure#
Follow these conventions when writing tests:
File naming: Test files should be named
test_*.pyFunction naming: Test functions should be named
test_*Use fixtures: Leverage pytest fixtures for setup and teardown
Docstrings: Add docstrings to explain what the test validates
Example test structure:
import pytest
from conda_pypi.build import build_conda
def test_build_conda_package(tmp_path):
"""Test that a wheel can be converted to a conda package."""
# Arrange
wheel_path = tmp_path / "package-1.0.0-py3-none-any.whl"
# Act
result = build_conda(wheel_path, output_dir=tmp_path)
# Assert
assert result.exists()
assert result.suffix == ".conda"
Using Fixtures#
Common fixtures are defined in tests/conftest.py:
Local package index#
The pypi_local_index fixture provides a local package index for testing without network access:
def test_with_local_pypi(pypi_local_index):
"""Test using the local package index."""
# pypi_local_index is a URL like "http://localhost:8035"
# Use this in place of real PyPI for offline testing
pass
Conda Local Channel#
The conda_local_channel fixture provides a local conda channel server. See the Mock Channel Server Guide for details:
def test_with_local_channel(conda_local_channel):
"""Test using the local conda channel."""
# conda_local_channel is a URL like "http://localhost:8037"
pass
Conda Testing Fixtures#
The project uses conda.testing plugin which provides additional fixtures:
def test_with_conda_env(tmp_env):
"""Test using a temporary conda environment."""
# tmp_env provides a clean conda environment for testing
pass
Testing Best Practices#
Isolation: Each test should be independent and not rely on other tests
Cleanup: Use fixtures and
tmp_pathto ensure proper cleanupAssertions: Use descriptive assertion messages
Coverage: Aim for comprehensive coverage of both success and failure cases
Performance: Keep tests fast; use mocks for expensive operations
Offline First: Use local fixtures (
pypi_local_index,conda_local_channel) instead of real network calls
Example: Testing Package Conversion#
from pathlib import Path
from conda_pypi.translate import PackageRecord
from conda_pypi.build import build_conda
def test_wheel_to_conda_conversion(tmp_path, pypi_demo_package_wheel_path):
"""Test converting a wheel to conda package format.
This test verifies that:
1. The wheel is successfully converted to .conda format
2. The package metadata is correctly translated
3. The package can be indexed
"""
# Convert wheel to conda
output_dir = tmp_path / "output"
output_dir.mkdir()
conda_pkg = build_conda(pypi_demo_package_wheel_path, output_dir=output_dir)
# Verify the output
assert conda_pkg.exists()
assert conda_pkg.name.endswith(".conda")
# Verify it can be indexed
from conda_pypi.index import update_index
update_index(output_dir.parent)
Testing CLI Commands#
For testing CLI commands, use the conda_cli test fixture:
from conda.cli.main import main_subshell
def test_conda_pypi_install(conda_cli, tmp_env):
"""Test the conda pypi install command."""
result = conda_cli("pypi", "install", "requests", "--prefix", str(tmp_env))
assert result == 0
Test Infrastructure#
Local Test Servers#
The test suite uses local HTTP servers to avoid network dependencies:
Package index: Serves packages from
tests/pypi_local_index/Conda Channel Server: Serves packages from
tests/conda_local_channel/
For more information on the conda channel server, see the Mock Channel Server Guide.
Test Data#
tests/pypi_local_index/: Wheel files and package metadata for offline PyPI testingtests/conda_local_channel/: Pre-converted conda packages for dependency resolution testing
Continuous Integration#
Tests run automatically on GitHub Actions for:
All supported Python versions (3.10-3.13)
Multiple operating systems (Linux, macOS, Windows)
Pull requests and main branch commits
Ensure all tests pass locally before submitting a pull request.
Troubleshooting#
Tests Are Slow#
If tests are running slowly:
Run a subset of tests instead of the full suite
Check if benchmark tests are included (they should be excluded by default)
Consider using
-n autofor parallel test execution (if pytest-xdist is available)
Fixture Not Found#
If you see “fixture not found” errors:
Check that
tests/conftest.pyis presentVerify the fixture name is correct
Ensure the pytest plugin is loaded (
pytest_pluginsin conftest.py)