-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pip: refactor getting metadata and unit tests
- consolidate metadata extraction logic from files (pyproject.toml, setup.py, and setup.cfg) into a single function - consolidate extraction logic from the origin remote to a single function - clean up too many logging statements during execution - drastically simplify unit tests and speed up overall time - preserve the same coverage Signed-off-by: Michal Šoltis <[email protected]>
- Loading branch information
1 parent
9508fbb
commit 414e227
Showing
2 changed files
with
124 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
import re | ||
from copy import deepcopy | ||
from pathlib import Path | ||
|
@@ -10,6 +9,7 @@ | |
import pypi_simple | ||
import pytest | ||
from _pytest.logging import LogCaptureFixture | ||
from git.repo import Repo | ||
|
||
from cachi2.core.checksum import ChecksumInfo | ||
from cachi2.core.errors import ( | ||
|
@@ -24,13 +24,8 @@ | |
from cachi2.core.models.sbom import Component, Property | ||
from cachi2.core.package_managers import pip | ||
from cachi2.core.rooted_path import PathOutsideRoot, RootedPath | ||
from cachi2.core.scm import RepoID | ||
from tests.common_utils import GIT_REF, Symlink, write_file_tree | ||
|
||
THIS_MODULE_DIR = Path(__file__).resolve().parent | ||
PKG_DIR = RootedPath("/foo/package_dir") | ||
PKG_DIR_SUBPATH = PKG_DIR.join_within_root("subpath") | ||
MOCK_REPO_ID = RepoID("https://github.com/foolish/bar.git", "abcdef1234") | ||
CUSTOM_PYPI_ENDPOINT = "https://my-pypi.org/simple/" | ||
|
||
|
||
|
@@ -58,116 +53,97 @@ def make_dpi( | |
) | ||
|
||
|
||
@pytest.mark.parametrize("toml_exists", [True, False]) | ||
@pytest.mark.parametrize("toml_name", ["name_in_pyproject_toml", None]) | ||
@pytest.mark.parametrize("toml_version", ["version_in_pyproject_toml", None]) | ||
@pytest.mark.parametrize("py_exists", [True, False]) | ||
@pytest.mark.parametrize("py_name", ["name_in_setup_py", None]) | ||
@pytest.mark.parametrize("py_version", ["version_in_setup_py", None]) | ||
@pytest.mark.parametrize("cfg_exists", [True, False]) | ||
@pytest.mark.parametrize("cfg_name", ["name_in_setup_cfg", None]) | ||
@pytest.mark.parametrize("cfg_version", ["version_in_setup_cfg", None]) | ||
@pytest.mark.parametrize("repo_name_with_subpath", ["bar-subpath", None]) | ||
@mock.patch("cachi2.core.package_managers.pip.SetupCFG") | ||
@mock.patch("cachi2.core.package_managers.pip.SetupPY") | ||
@mock.patch("cachi2.core.package_managers.pip.PyProjectTOML") | ||
@mock.patch("cachi2.core.package_managers.pip.get_repo_id") | ||
def test_get_pip_metadata( | ||
mock_get_repo_id: mock.Mock, | ||
def test_get_pip_metadata_from_pyproject_toml( | ||
mock_pyproject_toml: mock.Mock, | ||
mock_setup_py: mock.Mock, | ||
mock_setup_cfg: mock.Mock, | ||
toml_exists: bool, | ||
toml_name: Optional[str], | ||
toml_version: Optional[str], | ||
py_exists: bool, | ||
py_name: Optional[str], | ||
py_version: Optional[str], | ||
cfg_exists: bool, | ||
cfg_name: Optional[str], | ||
cfg_version: Optional[str], | ||
repo_name_with_subpath: Optional[str], | ||
caplog: pytest.LogCaptureFixture, | ||
rooted_tmp_path: RootedPath, | ||
caplog: LogCaptureFixture, | ||
) -> None: | ||
""" | ||
Test get_pip_metadata() function. | ||
More thorough tests of pyproject.toml, setup.py and setup.cfg handling are in their respective classes. | ||
""" | ||
if not toml_exists: | ||
toml_name = None | ||
toml_version = None | ||
if not py_exists: | ||
py_name = None | ||
py_version = None | ||
if not cfg_exists: | ||
cfg_name = None | ||
cfg_version = None | ||
|
||
pyproject_toml = mock_pyproject_toml.return_value | ||
pyproject_toml.exists.return_value = toml_exists | ||
pyproject_toml.get_name.return_value = toml_name | ||
pyproject_toml.get_version.return_value = toml_version | ||
pyproject_toml.exists.return_value = True | ||
pyproject_toml.check_dynamic_version.return_value = True | ||
pyproject_toml.get_name.return_value = "foo" | ||
pyproject_toml.get_version.return_value = "0.1.0" | ||
|
||
setup_py = mock_setup_py.return_value | ||
setup_py.exists.return_value = py_exists | ||
setup_py.get_name.return_value = py_name | ||
setup_py.get_version.return_value = py_version | ||
name, version = pip._get_pip_metadata(rooted_tmp_path) | ||
assert "Checking pyproject.toml for metadata" in caplog.text | ||
assert "Dynamic version parsing from pyproject.toml is not supported" in caplog.text | ||
|
||
setup_cfg = mock_setup_cfg.return_value | ||
setup_cfg.exists.return_value = cfg_exists | ||
setup_cfg.get_name.return_value = cfg_name | ||
setup_cfg.get_version.return_value = cfg_version | ||
assert name == "foo" | ||
assert version == "0.1.0" | ||
|
||
mock_get_repo_id.return_value = MOCK_REPO_ID | ||
|
||
expect_name = toml_name or py_name or cfg_name or repo_name_with_subpath | ||
expect_version = toml_version or py_version or cfg_version | ||
@mock.patch("cachi2.core.package_managers.pip.SetupPY") | ||
def test_get_pip_metadata_from_setup_py( | ||
mock_setup_py: mock.Mock, | ||
rooted_tmp_path: RootedPath, | ||
caplog: LogCaptureFixture, | ||
) -> None: | ||
setup_py = mock_setup_py.return_value | ||
setup_py.exists.return_value = True | ||
setup_py.get_name.return_value = "bar" | ||
setup_py.get_version.return_value = "0.2.0" | ||
|
||
if expect_name: | ||
name, version = pip._get_pip_metadata(PKG_DIR_SUBPATH) | ||
name, version = pip._get_pip_metadata(rooted_tmp_path) | ||
assert "Checking setup.py for metadata" in caplog.text | ||
|
||
assert name == expect_name | ||
assert version == expect_version | ||
else: | ||
mock_get_repo_id.side_effect = UnsupportedFeature( | ||
"Cachi2 cannot process repositories that don't have an 'origin' remote" | ||
) | ||
with pytest.raises(PackageRejected) as exc_info: | ||
pip._get_pip_metadata(PKG_DIR_SUBPATH) | ||
assert str(exc_info.value) == "Could not take name from the repository origin url" | ||
return | ||
assert name == "bar" | ||
assert version == "0.2.0" | ||
|
||
assert pyproject_toml.get_name.called == toml_exists | ||
assert pyproject_toml.get_version.called == toml_exists | ||
|
||
find_name_in_setup_py = toml_name is None and py_exists | ||
find_version_in_setup_py = toml_version is None and py_exists | ||
find_name_in_setup_cfg = toml_name is None and py_name is None and cfg_exists | ||
find_version_in_setup_cfg = toml_version is None and py_version is None and cfg_exists | ||
@mock.patch("cachi2.core.package_managers.pip.SetupCFG") | ||
def test_get_pip_metadata_from_setup_cfg( | ||
mock_setup_cfg: mock.Mock, | ||
rooted_tmp_path: RootedPath, | ||
caplog: LogCaptureFixture, | ||
) -> None: | ||
setup_cfg = mock_setup_cfg.return_value | ||
setup_cfg.exists.return_value = True | ||
setup_cfg.get_name.return_value = "baz" | ||
setup_cfg.get_version.return_value = "0.3.0" | ||
|
||
name, version = pip._get_pip_metadata(rooted_tmp_path) | ||
assert "Checking setup.cfg for metadata" in caplog.text | ||
|
||
assert setup_py.get_name.called == find_name_in_setup_py | ||
assert setup_py.get_version.called == find_version_in_setup_py | ||
assert name == "baz" | ||
assert version == "0.3.0" | ||
|
||
assert setup_cfg.get_name.called == find_name_in_setup_cfg | ||
assert setup_cfg.get_version.called == find_version_in_setup_cfg | ||
|
||
if toml_exists: | ||
assert "Extracting metadata from pyproject.toml" in caplog.text | ||
@pytest.mark.parametrize( | ||
"origin_exists", | ||
[True, False], | ||
) | ||
@mock.patch("cachi2.core.package_managers.pip.PyProjectTOML") | ||
@mock.patch("cachi2.core.package_managers.pip.SetupPY") | ||
@mock.patch("cachi2.core.package_managers.pip.SetupCFG") | ||
def test_get_pip_metadata_from_remote_origin( | ||
mock_setup_cfg: mock.Mock, | ||
mock_setup_py: mock.Mock, | ||
mock_pyproject_toml: mock.Mock, | ||
origin_exists: bool, | ||
rooted_tmp_path_repo: RootedPath, | ||
) -> None: | ||
pyproject_toml = mock_pyproject_toml.return_value | ||
pyproject_toml.exists.return_value = False | ||
|
||
setup_py = mock_setup_py.return_value | ||
setup_py.exists.return_value = False | ||
|
||
if find_name_in_setup_py or find_version_in_setup_py: | ||
assert "Filling in missing metadata from setup.py" in caplog.text | ||
setup_cfg = mock_setup_cfg.return_value | ||
setup_cfg.exists.return_value = False | ||
|
||
if find_name_in_setup_cfg or find_version_in_setup_cfg: | ||
assert "Filling in missing metadata from setup.cfg" in caplog.text | ||
if origin_exists: | ||
repo = Repo(rooted_tmp_path_repo) | ||
repo.create_remote("origin", "[email protected]:user/repo.git") | ||
|
||
if not (toml_exists or py_exists or cfg_exists): | ||
assert "Processing metadata from git repository" in caplog.text | ||
name, version = pip._get_pip_metadata(rooted_tmp_path_repo) | ||
assert name == "repo" | ||
assert version is None | ||
else: | ||
with pytest.raises(PackageRejected) as exc_info: | ||
pip._get_pip_metadata(rooted_tmp_path_repo) | ||
|
||
if expect_name: | ||
assert f"Resolved package name: '{expect_name}'" in caplog.text | ||
if expect_version: | ||
assert f"Resolved package version: '{expect_version}'" in caplog.text | ||
assert str(exc_info.value) == "Unable to infer package name from origin URL" | ||
|
||
|
||
class TestPyprojectTOML: | ||
|