Skip to content

πŸ§ͺ test: Add unit tests for get_version fallback mechanisms#219

Open
bashandbone wants to merge 4 commits intomainfrom
jules-fix-test-init-4264728002802036473
Open

πŸ§ͺ test: Add unit tests for get_version fallback mechanisms#219
bashandbone wants to merge 4 commits intomainfrom
jules-fix-test-init-4264728002802036473

Conversation

@bashandbone
Copy link
Contributor

@bashandbone bashandbone commented Mar 16, 2026

🎯 What: The testing gap addressed
The get_version function in src/codeweaver/__init__.py lacked proper test coverage, specifically regarding its multi-level exception handling fallback logic.

πŸ“Š Coverage: What scenarios are now tested

  • The happy path retrieving version from codeweaver._version.__version__.
  • The first fallback path using importlib.metadata.version.
  • The git describe fallback path.
  • The git error fallback path (git describe fails).
  • The missing git executable fallback path.
  • The final catch-all Exception fallback path.

✨ Result: The improvement in test coverage
The get_version function is now fully tested under all its supported fallback conditions and failure states, providing safety for any future changes to version retrieval logic.


PR created automatically by Jules for task 4264728002802036473 started by @bashandbone

Summary by Sourcery

Add comprehensive test coverage for get_version’s version resolution and fallback behavior.

Tests:

  • Add unit tests covering version retrieval from the bundled _version module, importlib.metadata, and git describe.
  • Add tests for git-related failure scenarios, missing git binary, and generic exceptions to ensure get_version falls back to 0.0.0.

Added comprehensive unit tests for `get_version` in `tests/unit/test_init.py`. The tests verify that all mechanisms to retrieve the package version function as intended:
- Reading `codeweaver._version.__version__`
- Using `importlib.metadata.version`
- Falling back to `git describe`
- Handling missing `git` command
- Handling broader unexpected exceptions
All fallback scenarios now properly ensure the code returns the default `0.0.0` string or the valid version.

No functional code was changed, only new tests were introduced.

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

πŸ‘‹ Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a πŸ‘€ emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings March 16, 2026 02:39
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 16, 2026

Reviewer's Guide

Adds comprehensive unit tests for the get_version() function to cover all fallback paths (version file, importlib.metadata, git-based resolution, absence/failure of git, and generic exceptions), using monkeypatch and mocks to simulate each scenario deterministically.

File-Level Changes

Change Details Files
Add unit tests covering all get_version() fallback paths and failure modes.
  • Introduce pytest-based tests in tests/unit/test_init.py for get_version() behavior under different version resolution strategies.
  • Use monkeypatch to simulate presence/absence of codeweaver._version and to override importlib.metadata.version responses, including PackageNotFoundError.
  • Mock git availability with shutil.which and subprocess.run to exercise successful git describe output, git command failure, and missing git binary.
  • Add a test that forces an exception in the git resolution block (via shutil.which) to validate the catch-all exception handler returns the default version string.
tests/unit/test_init.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In tests where you want codeweaver._version to be absent, consider using monkeypatch.delitem(sys.modules, 'codeweaver._version', raising=False) instead of setting it to None to more accurately reflect the module not being importable.
  • There is quite a bit of repeated setup for mocking sys.modules['codeweaver._version'] and importlib.metadata.version; extracting this into a small helper or pytest fixture would reduce duplication and make the intent of each test clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In tests where you want `codeweaver._version` to be absent, consider using `monkeypatch.delitem(sys.modules, 'codeweaver._version', raising=False)` instead of setting it to `None` to more accurately reflect the module not being importable.
- There is quite a bit of repeated setup for mocking `sys.modules['codeweaver._version']` and `importlib.metadata.version`; extracting this into a small helper or pytest fixture would reduce duplication and make the intent of each test clearer.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click πŸ‘ or πŸ‘Ž on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds unit tests to cover codeweaver.get_version() across its primary path and multiple fallback/error paths to close a testing gap around exception-driven version resolution.

Changes:

  • Introduces unit tests for version resolution via _version, importlib.metadata, and git describe.
  • Adds tests for git-related failure modes (non-zero exit, missing git, unexpected exception).

πŸ’‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


from codeweaver import get_version

def test_get_version_from_version_file(monkeypatch):

assert get_version() == "1.2.3-file"

def test_get_version_from_importlib_metadata(monkeypatch):

assert get_version() == "1.2.3-metadata"

def test_get_version_from_git_success(monkeypatch):

assert get_version() == "0.0.0"

def test_get_version_no_git(monkeypatch):

assert get_version() == "0.0.0"

def test_get_version_exception(monkeypatch):
Comment on lines +37 to +43
# Mock codeweaver._version to not exist
monkeypatch.setitem(sys.modules, "codeweaver._version", None)

# Mock importlib.metadata.version to raise PackageNotFoundError
def mock_version(pkg_name):
raise importlib.metadata.PackageNotFoundError("code-weaver")
monkeypatch.setattr(importlib.metadata, "version", mock_version)
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: Test Coverage for get_version Function

Thank you for adding comprehensive test coverage for the get_version function! This is valuable work that tests all the fallback scenarios. However, there are several issues that need to be addressed before this can be merged.

Critical Issue: Missing Copyright Headers ❌

The CI is failing because the new test file is missing the required copyright and licensing information. This is blocking the merge.

Required Fix:
Add the following header at the top of tests/unit/test_init.py:

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

This must come before all other code (including imports).

Code Quality Issues

1. Import Organization

Missing from __future__ import annotations which is used consistently across the codebase. Add this as the first import after the copyright header.

2. Test Function Isolation Issues

The tests have a critical flaw: they're modifying sys.modules["codeweaver._version"] but the get_version() function has already been called at module import time (line 75 in __init__.py). This means:

  • The mocking in tests won't affect the already-cached __version__ value
  • Tests may pass due to timing/ordering but don't truly test the fallback logic
  • The function needs to be tested in a way that accounts for module-level caching

