-
Notifications
You must be signed in to change notification settings - Fork 2
🧪 test: Add unit tests for get_version fallback mechanisms #219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b5fd7d0
365de2e
107ad2b
1ad81ea
cdab0a8
141a7ba
6a2e4c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| # SPDX-FileCopyrightText: 2026 Knitli Inc. | ||
| # | ||
| # SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
|
||
| import importlib.metadata | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
|
|
||
| from unittest.mock import MagicMock | ||
|
|
||
| import pytest | ||
|
|
||
| import codeweaver | ||
|
|
||
|
Comment on lines
+5
to
+15
|
||
|
|
||
| @pytest.fixture | ||
| def mock_no_version_file(monkeypatch): | ||
| """Remove _version module from sys.modules and codeweaver module. | ||
|
Comment on lines
+17
to
+19
|
||
|
|
||
| A reload of the codeweaver module is necessary because the `get_version()` | ||
| function evaluates the imports (which populate its internal values) exactly once | ||
| at execution time. If we don't reload the module after changing sys.modules, | ||
| it may continue using the previously cached module references. | ||
|
Comment on lines
+21
to
+24
|
||
| """ | ||
| monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False) | ||
| if hasattr(codeweaver, "_version"): | ||
| monkeypatch.delattr(codeweaver, "_version", raising=False) | ||
|
|
||
| # We must also clear the internal import mechanisms tracking caching! | ||
| # A cleaner approach is simply to re-import get_version explicitly so it bounds to the current env | ||
| import importlib | ||
|
|
||
| importlib.reload(codeweaver) | ||
| return codeweaver.get_version | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def mock_no_metadata(monkeypatch, mock_no_version_file): | ||
| """Mock importlib.metadata to raise PackageNotFoundError for code-weaver.""" | ||
| _original_version = importlib.metadata.version | ||
|
|
||
| def mock_version(pkg_name): | ||
| if pkg_name == "code-weaver": | ||
| raise importlib.metadata.PackageNotFoundError("code-weaver") | ||
| return _original_version(pkg_name) | ||
|
|
||
| monkeypatch.setattr(importlib.metadata, "version", mock_version) | ||
|
Comment on lines
+43
to
+48
|
||
| return mock_no_version_file | ||
|
|
||
|
|
||
| def test_get_version_from_version_file(monkeypatch, mock_no_version_file): | ||
| """Test getting version from codeweaver._version.__version__.""" | ||
| mock_module = MagicMock() | ||
|
Comment on lines
+52
to
+54
|
||
| mock_module.__version__ = "1.2.3-file" | ||
| monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module) | ||
|
|
||
| # Need to reload codeweaver because we just changed sys.modules | ||
| import importlib | ||
|
|
||
| importlib.reload(codeweaver) | ||
|
|
||
| assert codeweaver.get_version() == "1.2.3-file" | ||
|
|
||
|
|
||
| def test_get_version_from_importlib_metadata(monkeypatch, mock_no_version_file): | ||
| """Test getting version from importlib.metadata.""" | ||
| get_version = mock_no_version_file | ||
| _original_version = importlib.metadata.version | ||
|
|
||
| def mock_version(pkg_name): | ||
| if pkg_name == "code-weaver": | ||
| return "1.2.3-metadata" | ||
| return _original_version(pkg_name) | ||
|
|
||
| monkeypatch.setattr(importlib.metadata, "version", mock_version) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Copilot reviewer is correct about this issue. The problem is that When you patch The fix: Patch these imports where they're actually used. Since
Recommended approach: from unittest.mock import patch
def test_get_version_from_git_success():
with patch('codeweaver._version.__version__', side_effect=ImportError):
with patch('importlib.metadata.version', side_effect=importlib.metadata.PackageNotFoundError):
with patch('shutil.which', return_value='/usr/bin/git'):
with patch('subprocess.run') as mock_run:
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = '1.2.3-git\n'
assert get_version() == '1.2.3-git'Alternatively, since the imports happen at runtime inside the function, you could use The key insight: lazy imports (imports inside functions) require different mocking strategies than module-level imports. |
||
|
|
||
| assert get_version() == "1.2.3-metadata" | ||
|
|
||
|
|
||
| def test_get_version_from_git_success(monkeypatch, mock_no_metadata): | ||
| """Test getting version from git describe.""" | ||
| get_version = mock_no_metadata | ||
| monkeypatch.setattr(shutil, "which", lambda cmd: "git" if cmd == "git" else None) | ||
|
|
||
| mock_result = MagicMock() | ||
| mock_result.returncode = 0 | ||
| mock_result.stdout = "1.2.3-git\n" | ||
|
|
||
| def mock_run(*args, **kwargs): | ||
| return mock_result | ||
|
|
||
| monkeypatch.setattr(subprocess, "run", mock_run) | ||
|
Comment on lines
+90
to
+93
|
||
|
|
||
| assert get_version() == "1.2.3-git" | ||
|
|
||
|
|
||
| def test_get_version_from_git_failure(monkeypatch, mock_no_metadata): | ||
| """Test git describe fails and returns 0.0.0.""" | ||
| get_version = mock_no_metadata | ||
| monkeypatch.setattr(shutil, "which", lambda cmd: "git" if cmd == "git" else None) | ||
|
|
||
| mock_result = MagicMock() | ||
| mock_result.returncode = 1 | ||
bashandbone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def mock_run(*args, **kwargs): | ||
| return mock_result | ||
|
|
||
| monkeypatch.setattr(subprocess, "run", mock_run) | ||
|
Comment on lines
+106
to
+109
|
||
|
|
||
| assert get_version() == "0.0.0" | ||
|
|
||
|
|
||
| def test_get_version_no_git(monkeypatch, mock_no_metadata): | ||
| """Test git is not installed, returns 0.0.0.""" | ||
| get_version = mock_no_metadata | ||
| monkeypatch.setattr(shutil, "which", lambda cmd: None) | ||
|
|
||
| assert get_version() == "0.0.0" | ||
|
|
||
|
|
||
| def test_get_version_exception(monkeypatch, mock_no_metadata): | ||
| """Test general exception in git block returns 0.0.0.""" | ||
| get_version = mock_no_metadata | ||
|
|
||
| def mock_which(cmd): | ||
| raise RuntimeError("Something went wrong") | ||
|
|
||
| monkeypatch.setattr(shutil, "which", mock_which) | ||
|
|
||
| assert get_version() == "0.0.0" | ||
Uh oh!
There was an error while loading. Please reload this page.