Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 24 additions & 19 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ jobs:
conda-version: 25.11.0
test-type: parallel
# maximum Python/conda combo
- python-version: '3.13'
- python-version: '3.14'
conda-version: canary
test-type: serial
- python-version: '3.13'
- python-version: '3.14'
conda-version: canary
test-type: parallel
env:
Expand Down Expand Up @@ -217,7 +217,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.13']
python-version: ['3.14']

steps:
- name: Checkout Source
Expand Down Expand Up @@ -281,15 +281,19 @@ jobs:
strategy:
fail-fast: false
matrix:
# test lower version (w/ stable conda) and upper version (w/ canary conda)
python-version: ['3.10']
conda-version: [release]
test-type: [serial, parallel]
include:
- python-version: '3.13'
# test lower version (w/ stable conda)
- python-version: '3.10'
conda-version: release
test-type: serial
- python-version: '3.10'
conda-version: release
test-type: parallel
# upper version (w/ canary conda)
- python-version: '3.14'
conda-version: canary
test-type: serial
- python-version: '3.13'
- python-version: '3.14'
conda-version: canary
test-type: parallel
env:
Expand Down Expand Up @@ -379,26 +383,27 @@ jobs:
needs: changes
if: github.event_name == 'schedule' || needs.changes.outputs.code == 'true'

# we still need intel macs so we are stuck on intel runners
# the issue is that there are recipes that depend on packages
# that do not exist for osx-arm64 - see #5388
runs-on: macos-15-intel
runs-on: macos-latest
defaults:
run:
# https://github.com/conda-incubator/setup-miniconda#use-a-default-shell
shell: bash -el {0}
strategy:
fail-fast: false
matrix:
# test lower version (w/ stable conda) and upper version (w/ canary conda)
python-version: ['3.10']
conda-version: [release]
test-type: [serial, parallel]
include:
- python-version: '3.13'
# test lower version (w/ stable conda)
- python-version: '3.10'
conda-version: release
test-type: serial
- python-version: '3.10'
conda-version: release
test-type: parallel
# upper version (w/ canary conda)
- python-version: '3.14'
conda-version: canary
test-type: serial
- python-version: '3.13'
- python-version: '3.14'
conda-version: canary
test-type: parallel
env:
Expand Down
27 changes: 26 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ repos:
recipe/meta\.yaml |
conda_build/templates/.*\.yaml |
docs/click/meta\.yaml |
docs/source/user-guide/tutorials/meta\.yaml
docs/source/user-guide/tutorials/meta\.yaml |
tests/local-channel/recipes/.+/meta\.yaml
)
# catch git merge/rebase problems
- id: check-merge-conflict
Expand All @@ -44,6 +45,9 @@ repos:
tests/requirements.*\.txt
)
args: [--unique]
- id: pretty-format-json
args: [--autofix]
files: tests/local-channel/.+/repodata\.json
# Python verification and formatting
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.6
Expand All @@ -65,6 +69,27 @@ repos:
args: [--fix]
# compatible replacement for black
- id: ruff-format
- repo: local
hooks:
- id: generate-test-recipes-readme
name: Generate test recipes README
entry: python tests/local-channel/recipes/generate_recipes_readme.py
language: python
language_version: python3.14
additional_dependencies: [pyyaml]
pass_filenames: false
files: |
(?x)^(
tests/local-channel/recipes/.+/meta.yaml |
tests/local-channel/recipes/generate_recipes_readme.py |
tests/local-channel/recipes/README.md
)$
# sphinx-autoapi doesn't support #: comments, use trailing docstrings instead
- id: no-sphinx-autodoc-comments
name: 'Disallow #: comments, require trailing docstrings'
entry: '^\s*#:'
language: pygrep
types: [python]
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.16.0
hooks:
Expand Down
1 change: 1 addition & 0 deletions recipe/conda_build_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ python:
- '3.11'
- '3.12'
- '3.13'
- '3.14'
11 changes: 11 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,14 @@
# SPDX-License-Identifier: BSD-3-Clause
# This is just here so that tests is a package, so that dotted relative
# imports work.

from pathlib import Path

TESTS_PATH = Path(__file__).parent
"Test suite root directory."

LOCAL_CHANNEL_PATH = TESTS_PATH / "local-channel"
"Local channel."

