Skip to content
Merged
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ jobs:
needs: eval-changes
uses: ./.github/workflows/validate.yml
with:
python-versions-linux: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]'
# It was a bit of overkill before testing every minor version, and since this project is all about
# SemVer, we should expect Python to adhere to that model to. Therefore Only test across 2 OS's but
# the lowest supported minor version and the latest stable minor version (just in case).
python-versions-linux: '["3.8", "3.13"]'
# Since the test suite takes ~4 minutes to complete on windows, and windows is billed higher
# we are only going to run it on the oldest version of python we support. The older version
# will be the most likely area to fail as newer minor versions maintain compatibility.
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ jobs:
group: ${{ github.workflow }}-validate-${{ github.ref_name }}
cancel-in-progress: true
with:
python-versions-linux: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]'
python-versions-windows: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]'
# It was a bit of overkill before testing every minor version, and since this project is all about
# SemVer, we should expect Python to adhere to that model to. Therefore Only test across 2 OS's but
# the lowest supported minor version and the latest stable minor version.
python-versions-linux: '["3.8", "3.13"]'
python-versions-windows: '["3.8", "3.13"]'
files-changed: ${{ needs.eval-changes.outputs.any-file-changes }}
build-files-changed: ${{ needs.eval-changes.outputs.build-changes }}
ci-files-changed: ${{ needs.eval-changes.outputs.ci-changes }}
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
name = "python-semantic-release"
version = "10.3.1"
description = "Automatic Semantic Versioning for Python projects"
requires-python = ">=3.8"
requires-python = "~= 3.8"
license = { text = "MIT" }
classifiers = [
"Programming Language :: Python",
Expand Down
33 changes: 28 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Note: fixtures are stored in the tests/fixtures directory for better organisation"""
"""Note: fixtures are stored in the tests/fixtures directory for better organization"""

from __future__ import annotations

Expand All @@ -9,21 +9,23 @@
from hashlib import md5
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast
from unittest import mock

import pytest
from click.testing import CliRunner
from filelock import FileLock
from git import Commit, Repo

from semantic_release.version.version import Version

from tests.const import PROJ_DIR
from tests.fixtures import *
from tests.util import copy_dir_tree, remove_dir_tree

if TYPE_CHECKING:
from tempfile import _TemporaryFileWrapper
from typing import Any, Callable, Generator, Protocol, Sequence, TypedDict
from typing import Any, Callable, Generator, Optional, Protocol, Sequence, TypedDict

from click.testing import Result
from filelock import AcquireReturnProxy
Expand Down Expand Up @@ -325,7 +327,7 @@ def _get_authorization_to_build_repo_cache(
def get_cached_repo_data(request: pytest.FixtureRequest) -> GetCachedRepoDataFn:
def _get_cached_repo_data(proj_dirname: str) -> RepoData | None:
cache_key = f"psr/repos/{proj_dirname}"
return request.config.cache.get(cache_key, None)
return cast("Optional[RepoData]", request.config.cache.get(cache_key, None))

return _get_cached_repo_data

Expand All @@ -335,6 +337,10 @@ def set_cached_repo_data(request: pytest.FixtureRequest) -> SetCachedRepoDataFn:
def magic_serializer(obj: Any) -> Any:
if isinstance(obj, Path):
return obj.__fspath__()

if isinstance(obj, Version):
return obj.__dict__

return obj

def _set_cached_repo_data(proj_dirname: str, data: RepoData) -> None:
Expand Down Expand Up @@ -386,13 +392,30 @@ def _build_repo_w_cache_checking(
with log_file_lock, log_file.open(mode="a") as afd:
afd.write(f"{stable_now_date().isoformat()}: {build_msg}...\n")

try:
# Try to build repository but catch any errors so that it doesn't cascade through all tests
# do to an unreleased lock
build_definition = build_repo_func(cached_repo_path)
except Exception:
remove_dir_tree(cached_repo_path, force=True)

if filelock:
filelock.lock.release()

with log_file_lock, log_file.open(mode="a") as afd:
afd.write(
f"{stable_now_date().isoformat()}: {build_msg}...FAILED\n"
)

raise

# Marks the date when the cached repo was created
set_cached_repo_data(
repo_name,
{
"build_date": today_date_str,
"build_spec_hash": build_spec_hash,
"build_definition": build_repo_func(cached_repo_path),
"build_definition": build_definition,
},
)

Expand Down
31 changes: 21 additions & 10 deletions tests/e2e/cmd_changelog/test_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from semantic_release.changelog.context import ChangelogMode
from semantic_release.cli.config import ChangelogOutputFormat
from semantic_release.hvcs.github import Github
from semantic_release.version.version import Version

from tests.const import (
CHANGELOG_SUBCMD,
Expand Down Expand Up @@ -77,6 +76,12 @@

from requests_mock import Mocker

from semantic_release.commit_parser.conventional import (
ConventionalCommitParser,
)
from semantic_release.commit_parser.emoji import EmojiCommitParser
from semantic_release.commit_parser.scipy import ScipyCommitParser

from tests.conftest import RunCliFn
from tests.e2e.conftest import RetrieveRuntimeContextFn
from tests.fixtures.example_project import (
Expand Down Expand Up @@ -867,9 +872,12 @@ def test_changelog_update_mode_unreleased_n_released(
commit_n_rtn_changelog_entry: CommitNReturnChangelogEntryFn,
changelog_file: Path,
insertion_flag: str,
get_commit_def_of_conventional_commit: GetCommitDefFn,
get_commit_def_of_emoji_commit: GetCommitDefFn,
get_commit_def_of_scipy_commit: GetCommitDefFn,
get_commit_def_of_conventional_commit: GetCommitDefFn[ConventionalCommitParser],
get_commit_def_of_emoji_commit: GetCommitDefFn[EmojiCommitParser],
get_commit_def_of_scipy_commit: GetCommitDefFn[ScipyCommitParser],
default_conventional_parser: ConventionalCommitParser,
default_emoji_parser: EmojiCommitParser,
default_scipy_parser: ScipyCommitParser,
):
"""
Given there are unreleased changes and a previous release in the changelog,
Expand All @@ -890,18 +898,23 @@ def test_changelog_update_mode_unreleased_n_released(
commit_n_section: Commit2Section = {
"conventional": {
"commit": get_commit_def_of_conventional_commit(
"perf: improve the performance of the application"
"perf: improve the performance of the application",
parser=default_conventional_parser,
),
"section": "Performance Improvements",
},
"emoji": {
"commit": get_commit_def_of_emoji_commit(
":zap: improve the performance of the application"
":zap: improve the performance of the application",
parser=default_emoji_parser,
),
"section": ":zap:",
},
"scipy": {
"commit": get_commit_def_of_scipy_commit("MAINT: fix an issue"),
"commit": get_commit_def_of_scipy_commit(
"MAINT: fix an issue",
parser=default_scipy_parser,
),
"section": "Fix",
},
}
Expand Down Expand Up @@ -1095,9 +1108,7 @@ def test_custom_release_notes_template(
) -> None:
"""Verify the template `.release_notes.md.j2` from `template_dir` is used."""
expected_call_count = 1
version = Version.parse(
get_versions_from_repo_build_def(repo_result["definition"])[-1]
)
version = get_versions_from_repo_build_def(repo_result["definition"])[-1]

# Setup
use_release_notes_template()
Expand Down
10 changes: 8 additions & 2 deletions tests/e2e/cmd_changelog/test_changelog_custom_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
if TYPE_CHECKING:
from pathlib import Path

from semantic_release.commit_parser.conventional import (
ConventionalCommitParser,
)

from tests.conftest import RunCliFn
from tests.fixtures.example_project import UpdatePyprojectTomlFn, UseCustomParserFn
from tests.fixtures.git_repo import BuiltRepoResult, GetCommitDefFn
Expand All @@ -31,17 +35,19 @@ def test_changelog_custom_parser_remove_from_changelog(
run_cli: RunCliFn,
update_pyproject_toml: UpdatePyprojectTomlFn,
use_custom_parser: UseCustomParserFn,
get_commit_def_of_conventional_commit: GetCommitDefFn,
get_commit_def_of_conventional_commit: GetCommitDefFn[ConventionalCommitParser],
changelog_md_file: Path,
default_md_changelog_insertion_flag: str,
default_conventional_parser: ConventionalCommitParser,
):
"""
Given when a changelog filtering custom parser is configured
When provided a commit message that matches the ignore syntax
Then the commit message is not included in the resulting changelog
"""
ignored_commit_def = get_commit_def_of_conventional_commit(
"chore: do not include me in the changelog"
"chore: do not include me in the changelog",
parser=default_conventional_parser,
)

# Because we are in init mode, the insertion flag is not present in the changelog
Expand Down
47 changes: 18 additions & 29 deletions tests/e2e/cmd_changelog/test_changelog_release_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import pytest
from pytest_lazy_fixtures import lf as lazy_fixture

from semantic_release.version.version import Version

from tests.const import CHANGELOG_SUBCMD, EXAMPLE_PROJECT_LICENSE, MAIN_PROG_NAME
from tests.fixtures.repos import (
repo_w_github_flow_w_default_release_channel_conventional_commits,
Expand All @@ -21,7 +19,7 @@
if TYPE_CHECKING:
from requests_mock import Mocker

from tests.conftest import GetStableDateNowFn, RunCliFn
from tests.conftest import GetCachedRepoDataFn, GetStableDateNowFn, RunCliFn
from tests.fixtures.example_project import UpdatePyprojectTomlFn
from tests.fixtures.git_repo import (
BuiltRepoResult,
Expand Down Expand Up @@ -56,19 +54,16 @@ def test_changelog_latest_release_notes(
repo_def = repo_result["definition"]
tag_format_str: str = get_cfg_value_from_def(repo_def, "tag_format_str") # type: ignore[assignment]
repo_actions_per_version = split_repo_actions_by_release_tags(
repo_definition=repo_def,
tag_format_str=tag_format_str,
repo_definition=repo_def
)
all_versions = get_versions_from_repo_build_def(repo_def)
latest_release_version = all_versions[-1]
release_tag = tag_format_str.format(version=latest_release_version)

expected_release_notes = generate_default_release_notes_from_def(
version_actions=repo_actions_per_version[release_tag],
version_actions=repo_actions_per_version[latest_release_version],
hvcs=get_hvcs_client_from_repo_def(repo_def),
previous_version=(
Version.parse(all_versions[-2]) if len(all_versions) > 1 else None
),
previous_version=(all_versions[-2] if len(all_versions) > 1 else None),
license_name=EXAMPLE_PROJECT_LICENSE,
mask_initial_release=get_cfg_value_from_def(repo_def, "mask_initial_release"),
)
Expand Down Expand Up @@ -130,19 +125,18 @@ def test_changelog_previous_release_notes(
repo_def = repo_result["definition"]
tag_format_str: str = get_cfg_value_from_def(repo_def, "tag_format_str") # type: ignore[assignment]
repo_actions_per_version = split_repo_actions_by_release_tags(
repo_definition=repo_def,
tag_format_str=tag_format_str,
repo_definition=repo_def
)
# Extract all versions except for the latest one
all_prev_versions = get_versions_from_repo_build_def(repo_def)[:-1]
latest_release_version = all_prev_versions[-1]
release_tag = tag_format_str.format(version=latest_release_version)

expected_release_notes = generate_default_release_notes_from_def(
version_actions=repo_actions_per_version[release_tag],
version_actions=repo_actions_per_version[latest_release_version],
hvcs=get_hvcs_client_from_repo_def(repo_def),
previous_version=(
Version.parse(all_prev_versions[-2]) if len(all_prev_versions) > 1 else None
all_prev_versions[-2] if len(all_prev_versions) > 1 else None
),
license_name=EXAMPLE_PROJECT_LICENSE,
mask_initial_release=mask_initial_release,
Expand Down Expand Up @@ -170,25 +164,25 @@ def test_changelog_previous_release_notes(


@pytest.mark.parametrize(
"repo_result, cache_key, mask_initial_release, license_name",
"repo_result, repo_fixture_name, mask_initial_release, license_name",
[
(
lazy_fixture(repo_w_trunk_only_conventional_commits.__name__),
f"psr/repos/{repo_w_trunk_only_conventional_commits.__name__}",
repo_w_trunk_only_conventional_commits.__name__,
True,
"BSD-3-Clause",
),
pytest.param(
lazy_fixture(repo_w_trunk_only_conventional_commits.__name__),
f"psr/repos/{repo_w_trunk_only_conventional_commits.__name__}",
repo_w_trunk_only_conventional_commits.__name__,
False,
"BSD-3-Clause",
marks=pytest.mark.comprehensive,
),
*[
pytest.param(
lazy_fixture(repo_fixture_name),
f"psr/repos/{repo_fixture_name}",
repo_fixture_name,
mask_initial_release,
"BSD-3-Clause",
marks=pytest.mark.comprehensive,
Expand Down Expand Up @@ -216,15 +210,15 @@ def test_changelog_release_notes_license_change(
split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn,
generate_default_release_notes_from_def: GenerateDefaultReleaseNotesFromDefFn,
update_pyproject_toml: UpdatePyprojectTomlFn,
cache: pytest.Cache,
cache_key: str,
repo_fixture_name: str,
stable_now_date: GetStableDateNowFn,
get_cached_repo_data: GetCachedRepoDataFn,
):
# Setup
repo_def = repo_result["definition"]
tag_format_str: str = get_cfg_value_from_def(repo_def, "tag_format_str") # type: ignore[assignment]

if not (repo_build_data := cache.get(cache_key, None)):
if not (repo_build_data := get_cached_repo_data(repo_fixture_name)):
pytest.fail("Repo build date not found in cache")

repo_build_datetime = datetime.strptime(repo_build_data["build_date"], "%Y-%m-%d")
Expand All @@ -236,7 +230,6 @@ def test_changelog_release_notes_license_change(

repo_actions_per_version = split_repo_actions_by_release_tags(
repo_definition=repo_def,
tag_format_str=tag_format_str,
)
# Extract all versions
all_versions = get_versions_from_repo_build_def(repo_def)
Expand All @@ -247,21 +240,17 @@ def test_changelog_release_notes_license_change(
prev_release_tag = tag_format_str.format(version=previous_release_version)

expected_release_notes = generate_default_release_notes_from_def(
version_actions=repo_actions_per_version[latest_release_tag],
version_actions=repo_actions_per_version[latest_release_version],
hvcs=get_hvcs_client_from_repo_def(repo_def),
previous_version=(
Version.parse(previous_release_version) if len(all_versions) > 1 else None
),
previous_version=(previous_release_version if len(all_versions) > 1 else None),
license_name=license_name,
mask_initial_release=mask_initial_release,
)

expected_prev_release_notes = generate_default_release_notes_from_def(
version_actions=repo_actions_per_version[prev_release_tag],
version_actions=repo_actions_per_version[previous_release_version],
hvcs=get_hvcs_client_from_repo_def(repo_def),
previous_version=(
Version.parse(all_versions[-3]) if len(all_versions) > 2 else None
),
previous_version=(all_versions[-3] if len(all_versions) > 2 else None),
license_name=EXAMPLE_PROJECT_LICENSE,
mask_initial_release=mask_initial_release,
)
Expand Down
Loading
Loading