a0c8464e58
Build Package / build (ubuntu-latest) (push) Failing after 1s
CodeQL / Analyze (python) (push) Failing after 1s
Core Typecheck / core-typecheck (push) Failing after 1s
Linting / lint (push) Failing after 1s
llama-dev tests / test-llama-dev (push) Failing after 1s
Publish Sub-Package to PyPI if Needed / publish_subpackage_if_needed (push) Has been skipped
Sync Docs to Developer Hub / sync-docs (push) Failing after 0s
Build Package / build (windows-latest) (push) Has been cancelled
582 lines
18 KiB
Python
582 lines
18 KiB
Python
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
from llama_dev import test as llama_dev_test
|
|
from llama_dev.cli import cli
|
|
from llama_dev.test import ResultStatus, _pytest, _run_tests
|
|
|
|
|
|
def mocked_coverage_failed(*args, **kwargs):
|
|
return {
|
|
"package": Path(__file__).parent.parent / "data" / "test_integration",
|
|
"status": ResultStatus.COVERAGE_FAILED,
|
|
"stdout": "",
|
|
"stderr": "Coverage below threshold",
|
|
"time": "0.1s",
|
|
}
|
|
|
|
|
|
def mocked_install_failed(*args, **kwargs):
|
|
return {
|
|
"package": Path(__file__).parent.parent / "data" / "test_integration",
|
|
"status": ResultStatus.INSTALL_FAILED,
|
|
"stdout": "",
|
|
"stderr": "Install failed",
|
|
"time": "0.1s",
|
|
}
|
|
|
|
|
|
def mocked_skip_failed_unsupported_python_version(*args, **kwargs):
|
|
return {
|
|
"package": Path(__file__).parent.parent / "data" / "test_integration",
|
|
"status": ResultStatus.UNSUPPORTED_PYTHON_VERSION,
|
|
"stdout": "",
|
|
"stderr": "Not compatible with Python",
|
|
"time": "0.1s",
|
|
}
|
|
|
|
|
|
def mocked_skip_failed_no_tests(*args, **kwargs):
|
|
return {
|
|
"package": Path(__file__).parent.parent / "data" / "test_integration",
|
|
"status": ResultStatus.NO_TESTS,
|
|
"stdout": "",
|
|
"stderr": "package has no tests",
|
|
"time": "0.1s",
|
|
}
|
|
|
|
|
|
def mocked_success(*args, **kwargs):
|
|
return {
|
|
"package": Path(__file__).parent.parent / "data" / "test_integration",
|
|
"status": ResultStatus.TESTS_PASSED,
|
|
"stdout": "",
|
|
"stderr": "",
|
|
"time": "0.1s",
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def changed_packages():
|
|
return {
|
|
Path("/fake/package/dependency1"),
|
|
Path("/fake/package/dependency2"),
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def package_data():
|
|
return {
|
|
"project": {
|
|
"dependencies": ["dependency1", "requests"],
|
|
"requires-python": ">=3.8",
|
|
}
|
|
}
|
|
|
|
|
|
def test_test_command_base_ref():
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(cli, ["test", "--base-ref"])
|
|
assert result.exit_code != 0
|
|
assert "Error: Option '--base-ref' requires an argument." in result.output
|
|
|
|
result = runner.invoke(cli, ["test", "--base-ref="])
|
|
assert result.exit_code != 0
|
|
assert "Error: Option '--base-ref' cannot be empty." in result.output
|
|
|
|
|
|
def test_test_command_requires_base_ref_or_packages():
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(cli, ["test"])
|
|
assert result.exit_code != 1
|
|
assert (
|
|
"Error: Either pass '--base-ref' or provide at least one package name."
|
|
in result.output
|
|
)
|
|
|
|
|
|
def test_test_command_cov_fail_under_requires_cov():
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli, ["test", "--base-ref", "main", "--cov-fail-under", "80"]
|
|
)
|
|
assert result.exit_code != 0
|
|
assert "You have to pass --cov in order to use --cov-fail-under" in result.output
|
|
|
|
|
|
@mock.patch("llama_dev.test.find_all_packages")
|
|
@mock.patch("llama_dev.test.get_changed_files")
|
|
@mock.patch("llama_dev.test.get_changed_packages")
|
|
@mock.patch("llama_dev.test.get_dependants_packages")
|
|
@mock.patch("llama_dev.test.concurrent.futures.ProcessPoolExecutor")
|
|
def test_workers_parameter(
|
|
mock_pool,
|
|
mock_get_dependants,
|
|
mock_get_changed_packages,
|
|
mock_get_changed_files,
|
|
mock_find_all_packages,
|
|
):
|
|
# Setup minimal test data
|
|
mock_find_all_packages.return_value = set()
|
|
mock_get_changed_files.return_value = set()
|
|
mock_get_changed_packages.return_value = set()
|
|
mock_get_dependants.return_value = set()
|
|
|
|
runner = CliRunner()
|
|
runner.invoke(cli, ["test", "--base-ref", "main", "--workers", "16"])
|
|
mock_pool.assert_called_once_with(max_workers=16)
|
|
|
|
|
|
@mock.patch("llama_dev.test.find_all_packages")
|
|
@mock.patch("llama_dev.test.get_changed_files")
|
|
@mock.patch("llama_dev.test.get_changed_packages")
|
|
@mock.patch("llama_dev.test.get_dependants_packages")
|
|
def test_coverage_failures(
|
|
mock_get_dependants,
|
|
mock_get_changed_packages,
|
|
mock_get_changed_files,
|
|
mock_find_all_packages,
|
|
monkeypatch,
|
|
data_path,
|
|
):
|
|
mock_find_all_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_changed_files.return_value = {Path("/fake/repo/package1/file.py")}
|
|
mock_get_changed_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_dependants.return_value = set()
|
|
|
|
monkeypatch.setattr(llama_dev_test, "_run_tests", mocked_coverage_failed)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli,
|
|
[
|
|
"--repo-root",
|
|
data_path,
|
|
"test",
|
|
"--base-ref",
|
|
"main",
|
|
"--cov",
|
|
"--cov-fail-under",
|
|
"40",
|
|
],
|
|
)
|
|
|
|
# Check console output
|
|
assert result.exit_code == 1
|
|
assert "❌ test_integration failed" in result.stdout
|
|
assert "Error:\nCoverage below threshold" in result.stdout
|
|
|
|
|
|
@mock.patch("llama_dev.test.find_all_packages")
|
|
@mock.patch("llama_dev.test.get_changed_files")
|
|
@mock.patch("llama_dev.test.get_changed_packages")
|
|
@mock.patch("llama_dev.test.get_dependants_packages")
|
|
def test_install_failures(
|
|
mock_get_dependants,
|
|
mock_get_changed_packages,
|
|
mock_get_changed_files,
|
|
mock_find_all_packages,
|
|
monkeypatch,
|
|
data_path,
|
|
):
|
|
mock_find_all_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_changed_files.return_value = {Path("/fake/repo/package1/file.py")}
|
|
mock_get_changed_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_dependants.return_value = set()
|
|
|
|
monkeypatch.setattr(llama_dev_test, "_run_tests", mocked_install_failed)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli,
|
|
["--repo-root", data_path, "test", "--base-ref", "main"],
|
|
)
|
|
|
|
# Check console output
|
|
assert result.exit_code == 0
|
|
assert "❗ Unable to build package test_integration" in result.stdout
|
|
assert "Error:\nInstall failed" in result.stdout
|
|
|
|
|
|
@mock.patch("llama_dev.test.find_all_packages")
|
|
@mock.patch("llama_dev.test.get_changed_files")
|
|
@mock.patch("llama_dev.test.get_changed_packages")
|
|
@mock.patch("llama_dev.test.get_dependants_packages")
|
|
def test_skip_failures_no_tests(
|
|
mock_get_dependants,
|
|
mock_get_changed_packages,
|
|
mock_get_changed_files,
|
|
mock_find_all_packages,
|
|
monkeypatch,
|
|
data_path,
|
|
):
|
|
mock_find_all_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_changed_files.return_value = {Path("/fake/repo/package1/file.py")}
|
|
mock_get_changed_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_dependants.return_value = set()
|
|
|
|
monkeypatch.setattr(llama_dev_test, "_run_tests", mocked_skip_failed_no_tests)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli,
|
|
["--repo-root", data_path, "test", "--base-ref", "main"],
|
|
)
|
|
|
|
# Check console output
|
|
assert result.exit_code == 0
|
|
assert "1 packages were skipped" in result.stdout
|
|
|
|
|
|
@mock.patch("llama_dev.test.find_all_packages")
|
|
@mock.patch("llama_dev.test.get_changed_files")
|
|
@mock.patch("llama_dev.test.get_changed_packages")
|
|
@mock.patch("llama_dev.test.get_dependants_packages")
|
|
def test_skip_failures_unsupported_python(
|
|
mock_get_dependants,
|
|
mock_get_changed_packages,
|
|
mock_get_changed_files,
|
|
mock_find_all_packages,
|
|
monkeypatch,
|
|
data_path,
|
|
):
|
|
mock_find_all_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_changed_files.return_value = {Path("/fake/repo/package1/file.py")}
|
|
mock_get_changed_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_dependants.return_value = set()
|
|
|
|
monkeypatch.setattr(
|
|
llama_dev_test, "_run_tests", mocked_skip_failed_unsupported_python_version
|
|
)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli,
|
|
["--repo-root", data_path, "test", "--base-ref", "main"],
|
|
)
|
|
|
|
# Check console output
|
|
assert result.exit_code == 0
|
|
assert (
|
|
"1 packages were skipped due to Python version incompatibility" in result.stdout
|
|
)
|
|
|
|
|
|
@mock.patch("llama_dev.test.find_all_packages")
|
|
@mock.patch("llama_dev.test.get_changed_files")
|
|
@mock.patch("llama_dev.test.get_changed_packages")
|
|
@mock.patch("llama_dev.test.get_dependants_packages")
|
|
def test_success(
|
|
mock_get_dependants,
|
|
mock_get_changed_packages,
|
|
mock_get_changed_files,
|
|
mock_find_all_packages,
|
|
monkeypatch,
|
|
data_path,
|
|
):
|
|
mock_find_all_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_changed_files.return_value = {Path("/fake/repo/package1/file.py")}
|
|
mock_get_changed_packages.return_value = {Path("/fake/repo/package1")}
|
|
mock_get_dependants.return_value = set()
|
|
|
|
monkeypatch.setattr(llama_dev_test, "_run_tests", mocked_success)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli,
|
|
["--repo-root", data_path, "test", "--base-ref", "main"],
|
|
)
|
|
|
|
# Check console output
|
|
assert result.exit_code == 0
|
|
assert "Tests passed for 1 packages." in result.stdout
|
|
|
|
|
|
@mock.patch("llama_dev.test.find_all_packages")
|
|
@mock.patch("llama_dev.test.get_changed_files")
|
|
@mock.patch("llama_dev.test.get_changed_packages")
|
|
@mock.patch("llama_dev.test.get_dependants_packages")
|
|
def test_package_parameter(
|
|
mock_get_dependants,
|
|
mock_get_changed_packages,
|
|
mock_get_changed_files,
|
|
mock_find_all_packages,
|
|
data_path,
|
|
):
|
|
# Setup minimal test data
|
|
mock_find_all_packages.return_value = set()
|
|
mock_get_changed_files.return_value = set()
|
|
mock_get_changed_packages.return_value = set()
|
|
mock_get_dependants.return_value = set()
|
|
|
|
runner = CliRunner()
|
|
runner.invoke(
|
|
cli,
|
|
[
|
|
"--repo-root",
|
|
data_path,
|
|
"test",
|
|
"--base-ref",
|
|
"main",
|
|
"package_1",
|
|
"package_2",
|
|
],
|
|
)
|
|
mock_get_dependants.assert_called_with(
|
|
{data_path / "package_1", data_path / "package_2"}, set()
|
|
)
|
|
|
|
|
|
#
|
|
# Tests for the utility methods, we call them directly not through cli execution
|
|
#
|
|
|
|
|
|
@mock.patch("llama_dev.pkg.cmd_exec.subprocess.run")
|
|
def test__pytest(mock_subprocess):
|
|
mock_subprocess.return_value = mock.Mock(
|
|
returncode=0, stdout="Command output", stderr=""
|
|
)
|
|
|
|
_pytest(Path(), {}, cov=False)
|
|
assert mock_subprocess.call_args[0][0] == [
|
|
"uv",
|
|
"run",
|
|
"--no-sync",
|
|
"--",
|
|
"pytest",
|
|
"-q",
|
|
"--disable-warnings",
|
|
"--disable-pytest-warnings",
|
|
]
|
|
|
|
mock_subprocess.reset_mock()
|
|
_pytest(Path(), {}, cov=True)
|
|
assert mock_subprocess.call_args[0][0] == [
|
|
"uv",
|
|
"run",
|
|
"--no-sync",
|
|
"--",
|
|
"pytest",
|
|
"-q",
|
|
"--disable-warnings",
|
|
"--disable-pytest-warnings",
|
|
"--cov=.",
|
|
"--cov-report=xml",
|
|
]
|
|
|
|
|
|
def test_incompatible_python_version(changed_packages):
|
|
with (
|
|
mock.patch(
|
|
"llama_dev.test.load_pyproject",
|
|
return_value={"project": {"requires-python": ">=3.10"}},
|
|
),
|
|
mock.patch("llama_dev.test.is_python_version_compatible", return_value=False),
|
|
mock.patch("llama_dev.test.package_has_tests", return_value=True),
|
|
):
|
|
result = _run_tests(Path(), changed_packages, "main", False, 0)
|
|
|
|
assert result["status"] == ResultStatus.UNSUPPORTED_PYTHON_VERSION
|
|
assert "Not compatible with Python" in result["stderr"]
|
|
assert "package has no tests" not in result["stderr"]
|
|
|
|
|
|
def test_no_package_tests(changed_packages):
|
|
with (
|
|
mock.patch(
|
|
"llama_dev.test.load_pyproject",
|
|
return_value={"project": {"requires-python": ">=3.8"}},
|
|
),
|
|
mock.patch("llama_dev.test.is_python_version_compatible", return_value=True),
|
|
mock.patch("llama_dev.test.package_has_tests", return_value=False),
|
|
):
|
|
result = _run_tests(Path(), changed_packages, "main", False, 0)
|
|
|
|
assert result["status"] == ResultStatus.NO_TESTS
|
|
assert "package has no tests" in result["stderr"]
|
|
assert "Not compatible with Python" not in result["stderr"]
|
|
|
|
|
|
def test_install_dependencies_failure(changed_packages, package_data):
|
|
with (
|
|
mock.patch("llama_dev.test.load_pyproject", return_value=package_data),
|
|
mock.patch("llama_dev.test.is_python_version_compatible", return_value=True),
|
|
mock.patch(
|
|
"llama_dev.test._uv_sync",
|
|
return_value=mock.Mock(
|
|
returncode=1, stdout="stdout output", stderr="install error"
|
|
),
|
|
),
|
|
):
|
|
result = _run_tests(Path(), changed_packages, "main", False, 0)
|
|
|
|
assert result["status"] == ResultStatus.INSTALL_FAILED
|
|
assert result["stderr"] == "install error"
|
|
|
|
|
|
def test_install_local_packages_failure(changed_packages, package_data):
|
|
with (
|
|
mock.patch("llama_dev.test.load_pyproject", return_value=package_data),
|
|
mock.patch("llama_dev.test.is_python_version_compatible", return_value=True),
|
|
mock.patch(
|
|
"llama_dev.test._uv_sync",
|
|
return_value=mock.Mock(returncode=0),
|
|
),
|
|
mock.patch(
|
|
"llama_dev.test.get_dep_names", return_value=["dependency1", "dependency2"]
|
|
),
|
|
mock.patch(
|
|
"llama_dev.test._uv_install_local",
|
|
return_value=mock.Mock(
|
|
returncode=1, stdout="stdout", stderr="local install error"
|
|
),
|
|
),
|
|
):
|
|
result = _run_tests(Path(), changed_packages, "main", False, 0)
|
|
|
|
assert result["status"] == ResultStatus.INSTALL_FAILED
|
|
assert result["stderr"] == "local install error"
|
|
|
|
|
|
def test_pytest_failure(changed_packages, package_data):
|
|
with (
|
|
mock.patch("llama_dev.test.load_pyproject", return_value=package_data),
|
|
mock.patch("llama_dev.test.is_python_version_compatible", return_value=True),
|
|
mock.patch(
|
|
"llama_dev.test._uv_sync",
|
|
return_value=mock.Mock(returncode=0),
|
|
),
|
|
mock.patch("llama_dev.test.get_dep_names", return_value=["dependency1"]),
|
|
mock.patch(
|
|
"llama_dev.test._uv_install_local",
|
|
return_value=mock.Mock(returncode=0),
|
|
),
|
|
mock.patch(
|
|
"llama_dev.test._pytest",
|
|
return_value=mock.Mock(
|
|
returncode=1, stdout="test output", stderr="test failures"
|
|
),
|
|
),
|
|
):
|
|
result = _run_tests(Path(), changed_packages, "main", False, 0)
|
|
|
|
assert result["status"] == ResultStatus.TESTS_FAILED
|
|
assert result["stdout"] == "test output"
|
|
assert result["stderr"] == "test failures"
|
|
|
|
|
|
def test_coverage_failure(changed_packages, package_data):
|
|
with (
|
|
mock.patch("llama_dev.test.load_pyproject", return_value=package_data),
|
|
mock.patch("llama_dev.test.is_python_version_compatible", return_value=True),
|
|
mock.patch(
|
|
"llama_dev.test._uv_sync",
|
|
return_value=mock.Mock(returncode=0),
|
|
),
|
|
mock.patch("llama_dev.test.get_dep_names", return_value=[]),
|
|
mock.patch(
|
|
"llama_dev.test._pytest",
|
|
return_value=mock.Mock(returncode=0, stdout="tests passed"),
|
|
),
|
|
mock.patch(
|
|
"llama_dev.test._diff_cover",
|
|
return_value=mock.Mock(
|
|
returncode=1,
|
|
stdout="coverage output",
|
|
stderr="coverage below threshold",
|
|
),
|
|
),
|
|
):
|
|
result = _run_tests(Path(), changed_packages, "main", True, 80)
|
|
|
|
assert result["status"] == ResultStatus.COVERAGE_FAILED
|
|
assert result["stderr"] == "coverage below threshold"
|
|
|
|
|
|
def test_successful_run(changed_packages, package_data):
|
|
with (
|
|
mock.patch("llama_dev.test.load_pyproject", return_value=package_data),
|
|
mock.patch("llama_dev.test.is_python_version_compatible", return_value=True),
|
|
mock.patch(
|
|
"llama_dev.test._uv_sync",
|
|
return_value=mock.Mock(returncode=0),
|
|
),
|
|
mock.patch("llama_dev.test.get_dep_names", return_value=[]),
|
|
mock.patch(
|
|
"llama_dev.test._pytest",
|
|
return_value=mock.Mock(returncode=0, stdout="all tests passed", stderr=""),
|
|
),
|
|
):
|
|
result = _run_tests(Path(), changed_packages, "main", False, 0)
|
|
|
|
assert result["status"] == ResultStatus.TESTS_PASSED
|
|
assert result["stdout"] == "all tests passed"
|
|
assert "time" in result
|
|
|
|
|
|
def test_successful_run_with_coverage(package_data, changed_packages):
|
|
"""Test a successful run with coverage checking."""
|
|
with (
|
|
mock.patch("llama_dev.test.load_pyproject", return_value=package_data),
|
|
mock.patch("llama_dev.test.is_python_version_compatible", return_value=True),
|
|
mock.patch(
|
|
"llama_dev.test._uv_sync",
|
|
return_value=mock.Mock(returncode=0),
|
|
),
|
|
mock.patch("llama_dev.test.get_dep_names", return_value=[]),
|
|
mock.patch(
|
|
"llama_dev.test._pytest",
|
|
return_value=mock.Mock(returncode=0, stdout="all tests passed", stderr=""),
|
|
),
|
|
mock.patch(
|
|
"llama_dev.test._diff_cover",
|
|
return_value=mock.Mock(returncode=0, stdout="coverage ok", stderr=""),
|
|
),
|
|
):
|
|
result = _run_tests(Path(), changed_packages, "main", True, 80)
|
|
|
|
assert result["status"] == ResultStatus.TESTS_PASSED
|
|
assert result["stdout"] == "coverage ok"
|
|
assert "time" in result
|
|
|
|
|
|
def test__trim():
|
|
from llama_dev.test import MAX_CONSOLE_PRINT_LINES, _trim
|
|
|
|
# Test with a short message (less than MAX_CONSOLE_PRINT_LINES)
|
|
short_msg = "Line 1\nLine 2\nLine 3"
|
|
assert _trim(False, short_msg) == short_msg
|
|
assert _trim(True, short_msg) == short_msg
|
|
|
|
# Test with a long message (more than MAX_CONSOLE_PRINT_LINES)
|
|
long_msg = "\n".join([f"Line {i}" for i in range(1, MAX_CONSOLE_PRINT_LINES + 10)])
|
|
|
|
# In non-debug mode, the message should be truncated
|
|
trimmed = _trim(False, long_msg)
|
|
trimmed_lines = trimmed.split("\n")
|
|
|
|
# Should have MAX_CONSOLE_PRINT_LINES lines plus the additional "truncated" message line
|
|
assert len(trimmed_lines) == MAX_CONSOLE_PRINT_LINES + 1
|
|
|
|
# The first MAX_CONSOLE_PRINT_LINES lines should be from the original message
|
|
for i in range(MAX_CONSOLE_PRINT_LINES):
|
|
assert trimmed_lines[i] == f"Line {i + 1}"
|
|
|
|
# The last line should be the truncation message
|
|
assert (
|
|
"<-- llama-dev: output truncated, pass '--debug' to see the full log -->"
|
|
in trimmed_lines[-1]
|
|
)
|
|
|
|
# In debug mode, the message should not be truncated
|
|
debug_trimmed = _trim(True, long_msg)
|
|
assert debug_trimmed == long_msg
|
|
assert (
|
|
len(debug_trimmed.split("\n")) == MAX_CONSOLE_PRINT_LINES + 9
|
|
) # Original number of lines
|