METADATA_V2_PATH = TESTS_PATH / "test-recipes" / "metadata-v2"
"Test recipes using the local channel."
85 changes: 41 additions & 44 deletions tests/cli/test_main_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from conda_build.os_utils.external import find_executable
from conda_build.utils import get_build_folders, on_win, package_has_file

from .. import LOCAL_CHANNEL_PATH, METADATA_V2_PATH
from ..utils import metadata_dir
from ..utils import reset_config as _reset_config

Expand All @@ -42,31 +43,42 @@ def test_build_empty_sections(conda_build_test_recipe_envvar: str):


@pytest.mark.serial
def test_build_add_channel():
"""This recipe requires the conda_build_test_requirement package, which is
only on the conda_build_test channel. This verifies that the -c argument
works."""
def test_build_add_channel(
mock_channels: list[str], # mock context.channels so its empty
):
"""This recipe requires the local-channel::pkg-small-executable package.
This verifies that the --channel argument works."""
main_build.execute(
[
f"--channel={LOCAL_CHANNEL_PATH}",
str(METADATA_V2_PATH / "recipe_requiring_external_channel"),
]
)

args = [
"-c",
"conda_build_test",
"--no-activate",
"--no-anaconda-upload",
os.path.join(metadata_dir, "_recipe_requiring_external_channel"),
]
main_build.execute(args)

def test_build_with_empty_channel_fails(
mock_channels: list[str], # mock context.channels so its empty
) -> None:
"""This recipe requires the local-channel::pkg-small-executable package.
Since the channel is empty, we get a PackagesNotFoundError."""
with pytest.raises(PackagesNotFoundError):
main_build.execute(
[
str(METADATA_V2_PATH / "recipe_requiring_external_channel"),
]
)

def test_build_without_channel_fails(testing_workdir):
# remove the conda forge channel from the arguments and make sure that we fail. If we don't,
# we probably have channels in condarc, and this is not a good test.
args = [
"--no-anaconda-upload",
"--no-activate",
os.path.join(metadata_dir, "_recipe_requiring_external_channel"),
]

def test_build_without_channel_fails():
"""This recipe requires the local-channel::pkg-small-executable package.
Since the channel is missing the package, we get a DependencyNeedsBuildingError."""
# DependencyNeedsBuildingError → I found some packages just not the ones I need
with pytest.raises(DependencyNeedsBuildingError):
main_build.execute(args)
main_build.execute(
[
str(METADATA_V2_PATH / "recipe_requiring_external_channel"),
]
)