Recommendation: Consider whether these tests actually validate the intended behavior, or if a different testing approach is needed (e.g., testing the function before it's cached, or refactoring to make it more testable).

3. Inconsistent Style

  • Lines 62-67: Inconsistent blank line spacing compared to other test functions
  • Consider using pytest.fixture for common mocking setups to reduce duplication

4. Missing Type Annotations

While not required for test functions, parameter types could improve clarity (e.g., monkeypatch: pytest.MonkeyPatch).

Test Coverage Assessment βœ…

Positive aspects:

  • All 6 fallback paths are tested
  • Good use of pytest's monkeypatch fixture
  • Clear docstrings for each test
  • Appropriate use of mocks to avoid external dependencies

Coverage scenarios tested:

  1. βœ… Happy path (_version module)
  2. βœ… importlib.metadata fallback
  3. βœ… git describe success
  4. βœ… git describe failure
  5. βœ… Missing git executable
  6. βœ… Exception handling

Security Considerations

The tests appropriately mock subprocess calls, which is good practice. No security concerns identified.

Recommendations

  1. Immediate: Add the copyright header to fix the failing CI
  2. Important: Address the test isolation issue - the current tests may not be testing what they appear to test due to module-level caching
  3. Nice to have: Add from __future__ import annotations, clean up formatting inconsistencies

Verdict

Request Changes - The PR cannot be merged due to:

  1. Failing CI checks (missing copyright header)
  2. Potential test isolation issues that may lead to false positives

Once the copyright header is added and the test isolation issue is addressed, this will be a valuable addition to the test suite.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Signed-off-by: Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 16, 2026 13:13
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds unit tests to cover codeweaver.get_version() resolution order and its fallback behavior across multiple failure modes.

Changes:

  • Introduces a new unit test module covering version retrieval via _version, importlib.metadata, and git describe.
  • Adds tests for git failures, missing git executable, and exception-driven fallback to "0.0.0".

πŸ’‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return "1.2.3-metadata"
raise importlib.metadata.PackageNotFoundError("code-weaver")

monkeypatch.setattr(importlib.metadata, "version", mock_version)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Copilot reviewer is correct about this issue. The problem is that get_version() uses lazy imports - it imports importlib.metadata, shutil, and subprocess inside the function at runtime (see lines 46, 51-52 in src/codeweaver/init.py:37-72).

When you patch shutil.which or importlib.metadata.version at the stdlib module level, the import shutil statement inside get_version() creates a fresh local reference that may not see your monkeypatch.

The fix: Patch these imports where they're actually used. Since get_version() is in codeweaver module, you need to patch them there. However, since these are lazy imports (imported inside the function), the best approach is to:

  1. For importlib.metadata.version: Patch it at importlib.metadata.version level (this should work since it's called as importlib.metadata.version())
  2. For shutil.which and subprocess.run: These are trickier because they're imported inside the function

Recommended approach:
Use unittest.mock.patch with the actual import path. For lazy imports inside functions, you need to patch at the point where the module is imported. Here's an example pattern:

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 monkeypatch.setattr on the actual module objects before they're imported, or use importlib machinery to ensure your mocks are in place.

The key insight: lazy imports (imports inside functions) require different mocking strategies than module-level imports.

monkeypatch.setattr(importlib.metadata, "version", mock_version)

# Mock shutil.which to return a git path
monkeypatch.setattr(shutil, "which", lambda cmd: "/usr/bin/git" if cmd == "git" else None)
return mock_result
return MagicMock(returncode=1)

monkeypatch.setattr(subprocess, "run", mock_run)
Comment on lines +65 to +68
monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)
def mock_version(pkg_name):
raise importlib.metadata.PackageNotFoundError("code-weaver")
monkeypatch.setattr(importlib.metadata, "version", mock_version)
Comment on lines +99 to +100
def test_get_version_exception(monkeypatch):
"""Test general exception in git block returns 0.0.0."""
Comment on lines +106 to +109
# Mock shutil.which to raise an exception
def mock_which(cmd):
raise RuntimeError("Something went wrong")
monkeypatch.setattr(shutil, "which", mock_which)
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Findings

Thank you for adding comprehensive test coverage for the get_version function! The test scenarios are thorough and cover all the fallback paths. However, I've identified several issues that need to be addressed:

Critical Issues

1. Test Isolation Problem

The current test implementation has a fundamental issue with module import timing:

  • Line 7: from codeweaver import get_version triggers the import of codeweaver.__init__
  • When __init__.py loads, line 75 executes: __version__: Final[str] = get_version()
  • This means get_version() runs before any test can set up mocks
  • The module-level __version__ is already computed and cached with the real environment

Impact: Tests like test_get_version_from_version_file are setting up mocks AFTER the import has already happened, which may not test what's intended.

Suggested Fix: Consider using importlib.import_module within each test to reload the module, or mock at a different level. Alternatively, import the function lazily or use pytest fixtures to ensure proper isolation.

2. Missing SPDX Copyright Headers

All test files in this repository include SPDX headers (see tests/unit/ui/test_unified_ux.py:1-4 or tests/unit/conftest.py:1-4). Please add:

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

Minor Issues

3. Missing from __future__ import annotations

For consistency with other test files (see tests/unit/conftest.py:8), consider adding this import after the SPDX headers.

4. Missing Module Docstring

Other test files include descriptive docstrings. Consider adding one after the SPDX headers to explain what's being tested.

Recommendations

  1. Fix the import isolation issue - This is the most critical issue that may cause tests to pass incorrectly
  2. Add SPDX headers - Required for licensing compliance
  3. Add from __future__ import annotations - For consistency
  4. Add module docstring - For better documentation

CI Status

Note: Some test jobs are currently failing or pending. Please ensure all tests pass after addressing these issues.

Let me know if you'd like help implementing these fixes!

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review of PR #219: Unit Tests for get_version Function

Thank you for adding comprehensive test coverage for the get_version function! This PR adds valuable tests for all the fallback scenarios. However, there are several critical issues that need to be addressed before this can be merged.


🚨 Critical Issues

1. Missing Copyright Header

The test file is missing the required SPDX copyright header that is used consistently across the codebase. This needs to be added at the top of the file:

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

See tests/unit/conftest.py:1-4 or tests/unit/core/test_discovery.py:1-4 for reference.

2. Test Isolation Problem - Tests Don't Actually Test the Fallback Logic

This is a fundamental issue with the test design. The get_version() function is called at module import time (line 75 of src/codeweaver/__init__.py):

__version__: Final[str] = get_version()

When you import get_version in your test file (line 7):

from codeweaver import get_version

The function has already been executed and the version has been cached. Your tests are calling get_version() again with mocked modules, but:

  • The import statements inside get_version() are executed fresh each time it's called
  • However, the tests assume that the mocking of sys.modules will affect these imports

The problem: When you use monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False), this doesn't actually make the import fail in subsequent calls to get_version() because:

  1. The from codeweaver._version import __version__ statement in get_version() will still try to import from disk
  2. If the module exists on disk, it will still import successfully

Testing approach needed:

  • These tests need to be restructured to actually test the fallback logic properly
  • Consider using importlib.reload() or testing in isolated subprocess
  • Or mock at a different level (e.g., mock __import__ or use unittest.mock.patch)

3. Missing Type Annotations

The codebase uses type hints consistently. Add type annotations to test functions:

def test_get_version_from_version_file(monkeypatch: pytest.MonkeyPatch) -> None:

4. Missing pytest Imports and Markers

Looking at other test files in the codebase (e.g., tests/unit/core/test_discovery.py:11-17), you should:

  • Import pytest
  • Add the pytestmark = [pytest.mark.unit] marker to identify these as unit tests

πŸ’‘ Code Quality Suggestions

5. Code Duplication

There's significant repetition in the test setup. Consider using a pytest fixture for common mocking scenarios:

@pytest.fixture
def mock_no_version_module(monkeypatch: pytest.MonkeyPatch) -> None:
    """Mock codeweaver._version to not exist."""
    monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)

