"""Tests for check_build_dependencies module."""
import importlib.util
import sys
from pathlib import Path
from unittest import mock
import pytest
_MODULE_PATH = Path(__file__).resolve().parents[2] / "version" / "check_build_dependencies.py"
spec = importlib.util.spec_from_file_location("check_build_dependencies", _MODULE_PATH)
_check_build_dependencies = importlib.util.module_from_spec(spec)
sys.modules["check_build_dependencies"] = _check_build_dependencies
spec.loader.exec_module(_check_build_dependencies)
Receiver = _check_build_dependencies.Receiver
parse_version_line = _check_build_dependencies.parse_version_line
read_pkg_version = _check_build_dependencies.read_pkg_version
check_build_dep_item = _check_build_dependencies.check_build_dep_item
check_build_dep = _check_build_dependencies.check_build_dep
check_build_deps = _check_build_dependencies.check_build_deps
main = _check_build_dependencies.main
class TestReceiver:
"""Test Receiver NamedTuple."""
@staticmethod
def test_receiver_creation():
"""Test Receiver can be created with warn_msgs and err_msgs."""
recv = Receiver(warn_msgs=[], err_msgs=[])
assert recv.warn_msgs == []
assert recv.err_msgs == []
@staticmethod
def test_receiver_with_messages():
"""Test Receiver with initial messages."""
recv = Receiver(
warn_msgs=['warning1', 'warning2'],
err_msgs=['error1']
)
assert len(recv.warn_msgs) == 2
assert len(recv.err_msgs) == 1
class TestParseVersionLine:
"""Test parse_version_line function."""
@staticmethod
def test_parse_version_line_basic():
"""Test parsing basic version line."""
assert parse_version_line('Version=1.0.0') == '1.0.0'
@staticmethod
def test_parse_version_line_with_alpha():
"""Test parsing version line with alpha suffix."""
assert parse_version_line('Version=1.0.0-alpha') == '1.0.0'
@staticmethod
def test_parse_version_line_with_beta():
"""Test parsing version line with beta suffix."""
assert parse_version_line('Version=2.5.0-beta.3') == '2.5.0'
@staticmethod
def test_parse_version_line_with_rc():
"""Test parsing version line with rc suffix."""
assert parse_version_line('Version=3.0.0-rc1') == '3.0.0'
@staticmethod
def test_parse_version_line_with_whitespace():
"""Test parsing version line with leading/trailing whitespace."""
assert parse_version_line(' Version=1.2.3 ') == '1.2.3'
@staticmethod
def test_parse_version_line_complex_version():
"""Test parsing complex version line."""
assert parse_version_line('Version=10.20.30-alpha+build.123') == '10.20.30'
class TestReadPkgVersion:
"""Test read_pkg_version function."""
@staticmethod
def test_read_pkg_version_success(tmp_path):
"""Test reading version from existing file."""
info_dir = tmp_path / 'share' / 'info' / 'test_pkg'
info_dir.mkdir(parents=True)
version_file = info_dir / 'version.info'
version_file.write_text('Version=2.5.0-alpha\nOtherField=value\n')
recv = Receiver([], [])
result = read_pkg_version(recv, str(tmp_path), 'test_pkg')
assert result == '2.5.0'
assert recv.err_msgs == []
@staticmethod
def test_read_pkg_version_file_not_exists(tmp_path):
"""Test reading version when file does not exist."""
recv = Receiver([], [])
result = read_pkg_version(recv, str(tmp_path), 'nonexistent_pkg')
assert result is None
assert len(recv.err_msgs) == 1
assert 'does not exist' in recv.err_msgs[0]
@staticmethod
def test_read_pkg_version_no_version_field(tmp_path):
"""Test reading version when Version field is missing."""
info_dir = tmp_path / 'share' / 'info' / 'test_pkg'
info_dir.mkdir(parents=True)
version_file = info_dir / 'version.info'
version_file.write_text('OtherField=value\nAnotherField=value2\n')
recv = Receiver([], [])
result = read_pkg_version(recv, str(tmp_path), 'test_pkg')
assert result is None
assert len(recv.err_msgs) == 1
assert 'version field was not found' in recv.err_msgs[0]
@staticmethod
def test_read_pkg_version_empty_file(tmp_path):
"""Test reading version from empty file."""
info_dir = tmp_path / 'share' / 'info' / 'test_pkg'
info_dir.mkdir(parents=True)
version_file = info_dir / 'version.info'
version_file.write_text('')
recv = Receiver([], [])
result = read_pkg_version(recv, str(tmp_path), 'test_pkg')
assert result is None
assert len(recv.err_msgs) == 1
@staticmethod
def test_read_pkg_version_graph_autofusion_success(tmp_path):
"""Test reading version from existing file."""
info_dir = tmp_path / 'share' / 'info' / 'graph_autofusion'
info_dir.mkdir(parents=True)
version_file = info_dir / 'version.info'
version_file.write_text('Version=2.5.0-alpha\nOtherField=value\n')
recv = Receiver([], [])
result = read_pkg_version(recv, str(tmp_path), 'graph-autofusion')
assert result == '2.5.0'
assert recv.err_msgs == []
class TestCheckBuildDepItemExtended:
"""Extended tests for check_build_dep_item."""
@staticmethod
def test_check_build_dep_item_equal_length_no_padding():
"""Test comparison when versions have equal length (no padding needed)."""
assert check_build_dep_item('1.0.0', '>=1.0.0') is True
assert check_build_dep_item('1.0.0', '<=1.0.0') is True
assert check_build_dep_item('1.0.0', '>1.0.0') is False
assert check_build_dep_item('1.0.0', '<1.0.0') is False
class TestCheckBuildDepItem:
"""Test check_build_dep_item function with various operators."""
@staticmethod
def test_check_build_dep_item_ge_equal():
"""Test >= operator with equal versions."""
assert check_build_dep_item('1.0.0', '>=1.0.0') is True
@staticmethod
def test_check_build_dep_item_ge_greater():
"""Test >= operator with greater version."""
assert check_build_dep_item('2.0.0', '>=1.0.0') is True
@staticmethod
def test_check_build_dep_item_ge_less():
"""Test >= operator with less version."""
assert check_build_dep_item('0.9.0', '>=1.0.0') is False
@staticmethod
def test_check_build_dep_item_ge_different_length():
"""Test >= operator with different length versions."""
assert check_build_dep_item('1.0.0.1', '>=1.0.0') is True
assert check_build_dep_item('1.0.0', '>=1.0.0.1') is False
@staticmethod
def test_check_build_dep_item_gt_greater():
"""Test > operator with greater version."""
assert check_build_dep_item('2.0.0', '>1.0.0') is True
@staticmethod
def test_check_build_dep_item_gt_equal():
"""Test > operator with equal versions."""
assert check_build_dep_item('1.0.0', '>1.0.0') is False
@staticmethod
def test_check_build_dep_item_gt_less():
"""Test > operator with less version."""
assert check_build_dep_item('0.9.0', '>1.0.0') is False
@staticmethod
def test_check_build_dep_item_le_equal():
"""Test <= operator with equal versions."""
assert check_build_dep_item('1.0.0', '<=1.0.0') is True
@staticmethod
def test_check_build_dep_item_le_less():
"""Test <= operator with less version."""
assert check_build_dep_item('0.9.0', '<=1.0.0') is True
@staticmethod
def test_check_build_dep_item_le_greater():
"""Test <= operator with greater version."""
assert check_build_dep_item('2.0.0', '<=1.0.0') is False
@staticmethod
def test_check_build_dep_item_lt_less():
"""Test < operator with less version."""
assert check_build_dep_item('0.9.0', '<1.0.0') is True
@staticmethod
def test_check_build_dep_item_lt_equal():
"""Test < operator with equal versions."""
assert check_build_dep_item('1.0.0', '<1.0.0') is False
@staticmethod
def test_check_build_dep_item_lt_greater():
"""Test < operator with greater version."""
assert check_build_dep_item('2.0.0', '<1.0.0') is False
@staticmethod
def test_check_build_dep_item_eq_equal():
"""Test equality with no prefix."""
assert check_build_dep_item('1.0.0', '1.0.0') is True
@staticmethod
def test_check_build_dep_item_eq_not_equal():
"""Test inequality with no prefix."""
assert check_build_dep_item('1.0.0', '1.0.1') is False
@staticmethod
def test_check_build_dep_item_eq_different_length():
"""Test equality with different length versions."""
assert check_build_dep_item('1.0.0', '1.0.0.0') is True
assert check_build_dep_item('1.0.0.1', '1.0.0') is True
assert check_build_dep_item('1.0.0.1', '1.0.0.2') is False
@staticmethod
def test_check_build_dep_item_complex_versions():
"""Test with complex version numbers."""
assert check_build_dep_item('1.10.0', '>=1.2.0') is True
assert check_build_dep_item('1.2.3.4.5', '>=1.2.3.4') is True
assert check_build_dep_item('2.0', '>=1.9.9') is True
class TestCheckBuildDep:
"""Test check_build_dep function with version ranges."""
@staticmethod
def test_check_build_dep_single_ge():
"""Test single >= dependency."""
assert check_build_dep('2.0.0', '>=1.0.0') is True
assert check_build_dep('0.9.0', '>=1.0.0') is False
@staticmethod
def test_check_build_dep_range_ge_and_lt():
"""Test version range with >= and <."""
assert check_build_dep('1.5.0', '>=1.0.0,<2.0.0') is True
assert check_build_dep('0.9.0', '>=1.0.0,<2.0.0') is False
assert check_build_dep('2.0.0', '>=1.0.0,<2.0.0') is False
@staticmethod
def test_check_build_dep_range_ge_and_le():
"""Test version range with >= and <=."""
assert check_build_dep('2.0.0', '>=1.0.0,<=2.0.0') is True
assert check_build_dep('2.0.1', '>=1.0.0,<=2.0.0') is False
@staticmethod
def test_check_build_dep_range_gt_and_lt():
"""Test version range with > and <."""
assert check_build_dep('1.5.0', '>1.0.0,<2.0.0') is True
assert check_build_dep('1.0.0', '>1.0.0,<2.0.0') is False
assert check_build_dep('2.0.0', '>1.0.0,<2.0.0') is False
@staticmethod
def test_check_build_dep_multiple_ranges():
"""Test multiple OR conditions."""
assert check_build_dep('0.5.0', '>=0.0.0,<1.0.0') is True
assert check_build_dep('1.5.0', '>=0.0.0,<1.0.0') is False
@staticmethod
def test_check_build_dep_multiple_lower_bounds():
"""Test range with multiple >= conditions (covers line 130)."""
assert check_build_dep('2.0.0', '>=1.0.0,>=1.5.0') is True
assert check_build_dep('1.2.0', '>=1.0.0,>=1.5.0') is False
assert check_build_dep('1.5.0', '>=1.0.0,>=1.5.0') is True
@staticmethod
def test_check_build_dep_simple_version():
"""Test simple version without operator (implies =)."""
assert check_build_dep('1.0.0', '1.0.0') is True
assert check_build_dep('1.0.1', '1.0.0') is False
@staticmethod
def test_check_build_dep_with_whitespace():
"""Test version range with whitespace."""
assert check_build_dep('1.5.0', '>=1.0.0, <2.0.0') is True
assert check_build_dep('1.5.0', ' >=1.0.0 , <2.0.0 ') is True
@staticmethod
def test_check_build_dep_empty_range():
"""Test with empty/invalid range - should work with valid parts."""
assert check_build_dep('1.0.0', '1.0.0') is True
class TestCheckBuildDeps:
"""Test check_build_deps function."""
@staticmethod
def test_check_build_deps_success(tmp_path):
"""Test check_build_deps with successful dependency check."""
info_dir = tmp_path / 'share' / 'info' / 'dep1'
info_dir.mkdir(parents=True)
(info_dir / 'version.info').write_text('Version=1.5.0\n')
recv = Receiver([], [])
deps = ['dep1', '>=1.0.0,<2.0.0']
check_build_deps(recv, str(tmp_path), deps)
assert recv.warn_msgs == []
assert recv.err_msgs == []
@staticmethod
def test_check_build_deps_version_mismatch(tmp_path):
"""Test check_build_deps with version mismatch."""
info_dir = tmp_path / 'share' / 'info' / 'dep1'
info_dir.mkdir(parents=True)
(info_dir / 'version.info').write_text('Version=2.5.0\n')
recv = Receiver([], [])
deps = ['dep1', '>=1.0.0,<2.0.0']
check_build_deps(recv, str(tmp_path), deps)
assert len(recv.warn_msgs) == 1
assert 'Check build dependency failed' in recv.warn_msgs[0]
assert recv.err_msgs == []
@staticmethod
def test_check_build_deps_multiple_deps(tmp_path):
"""Test check_build_deps with multiple dependencies."""
for pkg, version in [('dep1', '1.5.0'), ('dep2', '3.2.0')]:
info_dir = tmp_path / 'share' / 'info' / pkg
info_dir.mkdir(parents=True)
(info_dir / 'version.info').write_text(f'Version={version}\n')
recv = Receiver([], [])
deps = ['dep1', '>=1.0.0,<2.0.0', 'dep2', '>=3.0.0']
check_build_deps(recv, str(tmp_path), deps)
assert recv.warn_msgs == []
assert recv.err_msgs == []
@staticmethod
def test_check_build_deps_file_not_exists(tmp_path):
"""Test check_build_deps when version file doesn't exist."""
recv = Receiver([], [])
deps = ['nonexistent', '>=1.0.0']
check_build_deps(recv, str(tmp_path), deps)
assert len(recv.err_msgs) == 1
assert 'does not exist' in recv.err_msgs[0]
assert recv.warn_msgs == []
@staticmethod
def test_check_build_deps_version_not_found(tmp_path):
"""Test check_build_deps when version field not found."""
info_dir = tmp_path / 'share' / 'info' / 'dep1'
info_dir.mkdir(parents=True)
(info_dir / 'version.info').write_text('OtherField=value\n')
recv = Receiver([], [])
deps = ['dep1', '>=1.0.0']
check_build_deps(recv, str(tmp_path), deps)
assert len(recv.err_msgs) == 1
assert 'version field was not found' in recv.err_msgs[0]
@staticmethod
def test_check_build_deps_invalid_version_raises_error(tmp_path, monkeypatch):
"""Test check_build_deps with invalid version that raises ValueError."""
info_dir = tmp_path / 'share' / 'info' / 'dep1'
info_dir.mkdir(parents=True)
(info_dir / 'version.info').write_text('Version=1.0.0\n')
def mock_check_build_dep(version, dep_info):
raise ValueError("Invalid version")
monkeypatch.setattr(
'check_build_dependencies.check_build_dep',
mock_check_build_dep
)
recv = Receiver([], [])
deps = ['dep1', 'invalid_dep_info']
check_build_deps(recv, str(tmp_path), deps)
assert len(recv.err_msgs) == 1
assert 'Check build dependency error' in recv.err_msgs[0]
@staticmethod
def test_check_build_deps_mixed_results(tmp_path):
"""Test check_build_deps with mixed success and failure."""
info_dir1 = tmp_path / 'share' / 'info' / 'dep1'
info_dir1.mkdir(parents=True)
(info_dir1 / 'version.info').write_text('Version=1.5.0\n')
info_dir2 = tmp_path / 'share' / 'info' / 'dep2'
info_dir2.mkdir(parents=True)
(info_dir2 / 'version.info').write_text('Version=0.5.0\n')
recv = Receiver([], [])
deps = ['dep1', '>=1.0.0', 'dep2', '>=1.0.0']
check_build_deps(recv, str(tmp_path), deps)
assert len(recv.warn_msgs) == 1
assert 'dep2' in recv.warn_msgs[0]
assert recv.err_msgs == []
class TestMain:
"""Test main function."""
@staticmethod
def test_main_success(tmp_path):
"""Test main function with successful execution."""
info_dir = tmp_path / 'share' / 'info' / 'dep1'
info_dir.mkdir(parents=True)
(info_dir / 'version.info').write_text('Version=1.5.0\n')
with mock.patch('sys.argv', ['check_build_dependencies.py', str(tmp_path), 'dep1', '>=1.0.0']):
result = main()
assert result is True
@staticmethod
def test_main_odd_deps_count(caplog):
"""Test main function with odd number of deps."""
with mock.patch('sys.argv', ['check_build_dependencies.py', '/tmp', 'dep1']):
result = main()
assert result is False
assert 'even number of elements' in caplog.text
@staticmethod
def test_main_no_deps(tmp_path):
"""Test main function with no deps."""
with mock.patch('sys.argv', ['check_build_dependencies.py', str(tmp_path)]):
result = main()
assert result is True
@staticmethod
def test_main_with_warning(tmp_path, caplog):
"""Test main function that generates warnings."""
info_dir = tmp_path / 'share' / 'info' / 'dep1'
info_dir.mkdir(parents=True)
(info_dir / 'version.info').write_text('Version=0.5.0\n')
with mock.patch('sys.argv', ['check_build_dependencies.py', str(tmp_path), 'dep1', '>=1.0.0']):
result = main()
assert result is True
assert 'Check build dependency failed' in caplog.text
@staticmethod
def test_main_with_error(tmp_path, caplog):
"""Test main function that generates errors."""
with mock.patch('sys.argv', ['check_build_dependencies.py', str(tmp_path), 'dep1', '>=1.0.0']):
result = main()
assert result is False
assert 'does not exist' in caplog.text
@staticmethod
def test_main_multiple_deps_with_warnings(tmp_path, caplog):
"""Test main with multiple deps where some fail."""
info_dir1 = tmp_path / 'share' / 'info' / 'dep1'
info_dir1.mkdir(parents=True)
(info_dir1 / 'version.info').write_text('Version=2.0.0\n')
info_dir2 = tmp_path / 'share' / 'info' / 'dep2'
info_dir2.mkdir(parents=True)
with mock.patch('sys.argv', [
'check_build_dependencies.py', str(tmp_path),
'dep1', '>=1.0.0',
'dep2', '>=1.0.0'
]):
result = main()
assert result is False