def test_no_filename_hash(testing_workdir, testing_metadata, capfd):
Expand Down Expand Up @@ -322,11 +334,11 @@ def test_no_force_upload(

@pytest.mark.slow
def test_conda_py_no_period(testing_workdir, testing_metadata, monkeypatch):
monkeypatch.setenv("CONDA_PY", "36")
monkeypatch.setenv("CONDA_PY", "313")
testing_metadata.meta["requirements"] = {"host": ["python"], "run": ["python"]}
api.output_yaml(testing_metadata, "meta.yaml")
outputs = api.build(testing_workdir, notest=True)
assert any("py36" in output for output in outputs)
assert any("py313" in output for output in outputs)


def test_build_skip_existing(
Expand Down Expand Up @@ -459,21 +471,17 @@ def test_relative_path_test_artifact(
main_build.execute(args)


def test_test_extra_dep(testing_metadata):
testing_metadata.meta["test"]["imports"] = ["imagesize"]
def test_test_extra_dep(testing_metadata, local_channel: Path):
testing_metadata.meta["test"]["commands"] = ["small"]
api.output_yaml(testing_metadata, "meta.yaml")
output = api.build(testing_metadata, notest=True, anaconda_upload=False)[0]

# tests version constraints. CLI would quote this - "click <6.7"
args = [output, "-t", "--extra-deps", "imagesize <1.0"]
# extra_deps will add it in
main_build.execute(args)
# tests version constraints
main_build.execute([output, "-t", "--extra-deps", "pkg-small-executable <2.0.0"])

# missing click dep will fail tests
# missing small-executable dep will fail tests
with pytest.raises(CondaBuildUserError):
args = [output, "-t"]
# extra_deps will add it in
main_build.execute(args)
main_build.execute([output, "-t"])


@pytest.mark.parametrize(
Expand Down Expand Up @@ -539,17 +547,6 @@ def test_user_warning(tmpdir, recwarn):
assert not recwarn.list


def test_build_with_empty_channel_fails(empty_channel: Path) -> None:
with pytest.raises(PackagesNotFoundError):
main_build.execute(
[
"--override-channels",
f"--channel={empty_channel}",
os.path.join(metadata_dir, "_recipe_requiring_external_channel"),
]
)


def test_build_with_v1_recipe() -> None:
"""Test building a v1 recipe"""
recipe = os.path.join(metadata_dir, "..", "variants", "32_v1_recipe")
Expand Down
55 changes: 24 additions & 31 deletions tests/cli/test_main_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from conda_build import api
from conda_build.cli import main_render

from .. import LOCAL_CHANNEL_PATH, METADATA_V2_PATH
from ..utils import metadata_dir

if TYPE_CHECKING:
Expand All @@ -22,43 +23,35 @@
from conda_build.metadata import MetaData


def test_render_add_channel(tmp_path: Path) -> None:
"""This recipe requires the conda_build_test_requirement package, which is
only on the conda_build_test channel. This verifies that the -c argument
works for rendering."""
rendered_filename = os.path.join(tmp_path, "out.yaml")
args = [
"-c",
"conda_build_test",
os.path.join(metadata_dir, "_recipe_requiring_external_channel"),
"--file",
rendered_filename,
]
main_render.execute(args)
with open(rendered_filename) as rendered_file:
rendered_meta = yaml.safe_load(rendered_file)
required_package_string = [
pkg
for pkg in rendered_meta["requirements"]["build"]
if "conda_build_test_requirement" in pkg
][0]
required_package_details = required_package_string.split(" ")
assert len(required_package_details) > 1, (
"Expected version number on successful "
f"rendering, but got only {required_package_details}"
)
assert required_package_details[1] == "1.0", (
f"Expected version number 1.0 on successful rendering, but got {required_package_details[1]}"
def test_render_add_channel(
tmp_path: Path,
mock_channels: list[str], # mock context.channels so its empty
) -> None:
"""This recipe requires the local-channel::pkg-small-executable package.
This verifies that the --channel argument works."""
rendered_filename = tmp_path / "out.yaml"
main_render.execute(
[
f"--channel={LOCAL_CHANNEL_PATH}",
str(METADATA_V2_PATH / "recipe_requiring_external_channel"),
f"--file={rendered_filename}",
]
)

rendered_meta = yaml.safe_load(rendered_filename.read_text())
reqs = rendered_meta["requirements"]["build"]
assert len(reqs) == 1
assert reqs[0] == "pkg-small-executable 1.0.0 0"


def test_render_with_empty_channel_fails(tmp_path: Path, empty_channel: Path) -> None:
def test_render_with_empty_channel_fails(
tmp_path: Path,
mock_channels: list[str], # mock context.channels so its empty
) -> None:
with pytest.raises(PackagesNotFoundError):
main_render.execute(
[
"--override-channels",
f"--channel={empty_channel}",
os.path.join(metadata_dir, "_recipe_requiring_external_channel"),
str(METADATA_V2_PATH / "recipe_requiring_external_channel"),
f"--file={tmp_path / 'out.yaml'}",
]
)
Expand Down
20 changes: 20 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@
from conda_build.utils import check_call_env, copy_into, prepend_bin_path
from conda_build.variants import get_default_variant

from . import LOCAL_CHANNEL_PATH

if TYPE_CHECKING:
from collections.abc import Iterator

from pytest import FixtureRequest, MonkeyPatch
from pytest_mock import MockerFixture


@pytest.hookimpl
Expand Down Expand Up @@ -282,3 +285,20 @@ def empty_channel(tmp_path_factory: pytest.TempPathFactory) -> Path:
channel = tmp_path_factory.mktemp("empty_channel", numbered=False)
update_index(channel)
return channel


@pytest.fixture
def mock_channels(mocker: MockerFixture) -> list[str]:
channels: list[str] = []
mocker.patch(
"conda.base.context.Context.channels",
new_callable=mocker.PropertyMock,
return_value=channels,
)
return channels


@pytest.fixture
def local_channel(mock_channels: list[str]) -> Path:
mock_channels.append(str(LOCAL_CHANNEL_PATH))
return LOCAL_CHANNEL_PATH
Loading
Loading