@pytest.fixture  
def mock_no_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
    """Mock importlib.metadata.version to raise PackageNotFoundError."""
    def mock_version(pkg_name: str) -> str:
        raise importlib.metadata.PackageNotFoundError("code-weaver")
    monkeypatch.setattr(importlib.metadata, "version", mock_version)

6. Inconsistent Formatting

  • Lines 66-67: Missing blank line after monkeypatch.delitem (inconsistent with other tests)
  • Consider running the codebase's formatter/linter

7. Better Mock Verification

In test_get_version_from_git_success, the mock_run function checks specific indices which is fragile:

if args[0][0] == "/usr/bin/git" and args[0][1] == "describe":

Consider using more robust mocking with unittest.mock.patch or checking the full command list.


βœ… What Works Well

  • Comprehensive coverage of all 6 fallback paths
  • Clear, descriptive docstrings for each test
  • Good use of monkeypatch fixture for test isolation (in principle)
  • Appropriate mocking to avoid external dependencies
  • Tests are focused and test one scenario each

πŸ“Š CI Status

The tests are currently failing in CI for Python 3.13t, 3.14, and 3.14t. The copyright header issue is likely blocking, but the test isolation problem may also be causing failures.


πŸ”§ Recommended Actions

  1. Immediate: Add the SPDX copyright header
  2. Critical: Fix the test isolation issue - the tests need to actually verify the fallback logic works
  3. Required: Add type annotations and pytest imports/markers
  4. Recommended: Reduce duplication with fixtures
  5. Nice-to-have: Format code consistently with the rest of the codebase

Verdict: Request Changes ⚠️

This PR cannot be merged until:

  1. The copyright header is added (blocking CI)
  2. The test isolation issue is resolved (tests may not be testing what they claim to test)
  3. Type annotations and pytest markers are added (to match codebase standards)

Once these issues are addressed, this will be a valuable addition to the test suite. Please let me know if you'd like help implementing any of these fixes!

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review of PR #219: Unit Tests for get_version Function

Thank you for adding comprehensive test coverage for the get_version function! This PR adds valuable tests for all the fallback scenarios. However, there are several critical issues that need to be addressed before this can be merged.


🚨 Critical Issues

1. Missing Copyright Header

The test file is missing the required SPDX copyright header that is used consistently across the codebase. This needs to be added at the top of the file:

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

See tests/unit/conftest.py:1-4 or tests/unit/core/test_discovery.py:1-4 for reference.

2. Test Isolation Problem - Tests Don't Actually Test the Fallback Logic

This is a fundamental issue with the test design. The get_version() function is called at module import time (line 75 of src/codeweaver/__init__.py):

__version__: Final[str] = get_version()

When you import get_version in your test file (line 7):

from codeweaver import get_version

The function has already been executed and the version has been cached. Your tests are calling get_version() again with mocked modules, but:

  • The import statements inside get_version() are executed fresh each time it's called
  • However, the tests assume that the mocking of sys.modules will affect these imports

The problem: When you use monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False), this doesn't actually make the import fail in subsequent calls to get_version() because:

  1. The from codeweaver._version import __version__ statement in get_version() will still try to import from disk
  2. If the module exists on disk, it will still import successfully

Testing approach needed:

  • These tests need to be restructured to actually test the fallback logic properly
  • Consider using importlib.reload() or testing in isolated subprocess
  • Or mock at a different level (e.g., mock __import__ or use unittest.mock.patch)

3. Missing Type Annotations

The codebase uses type hints consistently. Add type annotations to test functions:

def test_get_version_from_version_file(monkeypatch: pytest.MonkeyPatch) -> None:

4. Missing pytest Imports and Markers

Looking at other test files in the codebase (e.g., tests/unit/core/test_discovery.py:11-17), you should:

  • Import pytest
  • Add the pytestmark = [pytest.mark.unit] marker to identify these as unit tests

πŸ’‘ Code Quality Suggestions

5. Code Duplication

There's significant repetition in the test setup. Consider using a pytest fixture for common mocking scenarios:

@pytest.fixture
def mock_no_version_module(monkeypatch: pytest.MonkeyPatch) -> None:
    """Mock codeweaver._version to not exist."""
    monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)

@pytest.fixture  
def mock_no_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
    """Mock importlib.metadata.version to raise PackageNotFoundError."""
    def mock_version(pkg_name: str) -> str:
        raise importlib.metadata.PackageNotFoundError("code-weaver")
    monkeypatch.setattr(importlib.metadata, "version", mock_version)

6. Inconsistent Formatting

  • Lines 66-67: Missing blank line after monkeypatch.delitem (inconsistent with other tests)
  • Consider running the codebase's formatter/linter

7. Better Mock Verification

In test_get_version_from_git_success, the mock_run function checks specific indices which is fragile:

if args[0][0] == "/usr/bin/git" and args[0][1] == "describe":

Consider using more robust mocking with unittest.mock.patch or checking the full command list.


βœ… What Works Well

  • Comprehensive coverage of all 6 fallback paths
  • Clear, descriptive docstrings for each test
  • Good use of monkeypatch fixture for test isolation (in principle)
  • Appropriate mocking to avoid external dependencies
  • Tests are focused and test one scenario each

πŸ“Š CI Status

The tests are currently failing in CI for Python 3.13t, 3.14, and 3.14t. The copyright header issue is likely blocking, but the test isolation problem may also be causing failures.


πŸ”§ Recommended Actions

  1. Immediate: Add the SPDX copyright header
  2. Critical: Fix the test isolation issue - the tests need to actually verify the fallback logic works
  3. Required: Add type annotations and pytest imports/markers
  4. Recommended: Reduce duplication with fixtures
  5. Nice-to-have: Format code consistently with the rest of the codebase

Verdict: Request Changes ⚠️

This PR cannot be merged until:

  1. The copyright header is added (blocking CI)
  2. The test isolation issue is resolved (tests may not be testing what they claim to test)
  3. Type annotations and pytest markers are added (to match codebase standards)

Once these issues are addressed, this will be a valuable addition to the test suite. Please let me know if you'd like help implementing any of these fixes!

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Thanks for adding comprehensive test coverage for the get_version function! The tests cover all the fallback paths well. However, there are several issues that need to be addressed to match the project's coding standards:

Required Changes

  1. Missing SPDX Headers: Add the standard copyright headers at the top of the file:
# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0
  1. Missing Module Docstring: Add a docstring after the headers explaining what this test file covers (see tests/unit/telemetry/test_privacy_serialization.py for reference)

  2. Missing pytest markers: Add pytestmark = [pytest.mark.unit] after the imports

  3. Missing future import: Add from __future__ import annotations as the first import

  4. Import Order: Reorganize imports to follow Python conventions:

    • Standard library imports (importlib.metadata, shutil, subprocess, sys)
    • Third-party imports (from unittest.mock import MagicMock)
    • Local imports (from codeweaver import get_version)
    • Import pytest

Test Design Consideration

