blob: 13968a7ce6591d29631e67f76cf25f59055e4c0e [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
from unittest.mock import patch
import pytest
from click import UsageError
from click.testing import CliRunner
from airflow_breeze.commands.developer_commands import build_docs, down, run
from airflow_breeze.global_constants import DEFAULT_PYTHON_MAJOR_MINOR_VERSION
@pytest.fixture
def runner():
return CliRunner()
def test_down_rejects_conflicting_project_selectors(runner):
with pytest.raises(UsageError, match="--all-worktrees and --project-name cannot be used together"):
runner.invoke(
down,
["--all-worktrees", "--project-name", "foobar"],
standalone_mode=False,
catch_exceptions=False,
)
@pytest.mark.parametrize("linked", [False, True])
def test_down_preserves_volumes_without_startup_cleanup(runner, tmp_path, linked):
with (
patch(
"airflow_breeze.commands.developer_commands.perform_environment_checks", autospec=True
) as checks,
patch(
"airflow_breeze.commands.developer_commands.bring_compose_projects_down",
autospec=True,
return_value=[],
) as teardown,
patch(
"airflow_breeze.commands.developer_commands.get_main_git_dir_for_worktree",
autospec=True,
return_value=tmp_path if linked else None,
),
patch("airflow_breeze.commands.developer_commands.AIRFLOW_ROOT_PATH", tmp_path),
):
result = runner.invoke(down, ["--preserve-volumes"])
assert result.exit_code == 0
checks.assert_called_once_with(cleanup_stale_worktrees=False)
assert teardown.call_args.kwargs["preserve_volumes"] is True
assert teardown.call_args.kwargs["current_worktree"] == (str(tmp_path.resolve()) if linked else "")
class TestBuildDocsPythonVersion:
"""`breeze build-docs` always documents on the default Python.
The Sphinx config mocks third-party modules and what that mocking does depends on the
interpreter, so the docs build must not follow whatever Python the caller happens to have
selected - see the comment in ``_build_python_docs``.
"""
@pytest.fixture(autouse=True)
def _no_docker(self, monkeypatch):
monkeypatch.setenv("SKIP_SAVING_CHOICES", "true")
for name in (
"perform_environment_checks",
"fix_ownership_using_docker",
"cleanup_python_generated_files",
):
monkeypatch.setattr(f"airflow_breeze.commands.developer_commands.{name}", lambda *a, **kw: None)
def _invoke(self, runner: CliRunner, args: list[str], env: dict[str, str] | None = None):
with (
patch("airflow_breeze.commands.developer_commands.build_ci_image_if_needed") as mock_build,
patch("airflow_breeze.commands.developer_commands.execute_command_in_shell") as mock_shell,
):
mock_shell.return_value.returncode = 0
runner.invoke(build_docs, args, env=env, catch_exceptions=False)
return mock_build, mock_shell
def test_environment_python_does_not_change_the_docs_build(self, runner):
# PYTHON_MAJOR_MINOR_VERSION is set on every job of the docs publishing workflow, so an
# option reading it silently decided what the docs were built with.
mock_build, mock_shell = self._invoke(
runner, ["--docs-only"], env={"PYTHON_MAJOR_MINOR_VERSION": "3.12"}
)
assert mock_build.call_args.kwargs["command_params"].python == DEFAULT_PYTHON_MAJOR_MINOR_VERSION
assert mock_shell.call_args.args[0].python == DEFAULT_PYTHON_MAJOR_MINOR_VERSION
def test_python_option_is_rejected(self, runner):
result = runner.invoke(build_docs, ["--python", "3.12", "--docs-only"])
assert result.exit_code != 0
assert "no such option" in result.output.lower()
class TestRunIncludeMypyVolume:
@pytest.fixture(autouse=True)
def _no_docker(self, monkeypatch):
monkeypatch.setenv("SKIP_SAVING_CHOICES", "true")
monkeypatch.delenv("INCLUDE_MYPY_VOLUME", raising=False)
monkeypatch.setattr(
"airflow_breeze.commands.developer_commands.bring_compose_project_down", lambda *a, **kw: None
)
for name in ("fix_ownership_using_docker", "remove_docker_networks"):
monkeypatch.setattr(f"airflow_breeze.utils.docker_command_utils.{name}", lambda *a, **kw: None)
def _invoke(self, runner: CliRunner, args: list[str], env: dict[str, str] | None = None):
with (
patch("airflow_breeze.commands.ci_image_commands.build_ci_image_if_needed"),
patch("airflow_breeze.utils.docker_command_utils.execute_command_in_shell") as mock_shell,
):
mock_shell.return_value.returncode = 0
runner.invoke(run, [*args, "true"], env=env, catch_exceptions=False)
return mock_shell.call_args.kwargs["shell_params"]
@pytest.mark.parametrize(
("args", "env", "expected"),
[
pytest.param([], None, False, id="default"),
pytest.param(["--include-mypy-volume"], None, True, id="flag"),
pytest.param([], {"INCLUDE_MYPY_VOLUME": "true"}, True, id="env"),
],
)
def test_include_mypy_volume_is_passed_to_shell_params(self, runner, args, env, expected):
assert self._invoke(runner, args, env=env).include_mypy_volume is expected