There's a subtle issue with these tests: get_version() is called at module import time in src/codeweaver/__init__.py:75:

__version__: Final[str] = get_version()

This means the version is cached when codeweaver is first imported. Your tests are currently testing the function in isolation (which is still valuable), but they may not fully reflect the module initialization behavior. Consider if this is the intended behavior or if you need to add module reloading logic.

Minor Issue

Some tests are failing on Python 3.13t, 3.14, and 3.14t (experimental). Please investigate these failures to ensure the tests are robust across all supported Python versions.

Overall, the test logic is sound and provides good coverage. Once the formatting and style issues are addressed, this will be a solid addition to the test suite.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Thank you for adding comprehensive test coverage for the get_version() function! This PR addresses an important testing gap. However, there are two issues that need to be addressed before this can be merged:

πŸ”΄ Critical Issue: Test Implementation Bug

The monkeypatching approach used in these tests won't work correctly due to how get_version() uses lazy imports. The Copilot reviewer correctly identified this issue in comment 2940333916.

Problem: The get_version() function imports importlib.metadata, shutil, and subprocess inside the function at runtime (lazy imports on lines 46, 51-52 in src/codeweaver/init.py:37-72). When you patch these modules at the stdlib level using monkeypatch.setattr(shutil, 'which', ...), the import shutil statement that executes inside get_version() creates a fresh reference that won't see your monkeypatch.

Solution: You need to use unittest.mock.patch to intercept the imports at the module level. Here's the recommended pattern:

from unittest.mock import patch, MagicMock

def test_get_version_from_git_success():
    """Test getting version from git describe."""
    with patch('codeweaver._version.__version__', side_effect=ImportError):
        with patch('importlib.metadata.version') as mock_version:
            mock_version.side_effect = importlib.metadata.PackageNotFoundError('code-weaver')
            with patch('shutil.which', return_value='/usr/bin/git'):
                with patch('subprocess.run') as mock_run:
                    mock_result = MagicMock()
                    mock_result.returncode = 0
                    mock_result.stdout = '1.2.3-git\n'
                    mock_run.return_value = mock_result
                    
                    assert get_version() == '1.2.3-git'

All six test functions need to be updated to use unittest.mock.patch instead of monkeypatch.

πŸ”΄ CI Failure: Missing REUSE Headers

The CI is failing because tests/unit/test_init.py is missing the required copyright and licensing headers. Every file in this project needs SPDX headers.

Add this at the top of the file:

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0
"""Unit tests for get_version fallback mechanisms."""

See tests/unit/cli/test_httpx_lazy_import.py:1-4 for the format.

βœ… What's Good

  1. Excellent test coverage - You've identified all 6 fallback paths in get_version()
  2. Clear documentation - Good docstrings explaining each test scenario
  3. Comprehensive edge cases - Testing success and failure paths for git operations

πŸ“‹ Action Items

  • Add REUSE compliance headers to tests/unit/test_init.py
  • Replace monkeypatch with unittest.mock.patch for all lazy imports
  • Test that the mocks actually work by running the tests locally

Once these issues are fixed, this will be a valuable addition to the test suite!

@github-actions
Copy link
Contributor

PR Review for #219: Unit tests for get_version fallback mechanisms

Thank you for adding comprehensive test coverage for the get_version() function! The test suite covers all the important fallback scenarios. However, there are a few issues that need to be addressed before this can be merged.

Critical Issues

1. Missing SPDX Headers ❌

The test file is missing the required copyright and licensing information headers. This is causing the CI checks to fail.

Required headers (add at the top of tests/unit/test_init.py):

# SPDX-FileCopyrightText: 2026 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

Code Quality Issues

2. Test Isolation Concerns ⚠️

The tests manipulate sys.modules which could cause issues with test isolation. While monkeypatch should handle cleanup, there's a risk of tests affecting each other when run in sequence.

Recommendation: Consider using monkeypatch.context() or ensuring proper cleanup, especially in test_get_version_from_version_file:

def test_get_version_from_version_file(monkeypatch):
    """Test getting version from codeweaver._version.__version__."""
    # Ensure clean state first
    monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)
    
    # Now mock the module
    mock_module = MagicMock()
    mock_module.__version__ = "1.2.3-file"
    monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module)

    assert get_version() == "1.2.3-file"

3. Incomplete import statement mocking in test_get_version_from_version_file

The test mocks sys.modules["codeweaver._version"], but the get_version() function does from codeweaver._version import __version__. While this should work, it would be more explicit to also ensure the module is importable by adding __spec__ to the mock:

mock_module = MagicMock()
mock_module.__version__ = "1.2.3-file"
mock_module.__spec__ = MagicMock()  # Makes it look like a real module
monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module)

Positive Aspects βœ…

  • Excellent coverage of all fallback paths in get_version() at src/codeweaver/init.py:37
  • Clear, descriptive test names and docstrings
  • Proper use of monkeypatch fixture for mocking
  • Tests verify both success and failure scenarios for each fallback level
  • Good use of mocking to avoid side effects (no actual subprocess calls or network requests)

Action Required

  1. Add SPDX headers to the test file (critical - blocking)
  2. Consider improving test isolation by ensuring clean state before mocking (recommended)
  3. Once headers are added, the reuse lint check should pass and tests should run

Verdict

Request Changes - The missing SPDX headers are causing CI failures. Once that's fixed, this will be a valuable addition to the test suite.


Review performed by Claude Code

Added unit tests for `get_version` in `tests/unit/test_init.py`. The tests verify that all mechanisms to retrieve the package version function as intended:
- Reading `codeweaver._version.__version__`
- Using `importlib.metadata.version`
- Falling back to `git describe`
- Handling missing `git` command
- Handling broader unexpected exceptions

Uses `monkeypatch` safely by keeping original references to avoid polluting the global namespace and Pytest internal packages. No functional code changes.

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
import sys
from unittest.mock import MagicMock

import pytest
@github-actions
Copy link
Contributor

Code Review

Summary

This PR adds comprehensive unit tests for the get_version() function in src/codeweaver/__init__.py. The tests cover all fallback paths and exception scenarios, which significantly improves code coverage and reliability for version retrieval logic.

Positive Aspects

βœ… Comprehensive coverage: All six fallback scenarios are properly tested:

  • Happy path (version from _version module)
  • First fallback (importlib.metadata)
  • Git describe success path
  • Git describe failure path
  • Missing git executable path
  • Generic exception handling

βœ… Proper mocking strategy: Uses monkeypatch effectively to isolate each test case without side effects

βœ… Clear test names: Each test function clearly describes what scenario it's testing

βœ… Preserves original behavior: Stores _original_version to avoid breaking other packages' version lookups

Issues to Address

1. Missing Copyright Header ❗

The new test file lacks the standard SPDX copyright header used throughout the codebase. All test files include:

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

See: tests/unit/cli/test_doctor_command.py:1-4

2. Missing Module Docstring ❗

The file should have a module-level docstring explaining its purpose, following the pattern in other test files:

"""Unit tests for get_version function fallback mechanisms."""

See: tests/unit/telemetry/test_privacy_serialization.py:5-10

3. Missing Pytest Marker ❗

The file should include the standard pytest marker at the module level:

pytestmark = [pytest.mark.unit]

See: tests/unit/cli/test_cli_helpers.py:22

4. Missing from __future__ import annotations

For consistency with the codebase style, add this import at the top (after copyright header).

See: tests/unit/conftest.py:8

Minor Suggestions

πŸ’‘ Test organization: Consider grouping related tests in a test class for better organization:

class TestGetVersion:
    """Tests for the get_version function."""
    
    def test_from_version_file(self, monkeypatch):
        ...
    
    def test_from_importlib_metadata(self, monkeypatch):
        ...

This pattern is used in tests/unit/telemetry/test_privacy_serialization.py:33

πŸ’‘ Import ordering: The imports could be better organized:

  1. Future imports
  2. Standard library (grouped: importlib.metadata, shutil, subprocess, sys)
  3. Third-party (pytest)
  4. Local imports (codeweaver)

CI Status

⚠️ Some test jobs are failing/pending:

  • Test Python 3.13t (experimental) - failed
  • Test Python 3.14 - failed
  • Test Python 3.14t (experimental) - failed
  • Test Python 3.12 - pending
  • Test Python 3.13 - pending

The failures may be related to the missing pytest markers or copyright headers, which can affect lint checks. Please investigate the CI logs once the runs complete.

Recommendation

Request changes - The code quality is good and tests are well-written, but the missing copyright header, docstring, and pytest marker need to be added for consistency with codebase standards before merging.


Note: Once the above issues are addressed, this will be an excellent addition to the test suite! 🎯

Added comprehensive unit tests for `get_version` in `tests/unit/test_init.py`. The tests verify that all mechanisms to retrieve the package version function as intended:
- Reading `codeweaver._version.__version__`
- Using `importlib.metadata.version`
- Falling back to `git describe`
- Handling missing `git` command
- Handling broader unexpected exceptions

Uses `monkeypatch` safely by keeping original references to avoid polluting the global namespace and Pytest internal packages. No functional code changes.

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 16, 2026 14:23
@@ -0,0 +1,16 @@
import sys
import codeweaver
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds unit tests to cover codeweaver.get_version()’s fallback chain (bundled _version, importlib.metadata, and git-based resolution), aiming to close coverage gaps around exception-driven fallbacks.

Changes:

  • Add unit tests covering version resolution via _version, importlib.metadata, git describe, and failure fallbacks.
  • Add an extra top-level test/debug file exercising get_version().

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
tests/unit/test_init.py New unit tests for get_version() fallback behaviors.
test_import_final.py Adds a standalone test/debug script that invokes pytest at import time.

πŸ’‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +15 to +16

pytest.main(["-s", "test_import_final.py"])
Comment on lines +1 to +12
import importlib.metadata
import shutil
import subprocess
import sys
from unittest.mock import MagicMock

import pytest

from codeweaver import get_version

# Store the original version function to fall back to it for other packages
_original_version = importlib.metadata.version
Comment on lines +51 to +54
def mock_run(*args, **kwargs):
return mock_result

monkeypatch.setattr(subprocess, "run", mock_run)
Comment on lines +73 to +76
def mock_run(*args, **kwargs):
return mock_result

monkeypatch.setattr(subprocess, "run", mock_run)
@github-actions
Copy link
Contributor

PR Review - Comprehensive Analysis

Thank you for adding test coverage for the get_version() function! I've reviewed PR #219 and identified several critical issues that need to be addressed.

🚨 Critical Issues

1. Accidentally Committed Entire Codebase

The commit 1ad81ea appears to have accidentally included the entire codebase in addition to the test file changes. The commit shows 473+ files changed with massive additions. This suggests a rebase or merge issue.

Evidence: The commit includes files like:

  • .claude/agents/cli-ux-designer.md
  • docs-site/ entire directory
  • scripts/ directory
  • Many configuration files that shouldn't be part of this PR

Impact: This makes the PR extremely difficult to review and will cause merge conflicts.

Required Action: The PR branch needs to be cleaned up to only include the intended changes:

  • tests/unit/test_init.py (the actual test file)
  • test_import_final.py should be removed (appears to be a debug file)

2. Debug File Included in PR

The file test_import_final.py at the repository root appears to be a temporary debugging file and should not be committed.

Required Action: Remove test_import_final.py from the PR.

3. Missing SPDX Copyright Headers

The test file tests/unit/test_init.py is missing the required copyright and license headers that are standard in this codebase.

Required headers (see tests/unit/cli/test_doctor_command.py:1-4):

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0

4. Test File is Identical to Current Main Branch

Interestingly, when I checked the current HEAD, tests/unit/test_init.py already exists with identical content. This means the tests may have already been merged to main in a previous commit.

Required Action: Verify whether these tests are already on main. If so, this PR may be redundant.

⚠️ Code Quality Issues (if tests are indeed new)

If the tests are new additions, here are recommendations:

  1. Missing Module Docstring: Add a module-level docstring:

    """Unit tests for get_version function fallback mechanisms."""
  2. Missing from __future__ import annotations: For consistency with codebase style (see tests/unit/conftest.py:8)

  3. Consider adding pytest marker: Some test files include pytestmark = [pytest.mark.unit] at module level

βœ… Test Quality (Positive Aspects)

The test implementation itself is well-done:

  • βœ… Comprehensive coverage of all 6 fallback paths in get_version() at src/codeweaver/__init__.py:37
  • βœ… Clear, descriptive test names and docstrings
  • βœ… Proper use of monkeypatch for isolation
  • βœ… Tests both success and failure scenarios
  • βœ… Clever preservation of _original_version to avoid breaking other package lookups
  • βœ… Good mocking strategy avoiding actual subprocess calls

πŸ”΄ CI Status

Several test jobs are failing or pending:

  • ❌ Test Python 3.13t (experimental) - failed
  • ❌ Test Python 3.14 - failed
  • ❌ Test Python 3.14t (experimental) - failed
  • ⏳ Test Python 3.12 - pending
  • ⏳ Test Python 3.13 - pending

These failures may be related to the commit issues or missing headers.

πŸ“‹ Action Items (Priority Order)

  1. CRITICAL: Clean up the PR branch to remove accidentally committed files
  2. CRITICAL: Verify if tests already exist on main - if so, close this PR
  3. HIGH: Remove test_import_final.py debug file
  4. HIGH: Add SPDX copyright headers to test file (if new)
  5. MEDIUM: Add module docstring and future imports
  6. LOW: Consider adding pytest markers for consistency

🎯 Recommendation

REQUEST CHANGES - The PR cannot be merged in its current state due to the accidentally committed codebase. The branch needs to be rebased or recreated with only the intended test file changes.

Once the branch is cleaned up and the SPDX headers are added, the test quality itself is excellent and would be a valuable addition (if not already present on main).


Note: I recommend creating a fresh branch from main with only the test file changes to resolve the commit issues cleanly.

@github-actions
Copy link
Contributor

Code Review - PR #219

Thank you for adding test coverage for the get_version function! The goal of achieving comprehensive test coverage for the version fallback logic is excellent. However, there are several critical issues that need to be addressed before this PR can be merged.

πŸ”΄ Critical Issues

1. Module Import Timing Problem

The biggest issue is that get_version() is called at module import time in src/codeweaver/__init__.py:75:

__version__: Final[str] = get_version()

This means the version is determined and cached when the codeweaver module is first imported. Your tests import get_version at the module level (line 9):

from codeweaver import get_version

By the time the tests run, get_version() has already been executed and __version__ is already set. Monkeypatching sys.modules after import has no effect on the cached value.

Solution: You need to reload the codeweaver module in each test after applying the monkeypatch. Use importlib.reload():

import importlib
import codeweaver

def test_get_version_from_version_file(monkeypatch):
    mock_module = MagicMock()
    mock_module.__version__ = "1.2.3-file"
    monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module)
    
    # Reload the module to re-execute get_version()
    importlib.reload(codeweaver)
    
    assert codeweaver.get_version() == "1.2.3-file"

2. Incorrect Mocking of _version Module

In test_get_version_from_version_file, setting sys.modules["codeweaver._version"] to a MagicMock doesn't properly support the import pattern:

from codeweaver._version import __version__

The mock module needs to have __version__ as an attribute that can be imported.

Current code (lines 16-18):

mock_module = MagicMock()
mock_module.__version__ = "1.2.3-file"
monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module)

While this sets the attribute, it may not work correctly with Python's import machinery. A more reliable approach is to use a simple object or module type.

3. test_import_final.py Should Be Removed

The file test_import_final.py appears to be a debugging/scratch file and should not be committed:

  • It calls pytest.main(["- s", "test_import_final.py"]) at the module level (line 16)
  • This will cause pytest to run recursively when discovered
  • It's not a proper test file following the project's conventions
  • It will likely cause test failures or unexpected behavior

⚠️ Additional Issues

4. Module State Pollution Between Tests

Each test modifies sys.modules and potentially other global state. Without proper cleanup and module reloading, tests may interfere with each other. Consider using importlib.reload() and ensuring monkeypatch properly cleans up.

5. Missing pytest-mock or Fixture Usage

The tests could benefit from using pytest fixtures to set up and tear down the mocked environment more cleanly.

βœ… What Works Well

  • Comprehensive coverage: You've identified all the fallback paths in get_version()
  • Good test names: Clear, descriptive names that explain what's being tested
  • Good docstrings: Each test has a clear docstring
  • Proper exception handling tests: Testing both expected and unexpected exceptions

πŸ“‹ Recommendations

  1. Remove test_import_final.py - This debugging file should not be in the repo
  2. Fix the module reloading issue - Use importlib.reload() after each monkeypatch
  3. Consider testing at a different level - Instead of testing get_version() directly, you might want to test the behavior by setting up different environments before importing codeweaver
  4. Add cleanup - Ensure each test properly cleans up module state to avoid test pollution
  5. Consider parametrized tests - Some of these tests could be combined using @pytest.mark.parametrize

Example of Fixed Test

import importlib
import importlib.metadata
import sys
from types import ModuleType

def test_get_version_from_version_file(monkeypatch):
    """Test getting version from codeweaver._version.__version__."""
    # Create a real module object instead of MagicMock
    mock_module = ModuleType("_version")
    mock_module.__version__ = "1.2.3-file"
    monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module)
    
    # Reimport codeweaver to trigger get_version() again
    import codeweaver
    importlib.reload(codeweaver)
    
    assert codeweaver.__version__ == "1.2.3-file"
    assert codeweaver.get_version() == "1.2.3-file"

🎯 Next Steps

  1. Remove test_import_final.py
  2. Update all tests to properly reload the module after monkeypatching
  3. Ensure tests clean up properly and don't interfere with each other
  4. Run the tests locally to verify they pass
  5. Push the updates

The test strategy is sound, but the implementation needs these fixes to work correctly with Python's module import caching. Let me know if you need clarification on any of these points!


Review provided by Claude Code

@github-actions
Copy link
Contributor

PR Review Summary

Thank you for adding comprehensive test coverage for the get_version() function! The test implementation is thorough and covers all the fallback paths. However, there are several issues that need to be addressed:

Critical Issues

1. Missing SPDX Headers ❌

File: tests/unit/test_init.py

The test file is missing required copyright and licensing headers. Based on the codebase standards, please add:

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0
"""Unit tests for get_version fallback mechanisms."""

2. Incorrect test file in repository root ❌

File: test_import_final.py (in repo root)

This appears to be a scratch/debug file that should not be committed. It:

  • Contains test code with pytest.main() that runs immediately
  • Is in the wrong location (repo root instead of tests/)
  • Does not follow the project's test structure

Recommendation: Remove this file entirely from the PR.

Code Quality Issues

3. Missing pytest markers ⚠️

File: tests/unit/test_init.py

Looking at the existing test patterns in the codebase (e.g., tests/unit/cli/test_httpx_lazy_import.py), unit tests should have appropriate pytest markers. Consider adding:

@pytest.mark.unit

to each test function or as a class decorator if you group them.

4. Test isolation concerns ⚠️

File: tests/unit/test_init.py

The tests directly manipulate sys.modules["codeweaver._version"]. While monkeypatch should handle cleanup, the current approach has some issues:

  • In test_get_version_from_version_file(): You're setting sys.modules["codeweaver._version"] to a mock, but get_version() tries to import from codeweaver._version. The import statement might not work as expected with your mock.

  • Better approach: Consider using monkeypatch.setattr() to patch the import after it's been imported, or use importlib.reload() if you need to test the import itself.

5. Docstring consistency πŸ“

File: tests/unit/test_init.py

The test docstrings are good and descriptive. Consider making them slightly more consistent with the codebase style. The existing tests use both single-line and multi-line formats appropriately.

Positive Aspects βœ…

  1. Excellent test coverage - All fallback paths are tested
  2. Good use of mocking - Properly mocks external dependencies (git, importlib)
  3. Clear test names - Test function names clearly indicate what they're testing
  4. Proper use of monkeypatch - Following pytest best practices

Test Logic Review

The test implementations look correct for testing each fallback scenario:

  • βœ… Version file present
  • βœ… importlib.metadata fallback
  • βœ… Git describe success
  • βœ… Git describe failure
  • βœ… Git not installed
  • βœ… Generic exception handling

Recommendations

  1. Remove test_import_final.py from the repository root
  2. Add SPDX headers to tests/unit/test_init.py
  3. Add @pytest.mark.unit markers to the test functions
  4. Consider reviewing the sys.modules manipulation approach for better test isolation
  5. Ensure the tests pass locally before pushing (some CI tests are still failing)

Once these issues are addressed, this will be a valuable addition to the test suite!

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #219 - Unit Tests for get_version Function

Thank you for adding comprehensive test coverage for the get_version function! This addresses an important testing gap. However, after reviewing the code and CI feedback, I've identified several critical issues that need to be addressed before this can be merged.


🚨 Critical Issues

1. Unexpected File: test_import_final.py

The file test_import_final.py appears to be a scratch/debugging file that should not be included in this PR:

  • It's in the repository root (not in tests/ directory)
  • Contains a single test that calls pytest.main() directly (line 16)
  • Doesn't follow the project's testing patterns
  • Not mentioned in the PR description

Action Required: Remove this file from the PR.


2. Missing SPDX Copyright Headers

The test file tests/unit/test_init.py is missing required copyright headers. All files in this project require SPDX headers.

Required addition at the top of tests/unit/test_init.py:

# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0
"""Unit tests for get_version fallback mechanisms."""

from __future__ import annotations

Reference: tests/unit/conftest.py:1-8


3. Test Isolation Issue

The current monkeypatching approach has a fundamental limitation. The get_version() function uses lazy imports (imports happen inside the function), which means:

# In test_get_version_from_importlib_metadata (line 24)
monkeypatch.setitem(sys.modules, "codeweaver._version", None)

This sets the module to None, but when get_version() executes from codeweaver._version import __version__ (line 43 of __init__.py), Python will:

  1. See sys.modules["codeweaver._version"] = None
  2. Still attempt a fresh import from disk
  3. If the _version.py file exists, import it successfully

The tests may pass in CI because the _version.py file might not exist in the test environment, but they don't accurately test the fallback logic.

Recommended approach: Use unittest.mock.patch to intercept the import at a deeper level, or use importlib.reload() with proper module cleanup. The Sourcery AI review correctly suggests using monkeypatch.delitem(sys.modules, 'codeweaver._version', raising=False) which is better than setting to None.


4. Missing Import and Markers

Looking at other test files (e.g., tests/unit/cli/test_httpx_lazy_import.py:29), the following are missing:

from __future__ import annotations  # Should be first import after headers

# After imports, before test functions:
pytestmark = [pytest.mark.unit]

πŸ’‘ Code Quality Improvements

5. Code Duplication (Sourcery's suggestion is valid)

There's significant repetition in setting up mocks. Consider extracting common patterns into pytest fixtures:

@pytest.fixture
def mock_no_version_module(monkeypatch: pytest.MonkeyPatch) -> None:
    """Mock codeweaver._version to not exist."""
    monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)

@pytest.fixture  
def mock_no_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
    """Mock importlib.metadata.version to raise PackageNotFoundError."""
    def mock_version(pkg_name: str) -> str:
        if pkg_name == "code-weaver":
            raise importlib.metadata.PackageNotFoundError("code-weaver")
        return _original_version(pkg_name)
    monkeypatch.setattr(importlib.metadata, "version", mock_version)

Then tests can use:

def test_get_version_from_importlib_metadata(
    monkeypatch: pytest.MonkeyPatch,
    mock_no_version_module,  # Use fixture
) -> None:
    # Only the specific mocking for this test
    ...

6. Type Annotations

Add type annotations to match project conventions:

def test_get_version_from_version_file(monkeypatch: pytest.MonkeyPatch) -> None:

βœ… What's Working Well

  1. Comprehensive coverage - All 6 fallback paths are tested
  2. Clear documentation - Good docstrings for each test
  3. Proper use of mocking - Avoids external dependencies (git, network)
  4. Focused tests - Each test targets one specific scenario

πŸ“Š CI Status

Current failures:

  • Python 3.13t, 3.14, 3.14t tests are failing
  • Likely due to missing copyright headers (REUSE compliance check)

πŸ”§ Action Items (Priority Order)

  1. Remove test_import_final.py - This file should not be in the PR
  2. Add SPDX headers to tests/unit/test_init.py
  3. Add from __future__ import annotations and pytestmark
  4. Fix monkeypatch approach - Use delitem instead of setitem(..., None)
  5. Add type annotations to all test functions
  6. Consider refactoring - Extract common mocking into fixtures (optional but recommended)

Verdict: Request Changes ⚠️

This PR cannot be merged until issues #1-4 are resolved. Issue #5-6 are strongly recommended for code quality.

Once these are addressed, this will be a valuable addition to the test suite! The test logic is sound and the coverage is excellent - it just needs some structural fixes to align with project standards.

Let me know if you need any clarification on these points!

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: Test Coverage for get_version()

Summary

This PR adds comprehensive test coverage for the get_version() function in src/codeweaver/__init__.py. The tests cover all fallback paths including version file, importlib.metadata, git describe, and error scenarios. While the intent is excellent, there are several critical issues that need to be addressed.


Critical Issues

1. Function Caching Problem ⚠️

The most critical issue is that get_version() is called once at module import time to set __version__ (line 75 of __init__.py):

__version__: Final[str] = get_version()

This means:

  • When you import codeweaver or get_version in your tests, the function has already been called
  • The __version__ module-level variable is already set
  • Subsequent calls to get_version() will execute again, but the module's __version__ won't change
  • Your mocks are being set up after the initial call

Impact: Tests may pass or fail inconsistently depending on the order they run and what the actual environment looks like.

Solution: Consider one of these approaches:

  1. Reload the module after setting up mocks:
import importlib
import codeweaver
# ... setup mocks ...
importlib.reload(codeweaver)
version = codeweaver.__version__
  1. Test the function directly by importing it lazily or resetting sys.modules more thoroughly
  2. Use pytest fixtures with autouse=True to ensure clean state between tests

2. Stray Test File πŸ—‘οΈ

The file test_import_final.py at the repository root should be removed. It appears to be a debug/exploration file that shouldn't be committed:

  • It calls pytest.main() which would cause issues
  • It doesn't follow the project's test structure
  • It's not in the tests/ directory

Code Quality Issues

3. Mock Setup - Using None vs delitem

In multiple tests, you use:

monkeypatch.setitem(sys.modules, "codeweaver._version", None)

Issue: Setting a module to None in sys.modules doesn't truly simulate it being missing. Python will find None in sys.modules and treat it as "already imported (but empty)".

Better approach:

monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)

This more accurately simulates the module not being available.

4. Code Duplication

As Sourcery AI pointed out, there's significant duplication in mock setup. Consider creating a fixture:

@pytest.fixture
def mock_no_version_file(monkeypatch):
    """Remove _version module from sys.modules."""
    monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)

@pytest.fixture  
def mock_no_metadata(monkeypatch, mock_no_version_file):
    """Mock importlib.metadata to raise PackageNotFoundError."""
    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)

Test Coverage Analysis

The tests cover:

  • βœ… Version from _version file
  • βœ… Version from importlib.metadata
  • βœ… Version from git describe (success)
  • βœ… Git describe failure (non-zero exit)
  • βœ… Missing git binary
  • βœ… Generic exception handling

This is excellent coverage of all the fallback paths.


Minor Issues

5. Hardcoded Paths

monkeypatch.setattr(shutil, "which", lambda cmd: "/usr/bin/git" if cmd == "git" else None)

The hardcoded path /usr/bin/git works but could be more generic:

monkeypatch.setattr(shutil, "which", lambda cmd: "git" if cmd == "git" else None)

The actual path doesn't matter for the test; just that it's truthy.

6. Test Documentation

The docstrings are clear and helpful. Good work! βœ…


Current Test Failures

The CI shows failures on Python 3.13t, 3.14, and 3.14t. Without access to the detailed logs, I suspect these failures are related to:

  1. The caching issue described above
  2. Potential timing issues with module imports
  3. Possible conflicts with the actual installed version of codeweaver in the test environment

Recommendations

To fix this PR:

  1. Remove test_import_final.py
  2. Fix the caching issue by implementing module reload or refactoring how tests import get_version
  3. Use delitem instead of setting modules to None
  4. Create fixtures to reduce duplication
  5. Investigate test failures - run tests locally with pytest tests/unit/test_init.py -v to see the actual failures

Example of a fixed test:

def test_get_version_from_version_file(monkeypatch):
    """Test getting version from codeweaver._version.__version__."""
    # Clean up any existing imports
    monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)
    monkeypatch.delitem(sys.modules, "codeweaver", raising=False)
    
    # Set up mock BEFORE importing
    mock_module = MagicMock()
    mock_module.__version__ = "1.2.3-file"
    monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module)
    
    # Now import and reload
    import importlib
    import codeweaver
    importlib.reload(codeweaver)
    
    assert codeweaver.__version__ == "1.2.3-file"

Verdict

Request Changes ⚠️

While the test coverage strategy is excellent and the PR addresses an important gap, the implementation has critical issues that cause test failures. The main problem is the module-level caching of __version__ which requires careful handling in tests.

Once the issues above are addressed, this will be a valuable addition to the test suite!


Positive Notes πŸ‘

  • Excellent identification of a testing gap
  • Comprehensive coverage of all fallback scenarios
  • Clear test naming and documentation
  • Good use of pytest and monkeypatch

@bashandbone
Copy link
Contributor Author

Jules, please address the comments in the below PR review:

PR Review: Test Coverage for get_version()

Summary

This PR adds comprehensive test coverage for the get_version() function in src/codeweaver/__init__.py. The tests cover all fallback paths including version file, importlib.metadata, git describe, and error scenarios. While the intent is excellent, there are several critical issues that need to be addressed.

Critical Issues

1. Function Caching Problem ⚠️

The most critical issue is that get_version() is called once at module import time to set __version__ (line 75 of __init__.py):

__version__: Final[str] = get_version()

This means:

  • When you import codeweaver or get_version in your tests, the function has already been called
  • The __version__ module-level variable is already set
  • Subsequent calls to get_version() will execute again, but the module's __version__ won't change
  • Your mocks are being set up after the initial call

Impact: Tests may pass or fail inconsistently depending on the order they run and what the actual environment looks like.

Solution: Consider one of these approaches:

  1. Reload the module after setting up mocks:
import importlib
import codeweaver
# ... setup mocks ...
importlib.reload(codeweaver)
version = codeweaver.__version__
  1. Test the function directly by importing it lazily or resetting sys.modules more thoroughly
  2. Use pytest fixtures with autouse=True to ensure clean state between tests

2. Stray Test File πŸ—‘οΈ

The file test_import_final.py at the repository root should be removed. It appears to be a debug/exploration file that shouldn't be committed:

  • It calls pytest.main() which would cause issues
  • It doesn't follow the project's test structure
  • It's not in the tests/ directory

Code Quality Issues

3. Mock Setup - Using None vs delitem

In multiple tests, you use:

monkeypatch.setitem(sys.modules, "codeweaver._version", None)

Issue: Setting a module to None in sys.modules doesn't truly simulate it being missing. Python will find None in sys.modules and treat it as "already imported (but empty)".

Better approach:

monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)

This more accurately simulates the module not being available.

4. Code Duplication

As Sourcery AI pointed out, there's significant duplication in mock setup. Consider creating a fixture:

@pytest.fixture
def mock_no_version_file(monkeypatch):
    """Remove _version module from sys.modules."""
    monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)

@pytest.fixture  
def mock_no_metadata(monkeypatch, mock_no_version_file):
    """Mock importlib.metadata to raise PackageNotFoundError."""
    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)

Test Coverage Analysis

The tests cover:

  • βœ… Version from _version file
  • βœ… Version from importlib.metadata
  • βœ… Version from git describe (success)
  • βœ… Git describe failure (non-zero exit)
  • βœ… Missing git binary
  • βœ… Generic exception handling

This is excellent coverage of all the fallback paths.

Minor Issues

5. Hardcoded Paths

monkeypatch.setattr(shutil, "which", lambda cmd: "/usr/bin/git" if cmd == "git" else None)

The hardcoded path /usr/bin/git works but could be more generic:

monkeypatch.setattr(shutil, "which", lambda cmd: "git" if cmd == "git" else None)

The actual path doesn't matter for the test; just that it's truthy.

6. Test Documentation

The docstrings are clear and helpful. Good work! βœ…

Current Test Failures

The CI shows failures on Python 3.13t, 3.14, and 3.14t. Without access to the detailed logs, I suspect these failures are related to:

  1. The caching issue described above
  2. Potential timing issues with module imports
  3. Possible conflicts with the actual installed version of codeweaver in the test environment

Recommendations

To fix this PR:

  1. Remove test_import_final.py
  2. Fix the caching issue by implementing module reload or refactoring how tests import get_version
  3. Use delitem instead of setting modules to None
  4. Create fixtures to reduce duplication
  5. Investigate test failures - run tests locally with pytest tests/unit/test_init.py -v to see the actual failures

Example of a fixed test:

def test_get_version_from_version_file(monkeypatch):
    """Test getting version from codeweaver._version.__version__."""
    # Clean up any existing imports
    monkeypatch.delitem(sys.modules, "codeweaver._version", raising=False)
    monkeypatch.delitem(sys.modules, "codeweaver", raising=False)
    
    # Set up mock BEFORE importing
    mock_module = MagicMock()
    mock_module.__version__ = "1.2.3-file"
    monkeypatch.setitem(sys.modules, "codeweaver._version", mock_module)
    
    # Now import and reload
    import importlib
    import codeweaver
    importlib.reload(codeweaver)
    
    assert codeweaver.__version__ == "1.2.3-file"

Verdict

Request Changes ⚠️

While the test coverage strategy is excellent and the PR addresses an important gap, the implementation has critical issues that cause test failures. The main problem is the module-level caching of __version__ which requires careful handling in tests.

Once the issues above are addressed, this will be a valuable addition to the test suite!

Positive Notes πŸ‘

  • Excellent identification of a testing gap
  • Comprehensive coverage of all fallback scenarios
  • Clear test naming and documentation
  • Good use of pytest and monkeypatch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants